@@ -72,6 +72,20 class AuthSourcesController < ApplicationController | |||||
72 | redirect_to auth_sources_path |
|
72 | redirect_to auth_sources_path | |
73 | end |
|
73 | end | |
74 |
|
74 | |||
|
75 | def autocomplete_for_new_user | |||
|
76 | results = AuthSource.search(params[:term]) | |||
|
77 | ||||
|
78 | render :json => results.map {|result| { | |||
|
79 | 'value' => result[:login], | |||
|
80 | 'label' => "#{result[:login]} (#{result[:firstname]} #{result[:lastname]})", | |||
|
81 | 'login' => result[:login].to_s, | |||
|
82 | 'firstname' => result[:firstname].to_s, | |||
|
83 | 'lastname' => result[:lastname].to_s, | |||
|
84 | 'mail' => result[:mail].to_s, | |||
|
85 | 'auth_source_id' => result[:auth_source_id].to_s | |||
|
86 | }} | |||
|
87 | end | |||
|
88 | ||||
75 | private |
|
89 | private | |
76 |
|
90 | |||
77 | def find_auth_source |
|
91 | def find_auth_source |
@@ -48,6 +48,24 class AuthSource < ActiveRecord::Base | |||||
48 | write_ciphered_attribute(:account_password, arg) |
|
48 | write_ciphered_attribute(:account_password, arg) | |
49 | end |
|
49 | end | |
50 |
|
50 | |||
|
51 | def searchable? | |||
|
52 | false | |||
|
53 | end | |||
|
54 | ||||
|
55 | def self.search(q) | |||
|
56 | results = [] | |||
|
57 | AuthSource.all.each do |source| | |||
|
58 | begin | |||
|
59 | if source.searchable? | |||
|
60 | results += source.search(q) | |||
|
61 | end | |||
|
62 | rescue AuthSourceException => e | |||
|
63 | logger.error "Error while searching users in #{source.name}: #{e.message}" | |||
|
64 | end | |||
|
65 | end | |||
|
66 | results | |||
|
67 | end | |||
|
68 | ||||
51 | def allow_password_changes? |
|
69 | def allow_password_changes? | |
52 | self.class.allow_password_changes? |
|
70 | self.class.allow_password_changes? | |
53 | end |
|
71 | end |
@@ -64,6 +64,32 class AuthSourceLdap < AuthSource | |||||
64 | "LDAP" |
|
64 | "LDAP" | |
65 | end |
|
65 | end | |
66 |
|
66 | |||
|
67 | # Returns true if this source can be searched for users | |||
|
68 | def searchable? | |||
|
69 | !account.to_s.include?("$login") && %w(login firstname lastname mail).all? {|a| send("attr_#{a}?")} | |||
|
70 | end | |||
|
71 | ||||
|
72 | # Searches the source for users and returns an array of results | |||
|
73 | def search(q) | |||
|
74 | q = q.to_s.strip | |||
|
75 | return [] unless searchable? && q.present? | |||
|
76 | ||||
|
77 | results = [] | |||
|
78 | search_filter = base_filter & Net::LDAP::Filter.begins(self.attr_login, q) | |||
|
79 | ldap_con = initialize_ldap_con(self.account, self.account_password) | |||
|
80 | ldap_con.search(:base => self.base_dn, | |||
|
81 | :filter => search_filter, | |||
|
82 | :attributes => ['dn', self.attr_login, self.attr_firstname, self.attr_lastname, self.attr_mail], | |||
|
83 | :size => 10) do |entry| | |||
|
84 | attrs = get_user_attributes_from_ldap_entry(entry) | |||
|
85 | attrs[:login] = AuthSourceLdap.get_attr(entry, self.attr_login) | |||
|
86 | results << attrs | |||
|
87 | end | |||
|
88 | results | |||
|
89 | rescue Net::LDAP::LdapError => e | |||
|
90 | raise AuthSourceException.new(e.message) | |||
|
91 | end | |||
|
92 | ||||
67 | private |
|
93 | private | |
68 |
|
94 | |||
69 | def with_timeout(&block) |
|
95 | def with_timeout(&block) | |
@@ -84,6 +110,14 class AuthSourceLdap < AuthSource | |||||
84 | nil |
|
110 | nil | |
85 | end |
|
111 | end | |
86 |
|
112 | |||
|
113 | def base_filter | |||
|
114 | filter = Net::LDAP::Filter.eq("objectClass", "*") | |||
|
115 | if f = ldap_filter | |||
|
116 | filter = filter & f | |||
|
117 | end | |||
|
118 | filter | |||
|
119 | end | |||
|
120 | ||||
87 | def validate_filter |
|
121 | def validate_filter | |
88 | if filter.present? && ldap_filter.nil? |
|
122 | if filter.present? && ldap_filter.nil? | |
89 | errors.add(:filter, :invalid) |
|
123 | errors.add(:filter, :invalid) | |
@@ -140,14 +174,8 class AuthSourceLdap < AuthSource | |||||
140 | else |
|
174 | else | |
141 | ldap_con = initialize_ldap_con(self.account, self.account_password) |
|
175 | ldap_con = initialize_ldap_con(self.account, self.account_password) | |
142 | end |
|
176 | end | |
143 | login_filter = Net::LDAP::Filter.eq( self.attr_login, login ) |
|
|||
144 | object_filter = Net::LDAP::Filter.eq( "objectClass", "*" ) |
|
|||
145 | attrs = {} |
|
177 | attrs = {} | |
146 |
|
178 | search_filter = base_filter & Net::LDAP::Filter.eq(self.attr_login, login) | ||
147 | search_filter = object_filter & login_filter |
|
|||
148 | if f = ldap_filter |
|
|||
149 | search_filter = search_filter & f |
|
|||
150 | end |
|
|||
151 |
|
179 | |||
152 | ldap_con.search( :base => self.base_dn, |
|
180 | ldap_con.search( :base => self.base_dn, | |
153 | :filter => search_filter, |
|
181 | :filter => search_filter, |
@@ -10,3 +10,21 | |||||
10 | <%= submit_tag l(:button_create_and_continue), :name => 'continue' %> |
|
10 | <%= submit_tag l(:button_create_and_continue), :name => 'continue' %> | |
11 | </p> |
|
11 | </p> | |
12 | <% end %> |
|
12 | <% end %> | |
|
13 | ||||
|
14 | <% if @auth_sources.present? && @auth_sources.any?(&:searchable?) %> | |||
|
15 | <%= javascript_tag do %> | |||
|
16 | observeAutocompleteField('user_login', '<%= escape_javascript autocomplete_for_new_user_auth_sources_path %>', { | |||
|
17 | select: function(event, ui) { | |||
|
18 | $('input#user_firstname').val(ui.item.firstname); | |||
|
19 | $('input#user_lastname').val(ui.item.lastname); | |||
|
20 | $('input#user_mail').val(ui.item.mail); | |||
|
21 | $('select#user_auth_source_id option').each(function(){ | |||
|
22 | if ($(this).attr('value') == ui.item.auth_source_id) { | |||
|
23 | $(this).attr('selected', true); | |||
|
24 | $('select#user_auth_source_id').trigger('change'); | |||
|
25 | } | |||
|
26 | }); | |||
|
27 | } | |||
|
28 | }); | |||
|
29 | <% end %> | |||
|
30 | <% end %> |
@@ -309,6 +309,9 RedmineApp::Application.routes.draw do | |||||
309 | member do |
|
309 | member do | |
310 | get 'test_connection', :as => 'try_connection' |
|
310 | get 'test_connection', :as => 'try_connection' | |
311 | end |
|
311 | end | |
|
312 | collection do | |||
|
313 | get 'autocomplete_for_new_user' | |||
|
314 | end | |||
312 | end |
|
315 | end | |
313 |
|
316 | |||
314 | match 'workflows', :controller => 'workflows', :action => 'index', :via => :get |
|
317 | match 'workflows', :controller => 'workflows', :action => 'index', :via => :get |
@@ -456,12 +456,12 function updateBulkEditFrom(url) { | |||||
456 | }); |
|
456 | }); | |
457 | } |
|
457 | } | |
458 |
|
458 | |||
459 | function observeAutocompleteField(fieldId, url) { |
|
459 | function observeAutocompleteField(fieldId, url, options) { | |
460 | $(document).ready(function() { |
|
460 | $(document).ready(function() { | |
461 | $('#'+fieldId).autocomplete({ |
|
461 | $('#'+fieldId).autocomplete($.extend({ | |
462 | source: url, |
|
462 | source: url, | |
463 | minLength: 2 |
|
463 | minLength: 2 | |
464 | }); |
|
464 | }, options)); | |
465 | }); |
|
465 | }); | |
466 | } |
|
466 | } | |
467 |
|
467 |
@@ -149,4 +149,20 class AuthSourcesControllerTest < ActionController::TestCase | |||||
149 | assert_not_nil flash[:error] |
|
149 | assert_not_nil flash[:error] | |
150 | assert_include 'Something went wrong', flash[:error] |
|
150 | assert_include 'Something went wrong', flash[:error] | |
151 | end |
|
151 | end | |
|
152 | ||||
|
153 | def test_autocomplete_for_new_user | |||
|
154 | AuthSource.expects(:search).with('foo').returns([ | |||
|
155 | {:login => 'foo1', :firstname => 'John', :lastname => 'Smith', :mail => 'foo1@example.net', :auth_source_id => 1}, | |||
|
156 | {:login => 'Smith', :firstname => 'John', :lastname => 'Doe', :mail => 'foo2@example.net', :auth_source_id => 1} | |||
|
157 | ]) | |||
|
158 | ||||
|
159 | get :autocomplete_for_new_user, :term => 'foo' | |||
|
160 | assert_response :success | |||
|
161 | assert_equal 'application/json', response.content_type | |||
|
162 | json = ActiveSupport::JSON.decode(response.body) | |||
|
163 | assert_kind_of Array, json | |||
|
164 | assert_equal 2, json.size | |||
|
165 | assert_equal 'foo1', json.first['value'] | |||
|
166 | assert_equal 'foo1 (John Smith)', json.first['label'] | |||
|
167 | end | |||
152 | end |
|
168 | end |
@@ -51,5 +51,9 class RoutingAuthSourcesTest < ActionController::IntegrationTest | |||||
51 | { :controller => 'auth_sources', :action => 'test_connection', |
|
51 | { :controller => 'auth_sources', :action => 'test_connection', | |
52 | :id => '1234' } |
|
52 | :id => '1234' } | |
53 | ) |
|
53 | ) | |
|
54 | assert_routing( | |||
|
55 | { :method => 'get', :path => "/auth_sources/autocomplete_for_new_user" }, | |||
|
56 | { :controller => 'auth_sources', :action => 'autocomplete_for_new_user' } | |||
|
57 | ) | |||
54 | end |
|
58 | end | |
55 | end |
|
59 | end |
@@ -124,6 +124,30 class AuthSourceLdapTest < ActiveSupport::TestCase | |||||
124 | auth_source.authenticate 'example1', '123456' |
|
124 | auth_source.authenticate 'example1', '123456' | |
125 | end |
|
125 | end | |
126 | end |
|
126 | end | |
|
127 | ||||
|
128 | def test_search_should_return_matching_entries | |||
|
129 | results = AuthSource.search("exa") | |||
|
130 | assert_equal 1, results.size | |||
|
131 | result = results.first | |||
|
132 | assert_kind_of Hash, result | |||
|
133 | assert_equal "example1", result[:login] | |||
|
134 | assert_equal "Example", result[:firstname] | |||
|
135 | assert_equal "One", result[:lastname] | |||
|
136 | assert_equal "example1@redmine.org", result[:mail] | |||
|
137 | assert_equal 1, result[:auth_source_id] | |||
|
138 | end | |||
|
139 | ||||
|
140 | def test_search_with_no_match_should_return_an_empty_array | |||
|
141 | results = AuthSource.search("wro") | |||
|
142 | assert_equal [], results | |||
|
143 | end | |||
|
144 | ||||
|
145 | def test_search_with_exception_should_return_an_empty_array | |||
|
146 | Net::LDAP.stubs(:new).raises(Net::LDAP::LdapError, 'Cannot connect') | |||
|
147 | ||||
|
148 | results = AuthSource.search("exa") | |||
|
149 | assert_equal [], results | |||
|
150 | end | |||
127 | else |
|
151 | else | |
128 | puts '(Test LDAP server not configured)' |
|
152 | puts '(Test LDAP server not configured)' | |
129 | end |
|
153 | end |
General Comments 0
You need to be logged in to leave comments.
Login now