commit 936a14e225037aca4cdeac11c843c7985e636c88
Author: Ismaël Bouya <ismael.bouya@normalesup.org>
Date: Mon Jul 24 19:58:24 2017 +0200
Add LDAP to diaspora
diff --git a/Gemfile b/Gemfile
index 414b0138d..2a934e9c9 100644
--- a/Gemfile
+++ b/Gemfile
@@ -217,6 +217,9 @@ gem "thor", "0.19.1"
# gem "therubyracer", :platform => :ruby
+# LDAP
+gem 'net-ldap', '~> 0.16'
+
group :production do # we don"t install these on travis to speed up test runs
# Analytics
diff --git a/Gemfile.lock b/Gemfile.lock
index 84f8172e4..cdbf19fcd 100644
--- a/Gemfile.lock 2019-01-13 19:55:52.538561762 +0100
+++ b/Gemfile.lock 2019-01-13 19:58:11.087099067 +0100
@@ -398,6 +398,7 @@
mysql2 (0.5.2)
naught (1.1.0)
nenv (0.3.0)
+ net-ldap (0.16.1)
nio4r (2.3.1)
nokogiri (1.8.5)
mini_portile2 (~> 2.3.0)
@@ -820,6 +821,7 @@
minitest
mobile-fu (= 1.4.0)
mysql2 (= 0.5.2)
+ net-ldap (~> 0.16)
nokogiri (= 1.8.5)
omniauth (= 1.8.1)
omniauth-tumblr (= 1.2)
diff --git a/app/models/user.rb b/app/models/user.rb
index 940a48f25..d1e2beeee 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -337,6 +337,12 @@ class User < ActiveRecord::Base
end
def send_confirm_email
+ if skip_email_confirmation?
+ self.email = unconfirmed_email
+ self.unconfirmed_email = nil
+ save
+ end
+
return if unconfirmed_email.blank?
Workers::Mail::ConfirmEmail.perform_async(id)
end
@@ -554,6 +560,14 @@ class User < ActiveRecord::Base
end
end
+ def ldap_user?
+ AppConfig.ldap.enable? && ldap_dn.present?
+ end
+
+ def skip_email_confirmation?
+ ldap_user? && AppConfig.ldap.skip_email_confirmation?
+ end
+
private
def clearable_fields
diff --git a/config/defaults.yml b/config/defaults.yml
index c046aff07..66e9afa13 100644
--- a/config/defaults.yml
+++ b/config/defaults.yml
@@ -202,6 +202,20 @@ defaults:
scope: tags
include_user_tags: false
pod_tags:
+ ldap:
+ enable: false
+ host: localhost
+ port: 389
+ only_ldap: true
+ mail_attribute: mail
+ skip_email_confirmation: true
+ use_bind_dn: true
+ bind_dn: "cn=diaspora,dc=example,dc=com"
+ bind_pw: "password"
+ search_base: "dc=example,dc=com"
+ search_filter: "uid=%{username}"
+ bind_template: "uid=%{username},dc=example,dc=com"
+
development:
environment:
diff --git a/config/diaspora.yml.example b/config/diaspora.yml.example
index b2573625d..c357c8651 100644
--- a/config/diaspora.yml.example
+++ b/config/diaspora.yml.example
@@ -710,6 +710,36 @@ configuration: ## Section
## If scope is 'tags', a comma separated list of tags here can be set.
## For example "linux,diaspora", to receive posts related to these tags
#pod_tags:
+ ldap:
+ # Uncomment next line if you want to use LDAP on your instance
+ enable: true
+ host: localhost
+ port: 389
+ # Use only LDAP authentication (don't try other means)
+ only_ldap: true
+ # LDAP attribute to find the user's e-mail. Necessary to create accounts
+ # for not existing users
+ mail_attribute: mail
+ # Skip e-mail confirmation when creating an account via LDAP.
+ skip_email_confirmation: true
+ # ----- Using bind_dn and bind_pw
+ # bind_dn and bind_pw may be used if the diaspora instance
+ # should be able to connect to LDAP to find and search for users.
+
+ use_bind_dn: true
+ bind_dn: "cn=diaspora,dc=example,dc=com"
+ bind_pw: "password"
+ search_base: "dc=example,dc=com"
+ # This is the filter with which to search for the user. %{username} will
+ # be replaced by the given login.
+ search_filter: "uid=%{username}"
+ #
+ # ----- Using template
+ # This setting doesn't require a diaspora LDAP user. Use a template, and
+ # diaspora will try to login with the templated dn and password
+ #
+ # bind_template: "uid=%{username},dc=example,dc=com"
+
## Here you can override settings defined above if you need
## to have them different in different environments.
diff --git a/config/initializers/0_ldap_authenticatable.rb b/config/initializers/0_ldap_authenticatable.rb
new file mode 100644
index 000000000..49846502f
--- /dev/null
+++ b/config/initializers/0_ldap_authenticatable.rb
@@ -0,0 +1,82 @@
+require 'net/ldap'
+require 'devise/strategies/authenticatable'
+
+module Devise
+ module Strategies
+ class LdapAuthenticatable < Authenticatable
+ def valid?
+ AppConfig.ldap.enable? && params[:user].present?
+ end
+
+ def authenticate!
+ ldap = Net::LDAP.new(
+ host: AppConfig.ldap.host,
+ port: AppConfig.ldap.port,
+ encryption: :simple_tls,
+ )
+
+ if AppConfig.ldap.use_bind_dn?
+ ldap.auth AppConfig.ldap.bind_dn, AppConfig.ldap.bind_pw
+
+ if !ldap.bind
+ return fail(:ldap_configuration_error)
+ end
+
+ search_filter = AppConfig.ldap.search_filter % { username: params[:user][:username] }
+
+ result = ldap.search(base: AppConfig.ldap.search_base, filter: search_filter, result_set: true)
+
+ if result.count != 1
+ return login_fail
+ end
+
+ user_dn = result.first.dn
+ user_email = result.first[AppConfig.ldap.mail_attribute].first
+ else
+ user_dn = AppConfig.ldap.bind_template % { username: params[:user][:username] }
+ end
+
+ ldap.auth user_dn, params[:user][:password]
+
+ if ldap.bind
+ user = User.find_by(ldap_dn: user_dn)
+
+ # We don't want to trust too much the email attribute from
+ # LDAP: if the user can edit it himself, he may login as
+ # anyone
+ if user.nil?
+ if !AppConfig.ldap.use_bind_dn?
+ result = ldap.search(base: user_dn, scope: Net::LDAP::SearchScope_BaseObject, filter: "(objectClass=*)", result_set: true)
+ user_email = result.first[AppConfig.ldap.mail_attribute].first
+ end
+
+ if user_email.present? && User.find_by(email: user_email).nil?
+ # Password is used for remember_me token
+ user = User.build(email: user_email, ldap_dn: user_dn, password: SecureRandom.hex, username: params[:user][:username])
+ user.save
+ user.seed_aspects
+ elsif User.find_by(email: user_email).present?
+ return fail(:ldap_existing_email)
+ else
+ return fail(:ldap_cannot_create_account_without_email)
+ end
+ end
+
+ success!(user)
+ else
+ return login_fail
+ end
+ end
+
+ def login_fail
+ if AppConfig.ldap.only_ldap?
+ return fail(:ldap_invalid_login)
+ else
+ return pass
+ end
+ end
+ end
+ end
+end
+
+Warden::Strategies.add(:ldap_authenticatable, Devise::Strategies::LdapAuthenticatable)
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 3698e2373..14e88063e 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -250,10 +250,9 @@ Devise.setup do |config|
# If you want to use other strategies, that are not supported by Devise, or
# change the failure app, you can configure them inside the config.warden block.
#
- # config.warden do |manager|
- # manager.intercept_401 = false
- # manager.default_strategies(:scope => :user).unshift :some_external_strategy
- # end
+ config.warden do |manager|
+ manager.default_strategies(scope: :user).unshift :ldap_authenticatable
+ end
# ==> Mountable engine configurations
# When using Devise inside an engine, let's call it `MyEngine`, and this engine
diff --git a/db/migrate/20170724182100_add_ldap_dn_to_users.rb b/db/migrate/20170724182100_add_ldap_dn_to_users.rb
new file mode 100644
index 000000000..f5cc84d11
--- /dev/null
+++ b/db/migrate/20170724182100_add_ldap_dn_to_users.rb
@@ -0,0 +1,6 @@
+class AddLdapDnToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :ldap_dn, :text, null: true, default: nil
+ add_index :users, ['ldap_dn'], :length => { "ldap_dn" => 191 }
+ end
+end