Send Dynamic params with omniauth-saml - ruby-on-rails

I need a way to send dynamic params using omniauth-saml from SP TO IDP. The requirement is there are 2 websites website 1 and website 2. Website 1 is controlled by another team where saml is already implemented. On my website, I have added a button and on click of it, I will send a request to website 1. Along with the request I need to send user parameters such as first_name, last_name, email & some custom attributes. In my previous stackoverflow post I was able to understand that I need to make use of omniauth-saml and some basic details. But the issue which I am still not able to send dynamic attributes.
When I am going through the documentation I believe I need to make use of
:idp_sso_target_url_runtime_params => {:original_request_param => :mapped_idp_param},
But I am not sure how can I pass dynamic params through it. In my previous post, a person referred me to do a monkey patch but it didn't work for me. Could anyone has any suggestion
Rails.application.config.middleware.use OmniAuth::Builder do
provider :saml,
#:assertion_consumer_service_url => "consumer_service_url",
:issuer => "my_application",
:idp_sso_target_url => "target_url",
:idp_sso_target_url_runtime_params => {:original_request_param => :mapped_idp_param},
:idp_cert => "-----BEGIN CERTIFICATE-----\n...-----END CERTIFICATE-----",
:name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
end

You can pass dynamic values via middleware adapter.
Like:
app/service/saml_idp_setting_adapter.rb
class SamlIdpSettingAdapter
def self.settings(issuer)
idp = ::IdentityProvider.find_by_issuer(issuer)
if idp.present?
{
assertion_consumer_service_url: "#{ENV['APP_URL']}/users/saml/auth",
assertion_consumer_service_binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
name_identifier_format: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
issuer: "issuer",
idp_entity_id: idp.entity_id,
idp_slo_service_url: idp.slo_target_url,
idp_sso_service_url: idp.sso_target_url,
idp_cert_fingerprint: idp.cert_fingerprint,
idp_cert_fingerprint_algorithm: 'http://www.w3.org/2000/09/xmldsig#sha256'
}
else
{}
end
end
end
and setup initialiser file with above adaptor
Rails.application.config.middleware.use OmniAuth::Builder do
provider :saml,
:idp_settings_adapter => SamlIdpSettingAdapter
end

Related

Applying additional oauth scopes in an omniauth initializer

I am trying to apply the coinbase wallet API with oauth to use its send functionality. I have been able to connect to the API and use its endpoints, but whenever I try to use the send functionality, I am thrown the error Invalid amount for meta[send_limit_amount]. My omniauth initializer looks like this:
provider :coinbase, , ENV['CLIENT_ID'], ENV['CLIENT_SECRET'],
scope: 'wallet:user:read wallet:user:email wallet:accounts:read wallet:transactions:send'
The reason for this error is because, in order to use the send functionality, coinbase requires additional parameter meta[send_limit_amount]. Where and how am I supposed to apply this additional scope?
UPDATE: So I've made some progress in that I am able to attach one meta scope to my initializer, which seems to be sticking (as shown when I print out the auth_info). This is the current state of my initializer:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :coinbase, ENV['CLIENT_ID'], ENV['CLIENT_SECRET'], scope: 'wallet:user:read wallet:user:email wallet:accounts:read ', :meta => {'send_limit_currency' => 'BTC'}
end
# wallet:transactions:send
# :meta => {'send_limit_amount' => '0.0001'}
The problem now is that I cannot seem to figure the syntax necessary to add the send_limit_amount property to the oauth meta hash.
Managed to solve the problem with the following initializer;
Rails.application.config.middleware.use OmniAuth::Builder do
provider :coinbase, ENV['CLIENT_ID'], ENV['CLIENT_SECRET'],
scope: 'wallet:user:read wallet:user:email wallet:accounts:read wallet:transactions:send',
:meta => {'send_limit_amount' => 1}
end
Now, I need to either disable the two factor authentication or determine how to Re-play the request with CB-2FA-Token header

Creating a new user with credentials, then obtaining a token for that user with Doorkeeper in an API

I'm building an API, protected by Doorkeeper.
If I manually create the user (with password) in the backend, and then post the following to oauth/token, Doorkeeper successfully generates an access token for the user and returns it:
data = {
username: $("#email_sign_in").val(),
password: $("#password").val(),
grant_type: 'password',
client_id: '880c16e50aee5893446541a8a0b3788....',
client_secret: 'a5108e1a1aeb87d0bb49d33d8c50d....',
provider: 'identity'
}
However, I'm trying to get my head around how I could do a sign up flow.
I've happily got users/create working, in so far as it creates a user and password, but I'm not sure how to then generate the Doorkeeper::AccessToken in the next step, and return it to the client. Ideally, after creating the user in the user#create action I'd then redirect to POST to oauth/token, with the user's name and password, but I know that you can't redirect to a POST.
I've had a dig around the Doorkeeper source, but am getting a bit lost in all this clever middleware. Any advice on this is greatly appreciated!
It was the simplest of things! I was overcomplicating it by trying to POST, when in actual fact I could simply generate the DoorKeeper::AccessToken in user#create, and then return this.
Here's the code to generate the token:
access_token = Doorkeeper::AccessToken.create!(:application_id => application_id, :resource_owner_id => user_id)
I dig a bit in the doorkeeper source code, like the way that creating token using standard api way, you'd better using the following method if you are manually doing this.
find_or_create_for(application, resource_owner_id, scopes, expires_in, use_refresh_token)
for your case
access_token = Doorkeeper::AccessToken.find_or_create_for(application: application, resource_owner_id: user_id)
link to source code of doorkeeper
find_or_create_for in doorkeeper
In rails we can create access token using DoorKeeper using:
Doorkeeper::AccessToken.create!(
application_id: nil,
resource_owner_id: user.id,
expires_in: 2.hours,
scopes: 'public'
)
Ideally, the best answer is not the one you posted, I think itÅ› better to create a controller that inherits from Doorkeeper::TokensController:
# app/controllers/custom_tokens_controller.rb
class CustomTokensController < Doorkeeper::TokensController
# Override create action
def create
(... your custom code ...)
super
end
end
Then in routes.rb define a new route like post 'custom_tokens', to: 'custom_tokens#create' or whatever naming you prefer, but the action should be create.
More details about this solution here: https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Resource-Owner-Password-Credentials-flow

Set different facebook oauth scope per user with devise

I have a rails app with two separate types of users (call them A and B). Right now they can both sign in with facebook. However, I need B to be able to oauth with some extended permissions, and I DO NOT want A to give me the extended permissions.
Inside config/initializers/devise.rb
config.omniauth :facebook, "API_KEY", "API_SECRET", :client_options => {:ssl => {:ca_path => ' /path/to/my/ssl/stuff'}}
I know I can add
:scope => "extended_permissions"
But I only want the extended permissions to happen when B users sign up.
Since this is in an initializer is this even possible? Or can I somehow config.omniauth elsewhere in the app and keep devise happy?
It's clearly explained in the documentation
If you want to set the display format, auth_type, or scope on a
per-request basis, you can just pass it to the OmniAuth request phase
URL, for example: /auth/facebook?display=popup or
/auth/facebook?scope=email.
source: https://github.com/mkdynamic/omniauth-facebook#per-request-options

Rails custom consumer oauth strategy

I'm new to oauth and api integrations, and am having a hell (can I say that here) of a time trying to figure it out.
I'd like to connect my rails app to Magento (a php ecommerce cart).
They have some basic docs here:
http://www.magentocommerce.com/api/rest/authentication/oauth_authentication.html
While I understand the idea of oauth in principle, I have no idea how to implement a custom solution. I've used a few gems (ex: omniauth) to connect to Twitter, and that was fine, but I just don't know how to create my own strategy for connecting to Magento.
Does anyone know how to do it? Is there a walk-through or screencast somewhere I can use?
If not, what tools or approaches might you recommend for me to figure it out -- if only by trial and error?
Thanks in advance for your help!
Here are detailed instructions from the repo of the omniauth-magento gem / strategy I created:
Setting up Magento
Consumer key & secret
Set up a consumer in Magento and write down consumer key and consumer secret
Privileges
In the Magento Admin backend, go to System > Web Services > REST Roles, select Customer, and tick Retrieve under Customer. Add more privileges as needed.
Attributes
In the Magento Admin backend, go to System > Web Services > REST Attributes, select Customer, and tick Email, First name and Last name under Customer > Read. Add more attributes as needed.
Setting up Rails
Parts of these instructions are based on these OmniAuth instructions, which you can read in case you get stuck.
Devise
Install Devise if you haven't installed it
Add / replace this line in your routes.rb. This will be called once Magento has successfully authorized and returns to the Rails app.
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks"}
Magento oAuth strategy
Load this library into your Gemfile gem "omniauth-magento" and run bundle install
Modify config/initializers/devise.rb:
Devise.setup do |config|
# deactivate SSL on development environment
OpenSSL::SSL::VERIFY_PEER ||= OpenSSL::SSL::VERIFY_NONE if Rails.env.development?
config.omniauth :magento,
"ENTER_YOUR_MAGENTO_CONSUMER_KEY",
"ENTER_YOUR_MAGENTO_CONSUMER_SECRET",
{ :client_options => { :site => "ENTER_YOUR_MAGENTO_URL_WITHOUT_TRAILING_SLASH" } }
# example:
# config.omniauth :magento, "12a3", "45e6", { :client_options => { :site => "http://localhost/magento" } }
Optional: If you want to use the Admin API (as opposed to the Customer API), you need to overwrite the default authorize_path like so:
{ :client_options => { :authorize_path => "/admin/oauth_authorize", :site => ENTER_YOUR_MAGENTO_URL_WITHOUT_TRAILING_SLASH } }
In your folder controllers, create a subfolder users
In that subfolder app/controllers/users/, create a file *omniauth_callbacks_controller.rb* with the following code:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def magento
# You need to implement the method below in your model (e.g. app/models/user.rb)
#user = User.find_for_magento_oauth(request.env["omniauth.auth"], current_user)
if #user.persisted?
sign_in_and_redirect #user, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => "magento") if is_navigational_format?
else
session["devise.magento_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
User model & table
Here's an example of useful Magento information you can store in your User table once you have created these columns:
email
first_name
last_name
magento_id
magento_token
magento_secret
Optional: You might want to encrypt *magento_token* and *magento_secret* with the *attr_encrypted gem* for example (requires renaming magento_token to encrypted_magento_token and magento_secret to encrypted_magento_secret).
Set up your User model to be omniauthable :omniauthable, :omniauth_providers => [:magento] and create a method to save retrieved information after successfully authenticating.
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :recoverable,
:rememberable, :trackable, :validatable, :timeoutable,
:omniauthable, :omniauth_providers => [:magento]
def self.find_for_magento_oauth(auth, signed_in_resource=nil)
user = User.find_by(email: auth.info.email)
if !user
user = User.create!(
first_name: auth.info.first_name,
last_name: auth.info.last_name,
magento_id: auth.uid,
magento_token: auth.credentials.token,
magento_secret: auth.credentials.secret,
email: auth.info.email,
password: Devise.friendly_token[0,20]
)
else
user.update!(
magento_id: auth.uid,
magento_token: auth.credentials.token,
magento_secret: auth.credentials.secret
)
end
user
end
end
Link to start authentication
Add this line to your view <%= link_to "Sign in with Magento", user_omniauth_authorize_path(:magento) %>
Authenticating
Start your Rails server
Start your Magento server
Log into Magento with a customer account (or admin account if you want to use the Admin API)
In your Rails app, go to the view where you pasted this line <%= link_to "Sign in with Magento", user_omniauth_authorize_path(:magento) %>
Click on the link
You now should be directed to a Magento view where you are prompted to authorize access to the Magento user account
Once you have confirmed, you should get logged into Rails and redirected to the Rails callback URL specified above. The user should now have magento_id, magento_token and magento_secret stored.
Making API calls
Create a class that uses magento_token and magento_secret to do API calls for instance in *lib/magento_inspector.rb*. Example:
class MagentoInspector
require "oauth"
require "omniauth"
require "multi_json"
def initialize
#access_token = prepare_access_token(current_user) # or pass user in initialize method
#response = MultiJson.decode(#access_token.get("/api/rest/customers").body) # or pass query in initialize method, make sure privileges and attributes are enabled for query (see section at top)
end
private
# from http://behindtechlines.com/2011/08/using-the-tumblr-api-v2-on-rails-with-omniauth/
def prepare_access_token(user)
consumer = OAuth::Consumer.new("ENTER_YOUR_MAGENTO_CONSUMER_KEY", "ENTER_YOUR_MAGENTO_CONSUMER_SECRET", {:site => "ENTER_YOUR_MAGENTO_URL_WITHOUT_TRAILING_SLASH"})
token_hash = {:oauth_token => user.magento_token, :oauth_token_secret => user.magento_secret}
access_token = OAuth::AccessToken.from_hash(consumer, token_hash)
end
end
Make sure Rails loads files in the folder where this class is placed. For the lib folder, put this in config/application.rb: config.autoload_paths += Dir["#{config.root}/lib/**/"]
Perform query MagentoInspector.new
Extend class to suit your needs
I get this kind of guttural response when I hear the M-word (Magento), having spent several months attempting to make it do anything useful. But... assuming you have no choice, and assuming Magento offers a standard OAuth server, then you should be able to use OmniAuth to connect to Mag...blurrgh..ento.
Magento will need to provide several tokens, client_id and client_secret. You use these to request an access token for your app. Once you have it, you can reuse it semi-permanently. OmniAuth might be able to help you with that.
Once you have the access token, you'll need to pass an HTTP header that looks like Authentication: OAuth <access-token> with every request you make to the service.
Requests are made using standard https (I would hope, vs http) to the server. Within Rails you could roll your own REST client (using Net::HTTP and friends), but you might find a gem like RestClient, linked here. There are others out there -- check The Ruby Toolbox for more.
Does that get you started?

Problems with OAuth 2 Gem

I have a rails project using https://github.com/intridea/oauth2. In my rails app I have the following code:
ApplicationController.rb
def facebook_client
OAuth2::Client.new(FacebookOauthCredentials::APP_ID, FacebookOauthCredentials::APP_SECRET, :site => 'https://graph.facebook.com')
end
FacebookController.rb
def facebook_session_create(poster, featured_item)
redirect_to facebook_client.web_server.authorize_url(:scope => 'publish_stream', :redirect_uri => "http://localhost:3000/facebook/facebook_callback")
end
def facebook_callback
if(params[:code])
begin
access_token = facebook_client.web_server.get_access_token(params[:code], :redirect_uri => "http://localhost:3000/facebook/facebook_callback")
access_token.post('/me/feed', "testing #{rand(1000)}")
rescue OAuth2::HTTPError => e
render :text => e.response.body
end
end
end
Every time I run this code I get this response:
{"error":{"type":"OAuthException","message":"Error validating verification code."}}
However, I use the sinatra app supplied in the OAuth2 gem's readme file, it works fine.
def client
OAuth2::Client.new(FacebookOauthCredentials::APP_ID, FacebookOauthCredentials::APP_SECRET, :site => 'https://graph.facebook.com')
end
get '/auth/facebook' do
redirect client.web_server.authorize_url(
:redirect_uri => redirect_uri,
:scope => 'publish_stream'
)
end
get '/auth/facebook/callback' do
access_token = client.web_server.get_access_token(params[:code], :redirect_uri => redirect_uri)
begin
user = JSON.parse(access_token.post('/me/feed', :message => "testing # {rand(10000)}"))
rescue Exception => e
return e.response.body
end
user.inspect
end
def redirect_uri
uri = URI.parse(request.url)
uri.path = '/auth/facebook/callback'
uri.query = nil
uri.to_s
end
I have tried reproducing the steps using irb, but I an http 400 error. I'm not sure if it's for the same reason as the rails app, or if it's because I'm doing a hybrid of console and web browser operation. Any suggestions would be greatly appreciated.
I found the answer to my problem on this page Facebook graph API - OAuth Token
I ran into the exact same problem but
it turned out the issue is not the
encoding of the redirect_uri
parameter, or that I had a trailing
slash or question mark it's simply
that I passed in two different
redirect urls (had not read the
specification at that time).
The redirect_uri is only used as a
redirect once (the first time) to
redirect back to the relying party
with the "code" token. The 2nd time,
the redirect_uri is passed back to the
auth server but this time it's not
used as you'd expect (to redirect)
rather it's used by the authentication
server to verify the code. The server
responds with the access_token.
You'll notice facebook documentation
(which is terrible) says fetch
"Exchange it for an access token by
fetching ....
"
In summary, I didn't have to encode or
do anything special to the Uri, just
pass in the same redirect_uri twice,
and fetch the 2nd page to get the
access_token inside.
I didn't copy my original code correctly and the redirect uri I was passing to get the code was different than the uri I was passing to get the access token. Facebook's API documentation is terrible :(
I had this problem/error but finally found a different solution (by comparing my Dev app settings which was working) to my prod app settings (which was generating this error).
I went to the advanced settings page for my app in the Facebook Developer app:
https://developers.facebook.com/apps/YourAppID/advanced
Then I found the "Encrypted Access Token:" setting and turn it to "Enabled."

Resources