From 64c34813ffc9a1987007c3e5c49a230380c031f7 Mon Sep 17 00:00:00 2001 From: Mark Bussey Date: Wed, 6 Dec 2017 23:09:09 -0600 Subject: [PATCH] WIP - PImplement basic LDAP authentication Add devise_ldap_authenticatable - see https://github.com/cschiewek/devise_ldap_authenticatable#usage * Add gem to gemfile * Run devise_ldap_authenticatable generator * Add username to User model * Authenticate against username instead of email Add ladle wrapper for ApacheDS LDAP server * Add gem to gemfile * Add rake task to launch LDAP server * Add minimal ldap user setup file --- Gemfile | 2 + Gemfile.lock | 9 ++++ app/controllers/application_controller.rb | 3 ++ app/models/user.rb | 6 ++- app/views/devise/sessions/new.html.erb | 2 +- config/initializers/devise.rb | 17 ++++-- config/ldap.yml | 53 +++++++++++++++++++ config/ldap_data_dev.ldif | 34 ++++++++++++ .../20171207021357_add_username_to_users.rb | 6 +++ db/schema.rb | 4 +- lib/tasks/ladle.rake | 22 ++++++++ 11 files changed, 152 insertions(+), 6 deletions(-) create mode 100644 config/ldap.yml create mode 100644 config/ldap_data_dev.ldif create mode 100644 db/migrate/20171207021357_add_username_to_users.rb create mode 100644 lib/tasks/ladle.rake diff --git a/Gemfile b/Gemfile index d6c34a72..2f9f2d0b 100644 --- a/Gemfile +++ b/Gemfile @@ -66,8 +66,10 @@ gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] gem 'active_job_status', '~> 1.2.1' gem 'devise' +gem 'devise_ldap_authenticatable' gem 'devise-guests', '~> 0.6' gem 'handle-system', '0.1.1' +gem 'ladle' gem 'mysql2' gem 'react-rails' gem 'redis-activesupport' diff --git a/Gemfile.lock b/Gemfile.lock index 7c4f5437..c60d89c4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -219,6 +219,9 @@ GEM warden (~> 1.2.3) devise-guests (0.6.0) devise + devise_ldap_authenticatable (0.8.5) + devise (>= 3.4.1) + net-ldap (>= 0.6.0, <= 0.11) diff-lcs (1.3) docile (1.1.5) dotenv (2.2.1) @@ -449,6 +452,8 @@ GEM kaminari-core (1.0.1) kaminari_route_prefix (0.1.1) kaminari (~> 1.0) + ladle (1.0.1) + open4 (~> 1.0) launchy (2.4.3) addressable (~> 2.3) ld-patch (0.3.2) @@ -523,6 +528,7 @@ GEM nest (2.1.0) redic net-http-persistent (2.9.4) + net-ldap (0.11) net-scp (1.2.1) net-ssh (>= 2.6.5) net-ssh (4.1.0) @@ -545,6 +551,7 @@ GEM activesupport nokogiri (>= 1.4.2) solrizer (~> 3.3) + open4 (1.3.4) openseadragon (0.4.0) rails (> 3.2.0) orm_adapter (0.5.0) @@ -874,6 +881,7 @@ DEPENDENCIES database_cleaner devise devise-guests (~> 0.6) + devise_ldap_authenticatable dotenv-rails factory_girl_rails fcrepo_wrapper @@ -884,6 +892,7 @@ DEPENDENCIES hyrax (= 2.0.0) jbuilder (~> 2.5) jquery-rails + ladle listen (~> 3.0.5) mysql2 nokogiri (>= 1.8.1) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4163086e..01a5b63a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,4 +1,7 @@ class ApplicationController < ActionController::Base + rescue_from DeviseLdapAuthenticatable::LdapException do |exception| + render :text => exception, :status => 500 + end helper Openseadragon::OpenseadragonHelper # Adds a few additional behaviors into the application controller include Blacklight::Controller diff --git a/app/models/user.rb b/app/models/user.rb index bb59e96a..c739735c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -15,7 +15,7 @@ class User < ApplicationRecord include Blacklight::User # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable - devise :database_authenticatable, :registerable, + devise :ldap_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable # Method added by Blacklight; Blacklight uses #to_s on your @@ -49,4 +49,8 @@ def mailboxer_email(_object) def preferred_locale 'en' end + + def ldap_before_save + self.email = Devise::LDAP::Adapter.get_ldap_param(self.username,"mail").first + end end diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index 9f2265f6..969720f8 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -3,7 +3,7 @@ <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<%= f.label 'Tufts Username' %>
- <%= f.email_field :email, autofocus: true %> + <%= f.text_field :username, autofocus: true %>
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 86e57ef4..c0e0cb0e 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -1,6 +1,17 @@ # 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 = false + # 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. @@ -34,7 +45,7 @@ # 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] +config.authentication_keys = [:username] # 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 @@ -46,12 +57,12 @@ # 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.case_insensitive_keys = [:username] # Configure which authentication keys should have whitespace stripped. # These keys will have whitespace before and after removed upon creating or # modifying a user and when used to authenticate or find a user. Default is :email. - config.strip_whitespace_keys = [:email] + config.strip_whitespace_keys = [:username] # Tell if authentication through request.params is enabled. True by default. # It can be set to an array that will enable params authentication only for the diff --git a/config/ldap.yml b/config/ldap.yml new file mode 100644 index 00000000..77087313 --- /dev/null +++ b/config/ldap.yml @@ -0,0 +1,53 @@ +## 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=example,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" + - cn=admins,ou=groups,dc=example,dc=org + - cn=users,ou=groups,dc=example,dc=org + # 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=example,dc=org"] + ## 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 + require_attribute_presence: + mail: true + +## Environment + +development: + host: localhost + port: 3389 + attribute: cn + base: ou=people,dc=example,dc=org + admin_user: cn=admin,dc=test,dc=com + admin_password: admin_password + ssl: false + # <<: *AUTHORIZATIONS + +test: + host: localhost + port: 3389 + attribute: cn + base: ou=people,dc=example,dc=com + admin_user: cn=admin,dc=test,dc=com + admin_password: admin_password + ssl: false + # <<: *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 diff --git a/config/ldap_data_dev.ldif b/config/ldap_data_dev.ldif new file mode 100644 index 00000000..cd116834 --- /dev/null +++ b/config/ldap_data_dev.ldif @@ -0,0 +1,34 @@ + +version: 1 + +# people.example.org +dn: ou=people,dc=example,dc=org +objectClass: top +objectClass: organizationalUnit +ou: people + +# user.people.examle.org +dn: cn=user,ou=people,dc=example,dc=org +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +sn: Ffrind +givenName: Rhyw +uid: example_user +mail: user@example.org +cn: user +userPassword: password + +# admin.people.examle.org +dn: cn=admin,ou=people,dc=example,dc=org +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +sn: Arall +givenName: Rhywun +uid: admin_user +mail: admin@example.org +cn: admin +userPassword: password diff --git a/db/migrate/20171207021357_add_username_to_users.rb b/db/migrate/20171207021357_add_username_to_users.rb new file mode 100644 index 00000000..7ba31180 --- /dev/null +++ b/db/migrate/20171207021357_add_username_to_users.rb @@ -0,0 +1,6 @@ +class AddUsernameToUsers < ActiveRecord::Migration[5.0] + def change + add_column :users, :username, :string + add_index :users, :username, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index feb368c0..f7c155c6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171009170516) do +ActiveRecord::Schema.define(version: 20171207021357) do create_table "batch_tasks", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t| t.string "batch_type" @@ -546,8 +546,10 @@ t.string "arkivo_subscription" t.binary "zotero_token", limit: 65535 t.string "zotero_userid" + t.string "username" t.index ["email"], name: "index_users_on_email", unique: true, using: :btree t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree + t.index ["username"], name: "index_users_on_username", unique: true, using: :btree end create_table "version_committers", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t| diff --git a/lib/tasks/ladle.rake b/lib/tasks/ladle.rake new file mode 100644 index 00000000..beff72fa --- /dev/null +++ b/lib/tasks/ladle.rake @@ -0,0 +1,22 @@ +require 'ladle' + +'Start a ladle server' +task :ladle do + conf_path = Rails.root.join('config') + + server = Ladle::Server.new( + port: Rails.application.config_for(:ldap)['port'], + quiet: false, + ldif: conf_path.join('ldap_data_dev.ldif').to_s + ) + + begin + puts 'Starting LDAP server on port 3398' + server.start + sleep + rescue Interrupt + puts 'Stopping server' + ensure + server.stop + end +end