parent
e852872846
commit
47bdb9b33b
12 changed files with 117 additions and 13 deletions
1
Gemfile
1
Gemfile
|
@ -33,6 +33,7 @@ gem 'devise', '~> 4.4'
|
||||||
gem 'devise-two-factor', '~> 3.0'
|
gem 'devise-two-factor', '~> 3.0'
|
||||||
|
|
||||||
gem 'devise_pam_authenticatable2', '~> 8.0', install_if: -> { ENV['PAM_ENABLED'] == 'true' }
|
gem 'devise_pam_authenticatable2', '~> 8.0', install_if: -> { ENV['PAM_ENABLED'] == 'true' }
|
||||||
|
gem 'net-ldap', '~> 0.10', install_if: -> { ENV['LDAP_ENABLED'] == 'true' }
|
||||||
gem 'omniauth-cas', '~> 1.1', install_if: -> { ENV['CAS_ENABLED'] == 'true' }
|
gem 'omniauth-cas', '~> 1.1', install_if: -> { ENV['CAS_ENABLED'] == 'true' }
|
||||||
gem 'omniauth-saml', '~> 1.8', install_if: -> { ENV['SAML_ENABLED'] == 'true' }
|
gem 'omniauth-saml', '~> 1.8', install_if: -> { ENV['SAML_ENABLED'] == 'true' }
|
||||||
gem 'omniauth', '~> 1.2'
|
gem 'omniauth', '~> 1.2'
|
||||||
|
|
|
@ -316,6 +316,7 @@ GEM
|
||||||
multi_json (1.12.2)
|
multi_json (1.12.2)
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
necromancer (0.4.0)
|
necromancer (0.4.0)
|
||||||
|
net-ldap (0.16.1)
|
||||||
net-scp (1.2.1)
|
net-scp (1.2.1)
|
||||||
net-ssh (>= 2.6.5)
|
net-ssh (>= 2.6.5)
|
||||||
net-ssh (4.2.0)
|
net-ssh (4.2.0)
|
||||||
|
@ -666,6 +667,7 @@ DEPENDENCIES
|
||||||
memory_profiler
|
memory_profiler
|
||||||
microformats (~> 4.0)
|
microformats (~> 4.0)
|
||||||
mime-types (~> 3.1)
|
mime-types (~> 3.1)
|
||||||
|
net-ldap (~> 0.10)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
nsa (~> 0.2)
|
nsa (~> 0.2)
|
||||||
oj (~> 3.3)
|
oj (~> 3.3)
|
||||||
|
|
|
@ -14,7 +14,7 @@ class ApplicationController < ActionController::Base
|
||||||
helper_method :current_session
|
helper_method :current_session
|
||||||
helper_method :current_theme
|
helper_method :current_theme
|
||||||
helper_method :single_user_mode?
|
helper_method :single_user_mode?
|
||||||
helper_method :use_pam?
|
helper_method :use_seamless_external_login?
|
||||||
|
|
||||||
rescue_from ActionController::RoutingError, with: :not_found
|
rescue_from ActionController::RoutingError, with: :not_found
|
||||||
rescue_from ActiveRecord::RecordNotFound, with: :not_found
|
rescue_from ActiveRecord::RecordNotFound, with: :not_found
|
||||||
|
@ -76,8 +76,8 @@ class ApplicationController < ActionController::Base
|
||||||
@single_user_mode ||= Rails.configuration.x.single_user_mode && Account.exists?
|
@single_user_mode ||= Rails.configuration.x.single_user_mode && Account.exists?
|
||||||
end
|
end
|
||||||
|
|
||||||
def use_pam?
|
def use_seamless_external_login?
|
||||||
Devise.pam_authentication
|
Devise.pam_authentication || Devise.ldap_authentication
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_account
|
def current_account
|
||||||
|
|
|
@ -37,7 +37,7 @@ class Auth::SessionsController < Devise::SessionsController
|
||||||
if session[:otp_user_id]
|
if session[:otp_user_id]
|
||||||
User.find(session[:otp_user_id])
|
User.find(session[:otp_user_id])
|
||||||
elsif user_params[:email]
|
elsif user_params[:email]
|
||||||
if use_pam? && Devise.check_at_sign && user_params[:email].index('@').nil?
|
if use_seamless_external_login? && Devise.check_at_sign && user_params[:email].index('@').nil?
|
||||||
User.joins(:account).find_by(accounts: { username: user_params[:email] })
|
User.joins(:account).find_by(accounts: { username: user_params[:email] })
|
||||||
else
|
else
|
||||||
User.find_for_authentication(email: user_params[:email])
|
User.find_for_authentication(email: user_params[:email])
|
||||||
|
|
|
@ -52,7 +52,6 @@ class User < ApplicationRecord
|
||||||
devise :registerable, :recoverable, :rememberable, :trackable, :validatable,
|
devise :registerable, :recoverable, :rememberable, :trackable, :validatable,
|
||||||
:confirmable
|
:confirmable
|
||||||
|
|
||||||
devise :pam_authenticatable if Devise.pam_authentication
|
|
||||||
devise :omniauthable
|
devise :omniauthable
|
||||||
|
|
||||||
belongs_to :account, inverse_of: :user
|
belongs_to :account, inverse_of: :user
|
||||||
|
@ -117,6 +116,12 @@ class User < ApplicationRecord
|
||||||
acc.destroy! unless save
|
acc.destroy! unless save
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ldap_setup(_attributes)
|
||||||
|
self.confirmed_at = Time.now.utc
|
||||||
|
self.admin = false
|
||||||
|
save!
|
||||||
|
end
|
||||||
|
|
||||||
def confirmed?
|
def confirmed?
|
||||||
confirmed_at.present?
|
confirmed_at.present?
|
||||||
end
|
end
|
||||||
|
@ -247,17 +252,17 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def password_required?
|
def password_required?
|
||||||
return false if Devise.pam_authentication
|
return false if Devise.pam_authentication || Devise.ldap_authentication
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_reset_password_instructions
|
def send_reset_password_instructions
|
||||||
return false if encrypted_password.blank? && Devise.pam_authentication
|
return false if encrypted_password.blank? && (Devise.pam_authentication || Devise.ldap_authentication)
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_password!(new_password, new_password_confirmation)
|
def reset_password!(new_password, new_password_confirmation)
|
||||||
return false if encrypted_password.blank? && Devise.pam_authentication
|
return false if encrypted_password.blank? && (Devise.pam_authentication || Devise.ldap_authentication)
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -280,6 +285,17 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.ldap_get_user(attributes = {})
|
||||||
|
resource = joins(:account).find_by(accounts: { username: attributes[Devise.ldap_uid.to_sym].first })
|
||||||
|
|
||||||
|
if resource.blank?
|
||||||
|
resource = new(email: attributes[:mail].first, account_attributes: { username: attributes[Devise.ldap_uid.to_sym].first })
|
||||||
|
resource.ldap_setup(attributes)
|
||||||
|
end
|
||||||
|
|
||||||
|
resource
|
||||||
|
end
|
||||||
|
|
||||||
def self.authenticate_with_pam(attributes = {})
|
def self.authenticate_with_pam(attributes = {})
|
||||||
return nil unless Devise.pam_authentication
|
return nil unless Devise.pam_authentication
|
||||||
super
|
super
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
= simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f|
|
= simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f|
|
||||||
= render 'shared/error_messages', object: resource
|
= render 'shared/error_messages', object: resource
|
||||||
|
|
||||||
- if !use_pam? || resource.encrypted_password.present?
|
- if !use_seamless_external_login?? || resource.encrypted_password.present?
|
||||||
= f.input :reset_password_token, as: :hidden
|
= f.input :reset_password_token, as: :hidden
|
||||||
|
|
||||||
= f.input :password, autofocus: true, placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }
|
= f.input :password, autofocus: true, placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }
|
||||||
|
@ -13,6 +13,6 @@
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('auth.set_new_password'), type: :submit
|
= f.button :button, t('auth.set_new_password'), type: :submit
|
||||||
- else
|
- else
|
||||||
= t('simple_form.labels.defaults.pam_account')
|
%p.hint= t('users.seamless_external_login')
|
||||||
|
|
||||||
.form-footer= render 'auth/shared/links'
|
.form-footer= render 'auth/shared/links'
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: 'auth_edit' }) do |f|
|
= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: 'auth_edit' }) do |f|
|
||||||
= render 'shared/error_messages', object: resource
|
= render 'shared/error_messages', object: resource
|
||||||
|
|
||||||
- if !use_pam? || resource.encrypted_password.present?
|
- if !use_seamless_external_login? || resource.encrypted_password.present?
|
||||||
= f.input :email, placeholder: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
|
= f.input :email, placeholder: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
|
||||||
= f.input :password, placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }
|
= f.input :password, placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }
|
||||||
= f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }
|
= f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('generic.save_changes'), type: :submit
|
= f.button :button, t('generic.save_changes'), type: :submit
|
||||||
- else
|
- else
|
||||||
= t('simple_form.labels.defaults.pam_account')
|
%p.hint= t('users.seamless_external_login')
|
||||||
|
|
||||||
%hr/
|
%hr/
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
= render partial: 'shared/og'
|
= render partial: 'shared/og'
|
||||||
|
|
||||||
= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
|
= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
|
||||||
- if use_pam?
|
- if use_seamless_external_login?
|
||||||
= f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.username_or_email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username_or_email') }
|
= f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.username_or_email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username_or_email') }
|
||||||
- else
|
- else
|
||||||
= f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
|
= f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
|
||||||
|
|
|
@ -12,6 +12,7 @@ require_relative '../lib/paperclip/gif_transcoder'
|
||||||
require_relative '../lib/paperclip/video_transcoder'
|
require_relative '../lib/paperclip/video_transcoder'
|
||||||
require_relative '../lib/mastodon/snowflake'
|
require_relative '../lib/mastodon/snowflake'
|
||||||
require_relative '../lib/mastodon/version'
|
require_relative '../lib/mastodon/version'
|
||||||
|
require_relative '../lib/devise/ldap_authenticatable'
|
||||||
|
|
||||||
Dotenv::Railtie.load
|
Dotenv::Railtie.load
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,26 @@ module Devise
|
||||||
mattr_accessor :pam_controlled_service
|
mattr_accessor :pam_controlled_service
|
||||||
@@pam_controlled_service = nil
|
@@pam_controlled_service = nil
|
||||||
|
|
||||||
|
mattr_accessor :check_at_sign
|
||||||
|
@@check_at_sign = false
|
||||||
|
|
||||||
|
mattr_accessor :ldap_authentication
|
||||||
|
@@ldap_authentication = false
|
||||||
|
mattr_accessor :ldap_host
|
||||||
|
@@ldap_host = nil
|
||||||
|
mattr_accessor :ldap_port
|
||||||
|
@@ldap_port = nil
|
||||||
|
mattr_accessor :ldap_method
|
||||||
|
@@ldap_method = nil
|
||||||
|
mattr_accessor :ldap_base
|
||||||
|
@@ldap_base = nil
|
||||||
|
mattr_accessor :ldap_uid
|
||||||
|
@@ldap_uid = nil
|
||||||
|
mattr_accessor :ldap_bind_dn
|
||||||
|
@@ldap_bind_dn = nil
|
||||||
|
mattr_accessor :ldap_password
|
||||||
|
@@ldap_password = nil
|
||||||
|
|
||||||
class Strategies::PamAuthenticatable
|
class Strategies::PamAuthenticatable
|
||||||
def valid?
|
def valid?
|
||||||
super && ::Devise.pam_authentication
|
super && ::Devise.pam_authentication
|
||||||
|
@ -45,6 +65,8 @@ end
|
||||||
|
|
||||||
Devise.setup do |config|
|
Devise.setup do |config|
|
||||||
config.warden do |manager|
|
config.warden do |manager|
|
||||||
|
manager.default_strategies(scope: :user).unshift :ldap_authenticatable if Devise.ldap_authentication
|
||||||
|
manager.default_strategies(scope: :user).unshift :pam_authenticatable if Devise.pam_authentication
|
||||||
manager.default_strategies(scope: :user).unshift :two_factor_authenticatable
|
manager.default_strategies(scope: :user).unshift :two_factor_authenticatable
|
||||||
manager.default_strategies(scope: :user).unshift :two_factor_backupable
|
manager.default_strategies(scope: :user).unshift :two_factor_backupable
|
||||||
end
|
end
|
||||||
|
@ -324,4 +346,16 @@ Devise.setup do |config|
|
||||||
config.pam_default_service = ENV.fetch('PAM_DEFAULT_SERVICE') { 'rpam' }
|
config.pam_default_service = ENV.fetch('PAM_DEFAULT_SERVICE') { 'rpam' }
|
||||||
config.pam_controlled_service = ENV.fetch('PAM_CONTROLLED_SERVICE') { 'rpam' }
|
config.pam_controlled_service = ENV.fetch('PAM_CONTROLLED_SERVICE') { 'rpam' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if ENV['LDAP_ENABLED'] == 'true'
|
||||||
|
config.ldap_authentication = true
|
||||||
|
config.check_at_sign = true
|
||||||
|
config.ldap_host = ENV.fetch('LDAP_HOST', 'localhost')
|
||||||
|
config.ldap_port = ENV.fetch('LDAP_PORT', 389).to_i
|
||||||
|
config.ldap_method = ENV.fetch('LDAP_METHOD', :simple_tls).to_sym
|
||||||
|
config.ldap_base = ENV.fetch('LDAP_BASE')
|
||||||
|
config.ldap_bind_dn = ENV.fetch('LDAP_BIND_DN')
|
||||||
|
config.ldap_password = ENV.fetch('LDAP_PASSWORD')
|
||||||
|
config.ldap_uid = ENV.fetch('LDAP_UID', 'cn')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -769,4 +769,5 @@ en:
|
||||||
users:
|
users:
|
||||||
invalid_email: The e-mail address is invalid
|
invalid_email: The e-mail address is invalid
|
||||||
invalid_otp_token: Invalid two-factor code
|
invalid_otp_token: Invalid two-factor code
|
||||||
|
seamless_external_login: You are logged in via an external service, so password and e-mail settings are not available.
|
||||||
signed_in_as: 'Signed in as:'
|
signed_in_as: 'Signed in as:'
|
||||||
|
|
49
lib/devise/ldap_authenticatable.rb
Normal file
49
lib/devise/ldap_authenticatable.rb
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
if ENV['LDAP_ENABLED'] == 'true'
|
||||||
|
require 'net/ldap'
|
||||||
|
require 'devise/strategies/authenticatable'
|
||||||
|
|
||||||
|
module Devise
|
||||||
|
module Strategies
|
||||||
|
class LdapAuthenticatable < Authenticatable
|
||||||
|
def authenticate!
|
||||||
|
if params[:user]
|
||||||
|
ldap = Net::LDAP.new(
|
||||||
|
host: Devise.ldap_host,
|
||||||
|
port: Devise.ldap_port,
|
||||||
|
base: Devise.ldap_base,
|
||||||
|
encryption: {
|
||||||
|
method: Devise.ldap_method,
|
||||||
|
tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS,
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
method: :simple,
|
||||||
|
username: Devise.ldap_bind_dn,
|
||||||
|
password: Devise.ldap_password,
|
||||||
|
},
|
||||||
|
connect_timeout: 10
|
||||||
|
)
|
||||||
|
|
||||||
|
if (user_info = ldap.bind_as(base: Devise.ldap_base, filter: "(#{Devise.ldap_uid}=#{email})", password: password))
|
||||||
|
user = User.ldap_get_user(user_info.first)
|
||||||
|
success!(user)
|
||||||
|
else
|
||||||
|
return fail(:invalid_login)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def email
|
||||||
|
params[:user][:email]
|
||||||
|
end
|
||||||
|
|
||||||
|
def password
|
||||||
|
params[:user][:password]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Warden::Strategies.add(:ldap_authenticatable, Devise::Strategies::LdapAuthenticatable)
|
||||||
|
end
|
Loading…
Reference in a new issue