How to Ruby on Rails authentication with LDAP? [closed] - ruby-on-rails

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 5 years ago.
Improve this question
I'm developing a web app and I have an authentication method using bcrypt gemIt works fine, but I wanted to change the authentication method to LDAP because I'm using an intranet environment and want my users to be able to sign in with windows credentials.
I'm looking to use net-ldap gem but I can't find any good toturials/explanations online on how to implement this into my web application.
Can you help me with this?
How can I do this?

Here's a utility class I've used in the past to do multi-server LDAP check:
require 'net/ldap'
# Ldap.authenticate('user', 'password')
# => `true` if valid
# => `false` if invalid
# => `nil` if LDAP unavailable
class Ldap
def self.config
{
domain: 'mydomain',
servers: ['server1', 'server2']
}
end
def self.authenticate(login, pass)
return false if login.empty? or pass.empty?
config['servers'].each do |server|
auth = authenticate_against_server(login, pass, server, config['domain'])
return auth unless auth.nil?
end
nil
end
private
def self.authenticate_against_server(login, pass, host, domain)
conn = Net::LDAP.new(
host: host,
port: 636,
base: "dc=#{domain}, dc=local",
encryption: :simple_tls,
auth: { username: "#{login}##{domain}.local",
password: pass,
method: :simple }
)
Timeout::timeout(15) do
return conn.bind ? true : false
end
rescue Net::LDAP::LdapError => e
notify_ldap_admin(host, 'Error', e)
return nil
rescue Timeout::Error => e
notify_ldap_admin(host, 'Timeout', e)
return nil
end
def self.notify_ldap_admin(host, error_type, error)
msg = "LDAP #{error_type} on #{host}"
RAILS_DEFAULT_LOGGER.debug(msg)
DeveloperMailer.deliver_ldap_failure_msg(msg, error)
end
end

If you're fairly new, I'd avoid homebrewing authentication. Check out either
Devise w/ LDAP: https://github.com/cschiewek/devise_ldap_authenticatable
Omniauth w/ LDAP: https://github.com/intridea/omniauth-ldap
I started with Devise, and still use it for a few projects, but Omniauth is super powerful and more versatile in my opinion. You have to do more yourself though.
Further reading should include CanCanCan for authorization within your app, unless everybody gets everything that is.

Related

Getting "Authentication failure! invalid_credentials: OAuth2::Error" for custom omniauth strategy

Currently I am working on rails 4 project, and now I have to link / connect another application (not sso but for accessing API's) say example.com. (Note: example.com uses 3-legged oauth security architecture)
After searching found that I have to implement omniouth strategy.
For this I have refereed this link. As per Strategy-Contribution-Guide I am able to complete setup and request Phase, You can find my sample code here.
require 'multi_json'
require 'omniauth/strategies/oauth2'
require 'uri'
module OmniAuth
module Strategies
class MyAppStrategy < OmniAuth::Strategies::OAuth2
option :name, 'my_app_strategy'
option :client_options, {
site: site_url,
authorize_url: authorize_url,
request_url: request_url,
token_url: token_url,
token_method: :post,
header: { Accept: accept_header }
}
option :headers, { Accept: accept_header }
option :provider_ignores_state, true
def consumer
binding.pry
::OAuth::Consumer.new(options.client_id, options.client_secret, options.client_options)
end
def request_phase # rubocop:disable MethodLength
binding.pry
request_token = consumer.get_request_token({:oauth_callback => callback_url}, options.request_params)
session["oauth"] ||= {}
session["oauth"][name.to_s] = {"callback_confirmed" => request_token.callback_confirmed?, "request_token" => request_token.token, "request_secret" => request_token.secret}
if request_token.callback_confirmed?
redirect request_token.authorize_url(options[:authorize_params])
else
redirect request_token.authorize_url(options[:authorize_params].merge(:oauth_callback => callback_url))
end
rescue ::Timeout::Error => e
fail!(:timeout, e)
rescue ::Net::HTTPFatalError, ::OpenSSL::SSL::SSLError => e
fail!(:service_unavailable, e)
end
def callback_phase # rubocop:disable MethodLength
fail(OmniAuth::NoSessionError, "Session Expired") if session["oauth"].nil?
request_token = ::OAuth::RequestToken.new(consumer, session["oauth"][name.to_s].delete("request_token"), session["oauth"][name.to_s].delete("request_secret"))
opts = {}
if session["oauth"][name.to_s]["callback_confirmed"]
opts[:oauth_verifier] = request["oauth_verifier"]
else
opts[:oauth_callback] = 'http://localhost:3000/auth/callback' #callback_url
end
#access_token = request_token.get_access_token(opts)
super
rescue ::Timeout::Error => e
fail!(:timeout, e)
rescue ::Net::HTTPFatalError, ::OpenSSL::SSL::SSLError => e
fail!(:service_unavailable, e)
rescue ::OAuth::Unauthorized => e
fail!(:invalid_credentials, e)
rescue ::OmniAuth::NoSessionError => e
fail!(:session_expired, e)
end
def custom_build_access_token
binding.pry
verifier = request["oauth_verifier"]
client.auth_code.get_token(verifier, get_token_options(callback_url), deep_symbolize(options.auth_token_params))
end
alias_method :build_access_token, :custom_build_access_token
def raw_info
binding.pry
#raw_info ||= access_token.get('users/me').parsed || {}
end
private
def callback_url
options[:redirect_uri] || (full_host + script_name + callback_path)
end
def get_token_options(redirect_uri)
{ :redirect_uri => redirect_uri }.merge(token_params.to_hash(:symbolize_keys => true))
end
end
end
end
I am able redirect to example.com, also after login I am able to return to my callback_phase (you will ask how did you know, so answer is I have added binding.pry in callback_phase method for checking the flow).
But after executing the strategy I am getting following error
ERROR -- omniauth: (my_app_strategy) Authentication failure! invalid_credentials: OAuth2::Error.
After debugging found that I am getting this error for the super call (from callback_phase method).
First I though may be there are some credentials issue but I am able fetch access token using following (which is executing before the super call)
#access_token = request_token.get_access_token(opts)
Also for more information I am getting error for build_access_token which is the oauth2 method
You can refer this link for more info (just search the build_access_token on the page).
EDIT - 1
After debugging found that getting this issue from the request method.
(While making the faraday request). Here is the code snippet
response = connection.run_request(verb, url, opts[:body], opts[:headers]) do |req|
yield(req) if block_given?
end
Here is my faraday request
#<struct Faraday::Request method=:post, path="example.com/oauth/access_token", params={}, headers={"User-Agent"=>"Faraday v0.9.2", "Content-Type"=>"application/x-www-form-urlencoded"}, body={"grant_type"=>"authorization_code", "code"=>"aPexxxvUg", "client_id"=>"xxxxxur303GXEch7QK9k", "client_secret"=>"xxxxxxcad97b3d252e2bcdd393a", :redirect_uri=>"http://localhost:3000/auth/my_app_strategy/callback"}, options=#<Faraday::RequestOptions (empty)>>
In response I am getting following error message
HTTP Status 400 - Inadequate OAuth consumer credentials.
So can any one help to fix this issue?
Is there any other way to store the access token so that I can utilize this for communication purpose.
Thanks
First of all, I wan to make clear how Oauth2 works:
Oauth2, the protocol says:
You redirect the user to the provider sign in endpoint adding some required parameters (Ejm: PROVIDER/public/oauth?redirect_uri=MYWEB/oauthDemo&
response_type=code&client_id=ABCDE). Sometimes there is also a scope/permission/resource parameter that indicates whats your purpose.
-> Then the users signs in and is redirected to your endpoint MYWEB/public/oauth with a code
Now you have to request the access token doing a POST to the providers endpoint. Example:
POST PROVIDER?code=d5Q3HC7EGNH36SE3N&
client_id=d4HQNPFIXFD255H&
client_secret=1a98b7cb92407cbd8961cd8db778de53&
redirect_uri=https://example.com/oauthDemo&
grant_type=authorization_code
Now you have the access_token and you can use it to get information or decode it using JWT.
Having this clear, and seeing that your call seems corect:
#<struct Faraday::Request method=:post, path="PROVIDER/oauth/access_token", params={}, headers={"User-Agent"=>"Faraday v0.9.2", "Content-Type"=>"application/x-www-form-urlencoded"}, body={"grant_type"=>"authorization_code", "code"=>"aPexxxvUg", "client_id"=>"xxxxxur303GXEch7QK9k", "client_secret"=>"xxxxxxcad97b3d252e2bcdd393a", :redirect_uri=>"MYWEB/auth/my_app_strategy/callback"}, options=#<Faraday::RequestOptions (empty)>>
As the response is "HTTP Status 400 - Inadequate OAuth consumer credentials.", I think maybe you:
a. Your client is not well configured on the Provider. Usually you use to have a basic configuration on the provider site so he can recognise you. So maybe is not well configured.
b. There is a resource/permission/scope parameter missing or wrong configured on the first step (in the redirection to the provider). So when you ask for the token there is a problem.

OmniAuth OAuth 1 strategy for upwork API error

I'm using OmniAuth gem along with the specific provider gems for FB, Linkedin and G+, both for login, registration and information retrieval. I want to offer further integration with other API's in this case with Upwork's api, that uses OAuth 1.
I've set the App with Upwork and have working key and secret. I've set the loader to load my custom strategy (since it's not a gem) and it loads. I've set the provider to pass the key and secret which are stored in an env file.
All of that seems to be working now, after many hours into it.
I tried reading through the sparse information contained in OmniAuth's strategy guide, along with OAuth wiki, and looked into the gem files of other providers. I ended up copying a bit of the code I thought would be enough to work through this, at least, for login but I'm messing something up.
Whenever I go to the callback path for upwork, set automatically by omniauth I get an error.
Started GET "/auth/upwork" for ::1 at 2015-07-29 00:08:12 +0800
ActiveRecord::SchemaMigration Load (0.3ms) SELECT "schema_migrations".* FROM "schema_migrations"
I, [2015-07-29T00:08:12.169605 #24517] INFO -- omniauth: (upwork) Request phase initiated.
OAuth::Unauthorized (405 Method Not Allowed):
lib/omniauth/strategies/upwork.rb:18:in `request_phase'
Rendered /Users/mnussbaumer/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/actionpack-4.2.1/lib/action_dispatch/middleware/templates/rescues/_source.erb (5.1ms)
By the documentation this seems to be when I either try a GET to a POST only, or a POST to a GET only endpoint.
In Upwork's API reference they explicitly say that:
Get request token
Endpoint
POST /api/auth/v1/oauth/token/request
My strategy is currently as this:
require 'json'
require 'omniauth-oauth'
module OmniAuth
module Strategies
class Upwork < OmniAuth::Strategies::OAuth
option :client_options, {
:site => "https://www.upwork.com/api",
:request_token_path => "/api/auth/v1/oauth/token/request",
:authorize_url => "/services/api/auth",
:access_token_path => "api/auth/v1/oauth/token/access",
}
uid { request.params['user_id'] }
def request_phase
request_token = consumer.get_request_token(:oauth_callback => callback_url)
session['oauth'] ||= {}
session['oauth'][name.to_s] = {'callback_confirmed' => request_token.callback_confirmed?, 'request_token' => request_token.token, 'request_secret' => request_token.secret}
if request_token.callback_confirmed?
redirect request_token.authorize_url(options[:authorize_params].merge(:oauth_consumer_key => consumer.key))
else
redirect request_token.authorize_url(options[:authorize_params].merge(:oauth_callback => callback_url, :oauth_consumer_key => consumer.key))
end
rescue ::Timeout::Error => e
fail!(:timeout, e)
rescue ::Net::HTTPFatalError, ::OpenSSL::SSL::SSLError => e
fail!(:service_unavailable, e)
end
def raw_info
#raw_info ||= JSON.load(access_token.get('/me.json')).body
end
end
end
end
I tried changing "consumer.get_request_token" to "consumer.post_request_token" but I think that has nothing to do with it.
The request_phase was ripped off of a gem I found and the JSON.load from a different one. I thought it would work with only these 2 but it seems not. I'm learning slowly how to use all this and would like to build first a usable strategy and then provide it as a public gem for omniauth.
UpWork has an API documentation, and they even have a gem for ruby, but I would like to use OmniAuth for everything, plus, I'll need to figure out other API's in the future so I would like to know how to do this well.
https://developers.upwork.com/?lang=ruby#authentication_oauth-10
Anybody can help with this? Or with creating an OmniAuth gem for Upwork.
Thanks!
(edited to change the error - now it's much thinner output but it's the same error)
The request_phase method is actually a method that belongs to omniauth-oauth which you required on top of the upwork.rb and your class Upwork inherits it (OmniAuth::Strategies::OAuth). you don't have to override it.

How do you test HttpAuthentication::Digest in rails 4?

I'm upgrading from rails 3 to rails 4 and trying to get digest authentication working based on this example:
http://lightyearsoftware.com/2009/04/testing-http-digest-authentication-in-rails/
It looks like the 'process_with_test' method was removed, so I think I can just override the controller's process method like this:
def authenticate_with_http_digest(user = API_USERNAME, password = API_PASSWORD, realm = API_REALM)
ActionController::Base.class_eval { include ActionController::Testing }
#controller.instance_eval %Q(
alias real_process process
def process(name)
credentials = {
:uri => request.url,
:realm => "#{realm}",
:username => "#{user}",
:nonce => ActionController::HttpAuthentication::Digest.nonce(Rails.configuration.secret_key_base),
:opaque => ActionController::HttpAuthentication::Digest.opaque(Rails.configuration.secret_key_base)
}
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Digest.encode_credentials(request.request_method, credentials, "#{password}", false)
real_process(name)
end
)
end
I can see the new method gets called, but I still get 401 access denied errors when I call the controller. I'm not sure I am creating the digest authentication correctly, but I don't know which part is incorrect. Does anyone have tips for debugging this?
I had the same issue. I read through the Rails 4 test cases and built the below solution. Its not perfect by any stretch of the imagination but it works in my test environment. It is a drop-in solution for the original authenticate_with_http_digest helper method.
Gist here:
https://gist.github.com/illoyd/9429839
And for posterity:
# This should go into spec/support/auth_spec_helpers.rb (if you are using RSpec)
module AuthSpecHelpers
##
# Convenience method for setting the Digest Authentication details.
# To use, pass the username and password.
# The method and target are used for the initial request to get the digest auth headers. These will be translated into 'get :index' for example.
# The final 'header' parameter sets the request's authentication headers.
def authenticate_with_http_digest(user, password, method = :get, target = :index, header = 'HTTP_AUTHORIZATION')
#request.env[header] = encode_credentials(username: user, password: password, method: method, target: target)
end
##
# Shamelessly stolen from the Rails 4 test framework.
# See https://github.com/rails/rails/blob/a3b1105ada3da64acfa3843b164b14b734456a50/actionpack/test/controller/http_digest_authentication_test.rb
def encode_credentials(options)
options.reverse_merge!(:nc => "00000001", :cnonce => "0a4f113b", :password_is_ha1 => false)
password = options.delete(:password)
# Perform unauthenticated request to retrieve digest parameters to use on subsequent request
method = options.delete(:method) || 'GET'
target = options.delete(:target) || :index
case method.to_s.upcase
when 'GET'
get target
when 'POST'
post target
end
assert_response :unauthorized
credentials = decode_credentials(#response.headers['WWW-Authenticate'])
credentials.merge!(options)
path_info = #request.env['PATH_INFO'].to_s
uri = options[:uri] || path_info
credentials.merge!(:uri => uri)
#request.env["ORIGINAL_FULLPATH"] = path_info
ActionController::HttpAuthentication::Digest.encode_credentials(method, credentials, password, options[:password_is_ha1])
end
##
# Also shamelessly stolen from the Rails 4 test framework.
# See https://github.com/rails/rails/blob/a3b1105ada3da64acfa3843b164b14b734456a50/actionpack/test/controller/http_digest_authentication_test.rb
def decode_credentials(header)
ActionController::HttpAuthentication::Digest.decode_credentials(header)
end
end
# Don't forget to add to rspec's config (spec/spec_helper.rb)
RSpec.configure do |config|
# Include auth digest helper
config.include AuthSpecHelpers, :type => :controller
end
Happy testing.

Ldap search in net-ldap Ruby Rails [closed]

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 10 years ago.
I am trying to search ldap. This is doing successful authentication, I verified using a correct and incorrect password. Ldap connection is successful. Then when I make a search query it cannot read attributes from LDAP (AD). What is wrong here? Does ldap/AD has any restrictions/permissions on who can what?
#!/usr/bin/env ruby
require "net-ldap"
$username = String.new
class ActiveDirectoryUser
SERVER = '10.10.10.10'
PORT = 389
BASE = 'DC=mydomain,DC=com'
DOMAIN = 'mydomain.com'
def self.authenticate(login, pass)
conn = Net::LDAP.new :host => SERVER,
:port => PORT,
:base => BASE,
:auth => { :username => "#{login}##{DOMAIN}",
:password => pass,
:method => :simple }
if conn.bind
conn.search(
:base => BASE, :filter => Net::LDAP::Filter.eq(
"sAMAccountName", login ),
:attributes => %w[ givenName ], :return_result => true) do
|entry|
puts "givenName: #{entry.givenName}"
$username = entry.givenName
end
return true
else
return false
end
rescue Net::LDAP::LdapError => e
return false
end
end
if ActiveDirectoryUser.authenticate('myusername', 'mypassword')
puts "Authentication Successful! The user is "+$username
#### I get this,but blank username
else
puts "Authentication FAILED!"
end
-----------
# ./ad.rb
Authentication Successful! The user is
Thanks Terry for the answer.
It was a small issue. I was missing some details in the treebase. It is working now.
LDAP-compliant directory servers must provide access control, so it is possible that the LDAP client:
cannot search for an entry
cannot read any attributes from an entry
cannot read some attributes from an entry
It is also possible the entry for which the LDAP client searches does not exist - clients cannot read attributes from entries that do not exist.
The application code should check that the entry in question exists using command-line tools if questions exist as to whether the authentication was successful or whether an entry exists. For information about the command-line ldapsearch tool, see LDAP: using ldapsearch.

session cookie httponly false rails 3.1

I'm trying to turn httponly off for use in phonegap. I'm useing rails 3.1 and devise, each of which have reported (but not documented) ways of doing this, none of which work:
# application.rb
config.session_options = { :httponly => false } # no effect
config.session = { :httponly => false } # undefined method `session='
# devise.rb
config.cookie_options = { :httponly => false } # also no effect
to test I restarted the server, deleted the existing cookie, and reloaded the page. 'Http' column was still checked in the chrome debugger.
help!
This little snippet seems to work :
Testapp::Application.config.session_store :cookie_store, key: '_testapp_session', :domain => :all, :httponly => false
As far as I can tell, this is a bug in rails. Perhaps the option got removed, but the documentation stayed. Any ideas on this would be welcome!
I spent several thorough hours with ActionPack, and couln't find any reference to such a configuration option-- but I still don't have the full picture as to how it works. Specifically, there's the cookiestore which holdes cookies and writes them to the header (and is passed :httponly => true), but I couldn't find how the session is using the store-- with vague things like the Rails SessionManage module being a proverbial ghost town.
I hacked up a middleware which does the job:
# application.rb:
config.middleware.insert_before ActionDispatch::Cookies, "UnshieldCookie" # remove httponly.
# unshielded_cookie.rb
class UnshieldCookie
def initialize(app)
#app = app
end
def call(env)
status, headers, body = #app.call(env)
headers['Set-Cookie'].gsub!('HttpOnly', '') if headers['Set-Cookie'].present?
[status, headers, body]
end
end

Resources