1
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Gemfile
									
									
									
									
									
								
							| @@ -33,6 +33,7 @@ gem 'devise', '~> 4.4' | ||||
| gem 'devise-two-factor', '~> 3.0' | ||||
|  | ||||
| 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-saml', '~> 1.8', install_if: -> { ENV['SAML_ENABLED'] == 'true' } | ||||
| gem 'omniauth', '~> 1.2' | ||||
|   | ||||
| @@ -316,6 +316,7 @@ GEM | ||||
|     multi_json (1.12.2) | ||||
|     multipart-post (2.0.0) | ||||
|     necromancer (0.4.0) | ||||
|     net-ldap (0.16.1) | ||||
|     net-scp (1.2.1) | ||||
|       net-ssh (>= 2.6.5) | ||||
|     net-ssh (4.2.0) | ||||
| @@ -666,6 +667,7 @@ DEPENDENCIES | ||||
|   memory_profiler | ||||
|   microformats (~> 4.0) | ||||
|   mime-types (~> 3.1) | ||||
|   net-ldap (~> 0.10) | ||||
|   nokogiri (~> 1.8) | ||||
|   nsa (~> 0.2) | ||||
|   oj (~> 3.3) | ||||
|   | ||||
| @@ -14,7 +14,7 @@ class ApplicationController < ActionController::Base | ||||
|   helper_method :current_session | ||||
|   helper_method :current_theme | ||||
|   helper_method :single_user_mode? | ||||
|   helper_method :use_pam? | ||||
|   helper_method :use_seamless_external_login? | ||||
|  | ||||
|   rescue_from ActionController::RoutingError, 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? | ||||
|   end | ||||
|  | ||||
|   def use_pam? | ||||
|     Devise.pam_authentication | ||||
|   def use_seamless_external_login? | ||||
|     Devise.pam_authentication || Devise.ldap_authentication | ||||
|   end | ||||
|  | ||||
|   def current_account | ||||
|   | ||||
| @@ -37,7 +37,7 @@ class Auth::SessionsController < Devise::SessionsController | ||||
|     if session[:otp_user_id] | ||||
|       User.find(session[:otp_user_id]) | ||||
|     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] }) | ||||
|       else | ||||
|         User.find_for_authentication(email: user_params[:email]) | ||||
|   | ||||
| @@ -52,7 +52,6 @@ class User < ApplicationRecord | ||||
|   devise :registerable, :recoverable, :rememberable, :trackable, :validatable, | ||||
|          :confirmable | ||||
|  | ||||
|   devise :pam_authenticatable if Devise.pam_authentication | ||||
|   devise :omniauthable | ||||
|  | ||||
|   belongs_to :account, inverse_of: :user | ||||
| @@ -117,6 +116,12 @@ class User < ApplicationRecord | ||||
|     acc.destroy! unless save | ||||
|   end | ||||
|  | ||||
|   def ldap_setup(_attributes) | ||||
|     self.confirmed_at = Time.now.utc | ||||
|     self.admin = false | ||||
|     save! | ||||
|   end | ||||
|  | ||||
|   def confirmed? | ||||
|     confirmed_at.present? | ||||
|   end | ||||
| @@ -247,17 +252,17 @@ class User < ApplicationRecord | ||||
|   end | ||||
|  | ||||
|   def password_required? | ||||
|     return false if Devise.pam_authentication | ||||
|     return false if Devise.pam_authentication || Devise.ldap_authentication | ||||
|     super | ||||
|   end | ||||
|  | ||||
|   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 | ||||
|   end | ||||
|  | ||||
|   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 | ||||
|   end | ||||
|  | ||||
| @@ -280,6 +285,17 @@ class User < ApplicationRecord | ||||
|     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 = {}) | ||||
|     return nil unless Devise.pam_authentication | ||||
|     super | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| = simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| | ||||
|   = 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 :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 | ||||
|       = f.button :button, t('auth.set_new_password'), type: :submit | ||||
|   - else | ||||
|     = t('simple_form.labels.defaults.pam_account') | ||||
|     %p.hint= t('users.seamless_external_login') | ||||
|  | ||||
| .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| | ||||
|   = 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 :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' } | ||||
| @@ -13,7 +13,7 @@ | ||||
|     .actions | ||||
|       = f.button :button, t('generic.save_changes'), type: :submit | ||||
|   - else | ||||
|     = t('simple_form.labels.defaults.pam_account') | ||||
|     %p.hint= t('users.seamless_external_login') | ||||
|  | ||||
| %hr/ | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
|   = render partial: 'shared/og' | ||||
|  | ||||
| = 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') } | ||||
|   - 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') } | ||||
|   | ||||
| @@ -12,6 +12,7 @@ require_relative '../lib/paperclip/gif_transcoder' | ||||
| require_relative '../lib/paperclip/video_transcoder' | ||||
| require_relative '../lib/mastodon/snowflake' | ||||
| require_relative '../lib/mastodon/version' | ||||
| require_relative '../lib/devise/ldap_authenticatable' | ||||
|  | ||||
| Dotenv::Railtie.load | ||||
|  | ||||
|   | ||||
| @@ -36,6 +36,26 @@ module Devise | ||||
|   mattr_accessor :pam_controlled_service | ||||
|   @@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 | ||||
|     def valid? | ||||
|       super && ::Devise.pam_authentication | ||||
| @@ -45,6 +65,8 @@ end | ||||
|  | ||||
| Devise.setup do |config| | ||||
|   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_backupable | ||||
|   end | ||||
| @@ -324,4 +346,16 @@ Devise.setup do |config| | ||||
|     config.pam_default_service    = ENV.fetch('PAM_DEFAULT_SERVICE') { 'rpam' } | ||||
|     config.pam_controlled_service = ENV.fetch('PAM_CONTROLLED_SERVICE') { 'rpam' } | ||||
|   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 | ||||
|   | ||||
| @@ -769,4 +769,5 @@ en: | ||||
|   users: | ||||
|     invalid_email: The e-mail address is invalid | ||||
|     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:' | ||||
|   | ||||
							
								
								
									
										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 | ||||
		Reference in New Issue
	
	Block a user