From 6bb71e30500c0ea7e9a7ae96d221b10b0bff0fbb Mon Sep 17 00:00:00 2001 From: bearns Date: Wed, 14 Oct 2020 17:23:00 +0300 Subject: [PATCH] Some minor fixes --- redmine/redmine_ldap_passwd/README.md | 27 ++++++++ .../app/models/auth_source_ldap_passwd.rb | 57 ++++++++++++++++ .../redmine_ldap_passwd/config/locales/en.yml | 3 + redmine/redmine_ldap_passwd/init.rb | 36 ++++++++++ ...ne_ldap_passwd_account_controller_patch.rb | 66 +++++++++++++++++++ ...e_ldap_passwd_auth_sources_helper_patch.rb | 25 +++++++ ...redmine_ldap_passwd_my_controller_patch.rb | 64 ++++++++++++++++++ 7 files changed, 278 insertions(+) create mode 100644 redmine/redmine_ldap_passwd/README.md create mode 100644 redmine/redmine_ldap_passwd/app/models/auth_source_ldap_passwd.rb create mode 100644 redmine/redmine_ldap_passwd/config/locales/en.yml create mode 100644 redmine/redmine_ldap_passwd/init.rb create mode 100644 redmine/redmine_ldap_passwd/lib/redmine_ldap_passwd_account_controller_patch.rb create mode 100644 redmine/redmine_ldap_passwd/lib/redmine_ldap_passwd_auth_sources_helper_patch.rb create mode 100644 redmine/redmine_ldap_passwd/lib/redmine_ldap_passwd_my_controller_patch.rb diff --git a/redmine/redmine_ldap_passwd/README.md b/redmine/redmine_ldap_passwd/README.md new file mode 100644 index 0000000..94f1a2b --- /dev/null +++ b/redmine/redmine_ldap_passwd/README.md @@ -0,0 +1,27 @@ +# Redmine LDAP Passwd plugin >= Redmine 3.0 + +The plugin extends AuthSourceLdap to introduce the ability to recover or change user password. + +### Features + +* Allows to changed password and update LDAP record. +* Allows to recover password and update LDAP record. + +**Notes** + +* The solution has been tested on MS Active Directory only. It works only with SSL connection, please ensure SSL is configured on Active Directory side. + +### Install + +1. Follow Redmine [plugin installation instructions](http://www.redmine.org/projects/redmine/wiki/Plugins#Installing-a-plugin). +2. Add new LDAP connection and check the records in 'auth_sources' making sure column 'type'='AuthSourceLdapPasswd'. If it is not, update the record manually executing the SQL query. +3. Assign new LDAP connection to the specific users you would like to provide access through LDAP to. + +### Uninstall + +1. Follow Redmine [plugin uninstall instructions](http://www.redmine.org/projects/redmine/wiki/Plugins#Uninstalling-a-plugin). + +### Changelog + +* **3.0 (2016-05-31)** + * Initial version released. \ No newline at end of file diff --git a/redmine/redmine_ldap_passwd/app/models/auth_source_ldap_passwd.rb b/redmine/redmine_ldap_passwd/app/models/auth_source_ldap_passwd.rb new file mode 100644 index 0000000..2f77ee3 --- /dev/null +++ b/redmine/redmine_ldap_passwd/app/models/auth_source_ldap_passwd.rb @@ -0,0 +1,57 @@ +class AuthSourceLdapPasswd < AuthSourceLdap + def allow_password_changes? + self.tls + end + + def change_user_password(user, password, new_password) + return false unless AuthSourceLdapPasswd.change_password_allowed?(user) + + attrs = get_user_dn(user.login, password) + if attrs && attrs[:dn] + if self.account && self.account.include?("$login") + ldap_con = initialize_ldap_con(self.account.sub("$login", Net::LDAP::DN.escape(user.login)), password) + else + ldap_con = initialize_ldap_con(self.account, self.account_password) + end + + ops = [[:replace, :unicodePwd, AuthSourceLdapPasswd.str2unicodePwd(new_password)]] + ldap_con.modify :dn => attrs[:dn], :operations => ops + + result = ldap_con.get_operation_result + if result.code == 0 + user.passwd_changed_on = Time.now.change(:usec => 0) + user.save + + return true + else + return result + end + end + + false + end + + def self.str2unicodePwd(str) + ('"' + str + '"').encode("utf-16le").force_encoding("utf-8") + end + + def self.change_password_allowed?(user) + return false if user.nil? + AuthSourceLdapPasswd.name.eql?(user.auth_source.type) + end + + def self.is_password_valid(password) + return false if password.nil? || password.length < 7 + + s = 0 + contains = [ + password.match(/\p{Lower}/) ? 1 : 0, + password.match(/\p{Upper}/) ? 1 : 0, + password.match(/\p{Digit}/) ? 1 : 0, + password.match(/[^\\w\\d]+/) ? 1 : 0 + ] + contains.each { |a| s += a } + + return s >= 3 + end +end diff --git a/redmine/redmine_ldap_passwd/config/locales/en.yml b/redmine/redmine_ldap_passwd/config/locales/en.yml new file mode 100644 index 0000000..980bf2d --- /dev/null +++ b/redmine/redmine_ldap_passwd/config/locales/en.yml @@ -0,0 +1,3 @@ +en: + notice_new_password_and_confirmation_different: The new password is different from the confirmation password + notice_new_password_format: "1. The password should be at least seven characters long. 2. The password should contain characters from at least three of the following four categories: (a) English uppercase characters (A - Z) (b) English lowercase characters (a - z) (c) Base 10 digits (0 - 9) (d) Non-alphanumeric (For example: !, $, or %). 3. The password shouldn't contain three or more characters from the user's account name." \ No newline at end of file diff --git a/redmine/redmine_ldap_passwd/init.rb b/redmine/redmine_ldap_passwd/init.rb new file mode 100644 index 0000000..2cdb515 --- /dev/null +++ b/redmine/redmine_ldap_passwd/init.rb @@ -0,0 +1,36 @@ +require 'redmine' + +require_dependency 'redmine_ldap_passwd_my_controller_patch' +require_dependency 'redmine_ldap_passwd_auth_sources_helper_patch' +require_dependency 'redmine_ldap_passwd_account_controller_patch' + +Redmine::Plugin.register :redmine_ldap_passwd do + name 'Redmine LDAP Change Password' + author 'Yura Zaplavnov' + description 'The plugin extends AuthSourceLdap to introduce the ability to recover or change user password.' + version '3.0.1' + url 'https://github.com/xeagle2/redmine_ldap_passwd' + author_url 'https://github.com/xeagle2' +end + +require 'dispatcher' unless Rails::VERSION::MAJOR >= 3 + +if Rails::VERSION::MAJOR >= 5 + ActiveSupport::Reloader.to_prepare do + MyController.send(:include, RedmineLdapPasswd::MyControllerPatch) + AuthSourcesHelper.send(:include, RedmineLdapPasswd::AuthSourcesHelperPatch) + AccountController.send(:include, RedmineLdapPasswd::AccountControllerPatch) + end +elsif Rails::VERSION::MAJOR >= 3 + ActionDispatch::Callbacks.to_prepare do + MyController.send(:include, RedmineLdapPasswd::MyControllerPatch) + AuthSourcesHelper.send(:include, RedmineLdapPasswd::AuthSourcesHelperPatch) + AccountController.send(:include, RedmineLdapPasswd::AccountControllerPatch) + end +else + Dispatcher.to_prepare do + MyController.send(:include, RedmineLdapPasswd::MyControllerPatch) + AuthSourcesHelper.send(:include, RedmineLdapPasswd::AuthSourcesHelperPatch) + AccountController.send(:include, RedmineLdapPasswd::AccountControllerPatch) + end +end \ No newline at end of file diff --git a/redmine/redmine_ldap_passwd/lib/redmine_ldap_passwd_account_controller_patch.rb b/redmine/redmine_ldap_passwd/lib/redmine_ldap_passwd_account_controller_patch.rb new file mode 100644 index 0000000..47ab32a --- /dev/null +++ b/redmine/redmine_ldap_passwd/lib/redmine_ldap_passwd_account_controller_patch.rb @@ -0,0 +1,66 @@ +module RedmineLdapPasswd + module AccountControllerPatch + def self.included(base) + base.send(:extend, ClassMethods) + base.send(:include, InstanceMethods) + + base.class_eval do + unloadable # Send unloadable so it will not be unloaded in development + + if Rails::VERSION::MAJOR >= 5 + alias_method :lost_password_without_extension, :lost_password + alias_method :lost_password, :lost_password_with_extension + else + alias_method :lost_password, :extension + end + end + end + + module ClassMethods + end + + module InstanceMethods + def lost_password_with_extension + if params[:token] + @token = Token.find_token("recovery", params[:token].to_s) + if @token.nil? || @token.expired? + redirect_to home_url + return + end + + @user = @token.user + unless @user && @user.active? + redirect_to home_url + return + end + + if request.post? + if params[:new_password_confirmation] != params[:new_password] + flash.now[:error] = l(:notice_new_password_and_confirmation_different) + elsif !AuthSourceLdapPasswd.is_password_valid (params[:new_password]) + flash.now[:error] = l(:notice_new_password_format) + else + r = @user.auth_source.change_user_password(@user, '', params[:new_password]) + + if r == true + flash[:notice] = l(:notice_account_password_updated) + redirect_to signin_path + elsif r == false + lost_password_without_extension + else + flash.now[:error] = r.message + end + + return + end + end + + render :template => "account/password_recovery" + return + else + lost_password_without_extension + end + end + end + end +end \ No newline at end of file diff --git a/redmine/redmine_ldap_passwd/lib/redmine_ldap_passwd_auth_sources_helper_patch.rb b/redmine/redmine_ldap_passwd/lib/redmine_ldap_passwd_auth_sources_helper_patch.rb new file mode 100644 index 0000000..c3effe5 --- /dev/null +++ b/redmine/redmine_ldap_passwd/lib/redmine_ldap_passwd_auth_sources_helper_patch.rb @@ -0,0 +1,25 @@ +module RedmineLdapPasswd + module AuthSourcesHelperPatch + def self.included(base) # :nodoc: + base.send(:include, InstanceMethods) + + base.class_eval do + unloadable # Send unloadable so it will not be unloaded in development + + if Rails::VERSION::MAJOR >= 5 + alias_method :auth_source_partial_name_without_ignored_passwd, :auth_source_partial_name + alias_method :auth_source_partial_name, :auth_source_partial_name_with_ignored_passwd + else + alias_method :auth_source_partial_name, :ignored_passwd + end + end + end + + module InstanceMethods + # Make sure AuthSourceLdapPasswd is loaded with the same form as AuthSourceLdap + def auth_source_partial_name_with_ignored_passwd(auth_source) + "form_#{auth_source.class.name.underscore}".chomp('_passwd') + end + end + end +end \ No newline at end of file diff --git a/redmine/redmine_ldap_passwd/lib/redmine_ldap_passwd_my_controller_patch.rb b/redmine/redmine_ldap_passwd/lib/redmine_ldap_passwd_my_controller_patch.rb new file mode 100644 index 0000000..b61c628 --- /dev/null +++ b/redmine/redmine_ldap_passwd/lib/redmine_ldap_passwd_my_controller_patch.rb @@ -0,0 +1,64 @@ +module RedmineLdapPasswd + module MyControllerPatch + def self.included(base) + base.send(:extend, ClassMethods) + base.send(:include, InstanceMethods) + + base.class_eval do + unloadable # Send unloadable so it will not be unloaded in development + + if Rails::VERSION::MAJOR >= 5 + alias_method :password_without_extension, :password + alias_method :password, :password_with_extension + else + alias_method_chain :password, :extension + end + end + end + + module ClassMethods + end + + module InstanceMethods + def password_with_extension + @user = User.current + + unless @user.change_password_allowed? + flash[:error] = l(:notice_can_t_change_password) + redirect_to my_account_path + return + end + + if request.post? + if !@user.check_password?(params[:password]) + flash.now[:error] = l(:notice_account_wrong_password) + elsif params[:password] == params[:new_password] + flash.now[:error] = l(:notice_new_password_must_be_different) + elsif params[:new_password_confirmation] != params[:new_password] + flash.now[:error] = l(:notice_new_password_and_confirmation_different) + elsif AuthSourceLdapPasswd.change_password_allowed?(@user) + if AuthSourceLdapPasswd.is_password_valid (params[:new_password]) + r = @user.auth_source.change_user_password(@user, params[:password], params[:new_password]) + + if r == true + session[:ctime] = User.current.passwd_changed_on.utc.to_i + flash[:notice] = l(:notice_account_password_updated) + redirect_to my_account_path + elsif r == false + password_without_extension + else + flash.now[:error] = r.message + end + else + flash.now[:error] = l(:notice_new_password_format) + end + else + password_without_extension + end + end + rescue Net::LDAP::LdapError => e + raise AuthSourceException.new(e.message) + end + end + end +end \ No newline at end of file