@@ -72,6 +72,20 class AuthSourcesController < ApplicationController | |||
|
72 | 72 | redirect_to auth_sources_path |
|
73 | 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 | 89 | private |
|
76 | 90 | |
|
77 | 91 | def find_auth_source |
@@ -48,6 +48,24 class AuthSource < ActiveRecord::Base | |||
|
48 | 48 | write_ciphered_attribute(:account_password, arg) |
|
49 | 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 | 69 | def allow_password_changes? |
|
52 | 70 | self.class.allow_password_changes? |
|
53 | 71 | end |
@@ -64,6 +64,32 class AuthSourceLdap < AuthSource | |||
|
64 | 64 | "LDAP" |
|
65 | 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 | 93 | private |
|
68 | 94 | |
|
69 | 95 | def with_timeout(&block) |
@@ -84,6 +110,14 class AuthSourceLdap < AuthSource | |||
|
84 | 110 | nil |
|
85 | 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 | 121 | def validate_filter |
|
88 | 122 | if filter.present? && ldap_filter.nil? |
|
89 | 123 | errors.add(:filter, :invalid) |
@@ -140,14 +174,8 class AuthSourceLdap < AuthSource | |||
|
140 | 174 | else |
|
141 | 175 | ldap_con = initialize_ldap_con(self.account, self.account_password) |
|
142 | 176 | end |
|
143 | login_filter = Net::LDAP::Filter.eq( self.attr_login, login ) | |
|
144 | object_filter = Net::LDAP::Filter.eq( "objectClass", "*" ) | |
|
145 | 177 | attrs = {} |
|
146 | ||
|
147 | search_filter = object_filter & login_filter | |
|
148 | if f = ldap_filter | |
|
149 | search_filter = search_filter & f | |
|
150 | end | |
|
178 | search_filter = base_filter & Net::LDAP::Filter.eq(self.attr_login, login) | |
|
151 | 179 | |
|
152 | 180 | ldap_con.search( :base => self.base_dn, |
|
153 | 181 | :filter => search_filter, |
@@ -10,3 +10,21 | |||
|
10 | 10 | <%= submit_tag l(:button_create_and_continue), :name => 'continue' %> |
|
11 | 11 | </p> |
|
12 | 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 | 309 | member do |
|
310 | 310 | get 'test_connection', :as => 'try_connection' |
|
311 | 311 | end |
|
312 | collection do | |
|
313 | get 'autocomplete_for_new_user' | |
|
314 | end | |
|
312 | 315 | end |
|
313 | 316 | |
|
314 | 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 | 460 | $(document).ready(function() { |
|
461 | $('#'+fieldId).autocomplete({ | |
|
461 | $('#'+fieldId).autocomplete($.extend({ | |
|
462 | 462 | source: url, |
|
463 | 463 | minLength: 2 |
|
464 | }); | |
|
464 | }, options)); | |
|
465 | 465 | }); |
|
466 | 466 | } |
|
467 | 467 |
@@ -149,4 +149,20 class AuthSourcesControllerTest < ActionController::TestCase | |||
|
149 | 149 | assert_not_nil flash[:error] |
|
150 | 150 | assert_include 'Something went wrong', flash[:error] |
|
151 | 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 | 168 | end |
@@ -51,5 +51,9 class RoutingAuthSourcesTest < ActionController::IntegrationTest | |||
|
51 | 51 | { :controller => 'auth_sources', :action => 'test_connection', |
|
52 | 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 | 58 | end |
|
55 | 59 | end |
@@ -124,6 +124,30 class AuthSourceLdapTest < ActiveSupport::TestCase | |||
|
124 | 124 | auth_source.authenticate 'example1', '123456' |
|
125 | 125 | end |
|
126 | 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 | 151 | else |
|
128 | 152 | puts '(Test LDAP server not configured)' |
|
129 | 153 | end |
General Comments 0
You need to be logged in to leave comments.
Login now