In Rails 3 we used this nice little hack (at least it was contained and easily reused) - Writing a Test/Method for HTTP Digest Authentication
However, this method (process_with_new_base_test ) is completely gone in Rails 4 (master). Does anyone know the proper way of testing digest authentication in Rails 4?
Rails 4.0.b1 ActionController::Testing
Rails 3.2.x ActionController::Testing
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.
Here's a bit easier to use version: https://gist.github.com/murbanski/6b971a3edc91b562acaf
Related
I'm fairly new to Rails 4 and am experimenting with Devise and ldap_authenticatable and I see something that I'm not sure is right. When I authenticate to my Active Directory Devise works fine and stores the user in the MySQL database as expected. However, I seem to lose the user params and can't tell which user just authenticated. user_signed_in? returns false but if I hit the login link I get the message "already signed in" current_user is nil and set_user fails because params(:id) is nil. Seems like something is broken here but I'm not sure what the norm is as far as Devise setting or keeping user params alive.
Any ideas or helpful information?
User Model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :ldap_authenticatable, :trackable, :validatable
before_save :get_ldap_attrs
def get_ldap_attrs
self.firstname = Devise::LDAP::Adapter.get_ldap_param(self.email, 'givenName')
self.lastname = Devise::LDAP::Adapter.get_ldap_param(self.email, 'sn')
self.login = Devise::LDAP::Adapter.get_ldap_param(self.email, 'sAMAccountName')
self.email = Devise::LDAP::Adapter.get_ldap_param(self.email,'mail').first
self.studentid = Devise::LDAP::Adapter.get_ldap_param(self.email, 'title')
end
end
----
ldap.yaml
## Authorizations
# Uncomment out the merging for each environment that you'd like to include.
# You can also just copy and paste the tree (do not include the "authorizations") to each
# environment if you need something different per enviornment.
authorizations: &AUTHORIZATIONS
allow_unauthenticated_bind: false
group_base: ou=groups,dc=kentshill,dc=org
## Requires config.ldap_check_group_membership in devise.rb be true
# Can have multiple values, must match all to be authorized
required_groups:
# If only a group name is given, membership will be checked against "uniqueMember"
#- ########################
#- #######################
# If an array is given, the first element will be the attribute to check against, the second the group name
#- ["moreMembers", "cn=users,ou=groups,dc=test,dc=com"]
## Requires config.ldap_check_attributes in devise.rb to be true
## Can have multiple attributes and values, must match all to be authorized
require_attribute:
objectClass: inetOrgPerson
authorizationRole: postsAdmin
## Environment
development:
host: address
port: 636
attribute: mail
base: DN
admin_user: fqn user with privs
admin_password: password
ssl: true
# <<: *AUTHORIZATIONS
test:
host: localhost
port: 3389
attribute: cn
base: ou=people,dc=test,dc=com
admin_user: cn=admin,dc=test,dc=com
admin_password: admin_password
ssl: simple_tls
# <<: *AUTHORIZATIONS
production:
host: localhost
port: 636
attribute: cn
base: ou=people,dc=test,dc=com
admin_user: cn=admin,dc=test,dc=com
admin_password: admin_password
ssl: start_tls
# <<: *AUTHORIZATIONS
----------------
Devise initializer
# Use this hook to configure devise mailer, warden hooks and so forth.
# Many of these configuration options can be set straight in your model.
Devise.setup do |config|
# ==> LDAP Configuration
config.ldap_logger = true
config.ldap_create_user = true
config.ldap_update_password = true
#config.ldap_config = "#{Rails.root}/config/ldap.yml"
config.ldap_check_group_membership = false
#config.ldap_check_group_membership_without_admin = false
config.ldap_check_attributes = false
config.ldap_use_admin_to_bind = true
config.ldap_ad_group_check = false
# The secret key used by Devise. Devise uses this key to generate
# random tokens. Changing this key will render invalid all existing
# confirmation, reset password and unlock tokens in the database.
# Devise will use the `secret_key_base` on Rails 4+ applications as its `secret_key`
# by default. You can change it below and use your own secret key.
# config.secret_key = 'ead157a98cc1402f93c717c537225a807971f381bdb51063b22d9979b39e0db385493e0d392999152597ce52baf327d97ffc9a59371ea3258cd8f5fc6d158b75'
# ==> Mailer Configuration
# Configure the e-mail address which will be shown in Devise::Mailer,
# note that it will be overwritten if you use your own mailer class
# with default "from" parameter.
config.mailer_sender = 'please-change-me-at-config-initializers-devise#example.com'
# Configure the class responsible to send e-mails.
# config.mailer = 'Devise::Mailer'
# ==> ORM configuration
# Load and configure the ORM. Supports :active_record (default) and
# :mongoid (bson_ext recommended) by default. Other ORMs may be
# available as additional gems.
require 'devise/orm/active_record'
config.ldap_auth_username_builder = Proc.new() { |attribute, login, ldap| login }
# config.warden do |manager|
# manager.default_strategies(:scope => :user).unshift :ldap_authenticatable
# end
# ==> Configuration for any authentication mechanism
# Configure which keys are used when authenticating a user. The default is
# just :email. You can configure it to use [:username, :subdomain], so for
# authenticating a user, both parameters are required. Remember that those
# parameters are used only when authenticating and not when retrieving from
# session. If you need permissions, you should implement that in a before filter.
# You can also supply a hash where the value is a boolean determining whether
# or not authentication should be aborted when the value is not present.
config.authentication_keys = [:email]
# Configure parameters from the request object used for authentication. Each entry
# given should be a request method and it will automatically be passed to the
# find_for_authentication method and considered in your model lookup. For instance,
# if you set :request_keys to [:subdomain], :subdomain will be used on authentication.
# The same considerations mentioned for authentication_keys also apply to request_keys.
# config.request_keys = []
# Configure which authentication keys should be case-insensitive.
# These keys will be downcased upon creating or modifying a user and when used
# to authenticate or find a user. Default is :email.
config.case_insensitive_keys = [:email]
"config/initializers/devise.rb" 280L, 13721C
Rails 3.2.12, ruby 1.9.3
We allow users to specify the company they are with using a subdomain, like mycompany.example.com, but we redirect to the canonical example.com and need to remember that the user is from mycompany.
We have our environment set up so the config.session_store contains :domain => 'example.com (an alternative that also works is :domain => :all, :tld_length => 2) and this is supposed to work to allow sharing of session information between subdomains. There are a number of great posts, such as this one: Share session (cookies) between subdomains in Rails?
But before the redirect I am sending session.inspect to the log, and it's clearly getting a different session (two separate session ids, etc.). So the most basic issue is that I cannot use the session to remember the mycompany part before I strip it off.
I can work around that, but there are a number of cases where the same user will be from multiple companies (and part of this is our support team who needs to be able to switch companies).
I have tried this on Chrome and Safari on OS X. I am using "pow" so my local development environment has a domain like example.dev which helps rule out several issues (vs. normal localhost:3000 server).
Am I missing something? Is it indeed possible to share a cookie across domains?
UPDATE:
Example code called in a before_filter defined in ApplicationController:
def redirect_to_canonical_if_needed
logger.debug "Starting before_filter. session contains: #{session}"
if request.host != 'example.com'
session[:original_domain] = "Originally came from #{request.host}"
logger.debug "Redirecting, session contains: #{session}"
redirect_to 'http://example.com', :status => :moved_permanently
end
end
Setting added to config/environments/production.rb and removed from config/initializers/session_store.rb
config.session_store = { :key => 'example_session', :secret => "secret", :domain => :all, :tld_length => 2 }
or
config.session_store = { :key => 'example_session', :secret => "secret", :domain => 'example.com' }
And logging result, if I start from a fresh environment where no session exists going to the url a.example.com:
Starting before_filter, session contains: {}
Redirecting, session contains: {"session_id"=>"4de9b56fb540f7295cd3192cef07ba63", "original_domain"=>"a.example.com"}
Filter chain halted as :redirect_to_canonical_if_needed rendered or redirected
Completed 301 Moved Permanently in 2294ms (ActiveRecord: 855.7ms)
Started GET "/" for 123.456.789.123 at 2013-07-12 09:41:12 -0400
Processing by HomeController#index as HTML
Parameters: {}
Starting before_filter, session contains: {}
So the before filter fires on each new request. First request there's no session, hence the "not loaded" message. The test for need to redirect is true. I put something in the session and it gets an id and what I put in it. I do the redirect. New request occurs on the root domain, before filter fires again, and here's the issue: session is not initialized
This should work fine between the two I have setup the following on my dev
Application is at example.dev
I view and set a session variable at a.example.dev then visit b.example.dev and it is set as long as when (as you describe) you set domain to 'example.dev' for the session store
This code in my root controller/action does exactly what your describing
unless request.subdomain.to_s == 'another'
session[:original_domain] = request.subdomain.to_s
redirect_to 'http://another.' + request.domain.to_s
end
And viewing original_domain is available in the session
If you put the example code in I can have a look for any pitfalls
I want to set up Gitlab with our company's LDAP as a demo. But unfortunately I have to put in an admin password in gitlab.yml to make gitlab access the LDAP service. The problem actually is the administration, as they don't want to setup another account just for Gitlab. Is there any way to circumvent this without filling in my own password? Is there a way to make Gitlab establish the LDAP connection with only the provided user credentials?
Any ideas beside logging in as anonymous?
Already posted here.
I haven't tried it yet, but from the things I've build so far authenticating against LDAP and the informations from the config-file this user-account seems only to be needed when your LDAP does not support anonymous binding and searching.
So I would leave the two entries bind_dn and password commented out and try whether it works or not.
UPDATE
I've implemented LDAP-Autehntication in Gitlab and it's fairly easy.
In the gitlab.yml-file there is a section called ldap.
There you have to provide the informations to connect to your LDAP. It seems that all fields have to be given, there seems to be no fallback default! If you want to use anonymous binding for retrieval of the users DN supply an empty string for bind_dn and password. Commenting them out seems not to work! At least I got a 501 Error message.
More information can be found at https://github.com/patthoyts/gitlabhq/wiki/Setting-up-ldap-auth and (more outdated but still helpful) https://github.com/intridea/omniauth-ldap
I have patched gitlab to work this way and documented the process in https://foivos.zakkak.net/tutorials/gitlab_ldap_auth_without_querying_account/
I shamelessly copy the instructions here for self-completeness.
Note: This tutorial was last tested with gitlab 8.2 installed from source.
This tutorial aims to describe how to modify a Gitlab installation to
use the users credentials to authenticate with the LDAP server. By
default Gitlab relies on anonymous binding or a special querying user
to ask the LDAP server about the existence of a user before
authenticating her with her own credentials. For security reasons,
however, many administrators disable anonymous binding and forbid the
creation of special querying LDAP users.
In this tutorial we assume that we have a gitlab setup at
gitlab.example.com and an LDAP server running on ldap.example.com, and
users have a DN of the following form:
CN=username,OU=Users,OU=division,OU=department,DC=example,DC=com.
Patching
To make Gitlab work in such cases we need to partly modify its
authentication mechanism regarding LDAP.
First, we replace the omniauth-ldap module with this derivation. To
achieve this we apply the following patch to gitlab/Gemfile:
diff --git a/Gemfile b/Gemfile
index 1171eeb..f25bc60 100644
--- a/Gemfile
+++ b/Gemfile
## -44,4 +44,5 ## gem 'gitlab-grack', '~> 2.0.2', require: 'grack'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
-gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap"
+#gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap"
+gem 'gitlab_omniauth-ldap', :git => 'https://github.com/zakkak/omniauth-ldap.git', require: 'net-ldap', require: "omniauth-ldap"
Now, we need to perform the following actions:
sudo -u git -H bundle install --without development test mysql --path vendor/bundle --no-deployment
sudo -u git -H bundle install --deployment --without development test mysql aws
These commands will fetch the modified omniauth-ldap module in
gitlab/vendor/bundle/ruby/2.x.x/bundler/gems. Now that the module is
fetched, we need to modify it to use the DN our LDAP server expects. We
achieve this by patching lib/omniauth/strategies/ldap.rb in
gitlab/vendor/bundle/ruby/2.x.x/bundler/gems/omniauth-ldap with:
diff --git a/lib/omniauth/strategies/ldap.rb b/lib/omniauth/strategies/ldap.rb
index 9ea62b4..da5e648 100644
--- a/lib/omniauth/strategies/ldap.rb
+++ b/lib/omniauth/strategies/ldap.rb
## -39,7 +39,7 ## module OmniAuth
return fail!(:missing_credentials) if missing_credentials?
# The HACK! FIXME: do it in a more generic/configurable way
- #options[:bind_dn] = "CN=#{request['username']},OU=Test,DC=my,DC=example,DC=com"
+ #options[:bind_dn] = "CN=#{request['username']},OU=Users,OU=division,OU=department,DC=example,DC=com"
#options[:password] = request['password']
#adaptor = OmniAuth::LDAP::Adaptor.new #options
With this module, gitlab uses the user's credentials to bind to the LDAP
server and query it, as well as, to authenticate the user herself.
This however will only work as long as the users do not use ssh-keys to
authenticate with Gitlab. When authenticating through an ssh-key, by
default Gitlab queries the LDAP server to find out whether the
corresponding user is (still) a valid user or not. At this point, we
cannot use the user credentials to query the LDAP server, since the user
did not provide them to us. As a result we disable this mechanism,
essentially allowing users with registered ssh-keys but removed from the
LDAP server to still use our Gitlab setup. To prevent such users from
being able to still use your Gitlab setup, you will have to manually
delete their ssh-keys from any accounts in your setup.
To disable this mechanism we patch gitlab/lib/gitlab/ldap/access.rb
with:
diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb
index 16ff03c..9ebaeb6 100644
--- a/lib/gitlab/ldap/access.rb
+++ b/lib/gitlab/ldap/access.rb
## -14,15 +14,16 ## module Gitlab
end
def self.allowed?(user)
- self.open(user) do |access|
- if access.allowed?
- user.last_credential_check_at = Time.now
- user.save
- true
- else
- false
- end
- end
+ true
+ # self.open(user) do |access|
+ # if access.allowed?
+ # user.last_credential_check_at = Time.now
+ # user.save
+ # true
+ # else
+ # false
+ # end
+ # end
end
def initialize(user, adapter=nil)
## -32,20 +33,21 ## module Gitlab
end
def allowed?
- if Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter)
- return true unless ldap_config.active_directory
+ true
+ # if Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter)
+ # return true unless ldap_config.active_directory
- # Block user in GitLab if he/she was blocked in AD
- if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
- user.block unless user.blocked?
- false
- else
- user.activate if user.blocked? && !ldap_config.block_auto_created_users
- true
- end
- else
- false
- end
+ # # Block user in GitLab if he/she was blocked in AD
+ # if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
+ # user.block unless user.blocked?
+ # false
+ # else
+ # user.activate if user.blocked? && !ldap_config.block_auto_created_users
+ # true
+ # end
+ # else
+ # false
+ # end
rescue
false
end
Configuration
In gitlab.yml use something like the following (modify to your needs):
#
# 2. Auth settings
# ==========================
## LDAP settings
# You can inspect a sample of the LDAP users with login access by running:
# bundle exec rake gitlab:ldap:check RAILS_ENV=production
ldap:
enabled: true
servers:
##########################################################################
#
# Since GitLab 7.4, LDAP servers get ID's (below the ID is 'main'). GitLab
# Enterprise Edition now supports connecting to multiple LDAP servers.
#
# If you are updating from the old (pre-7.4) syntax, you MUST give your
# old server the ID 'main'.
#
##########################################################################
main: # 'main' is the GitLab 'provider ID' of this LDAP server
## label
#
# A human-friendly name for your LDAP server. It is OK to change the label later,
# for instance if you find out it is too large to fit on the web page.
#
# Example: 'Paris' or 'Acme, Ltd.'
label: 'LDAP_EXAMPLE_COM'
host: ldap.example.com
port: 636
uid: 'sAMAccountName'
method: 'ssl' # "tls" or "ssl" or "plain"
bind_dn: ''
password: ''
# This setting specifies if LDAP server is Active Directory LDAP server.
# For non AD servers it skips the AD specific queries.
# If your LDAP server is not AD, set this to false.
active_directory: true
# If allow_username_or_email_login is enabled, GitLab will ignore everything
# after the first '#' in the LDAP username submitted by the user on login.
#
# Example:
# - the user enters 'jane.doe#example.com' and 'p#ssw0rd' as LDAP credentials;
# - GitLab queries the LDAP server with 'jane.doe' and 'p#ssw0rd'.
#
# If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
# disable this setting, because the userPrincipalName contains an '#'.
allow_username_or_email_login: false
# To maintain tight control over the number of active users on your GitLab installation,
# enable this setting to keep new users blocked until they have been cleared by the admin
# (default: false).
block_auto_created_users: false
# Base where we can search for users
#
# Ex. ou=People,dc=gitlab,dc=example
#
base: 'OU=Users,OU=division,OU=department,DC=example,DC=com'
# Filter LDAP users
#
# Format: RFC 4515 http://tools.ietf.org/search/rfc4515
# Ex. (employeeType=developer)
#
# Note: GitLab does not support omniauth-ldap's custom filter syntax.
#
user_filter: '(&(objectclass=user)(objectclass=person))'
GitLab uses omniauth to manage multiple login sources (including LDAP).
So if you can somehow extend omniauth in order to manage the LDAP connection differently, you could fetch the password from a different source.
That would allow you to avoid keeping said password in the ldap section of the gitlab.yml config file.
When I update my Rails gems I find this errors (only in production, in development environment working good):
Curl::Err::SSLCACertificateError
Seems that is an SSL Certificate Authority Error, but why only in production is not working? And what can I do to resolve the problem?
My curb gems is v.0.8.3 with rails 3.2.8
(the mayor update was the rake, that now is v.10.0.2, but I don't know if this influence the good working of the curb gem).
FYI, this is the code that raise the error:
loginData = { :login => "myuser", :password => "mypass" }
loginJson = ActiveSupport::JSON.encode(loginData)
req = Curl::Easy.http_post("https://mysite.com", loginJson
) do |curl|
curl.headers['Content-type'] = 'application/json'
end
It sounds like you're using a self-signed SSL certificate on the server, which is fine. I do the same thing for internal services. You'll just want to make your client aware of the custom SSL certificate as well, that way it knows that it can be trusted.
Something like this should do the trick:
req = Curl::Easy.http_post("https://mysite.com", loginJson) do |curl|
curl.headers['Content-type'] = 'application/json'
curl.cacert = "/path/to/ca.crt"
curl.cert = "/path/to/cert.pem"
end
Of course, you'll probably want to extract these string constants into a config file.
I find an alternative solution, when I request a "curl" I put this additional propriety:
curl.ssl_verify_peer = false
I don't know if in term of security is the best solution, but for the moment it works...
I am trying to upload data to amazon s3 bucket.
I am using aws-s3 gem for this purpose.
I am giving right access key and secure key but still not able to execute S3Object.store/Bucket calls, though the connection is established. They return with error "AWS::S3::SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method."
Interestingly I am running another rails app with paperclip plugin to upload images to S3, and that is working like a charm! with same access key and secure key.
I have tried referencing some links mentioning same problem but to no luck.
[ https://forums.aws.amazon.com/thread.jspa?threadID=16020&tstart=0 ]
Any pointers/help/suggestions would be great. :)
I just got this problem because I did not supply the correct region in the request.
I am using fog and Carrierwave as per the railscast here and I had to configure the region in the config/initializer for Carrierwave
CarrierWave.configure do |config|
config.fog_credentials = {
provider: 'AWS', # required
aws_access_key_id: '[redacted]', # required unless using use_iam_profile
aws_secret_access_key: '[redacted]', # required unless using use_iam_profile
# use_iam_profile: false, # optional, defaults to false
region: 'eu-central-1', # optional, defaults to 'us-east-1'
# host: 's3.example.com', # optional, defaults to nil
# endpoint: 'https://s3.example.com:8080' # optional, defaults to nil
}
config.fog_directory = 'xxx' # required
# config.fog_public = false # optional, defaults to true
# config.fog_attributes = { cache_control: "public, max-age=#{365.days.to_i}" } # optional, defaults to {}
end
interestingly fog was redirected to the correct endpoint with the correct region by amazon, however, the redirected request got the failure on the authentication, maybe a problem with fog in such a situation. Fog did give a nice warning in the log
[fog][WARNING] fog: followed redirect to calm4-files.s3.amazonaws.com, connecting to the matching region will be more performant
but to be more accurate they should say not only more performant, but it will actually work as well