ldap plugin integration
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
FROM redmine
|
||||||
|
COPY ./redmine_ldap_passwd /usr/share/redmine/plugins/
|
||||||
@@ -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
|
||||||
+66
@@ -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
|
||||||
+25
@@ -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
|
||||||
Reference in New Issue
Block a user