parent
37c5f322e6
commit
6bb71e3050
@ -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. |
@ -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 |
@ -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." |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
Loading…
Reference in new issue