@@ -1,82 +1,96 | |||||
1 | # Redmine - project management software |
|
1 | # Redmine - project management software | |
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang |
|
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
6 | # as published by the Free Software Foundation; either version 2 |
|
6 | # as published by the Free Software Foundation; either version 2 | |
7 | # of the License, or (at your option) any later version. |
|
7 | # of the License, or (at your option) any later version. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU General Public License |
|
14 | # You should have received a copy of the GNU General Public License | |
15 | # along with this program; if not, write to the Free Software |
|
15 | # along with this program; if not, write to the Free Software | |
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 |
|
17 | |||
18 | class AuthSourcesController < ApplicationController |
|
18 | class AuthSourcesController < ApplicationController | |
19 | layout 'admin' |
|
19 | layout 'admin' | |
20 | menu_item :ldap_authentication |
|
20 | menu_item :ldap_authentication | |
21 |
|
21 | |||
22 | before_filter :require_admin |
|
22 | before_filter :require_admin | |
23 | before_filter :find_auth_source, :only => [:edit, :update, :test_connection, :destroy] |
|
23 | before_filter :find_auth_source, :only => [:edit, :update, :test_connection, :destroy] | |
24 |
|
24 | |||
25 | def index |
|
25 | def index | |
26 | @auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 25 |
|
26 | @auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 25 | |
27 | end |
|
27 | end | |
28 |
|
28 | |||
29 | def new |
|
29 | def new | |
30 | klass_name = params[:type] || 'AuthSourceLdap' |
|
30 | klass_name = params[:type] || 'AuthSourceLdap' | |
31 | @auth_source = AuthSource.new_subclass_instance(klass_name, params[:auth_source]) |
|
31 | @auth_source = AuthSource.new_subclass_instance(klass_name, params[:auth_source]) | |
32 | render_404 unless @auth_source |
|
32 | render_404 unless @auth_source | |
33 | end |
|
33 | end | |
34 |
|
34 | |||
35 | def create |
|
35 | def create | |
36 | @auth_source = AuthSource.new_subclass_instance(params[:type], params[:auth_source]) |
|
36 | @auth_source = AuthSource.new_subclass_instance(params[:type], params[:auth_source]) | |
37 | if @auth_source.save |
|
37 | if @auth_source.save | |
38 | flash[:notice] = l(:notice_successful_create) |
|
38 | flash[:notice] = l(:notice_successful_create) | |
39 | redirect_to auth_sources_path |
|
39 | redirect_to auth_sources_path | |
40 | else |
|
40 | else | |
41 | render :action => 'new' |
|
41 | render :action => 'new' | |
42 | end |
|
42 | end | |
43 | end |
|
43 | end | |
44 |
|
44 | |||
45 | def edit |
|
45 | def edit | |
46 | end |
|
46 | end | |
47 |
|
47 | |||
48 | def update |
|
48 | def update | |
49 | if @auth_source.update_attributes(params[:auth_source]) |
|
49 | if @auth_source.update_attributes(params[:auth_source]) | |
50 | flash[:notice] = l(:notice_successful_update) |
|
50 | flash[:notice] = l(:notice_successful_update) | |
51 | redirect_to auth_sources_path |
|
51 | redirect_to auth_sources_path | |
52 | else |
|
52 | else | |
53 | render :action => 'edit' |
|
53 | render :action => 'edit' | |
54 | end |
|
54 | end | |
55 | end |
|
55 | end | |
56 |
|
56 | |||
57 | def test_connection |
|
57 | def test_connection | |
58 | begin |
|
58 | begin | |
59 | @auth_source.test_connection |
|
59 | @auth_source.test_connection | |
60 | flash[:notice] = l(:notice_successful_connection) |
|
60 | flash[:notice] = l(:notice_successful_connection) | |
61 | rescue Exception => e |
|
61 | rescue Exception => e | |
62 | flash[:error] = l(:error_unable_to_connect, e.message) |
|
62 | flash[:error] = l(:error_unable_to_connect, e.message) | |
63 | end |
|
63 | end | |
64 | redirect_to auth_sources_path |
|
64 | redirect_to auth_sources_path | |
65 | end |
|
65 | end | |
66 |
|
66 | |||
67 | def destroy |
|
67 | def destroy | |
68 | unless @auth_source.users.exists? |
|
68 | unless @auth_source.users.exists? | |
69 | @auth_source.destroy |
|
69 | @auth_source.destroy | |
70 | flash[:notice] = l(:notice_successful_delete) |
|
70 | flash[:notice] = l(:notice_successful_delete) | |
71 | end |
|
71 | end | |
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 | |
78 | @auth_source = AuthSource.find(params[:id]) |
|
92 | @auth_source = AuthSource.find(params[:id]) | |
79 | rescue ActiveRecord::RecordNotFound |
|
93 | rescue ActiveRecord::RecordNotFound | |
80 | render_404 |
|
94 | render_404 | |
81 | end |
|
95 | end | |
82 | end |
|
96 | end |
@@ -1,74 +1,92 | |||||
1 | # Redmine - project management software |
|
1 | # Redmine - project management software | |
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang |
|
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
6 | # as published by the Free Software Foundation; either version 2 |
|
6 | # as published by the Free Software Foundation; either version 2 | |
7 | # of the License, or (at your option) any later version. |
|
7 | # of the License, or (at your option) any later version. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU General Public License |
|
14 | # You should have received a copy of the GNU General Public License | |
15 | # along with this program; if not, write to the Free Software |
|
15 | # along with this program; if not, write to the Free Software | |
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 |
|
17 | |||
18 | # Generic exception for when the AuthSource can not be reached |
|
18 | # Generic exception for when the AuthSource can not be reached | |
19 | # (eg. can not connect to the LDAP) |
|
19 | # (eg. can not connect to the LDAP) | |
20 | class AuthSourceException < Exception; end |
|
20 | class AuthSourceException < Exception; end | |
21 | class AuthSourceTimeoutException < AuthSourceException; end |
|
21 | class AuthSourceTimeoutException < AuthSourceException; end | |
22 |
|
22 | |||
23 | class AuthSource < ActiveRecord::Base |
|
23 | class AuthSource < ActiveRecord::Base | |
24 | include Redmine::SubclassFactory |
|
24 | include Redmine::SubclassFactory | |
25 | include Redmine::Ciphering |
|
25 | include Redmine::Ciphering | |
26 |
|
26 | |||
27 | has_many :users |
|
27 | has_many :users | |
28 |
|
28 | |||
29 | validates_presence_of :name |
|
29 | validates_presence_of :name | |
30 | validates_uniqueness_of :name |
|
30 | validates_uniqueness_of :name | |
31 | validates_length_of :name, :maximum => 60 |
|
31 | validates_length_of :name, :maximum => 60 | |
32 |
|
32 | |||
33 | def authenticate(login, password) |
|
33 | def authenticate(login, password) | |
34 | end |
|
34 | end | |
35 |
|
35 | |||
36 | def test_connection |
|
36 | def test_connection | |
37 | end |
|
37 | end | |
38 |
|
38 | |||
39 | def auth_method_name |
|
39 | def auth_method_name | |
40 | "Abstract" |
|
40 | "Abstract" | |
41 | end |
|
41 | end | |
42 |
|
42 | |||
43 | def account_password |
|
43 | def account_password | |
44 | read_ciphered_attribute(:account_password) |
|
44 | read_ciphered_attribute(:account_password) | |
45 | end |
|
45 | end | |
46 |
|
46 | |||
47 | def account_password=(arg) |
|
47 | def account_password=(arg) | |
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 | |
54 |
|
72 | |||
55 | # Does this auth source backend allow password changes? |
|
73 | # Does this auth source backend allow password changes? | |
56 | def self.allow_password_changes? |
|
74 | def self.allow_password_changes? | |
57 | false |
|
75 | false | |
58 | end |
|
76 | end | |
59 |
|
77 | |||
60 | # Try to authenticate a user not yet registered against available sources |
|
78 | # Try to authenticate a user not yet registered against available sources | |
61 | def self.authenticate(login, password) |
|
79 | def self.authenticate(login, password) | |
62 | AuthSource.where(:onthefly_register => true).all.each do |source| |
|
80 | AuthSource.where(:onthefly_register => true).all.each do |source| | |
63 | begin |
|
81 | begin | |
64 | logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug? |
|
82 | logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug? | |
65 | attrs = source.authenticate(login, password) |
|
83 | attrs = source.authenticate(login, password) | |
66 | rescue => e |
|
84 | rescue => e | |
67 | logger.error "Error during authentication: #{e.message}" |
|
85 | logger.error "Error during authentication: #{e.message}" | |
68 | attrs = nil |
|
86 | attrs = nil | |
69 | end |
|
87 | end | |
70 | return attrs if attrs |
|
88 | return attrs if attrs | |
71 | end |
|
89 | end | |
72 | return nil |
|
90 | return nil | |
73 | end |
|
91 | end | |
74 | end |
|
92 | end |
@@ -1,173 +1,201 | |||||
1 | # Redmine - project management software |
|
1 | # Redmine - project management software | |
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang |
|
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
6 | # as published by the Free Software Foundation; either version 2 |
|
6 | # as published by the Free Software Foundation; either version 2 | |
7 | # of the License, or (at your option) any later version. |
|
7 | # of the License, or (at your option) any later version. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU General Public License |
|
14 | # You should have received a copy of the GNU General Public License | |
15 | # along with this program; if not, write to the Free Software |
|
15 | # along with this program; if not, write to the Free Software | |
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 |
|
17 | |||
18 | require 'iconv' |
|
18 | require 'iconv' | |
19 | require 'net/ldap' |
|
19 | require 'net/ldap' | |
20 | require 'net/ldap/dn' |
|
20 | require 'net/ldap/dn' | |
21 | require 'timeout' |
|
21 | require 'timeout' | |
22 |
|
22 | |||
23 | class AuthSourceLdap < AuthSource |
|
23 | class AuthSourceLdap < AuthSource | |
24 | validates_presence_of :host, :port, :attr_login |
|
24 | validates_presence_of :host, :port, :attr_login | |
25 | validates_length_of :name, :host, :maximum => 60, :allow_nil => true |
|
25 | validates_length_of :name, :host, :maximum => 60, :allow_nil => true | |
26 | validates_length_of :account, :account_password, :base_dn, :filter, :maximum => 255, :allow_blank => true |
|
26 | validates_length_of :account, :account_password, :base_dn, :filter, :maximum => 255, :allow_blank => true | |
27 | validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true |
|
27 | validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true | |
28 | validates_numericality_of :port, :only_integer => true |
|
28 | validates_numericality_of :port, :only_integer => true | |
29 | validates_numericality_of :timeout, :only_integer => true, :allow_blank => true |
|
29 | validates_numericality_of :timeout, :only_integer => true, :allow_blank => true | |
30 | validate :validate_filter |
|
30 | validate :validate_filter | |
31 |
|
31 | |||
32 | before_validation :strip_ldap_attributes |
|
32 | before_validation :strip_ldap_attributes | |
33 |
|
33 | |||
34 | def initialize(attributes=nil, *args) |
|
34 | def initialize(attributes=nil, *args) | |
35 | super |
|
35 | super | |
36 | self.port = 389 if self.port == 0 |
|
36 | self.port = 389 if self.port == 0 | |
37 | end |
|
37 | end | |
38 |
|
38 | |||
39 | def authenticate(login, password) |
|
39 | def authenticate(login, password) | |
40 | return nil if login.blank? || password.blank? |
|
40 | return nil if login.blank? || password.blank? | |
41 |
|
41 | |||
42 | with_timeout do |
|
42 | with_timeout do | |
43 | attrs = get_user_dn(login, password) |
|
43 | attrs = get_user_dn(login, password) | |
44 | if attrs && attrs[:dn] && authenticate_dn(attrs[:dn], password) |
|
44 | if attrs && attrs[:dn] && authenticate_dn(attrs[:dn], password) | |
45 | logger.debug "Authentication successful for '#{login}'" if logger && logger.debug? |
|
45 | logger.debug "Authentication successful for '#{login}'" if logger && logger.debug? | |
46 | return attrs.except(:dn) |
|
46 | return attrs.except(:dn) | |
47 | end |
|
47 | end | |
48 | end |
|
48 | end | |
49 | rescue Net::LDAP::LdapError => e |
|
49 | rescue Net::LDAP::LdapError => e | |
50 | raise AuthSourceException.new(e.message) |
|
50 | raise AuthSourceException.new(e.message) | |
51 | end |
|
51 | end | |
52 |
|
52 | |||
53 | # test the connection to the LDAP |
|
53 | # test the connection to the LDAP | |
54 | def test_connection |
|
54 | def test_connection | |
55 | with_timeout do |
|
55 | with_timeout do | |
56 | ldap_con = initialize_ldap_con(self.account, self.account_password) |
|
56 | ldap_con = initialize_ldap_con(self.account, self.account_password) | |
57 | ldap_con.open { } |
|
57 | ldap_con.open { } | |
58 | end |
|
58 | end | |
59 | rescue Net::LDAP::LdapError => e |
|
59 | rescue Net::LDAP::LdapError => e | |
60 | raise AuthSourceException.new(e.message) |
|
60 | raise AuthSourceException.new(e.message) | |
61 | end |
|
61 | end | |
62 |
|
62 | |||
63 | def auth_method_name |
|
63 | def auth_method_name | |
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) | |
70 | timeout = self.timeout |
|
96 | timeout = self.timeout | |
71 | timeout = 20 unless timeout && timeout > 0 |
|
97 | timeout = 20 unless timeout && timeout > 0 | |
72 | Timeout.timeout(timeout) do |
|
98 | Timeout.timeout(timeout) do | |
73 | return yield |
|
99 | return yield | |
74 | end |
|
100 | end | |
75 | rescue Timeout::Error => e |
|
101 | rescue Timeout::Error => e | |
76 | raise AuthSourceTimeoutException.new(e.message) |
|
102 | raise AuthSourceTimeoutException.new(e.message) | |
77 | end |
|
103 | end | |
78 |
|
104 | |||
79 | def ldap_filter |
|
105 | def ldap_filter | |
80 | if filter.present? |
|
106 | if filter.present? | |
81 | Net::LDAP::Filter.construct(filter) |
|
107 | Net::LDAP::Filter.construct(filter) | |
82 | end |
|
108 | end | |
83 | rescue Net::LDAP::LdapError |
|
109 | rescue Net::LDAP::LdapError | |
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) | |
90 | end |
|
124 | end | |
91 | end |
|
125 | end | |
92 |
|
126 | |||
93 | def strip_ldap_attributes |
|
127 | def strip_ldap_attributes | |
94 | [:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr| |
|
128 | [:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr| | |
95 | write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil? |
|
129 | write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil? | |
96 | end |
|
130 | end | |
97 | end |
|
131 | end | |
98 |
|
132 | |||
99 | def initialize_ldap_con(ldap_user, ldap_password) |
|
133 | def initialize_ldap_con(ldap_user, ldap_password) | |
100 | options = { :host => self.host, |
|
134 | options = { :host => self.host, | |
101 | :port => self.port, |
|
135 | :port => self.port, | |
102 | :encryption => (self.tls ? :simple_tls : nil) |
|
136 | :encryption => (self.tls ? :simple_tls : nil) | |
103 | } |
|
137 | } | |
104 | options.merge!(:auth => { :method => :simple, :username => ldap_user, :password => ldap_password }) unless ldap_user.blank? && ldap_password.blank? |
|
138 | options.merge!(:auth => { :method => :simple, :username => ldap_user, :password => ldap_password }) unless ldap_user.blank? && ldap_password.blank? | |
105 | Net::LDAP.new options |
|
139 | Net::LDAP.new options | |
106 | end |
|
140 | end | |
107 |
|
141 | |||
108 | def get_user_attributes_from_ldap_entry(entry) |
|
142 | def get_user_attributes_from_ldap_entry(entry) | |
109 | { |
|
143 | { | |
110 | :dn => entry.dn, |
|
144 | :dn => entry.dn, | |
111 | :firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname), |
|
145 | :firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname), | |
112 | :lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname), |
|
146 | :lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname), | |
113 | :mail => AuthSourceLdap.get_attr(entry, self.attr_mail), |
|
147 | :mail => AuthSourceLdap.get_attr(entry, self.attr_mail), | |
114 | :auth_source_id => self.id |
|
148 | :auth_source_id => self.id | |
115 | } |
|
149 | } | |
116 | end |
|
150 | end | |
117 |
|
151 | |||
118 | # Return the attributes needed for the LDAP search. It will only |
|
152 | # Return the attributes needed for the LDAP search. It will only | |
119 | # include the user attributes if on-the-fly registration is enabled |
|
153 | # include the user attributes if on-the-fly registration is enabled | |
120 | def search_attributes |
|
154 | def search_attributes | |
121 | if onthefly_register? |
|
155 | if onthefly_register? | |
122 | ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail] |
|
156 | ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail] | |
123 | else |
|
157 | else | |
124 | ['dn'] |
|
158 | ['dn'] | |
125 | end |
|
159 | end | |
126 | end |
|
160 | end | |
127 |
|
161 | |||
128 | # Check if a DN (user record) authenticates with the password |
|
162 | # Check if a DN (user record) authenticates with the password | |
129 | def authenticate_dn(dn, password) |
|
163 | def authenticate_dn(dn, password) | |
130 | if dn.present? && password.present? |
|
164 | if dn.present? && password.present? | |
131 | initialize_ldap_con(dn, password).bind |
|
165 | initialize_ldap_con(dn, password).bind | |
132 | end |
|
166 | end | |
133 | end |
|
167 | end | |
134 |
|
168 | |||
135 | # Get the user's dn and any attributes for them, given their login |
|
169 | # Get the user's dn and any attributes for them, given their login | |
136 | def get_user_dn(login, password) |
|
170 | def get_user_dn(login, password) | |
137 | ldap_con = nil |
|
171 | ldap_con = nil | |
138 | if self.account && self.account.include?("$login") |
|
172 | if self.account && self.account.include?("$login") | |
139 | ldap_con = initialize_ldap_con(self.account.sub("$login", Net::LDAP::DN.escape(login)), password) |
|
173 | ldap_con = initialize_ldap_con(self.account.sub("$login", Net::LDAP::DN.escape(login)), password) | |
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, | |
154 | :attributes=> search_attributes) do |entry| |
|
182 | :attributes=> search_attributes) do |entry| | |
155 |
|
183 | |||
156 | if onthefly_register? |
|
184 | if onthefly_register? | |
157 | attrs = get_user_attributes_from_ldap_entry(entry) |
|
185 | attrs = get_user_attributes_from_ldap_entry(entry) | |
158 | else |
|
186 | else | |
159 | attrs = {:dn => entry.dn} |
|
187 | attrs = {:dn => entry.dn} | |
160 | end |
|
188 | end | |
161 |
|
189 | |||
162 | logger.debug "DN found for #{login}: #{attrs[:dn]}" if logger && logger.debug? |
|
190 | logger.debug "DN found for #{login}: #{attrs[:dn]}" if logger && logger.debug? | |
163 | end |
|
191 | end | |
164 |
|
192 | |||
165 | attrs |
|
193 | attrs | |
166 | end |
|
194 | end | |
167 |
|
195 | |||
168 | def self.get_attr(entry, attr_name) |
|
196 | def self.get_attr(entry, attr_name) | |
169 | if !attr_name.blank? |
|
197 | if !attr_name.blank? | |
170 | entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name] |
|
198 | entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name] | |
171 | end |
|
199 | end | |
172 | end |
|
200 | end | |
173 | end |
|
201 | end |
@@ -1,12 +1,30 | |||||
1 | <h2><%= link_to l(:label_user_plural), users_path %> » <%=l(:label_user_new)%></h2> |
|
1 | <h2><%= link_to l(:label_user_plural), users_path %> » <%=l(:label_user_new)%></h2> | |
2 |
|
2 | |||
3 | <%= labelled_form_for @user do |f| %> |
|
3 | <%= labelled_form_for @user do |f| %> | |
4 | <%= render :partial => 'form', :locals => { :f => f } %> |
|
4 | <%= render :partial => 'form', :locals => { :f => f } %> | |
5 | <% if email_delivery_enabled? %> |
|
5 | <% if email_delivery_enabled? %> | |
6 | <p><label><%= check_box_tag 'send_information', 1, true %> <%= l(:label_send_information) %></label></p> |
|
6 | <p><label><%= check_box_tag 'send_information', 1, true %> <%= l(:label_send_information) %></label></p> | |
7 | <% end %> |
|
7 | <% end %> | |
8 | <p> |
|
8 | <p> | |
9 | <%= submit_tag l(:button_create) %> |
|
9 | <%= submit_tag l(:button_create) %> | |
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 %> |
@@ -1,341 +1,344 | |||||
1 | # Redmine - project management software |
|
1 | # Redmine - project management software | |
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang |
|
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
6 | # as published by the Free Software Foundation; either version 2 |
|
6 | # as published by the Free Software Foundation; either version 2 | |
7 | # of the License, or (at your option) any later version. |
|
7 | # of the License, or (at your option) any later version. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU General Public License |
|
14 | # You should have received a copy of the GNU General Public License | |
15 | # along with this program; if not, write to the Free Software |
|
15 | # along with this program; if not, write to the Free Software | |
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 |
|
17 | |||
18 | RedmineApp::Application.routes.draw do |
|
18 | RedmineApp::Application.routes.draw do | |
19 | root :to => 'welcome#index', :as => 'home' |
|
19 | root :to => 'welcome#index', :as => 'home' | |
20 |
|
20 | |||
21 | match 'login', :to => 'account#login', :as => 'signin', :via => [:get, :post] |
|
21 | match 'login', :to => 'account#login', :as => 'signin', :via => [:get, :post] | |
22 | match 'logout', :to => 'account#logout', :as => 'signout', :via => [:get, :post] |
|
22 | match 'logout', :to => 'account#logout', :as => 'signout', :via => [:get, :post] | |
23 | match 'account/register', :to => 'account#register', :via => [:get, :post], :as => 'register' |
|
23 | match 'account/register', :to => 'account#register', :via => [:get, :post], :as => 'register' | |
24 | match 'account/lost_password', :to => 'account#lost_password', :via => [:get, :post], :as => 'lost_password' |
|
24 | match 'account/lost_password', :to => 'account#lost_password', :via => [:get, :post], :as => 'lost_password' | |
25 | match 'account/activate', :to => 'account#activate', :via => :get |
|
25 | match 'account/activate', :to => 'account#activate', :via => :get | |
26 |
|
26 | |||
27 | match '/news/preview', :controller => 'previews', :action => 'news', :as => 'preview_news', :via => [:get, :post] |
|
27 | match '/news/preview', :controller => 'previews', :action => 'news', :as => 'preview_news', :via => [:get, :post] | |
28 | match '/issues/preview/new/:project_id', :to => 'previews#issue', :as => 'preview_new_issue', :via => [:get, :post] |
|
28 | match '/issues/preview/new/:project_id', :to => 'previews#issue', :as => 'preview_new_issue', :via => [:get, :post] | |
29 | match '/issues/preview/edit/:id', :to => 'previews#issue', :as => 'preview_edit_issue', :via => [:get, :post] |
|
29 | match '/issues/preview/edit/:id', :to => 'previews#issue', :as => 'preview_edit_issue', :via => [:get, :post] | |
30 | match '/issues/preview', :to => 'previews#issue', :as => 'preview_issue', :via => [:get, :post] |
|
30 | match '/issues/preview', :to => 'previews#issue', :as => 'preview_issue', :via => [:get, :post] | |
31 |
|
31 | |||
32 | match 'projects/:id/wiki', :to => 'wikis#edit', :via => :post |
|
32 | match 'projects/:id/wiki', :to => 'wikis#edit', :via => :post | |
33 | match 'projects/:id/wiki/destroy', :to => 'wikis#destroy', :via => [:get, :post] |
|
33 | match 'projects/:id/wiki/destroy', :to => 'wikis#destroy', :via => [:get, :post] | |
34 |
|
34 | |||
35 | match 'boards/:board_id/topics/new', :to => 'messages#new', :via => [:get, :post], :as => 'new_board_message' |
|
35 | match 'boards/:board_id/topics/new', :to => 'messages#new', :via => [:get, :post], :as => 'new_board_message' | |
36 | get 'boards/:board_id/topics/:id', :to => 'messages#show', :as => 'board_message' |
|
36 | get 'boards/:board_id/topics/:id', :to => 'messages#show', :as => 'board_message' | |
37 | match 'boards/:board_id/topics/quote/:id', :to => 'messages#quote', :via => [:get, :post] |
|
37 | match 'boards/:board_id/topics/quote/:id', :to => 'messages#quote', :via => [:get, :post] | |
38 | get 'boards/:board_id/topics/:id/edit', :to => 'messages#edit' |
|
38 | get 'boards/:board_id/topics/:id/edit', :to => 'messages#edit' | |
39 |
|
39 | |||
40 | post 'boards/:board_id/topics/preview', :to => 'messages#preview', :as => 'preview_board_message' |
|
40 | post 'boards/:board_id/topics/preview', :to => 'messages#preview', :as => 'preview_board_message' | |
41 | post 'boards/:board_id/topics/:id/replies', :to => 'messages#reply' |
|
41 | post 'boards/:board_id/topics/:id/replies', :to => 'messages#reply' | |
42 | post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit' |
|
42 | post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit' | |
43 | post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy' |
|
43 | post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy' | |
44 |
|
44 | |||
45 | # Misc issue routes. TODO: move into resources |
|
45 | # Misc issue routes. TODO: move into resources | |
46 | match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues' |
|
46 | match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues' | |
47 | match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post] |
|
47 | match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post] | |
48 | match '/issues/changes', :to => 'journals#index', :as => 'issue_changes', :via => :get |
|
48 | match '/issues/changes', :to => 'journals#index', :as => 'issue_changes', :via => :get | |
49 | match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue' |
|
49 | match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue' | |
50 |
|
50 | |||
51 | match '/journals/diff/:id', :to => 'journals#diff', :id => /\d+/, :via => :get |
|
51 | match '/journals/diff/:id', :to => 'journals#diff', :id => /\d+/, :via => :get | |
52 | match '/journals/edit/:id', :to => 'journals#edit', :id => /\d+/, :via => [:get, :post] |
|
52 | match '/journals/edit/:id', :to => 'journals#edit', :id => /\d+/, :via => [:get, :post] | |
53 |
|
53 | |||
54 | get '/projects/:project_id/issues/gantt', :to => 'gantts#show', :as => 'project_gantt' |
|
54 | get '/projects/:project_id/issues/gantt', :to => 'gantts#show', :as => 'project_gantt' | |
55 | get '/issues/gantt', :to => 'gantts#show' |
|
55 | get '/issues/gantt', :to => 'gantts#show' | |
56 |
|
56 | |||
57 | get '/projects/:project_id/issues/calendar', :to => 'calendars#show', :as => 'project_calendar' |
|
57 | get '/projects/:project_id/issues/calendar', :to => 'calendars#show', :as => 'project_calendar' | |
58 | get '/issues/calendar', :to => 'calendars#show' |
|
58 | get '/issues/calendar', :to => 'calendars#show' | |
59 |
|
59 | |||
60 | get 'projects/:id/issues/report', :to => 'reports#issue_report', :as => 'project_issues_report' |
|
60 | get 'projects/:id/issues/report', :to => 'reports#issue_report', :as => 'project_issues_report' | |
61 | get 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :as => 'project_issues_report_details' |
|
61 | get 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :as => 'project_issues_report_details' | |
62 |
|
62 | |||
63 | match 'my/account', :controller => 'my', :action => 'account', :via => [:get, :post] |
|
63 | match 'my/account', :controller => 'my', :action => 'account', :via => [:get, :post] | |
64 | match 'my/account/destroy', :controller => 'my', :action => 'destroy', :via => [:get, :post] |
|
64 | match 'my/account/destroy', :controller => 'my', :action => 'destroy', :via => [:get, :post] | |
65 | match 'my/page', :controller => 'my', :action => 'page', :via => :get |
|
65 | match 'my/page', :controller => 'my', :action => 'page', :via => :get | |
66 | match 'my', :controller => 'my', :action => 'index', :via => :get # Redirects to my/page |
|
66 | match 'my', :controller => 'my', :action => 'index', :via => :get # Redirects to my/page | |
67 | match 'my/reset_rss_key', :controller => 'my', :action => 'reset_rss_key', :via => :post |
|
67 | match 'my/reset_rss_key', :controller => 'my', :action => 'reset_rss_key', :via => :post | |
68 | match 'my/reset_api_key', :controller => 'my', :action => 'reset_api_key', :via => :post |
|
68 | match 'my/reset_api_key', :controller => 'my', :action => 'reset_api_key', :via => :post | |
69 | match 'my/password', :controller => 'my', :action => 'password', :via => [:get, :post] |
|
69 | match 'my/password', :controller => 'my', :action => 'password', :via => [:get, :post] | |
70 | match 'my/page_layout', :controller => 'my', :action => 'page_layout', :via => :get |
|
70 | match 'my/page_layout', :controller => 'my', :action => 'page_layout', :via => :get | |
71 | match 'my/add_block', :controller => 'my', :action => 'add_block', :via => :post |
|
71 | match 'my/add_block', :controller => 'my', :action => 'add_block', :via => :post | |
72 | match 'my/remove_block', :controller => 'my', :action => 'remove_block', :via => :post |
|
72 | match 'my/remove_block', :controller => 'my', :action => 'remove_block', :via => :post | |
73 | match 'my/order_blocks', :controller => 'my', :action => 'order_blocks', :via => :post |
|
73 | match 'my/order_blocks', :controller => 'my', :action => 'order_blocks', :via => :post | |
74 |
|
74 | |||
75 | resources :users |
|
75 | resources :users | |
76 | match 'users/:id/memberships/:membership_id', :to => 'users#edit_membership', :via => :put, :as => 'user_membership' |
|
76 | match 'users/:id/memberships/:membership_id', :to => 'users#edit_membership', :via => :put, :as => 'user_membership' | |
77 | match 'users/:id/memberships/:membership_id', :to => 'users#destroy_membership', :via => :delete |
|
77 | match 'users/:id/memberships/:membership_id', :to => 'users#destroy_membership', :via => :delete | |
78 | match 'users/:id/memberships', :to => 'users#edit_membership', :via => :post, :as => 'user_memberships' |
|
78 | match 'users/:id/memberships', :to => 'users#edit_membership', :via => :post, :as => 'user_memberships' | |
79 |
|
79 | |||
80 | match 'watchers/new', :controller=> 'watchers', :action => 'new', :via => :get |
|
80 | match 'watchers/new', :controller=> 'watchers', :action => 'new', :via => :get | |
81 | match 'watchers', :controller=> 'watchers', :action => 'create', :via => :post |
|
81 | match 'watchers', :controller=> 'watchers', :action => 'create', :via => :post | |
82 | match 'watchers/append', :controller=> 'watchers', :action => 'append', :via => :post |
|
82 | match 'watchers/append', :controller=> 'watchers', :action => 'append', :via => :post | |
83 | match 'watchers/destroy', :controller=> 'watchers', :action => 'destroy', :via => :post |
|
83 | match 'watchers/destroy', :controller=> 'watchers', :action => 'destroy', :via => :post | |
84 | match 'watchers/watch', :controller=> 'watchers', :action => 'watch', :via => :post |
|
84 | match 'watchers/watch', :controller=> 'watchers', :action => 'watch', :via => :post | |
85 | match 'watchers/unwatch', :controller=> 'watchers', :action => 'unwatch', :via => :post |
|
85 | match 'watchers/unwatch', :controller=> 'watchers', :action => 'unwatch', :via => :post | |
86 | match 'watchers/autocomplete_for_user', :controller=> 'watchers', :action => 'autocomplete_for_user', :via => :get |
|
86 | match 'watchers/autocomplete_for_user', :controller=> 'watchers', :action => 'autocomplete_for_user', :via => :get | |
87 |
|
87 | |||
88 | resources :projects do |
|
88 | resources :projects do | |
89 | member do |
|
89 | member do | |
90 | get 'settings(/:tab)', :action => 'settings', :as => 'settings' |
|
90 | get 'settings(/:tab)', :action => 'settings', :as => 'settings' | |
91 | post 'modules' |
|
91 | post 'modules' | |
92 | post 'archive' |
|
92 | post 'archive' | |
93 | post 'unarchive' |
|
93 | post 'unarchive' | |
94 | post 'close' |
|
94 | post 'close' | |
95 | post 'reopen' |
|
95 | post 'reopen' | |
96 | match 'copy', :via => [:get, :post] |
|
96 | match 'copy', :via => [:get, :post] | |
97 | end |
|
97 | end | |
98 |
|
98 | |||
99 | resources :memberships, :shallow => true, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do |
|
99 | resources :memberships, :shallow => true, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do | |
100 | collection do |
|
100 | collection do | |
101 | get 'autocomplete' |
|
101 | get 'autocomplete' | |
102 | end |
|
102 | end | |
103 | end |
|
103 | end | |
104 |
|
104 | |||
105 | resource :enumerations, :controller => 'project_enumerations', :only => [:update, :destroy] |
|
105 | resource :enumerations, :controller => 'project_enumerations', :only => [:update, :destroy] | |
106 |
|
106 | |||
107 | get 'issues/:copy_from/copy', :to => 'issues#new', :as => 'copy_issue' |
|
107 | get 'issues/:copy_from/copy', :to => 'issues#new', :as => 'copy_issue' | |
108 | resources :issues, :only => [:index, :new, :create] do |
|
108 | resources :issues, :only => [:index, :new, :create] do | |
109 | resources :time_entries, :controller => 'timelog' do |
|
109 | resources :time_entries, :controller => 'timelog' do | |
110 | collection do |
|
110 | collection do | |
111 | get 'report' |
|
111 | get 'report' | |
112 | end |
|
112 | end | |
113 | end |
|
113 | end | |
114 | end |
|
114 | end | |
115 | # issue form update |
|
115 | # issue form update | |
116 | match 'issues/new', :controller => 'issues', :action => 'new', :via => [:put, :post], :as => 'issue_form' |
|
116 | match 'issues/new', :controller => 'issues', :action => 'new', :via => [:put, :post], :as => 'issue_form' | |
117 |
|
117 | |||
118 | resources :files, :only => [:index, :new, :create] |
|
118 | resources :files, :only => [:index, :new, :create] | |
119 |
|
119 | |||
120 | resources :versions, :except => [:index, :show, :edit, :update, :destroy] do |
|
120 | resources :versions, :except => [:index, :show, :edit, :update, :destroy] do | |
121 | collection do |
|
121 | collection do | |
122 | put 'close_completed' |
|
122 | put 'close_completed' | |
123 | end |
|
123 | end | |
124 | end |
|
124 | end | |
125 | get 'versions.:format', :to => 'versions#index' |
|
125 | get 'versions.:format', :to => 'versions#index' | |
126 | get 'roadmap', :to => 'versions#index', :format => false |
|
126 | get 'roadmap', :to => 'versions#index', :format => false | |
127 | get 'versions', :to => 'versions#index' |
|
127 | get 'versions', :to => 'versions#index' | |
128 |
|
128 | |||
129 | resources :news, :except => [:show, :edit, :update, :destroy] |
|
129 | resources :news, :except => [:show, :edit, :update, :destroy] | |
130 | resources :time_entries, :controller => 'timelog' do |
|
130 | resources :time_entries, :controller => 'timelog' do | |
131 | get 'report', :on => :collection |
|
131 | get 'report', :on => :collection | |
132 | end |
|
132 | end | |
133 | resources :queries, :only => [:new, :create] |
|
133 | resources :queries, :only => [:new, :create] | |
134 | resources :issue_categories, :shallow => true |
|
134 | resources :issue_categories, :shallow => true | |
135 | resources :documents, :except => [:show, :edit, :update, :destroy] |
|
135 | resources :documents, :except => [:show, :edit, :update, :destroy] | |
136 | resources :boards |
|
136 | resources :boards | |
137 | resources :repositories, :shallow => true, :except => [:index, :show] do |
|
137 | resources :repositories, :shallow => true, :except => [:index, :show] do | |
138 | member do |
|
138 | member do | |
139 | match 'committers', :via => [:get, :post] |
|
139 | match 'committers', :via => [:get, :post] | |
140 | end |
|
140 | end | |
141 | end |
|
141 | end | |
142 |
|
142 | |||
143 | match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get |
|
143 | match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get | |
144 | resources :wiki, :except => [:index, :new, :create], :as => 'wiki_page' do |
|
144 | resources :wiki, :except => [:index, :new, :create], :as => 'wiki_page' do | |
145 | member do |
|
145 | member do | |
146 | get 'rename' |
|
146 | get 'rename' | |
147 | post 'rename' |
|
147 | post 'rename' | |
148 | get 'history' |
|
148 | get 'history' | |
149 | get 'diff' |
|
149 | get 'diff' | |
150 | match 'preview', :via => [:post, :put] |
|
150 | match 'preview', :via => [:post, :put] | |
151 | post 'protect' |
|
151 | post 'protect' | |
152 | post 'add_attachment' |
|
152 | post 'add_attachment' | |
153 | end |
|
153 | end | |
154 | collection do |
|
154 | collection do | |
155 | get 'export' |
|
155 | get 'export' | |
156 | get 'date_index' |
|
156 | get 'date_index' | |
157 | end |
|
157 | end | |
158 | end |
|
158 | end | |
159 | match 'wiki', :controller => 'wiki', :action => 'show', :via => :get |
|
159 | match 'wiki', :controller => 'wiki', :action => 'show', :via => :get | |
160 | get 'wiki/:id/:version', :to => 'wiki#show' |
|
160 | get 'wiki/:id/:version', :to => 'wiki#show' | |
161 | delete 'wiki/:id/:version', :to => 'wiki#destroy_version' |
|
161 | delete 'wiki/:id/:version', :to => 'wiki#destroy_version' | |
162 | get 'wiki/:id/:version/annotate', :to => 'wiki#annotate' |
|
162 | get 'wiki/:id/:version/annotate', :to => 'wiki#annotate' | |
163 | get 'wiki/:id/:version/diff', :to => 'wiki#diff' |
|
163 | get 'wiki/:id/:version/diff', :to => 'wiki#diff' | |
164 | end |
|
164 | end | |
165 |
|
165 | |||
166 | resources :issues do |
|
166 | resources :issues do | |
167 | collection do |
|
167 | collection do | |
168 | match 'bulk_edit', :via => [:get, :post] |
|
168 | match 'bulk_edit', :via => [:get, :post] | |
169 | post 'bulk_update' |
|
169 | post 'bulk_update' | |
170 | end |
|
170 | end | |
171 | resources :time_entries, :controller => 'timelog' do |
|
171 | resources :time_entries, :controller => 'timelog' do | |
172 | collection do |
|
172 | collection do | |
173 | get 'report' |
|
173 | get 'report' | |
174 | end |
|
174 | end | |
175 | end |
|
175 | end | |
176 | resources :relations, :shallow => true, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy] |
|
176 | resources :relations, :shallow => true, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy] | |
177 | end |
|
177 | end | |
178 | match '/issues', :controller => 'issues', :action => 'destroy', :via => :delete |
|
178 | match '/issues', :controller => 'issues', :action => 'destroy', :via => :delete | |
179 |
|
179 | |||
180 | resources :queries, :except => [:show] |
|
180 | resources :queries, :except => [:show] | |
181 |
|
181 | |||
182 | resources :news, :only => [:index, :show, :edit, :update, :destroy] |
|
182 | resources :news, :only => [:index, :show, :edit, :update, :destroy] | |
183 | match '/news/:id/comments', :to => 'comments#create', :via => :post |
|
183 | match '/news/:id/comments', :to => 'comments#create', :via => :post | |
184 | match '/news/:id/comments/:comment_id', :to => 'comments#destroy', :via => :delete |
|
184 | match '/news/:id/comments/:comment_id', :to => 'comments#destroy', :via => :delete | |
185 |
|
185 | |||
186 | resources :versions, :only => [:show, :edit, :update, :destroy] do |
|
186 | resources :versions, :only => [:show, :edit, :update, :destroy] do | |
187 | post 'status_by', :on => :member |
|
187 | post 'status_by', :on => :member | |
188 | end |
|
188 | end | |
189 |
|
189 | |||
190 | resources :documents, :only => [:show, :edit, :update, :destroy] do |
|
190 | resources :documents, :only => [:show, :edit, :update, :destroy] do | |
191 | post 'add_attachment', :on => :member |
|
191 | post 'add_attachment', :on => :member | |
192 | end |
|
192 | end | |
193 |
|
193 | |||
194 | match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu, :via => [:get, :post] |
|
194 | match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu, :via => [:get, :post] | |
195 |
|
195 | |||
196 | resources :time_entries, :controller => 'timelog', :except => :destroy do |
|
196 | resources :time_entries, :controller => 'timelog', :except => :destroy do | |
197 | collection do |
|
197 | collection do | |
198 | get 'report' |
|
198 | get 'report' | |
199 | get 'bulk_edit' |
|
199 | get 'bulk_edit' | |
200 | post 'bulk_update' |
|
200 | post 'bulk_update' | |
201 | end |
|
201 | end | |
202 | end |
|
202 | end | |
203 | match '/time_entries/:id', :to => 'timelog#destroy', :via => :delete, :id => /\d+/ |
|
203 | match '/time_entries/:id', :to => 'timelog#destroy', :via => :delete, :id => /\d+/ | |
204 | # TODO: delete /time_entries for bulk deletion |
|
204 | # TODO: delete /time_entries for bulk deletion | |
205 | match '/time_entries/destroy', :to => 'timelog#destroy', :via => :delete |
|
205 | match '/time_entries/destroy', :to => 'timelog#destroy', :via => :delete | |
206 |
|
206 | |||
207 | get 'projects/:id/activity', :to => 'activities#index' |
|
207 | get 'projects/:id/activity', :to => 'activities#index' | |
208 | get 'projects/:id/activity.:format', :to => 'activities#index' |
|
208 | get 'projects/:id/activity.:format', :to => 'activities#index' | |
209 | get 'activity', :to => 'activities#index' |
|
209 | get 'activity', :to => 'activities#index' | |
210 |
|
210 | |||
211 | # repositories routes |
|
211 | # repositories routes | |
212 | get 'projects/:id/repository/:repository_id/statistics', :to => 'repositories#stats' |
|
212 | get 'projects/:id/repository/:repository_id/statistics', :to => 'repositories#stats' | |
213 | get 'projects/:id/repository/:repository_id/graph', :to => 'repositories#graph' |
|
213 | get 'projects/:id/repository/:repository_id/graph', :to => 'repositories#graph' | |
214 |
|
214 | |||
215 | get 'projects/:id/repository/:repository_id/changes(/*path(.:ext))', |
|
215 | get 'projects/:id/repository/:repository_id/changes(/*path(.:ext))', | |
216 | :to => 'repositories#changes' |
|
216 | :to => 'repositories#changes' | |
217 |
|
217 | |||
218 | get 'projects/:id/repository/:repository_id/revisions/:rev', :to => 'repositories#revision' |
|
218 | get 'projects/:id/repository/:repository_id/revisions/:rev', :to => 'repositories#revision' | |
219 | get 'projects/:id/repository/:repository_id/revision', :to => 'repositories#revision' |
|
219 | get 'projects/:id/repository/:repository_id/revision', :to => 'repositories#revision' | |
220 | post 'projects/:id/repository/:repository_id/revisions/:rev/issues', :to => 'repositories#add_related_issue' |
|
220 | post 'projects/:id/repository/:repository_id/revisions/:rev/issues', :to => 'repositories#add_related_issue' | |
221 | delete 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue' |
|
221 | delete 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue' | |
222 | get 'projects/:id/repository/:repository_id/revisions', :to => 'repositories#revisions' |
|
222 | get 'projects/:id/repository/:repository_id/revisions', :to => 'repositories#revisions' | |
223 | get 'projects/:id/repository/:repository_id/revisions/:rev/:action(/*path(.:ext))', |
|
223 | get 'projects/:id/repository/:repository_id/revisions/:rev/:action(/*path(.:ext))', | |
224 | :controller => 'repositories', |
|
224 | :controller => 'repositories', | |
225 | :format => false, |
|
225 | :format => false, | |
226 | :constraints => { |
|
226 | :constraints => { | |
227 | :action => /(browse|show|entry|raw|annotate|diff)/, |
|
227 | :action => /(browse|show|entry|raw|annotate|diff)/, | |
228 | :rev => /[a-z0-9\.\-_]+/ |
|
228 | :rev => /[a-z0-9\.\-_]+/ | |
229 | } |
|
229 | } | |
230 |
|
230 | |||
231 | get 'projects/:id/repository/statistics', :to => 'repositories#stats' |
|
231 | get 'projects/:id/repository/statistics', :to => 'repositories#stats' | |
232 | get 'projects/:id/repository/graph', :to => 'repositories#graph' |
|
232 | get 'projects/:id/repository/graph', :to => 'repositories#graph' | |
233 |
|
233 | |||
234 | get 'projects/:id/repository/changes(/*path(.:ext))', |
|
234 | get 'projects/:id/repository/changes(/*path(.:ext))', | |
235 | :to => 'repositories#changes' |
|
235 | :to => 'repositories#changes' | |
236 |
|
236 | |||
237 | get 'projects/:id/repository/revisions', :to => 'repositories#revisions' |
|
237 | get 'projects/:id/repository/revisions', :to => 'repositories#revisions' | |
238 | get 'projects/:id/repository/revisions/:rev', :to => 'repositories#revision' |
|
238 | get 'projects/:id/repository/revisions/:rev', :to => 'repositories#revision' | |
239 | get 'projects/:id/repository/revision', :to => 'repositories#revision' |
|
239 | get 'projects/:id/repository/revision', :to => 'repositories#revision' | |
240 | post 'projects/:id/repository/revisions/:rev/issues', :to => 'repositories#add_related_issue' |
|
240 | post 'projects/:id/repository/revisions/:rev/issues', :to => 'repositories#add_related_issue' | |
241 | delete 'projects/:id/repository/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue' |
|
241 | delete 'projects/:id/repository/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue' | |
242 | get 'projects/:id/repository/revisions/:rev/:action(/*path(.:ext))', |
|
242 | get 'projects/:id/repository/revisions/:rev/:action(/*path(.:ext))', | |
243 | :controller => 'repositories', |
|
243 | :controller => 'repositories', | |
244 | :format => false, |
|
244 | :format => false, | |
245 | :constraints => { |
|
245 | :constraints => { | |
246 | :action => /(browse|show|entry|raw|annotate|diff)/, |
|
246 | :action => /(browse|show|entry|raw|annotate|diff)/, | |
247 | :rev => /[a-z0-9\.\-_]+/ |
|
247 | :rev => /[a-z0-9\.\-_]+/ | |
248 | } |
|
248 | } | |
249 | get 'projects/:id/repository/:repository_id/:action(/*path(.:ext))', |
|
249 | get 'projects/:id/repository/:repository_id/:action(/*path(.:ext))', | |
250 | :controller => 'repositories', |
|
250 | :controller => 'repositories', | |
251 | :action => /(browse|show|entry|raw|changes|annotate|diff)/ |
|
251 | :action => /(browse|show|entry|raw|changes|annotate|diff)/ | |
252 | get 'projects/:id/repository/:action(/*path(.:ext))', |
|
252 | get 'projects/:id/repository/:action(/*path(.:ext))', | |
253 | :controller => 'repositories', |
|
253 | :controller => 'repositories', | |
254 | :action => /(browse|show|entry|raw|changes|annotate|diff)/ |
|
254 | :action => /(browse|show|entry|raw|changes|annotate|diff)/ | |
255 |
|
255 | |||
256 | get 'projects/:id/repository/:repository_id', :to => 'repositories#show', :path => nil |
|
256 | get 'projects/:id/repository/:repository_id', :to => 'repositories#show', :path => nil | |
257 | get 'projects/:id/repository', :to => 'repositories#show', :path => nil |
|
257 | get 'projects/:id/repository', :to => 'repositories#show', :path => nil | |
258 |
|
258 | |||
259 | # additional routes for having the file name at the end of url |
|
259 | # additional routes for having the file name at the end of url | |
260 | match 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/, :via => :get |
|
260 | match 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/, :via => :get | |
261 | match 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/, :via => :get |
|
261 | match 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/, :via => :get | |
262 | match 'attachments/download/:id', :controller => 'attachments', :action => 'download', :id => /\d+/, :via => :get |
|
262 | match 'attachments/download/:id', :controller => 'attachments', :action => 'download', :id => /\d+/, :via => :get | |
263 | match 'attachments/thumbnail/:id(/:size)', :controller => 'attachments', :action => 'thumbnail', :id => /\d+/, :via => :get, :size => /\d+/ |
|
263 | match 'attachments/thumbnail/:id(/:size)', :controller => 'attachments', :action => 'thumbnail', :id => /\d+/, :via => :get, :size => /\d+/ | |
264 | resources :attachments, :only => [:show, :destroy] |
|
264 | resources :attachments, :only => [:show, :destroy] | |
265 |
|
265 | |||
266 | resources :groups do |
|
266 | resources :groups do | |
267 | member do |
|
267 | member do | |
268 | get 'autocomplete_for_user' |
|
268 | get 'autocomplete_for_user' | |
269 | end |
|
269 | end | |
270 | end |
|
270 | end | |
271 |
|
271 | |||
272 | match 'groups/:id/users', :controller => 'groups', :action => 'add_users', :id => /\d+/, :via => :post, :as => 'group_users' |
|
272 | match 'groups/:id/users', :controller => 'groups', :action => 'add_users', :id => /\d+/, :via => :post, :as => 'group_users' | |
273 | match 'groups/:id/users/:user_id', :controller => 'groups', :action => 'remove_user', :id => /\d+/, :via => :delete, :as => 'group_user' |
|
273 | match 'groups/:id/users/:user_id', :controller => 'groups', :action => 'remove_user', :id => /\d+/, :via => :delete, :as => 'group_user' | |
274 | match 'groups/destroy_membership/:id', :controller => 'groups', :action => 'destroy_membership', :id => /\d+/, :via => :post |
|
274 | match 'groups/destroy_membership/:id', :controller => 'groups', :action => 'destroy_membership', :id => /\d+/, :via => :post | |
275 | match 'groups/edit_membership/:id', :controller => 'groups', :action => 'edit_membership', :id => /\d+/, :via => :post |
|
275 | match 'groups/edit_membership/:id', :controller => 'groups', :action => 'edit_membership', :id => /\d+/, :via => :post | |
276 |
|
276 | |||
277 | resources :trackers, :except => :show do |
|
277 | resources :trackers, :except => :show do | |
278 | collection do |
|
278 | collection do | |
279 | match 'fields', :via => [:get, :post] |
|
279 | match 'fields', :via => [:get, :post] | |
280 | end |
|
280 | end | |
281 | end |
|
281 | end | |
282 | resources :issue_statuses, :except => :show do |
|
282 | resources :issue_statuses, :except => :show do | |
283 | collection do |
|
283 | collection do | |
284 | post 'update_issue_done_ratio' |
|
284 | post 'update_issue_done_ratio' | |
285 | end |
|
285 | end | |
286 | end |
|
286 | end | |
287 | resources :custom_fields, :except => :show |
|
287 | resources :custom_fields, :except => :show | |
288 | resources :roles do |
|
288 | resources :roles do | |
289 | collection do |
|
289 | collection do | |
290 | match 'permissions', :via => [:get, :post] |
|
290 | match 'permissions', :via => [:get, :post] | |
291 | end |
|
291 | end | |
292 | end |
|
292 | end | |
293 | resources :enumerations, :except => :show |
|
293 | resources :enumerations, :except => :show | |
294 | match 'enumerations/:type', :to => 'enumerations#index', :via => :get |
|
294 | match 'enumerations/:type', :to => 'enumerations#index', :via => :get | |
295 |
|
295 | |||
296 | get 'projects/:id/search', :controller => 'search', :action => 'index' |
|
296 | get 'projects/:id/search', :controller => 'search', :action => 'index' | |
297 | get 'search', :controller => 'search', :action => 'index' |
|
297 | get 'search', :controller => 'search', :action => 'index' | |
298 |
|
298 | |||
299 | match 'mail_handler', :controller => 'mail_handler', :action => 'index', :via => :post |
|
299 | match 'mail_handler', :controller => 'mail_handler', :action => 'index', :via => :post | |
300 |
|
300 | |||
301 | match 'admin', :controller => 'admin', :action => 'index', :via => :get |
|
301 | match 'admin', :controller => 'admin', :action => 'index', :via => :get | |
302 | match 'admin/projects', :controller => 'admin', :action => 'projects', :via => :get |
|
302 | match 'admin/projects', :controller => 'admin', :action => 'projects', :via => :get | |
303 | match 'admin/plugins', :controller => 'admin', :action => 'plugins', :via => :get |
|
303 | match 'admin/plugins', :controller => 'admin', :action => 'plugins', :via => :get | |
304 | match 'admin/info', :controller => 'admin', :action => 'info', :via => :get |
|
304 | match 'admin/info', :controller => 'admin', :action => 'info', :via => :get | |
305 | match 'admin/test_email', :controller => 'admin', :action => 'test_email', :via => :get |
|
305 | match 'admin/test_email', :controller => 'admin', :action => 'test_email', :via => :get | |
306 | match 'admin/default_configuration', :controller => 'admin', :action => 'default_configuration', :via => :post |
|
306 | match 'admin/default_configuration', :controller => 'admin', :action => 'default_configuration', :via => :post | |
307 |
|
307 | |||
308 | resources :auth_sources do |
|
308 | resources :auth_sources 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 | |
315 | match 'workflows/edit', :controller => 'workflows', :action => 'edit', :via => [:get, :post] |
|
318 | match 'workflows/edit', :controller => 'workflows', :action => 'edit', :via => [:get, :post] | |
316 | match 'workflows/permissions', :controller => 'workflows', :action => 'permissions', :via => [:get, :post] |
|
319 | match 'workflows/permissions', :controller => 'workflows', :action => 'permissions', :via => [:get, :post] | |
317 | match 'workflows/copy', :controller => 'workflows', :action => 'copy', :via => [:get, :post] |
|
320 | match 'workflows/copy', :controller => 'workflows', :action => 'copy', :via => [:get, :post] | |
318 | match 'settings', :controller => 'settings', :action => 'index', :via => :get |
|
321 | match 'settings', :controller => 'settings', :action => 'index', :via => :get | |
319 | match 'settings/edit', :controller => 'settings', :action => 'edit', :via => [:get, :post] |
|
322 | match 'settings/edit', :controller => 'settings', :action => 'edit', :via => [:get, :post] | |
320 | match 'settings/plugin/:id', :controller => 'settings', :action => 'plugin', :via => [:get, :post], :as => 'plugin_settings' |
|
323 | match 'settings/plugin/:id', :controller => 'settings', :action => 'plugin', :via => [:get, :post], :as => 'plugin_settings' | |
321 |
|
324 | |||
322 | match 'sys/projects', :to => 'sys#projects', :via => :get |
|
325 | match 'sys/projects', :to => 'sys#projects', :via => :get | |
323 | match 'sys/projects/:id/repository', :to => 'sys#create_project_repository', :via => :post |
|
326 | match 'sys/projects/:id/repository', :to => 'sys#create_project_repository', :via => :post | |
324 | match 'sys/fetch_changesets', :to => 'sys#fetch_changesets', :via => :get |
|
327 | match 'sys/fetch_changesets', :to => 'sys#fetch_changesets', :via => :get | |
325 |
|
328 | |||
326 | match 'uploads', :to => 'attachments#upload', :via => :post |
|
329 | match 'uploads', :to => 'attachments#upload', :via => :post | |
327 |
|
330 | |||
328 | get 'robots.txt', :to => 'welcome#robots' |
|
331 | get 'robots.txt', :to => 'welcome#robots' | |
329 |
|
332 | |||
330 | Dir.glob File.expand_path("plugins/*", Rails.root) do |plugin_dir| |
|
333 | Dir.glob File.expand_path("plugins/*", Rails.root) do |plugin_dir| | |
331 | file = File.join(plugin_dir, "config/routes.rb") |
|
334 | file = File.join(plugin_dir, "config/routes.rb") | |
332 | if File.exists?(file) |
|
335 | if File.exists?(file) | |
333 | begin |
|
336 | begin | |
334 | instance_eval File.read(file) |
|
337 | instance_eval File.read(file) | |
335 | rescue Exception => e |
|
338 | rescue Exception => e | |
336 | puts "An error occurred while loading the routes definition of #{File.basename(plugin_dir)} plugin (#{file}): #{e.message}." |
|
339 | puts "An error occurred while loading the routes definition of #{File.basename(plugin_dir)} plugin (#{file}): #{e.message}." | |
337 | exit 1 |
|
340 | exit 1 | |
338 | end |
|
341 | end | |
339 | end |
|
342 | end | |
340 | end |
|
343 | end | |
341 | end |
|
344 | end |
@@ -1,586 +1,586 | |||||
1 | /* Redmine - project management software |
|
1 | /* Redmine - project management software | |
2 | Copyright (C) 2006-2012 Jean-Philippe Lang */ |
|
2 | Copyright (C) 2006-2012 Jean-Philippe Lang */ | |
3 |
|
3 | |||
4 | function checkAll(id, checked) { |
|
4 | function checkAll(id, checked) { | |
5 | if (checked) { |
|
5 | if (checked) { | |
6 | $('#'+id).find('input[type=checkbox]').attr('checked', true); |
|
6 | $('#'+id).find('input[type=checkbox]').attr('checked', true); | |
7 | } else { |
|
7 | } else { | |
8 | $('#'+id).find('input[type=checkbox]').removeAttr('checked'); |
|
8 | $('#'+id).find('input[type=checkbox]').removeAttr('checked'); | |
9 | } |
|
9 | } | |
10 | } |
|
10 | } | |
11 |
|
11 | |||
12 | function toggleCheckboxesBySelector(selector) { |
|
12 | function toggleCheckboxesBySelector(selector) { | |
13 | var all_checked = true; |
|
13 | var all_checked = true; | |
14 | $(selector).each(function(index) { |
|
14 | $(selector).each(function(index) { | |
15 | if (!$(this).is(':checked')) { all_checked = false; } |
|
15 | if (!$(this).is(':checked')) { all_checked = false; } | |
16 | }); |
|
16 | }); | |
17 | $(selector).attr('checked', !all_checked) |
|
17 | $(selector).attr('checked', !all_checked) | |
18 | } |
|
18 | } | |
19 |
|
19 | |||
20 | function showAndScrollTo(id, focus) { |
|
20 | function showAndScrollTo(id, focus) { | |
21 | $('#'+id).show(); |
|
21 | $('#'+id).show(); | |
22 | if (focus!=null) { |
|
22 | if (focus!=null) { | |
23 | $('#'+focus).focus(); |
|
23 | $('#'+focus).focus(); | |
24 | } |
|
24 | } | |
25 | $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100); |
|
25 | $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100); | |
26 | } |
|
26 | } | |
27 |
|
27 | |||
28 | function toggleRowGroup(el) { |
|
28 | function toggleRowGroup(el) { | |
29 | var tr = $(el).parents('tr').first(); |
|
29 | var tr = $(el).parents('tr').first(); | |
30 | var n = tr.next(); |
|
30 | var n = tr.next(); | |
31 | tr.toggleClass('open'); |
|
31 | tr.toggleClass('open'); | |
32 | while (n.length && !n.hasClass('group')) { |
|
32 | while (n.length && !n.hasClass('group')) { | |
33 | n.toggle(); |
|
33 | n.toggle(); | |
34 | n = n.next('tr'); |
|
34 | n = n.next('tr'); | |
35 | } |
|
35 | } | |
36 | } |
|
36 | } | |
37 |
|
37 | |||
38 | function collapseAllRowGroups(el) { |
|
38 | function collapseAllRowGroups(el) { | |
39 | var tbody = $(el).parents('tbody').first(); |
|
39 | var tbody = $(el).parents('tbody').first(); | |
40 | tbody.children('tr').each(function(index) { |
|
40 | tbody.children('tr').each(function(index) { | |
41 | if ($(this).hasClass('group')) { |
|
41 | if ($(this).hasClass('group')) { | |
42 | $(this).removeClass('open'); |
|
42 | $(this).removeClass('open'); | |
43 | } else { |
|
43 | } else { | |
44 | $(this).hide(); |
|
44 | $(this).hide(); | |
45 | } |
|
45 | } | |
46 | }); |
|
46 | }); | |
47 | } |
|
47 | } | |
48 |
|
48 | |||
49 | function expandAllRowGroups(el) { |
|
49 | function expandAllRowGroups(el) { | |
50 | var tbody = $(el).parents('tbody').first(); |
|
50 | var tbody = $(el).parents('tbody').first(); | |
51 | tbody.children('tr').each(function(index) { |
|
51 | tbody.children('tr').each(function(index) { | |
52 | if ($(this).hasClass('group')) { |
|
52 | if ($(this).hasClass('group')) { | |
53 | $(this).addClass('open'); |
|
53 | $(this).addClass('open'); | |
54 | } else { |
|
54 | } else { | |
55 | $(this).show(); |
|
55 | $(this).show(); | |
56 | } |
|
56 | } | |
57 | }); |
|
57 | }); | |
58 | } |
|
58 | } | |
59 |
|
59 | |||
60 | function toggleAllRowGroups(el) { |
|
60 | function toggleAllRowGroups(el) { | |
61 | var tr = $(el).parents('tr').first(); |
|
61 | var tr = $(el).parents('tr').first(); | |
62 | if (tr.hasClass('open')) { |
|
62 | if (tr.hasClass('open')) { | |
63 | collapseAllRowGroups(el); |
|
63 | collapseAllRowGroups(el); | |
64 | } else { |
|
64 | } else { | |
65 | expandAllRowGroups(el); |
|
65 | expandAllRowGroups(el); | |
66 | } |
|
66 | } | |
67 | } |
|
67 | } | |
68 |
|
68 | |||
69 | function toggleFieldset(el) { |
|
69 | function toggleFieldset(el) { | |
70 | var fieldset = $(el).parents('fieldset').first(); |
|
70 | var fieldset = $(el).parents('fieldset').first(); | |
71 | fieldset.toggleClass('collapsed'); |
|
71 | fieldset.toggleClass('collapsed'); | |
72 | fieldset.children('div').toggle(); |
|
72 | fieldset.children('div').toggle(); | |
73 | } |
|
73 | } | |
74 |
|
74 | |||
75 | function hideFieldset(el) { |
|
75 | function hideFieldset(el) { | |
76 | var fieldset = $(el).parents('fieldset').first(); |
|
76 | var fieldset = $(el).parents('fieldset').first(); | |
77 | fieldset.toggleClass('collapsed'); |
|
77 | fieldset.toggleClass('collapsed'); | |
78 | fieldset.children('div').hide(); |
|
78 | fieldset.children('div').hide(); | |
79 | } |
|
79 | } | |
80 |
|
80 | |||
81 | function initFilters(){ |
|
81 | function initFilters(){ | |
82 | $('#add_filter_select').change(function(){ |
|
82 | $('#add_filter_select').change(function(){ | |
83 | addFilter($(this).val(), '', []); |
|
83 | addFilter($(this).val(), '', []); | |
84 | }); |
|
84 | }); | |
85 | $('#filters-table td.field input[type=checkbox]').each(function(){ |
|
85 | $('#filters-table td.field input[type=checkbox]').each(function(){ | |
86 | toggleFilter($(this).val()); |
|
86 | toggleFilter($(this).val()); | |
87 | }); |
|
87 | }); | |
88 | $('#filters-table td.field input[type=checkbox]').live('click',function(){ |
|
88 | $('#filters-table td.field input[type=checkbox]').live('click',function(){ | |
89 | toggleFilter($(this).val()); |
|
89 | toggleFilter($(this).val()); | |
90 | }); |
|
90 | }); | |
91 | $('#filters-table .toggle-multiselect').live('click',function(){ |
|
91 | $('#filters-table .toggle-multiselect').live('click',function(){ | |
92 | toggleMultiSelect($(this).siblings('select')); |
|
92 | toggleMultiSelect($(this).siblings('select')); | |
93 | }); |
|
93 | }); | |
94 | $('#filters-table input[type=text]').live('keypress', function(e){ |
|
94 | $('#filters-table input[type=text]').live('keypress', function(e){ | |
95 | if (e.keyCode == 13) submit_query_form("query_form"); |
|
95 | if (e.keyCode == 13) submit_query_form("query_form"); | |
96 | }); |
|
96 | }); | |
97 | } |
|
97 | } | |
98 |
|
98 | |||
99 | function addFilter(field, operator, values) { |
|
99 | function addFilter(field, operator, values) { | |
100 | var fieldId = field.replace('.', '_'); |
|
100 | var fieldId = field.replace('.', '_'); | |
101 | var tr = $('#tr_'+fieldId); |
|
101 | var tr = $('#tr_'+fieldId); | |
102 | if (tr.length > 0) { |
|
102 | if (tr.length > 0) { | |
103 | tr.show(); |
|
103 | tr.show(); | |
104 | } else { |
|
104 | } else { | |
105 | buildFilterRow(field, operator, values); |
|
105 | buildFilterRow(field, operator, values); | |
106 | } |
|
106 | } | |
107 | $('#cb_'+fieldId).attr('checked', true); |
|
107 | $('#cb_'+fieldId).attr('checked', true); | |
108 | toggleFilter(field); |
|
108 | toggleFilter(field); | |
109 | $('#add_filter_select').val('').children('option').each(function(){ |
|
109 | $('#add_filter_select').val('').children('option').each(function(){ | |
110 | if ($(this).attr('value') == field) { |
|
110 | if ($(this).attr('value') == field) { | |
111 | $(this).attr('disabled', true); |
|
111 | $(this).attr('disabled', true); | |
112 | } |
|
112 | } | |
113 | }); |
|
113 | }); | |
114 | } |
|
114 | } | |
115 |
|
115 | |||
116 | function buildFilterRow(field, operator, values) { |
|
116 | function buildFilterRow(field, operator, values) { | |
117 | var fieldId = field.replace('.', '_'); |
|
117 | var fieldId = field.replace('.', '_'); | |
118 | var filterTable = $("#filters-table"); |
|
118 | var filterTable = $("#filters-table"); | |
119 | var filterOptions = availableFilters[field]; |
|
119 | var filterOptions = availableFilters[field]; | |
120 | var operators = operatorByType[filterOptions['type']]; |
|
120 | var operators = operatorByType[filterOptions['type']]; | |
121 | var filterValues = filterOptions['values']; |
|
121 | var filterValues = filterOptions['values']; | |
122 | var i, select; |
|
122 | var i, select; | |
123 |
|
123 | |||
124 | var tr = $('<tr class="filter">').attr('id', 'tr_'+fieldId).html( |
|
124 | var tr = $('<tr class="filter">').attr('id', 'tr_'+fieldId).html( | |
125 | '<td class="field"><input checked="checked" id="cb_'+fieldId+'" name="f[]" value="'+field+'" type="checkbox"><label for="cb_'+fieldId+'"> '+filterOptions['name']+'</label></td>' + |
|
125 | '<td class="field"><input checked="checked" id="cb_'+fieldId+'" name="f[]" value="'+field+'" type="checkbox"><label for="cb_'+fieldId+'"> '+filterOptions['name']+'</label></td>' + | |
126 | '<td class="operator"><select id="operators_'+fieldId+'" name="op['+field+']"></td>' + |
|
126 | '<td class="operator"><select id="operators_'+fieldId+'" name="op['+field+']"></td>' + | |
127 | '<td class="values"></td>' |
|
127 | '<td class="values"></td>' | |
128 | ); |
|
128 | ); | |
129 | filterTable.append(tr); |
|
129 | filterTable.append(tr); | |
130 |
|
130 | |||
131 | select = tr.find('td.operator select'); |
|
131 | select = tr.find('td.operator select'); | |
132 | for (i=0;i<operators.length;i++){ |
|
132 | for (i=0;i<operators.length;i++){ | |
133 | var option = $('<option>').val(operators[i]).text(operatorLabels[operators[i]]); |
|
133 | var option = $('<option>').val(operators[i]).text(operatorLabels[operators[i]]); | |
134 | if (operators[i] == operator) {option.attr('selected', true)}; |
|
134 | if (operators[i] == operator) {option.attr('selected', true)}; | |
135 | select.append(option); |
|
135 | select.append(option); | |
136 | } |
|
136 | } | |
137 | select.change(function(){toggleOperator(field)}); |
|
137 | select.change(function(){toggleOperator(field)}); | |
138 |
|
138 | |||
139 | switch (filterOptions['type']){ |
|
139 | switch (filterOptions['type']){ | |
140 | case "list": |
|
140 | case "list": | |
141 | case "list_optional": |
|
141 | case "list_optional": | |
142 | case "list_status": |
|
142 | case "list_status": | |
143 | case "list_subprojects": |
|
143 | case "list_subprojects": | |
144 | tr.find('td.values').append( |
|
144 | tr.find('td.values').append( | |
145 | '<span style="display:none;"><select class="value" id="values_'+fieldId+'_1" name="v['+field+'][]"></select>' + |
|
145 | '<span style="display:none;"><select class="value" id="values_'+fieldId+'_1" name="v['+field+'][]"></select>' + | |
146 | ' <span class="toggle-multiselect"> </span></span>' |
|
146 | ' <span class="toggle-multiselect"> </span></span>' | |
147 | ); |
|
147 | ); | |
148 | select = tr.find('td.values select'); |
|
148 | select = tr.find('td.values select'); | |
149 | if (values.length > 1) {select.attr('multiple', true)}; |
|
149 | if (values.length > 1) {select.attr('multiple', true)}; | |
150 | for (i=0;i<filterValues.length;i++){ |
|
150 | for (i=0;i<filterValues.length;i++){ | |
151 | var filterValue = filterValues[i]; |
|
151 | var filterValue = filterValues[i]; | |
152 | var option = $('<option>'); |
|
152 | var option = $('<option>'); | |
153 | if ($.isArray(filterValue)) { |
|
153 | if ($.isArray(filterValue)) { | |
154 | option.val(filterValue[1]).text(filterValue[0]); |
|
154 | option.val(filterValue[1]).text(filterValue[0]); | |
155 | if ($.inArray(filterValue[1], values) > -1) {option.attr('selected', true);} |
|
155 | if ($.inArray(filterValue[1], values) > -1) {option.attr('selected', true);} | |
156 | } else { |
|
156 | } else { | |
157 | option.val(filterValue).text(filterValue); |
|
157 | option.val(filterValue).text(filterValue); | |
158 | if ($.inArray(filterValue, values) > -1) {option.attr('selected', true);} |
|
158 | if ($.inArray(filterValue, values) > -1) {option.attr('selected', true);} | |
159 | } |
|
159 | } | |
160 | select.append(option); |
|
160 | select.append(option); | |
161 | } |
|
161 | } | |
162 | break; |
|
162 | break; | |
163 | case "date": |
|
163 | case "date": | |
164 | case "date_past": |
|
164 | case "date_past": | |
165 | tr.find('td.values').append( |
|
165 | tr.find('td.values').append( | |
166 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="10" class="value date_value" /></span>' + |
|
166 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="10" class="value date_value" /></span>' + | |
167 | ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="10" class="value date_value" /></span>' + |
|
167 | ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="10" class="value date_value" /></span>' + | |
168 | ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="3" class="value" /> '+labelDayPlural+'</span>' |
|
168 | ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="3" class="value" /> '+labelDayPlural+'</span>' | |
169 | ); |
|
169 | ); | |
170 | $('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions); |
|
170 | $('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions); | |
171 | $('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions); |
|
171 | $('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions); | |
172 | $('#values_'+fieldId).val(values[0]); |
|
172 | $('#values_'+fieldId).val(values[0]); | |
173 | break; |
|
173 | break; | |
174 | case "string": |
|
174 | case "string": | |
175 | case "text": |
|
175 | case "text": | |
176 | tr.find('td.values').append( |
|
176 | tr.find('td.values').append( | |
177 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="30" class="value" /></span>' |
|
177 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="30" class="value" /></span>' | |
178 | ); |
|
178 | ); | |
179 | $('#values_'+fieldId).val(values[0]); |
|
179 | $('#values_'+fieldId).val(values[0]); | |
180 | break; |
|
180 | break; | |
181 | case "relation": |
|
181 | case "relation": | |
182 | tr.find('td.values').append( |
|
182 | tr.find('td.values').append( | |
183 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="6" class="value" /></span>' + |
|
183 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="6" class="value" /></span>' + | |
184 | '<span style="display:none;"><select class="value" name="v['+field+'][]" id="values_'+fieldId+'_1"></select></span>' |
|
184 | '<span style="display:none;"><select class="value" name="v['+field+'][]" id="values_'+fieldId+'_1"></select></span>' | |
185 | ); |
|
185 | ); | |
186 | $('#values_'+fieldId).val(values[0]); |
|
186 | $('#values_'+fieldId).val(values[0]); | |
187 | select = tr.find('td.values select'); |
|
187 | select = tr.find('td.values select'); | |
188 | for (i=0;i<allProjects.length;i++){ |
|
188 | for (i=0;i<allProjects.length;i++){ | |
189 | var filterValue = allProjects[i]; |
|
189 | var filterValue = allProjects[i]; | |
190 | var option = $('<option>'); |
|
190 | var option = $('<option>'); | |
191 | option.val(filterValue[1]).text(filterValue[0]); |
|
191 | option.val(filterValue[1]).text(filterValue[0]); | |
192 | if (values[0] == filterValue[1]) {option.attr('selected', true)}; |
|
192 | if (values[0] == filterValue[1]) {option.attr('selected', true)}; | |
193 | select.append(option); |
|
193 | select.append(option); | |
194 | } |
|
194 | } | |
195 | case "integer": |
|
195 | case "integer": | |
196 | case "float": |
|
196 | case "float": | |
197 | tr.find('td.values').append( |
|
197 | tr.find('td.values').append( | |
198 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="6" class="value" /></span>' + |
|
198 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="6" class="value" /></span>' + | |
199 | ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="6" class="value" /></span>' |
|
199 | ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="6" class="value" /></span>' | |
200 | ); |
|
200 | ); | |
201 | $('#values_'+fieldId+'_1').val(values[0]); |
|
201 | $('#values_'+fieldId+'_1').val(values[0]); | |
202 | $('#values_'+fieldId+'_2').val(values[1]); |
|
202 | $('#values_'+fieldId+'_2').val(values[1]); | |
203 | break; |
|
203 | break; | |
204 | } |
|
204 | } | |
205 | } |
|
205 | } | |
206 |
|
206 | |||
207 | function toggleFilter(field) { |
|
207 | function toggleFilter(field) { | |
208 | var fieldId = field.replace('.', '_'); |
|
208 | var fieldId = field.replace('.', '_'); | |
209 | if ($('#cb_' + fieldId).is(':checked')) { |
|
209 | if ($('#cb_' + fieldId).is(':checked')) { | |
210 | $("#operators_" + fieldId).show().removeAttr('disabled'); |
|
210 | $("#operators_" + fieldId).show().removeAttr('disabled'); | |
211 | toggleOperator(field); |
|
211 | toggleOperator(field); | |
212 | } else { |
|
212 | } else { | |
213 | $("#operators_" + fieldId).hide().attr('disabled', true); |
|
213 | $("#operators_" + fieldId).hide().attr('disabled', true); | |
214 | enableValues(field, []); |
|
214 | enableValues(field, []); | |
215 | } |
|
215 | } | |
216 | } |
|
216 | } | |
217 |
|
217 | |||
218 | function enableValues(field, indexes) { |
|
218 | function enableValues(field, indexes) { | |
219 | var fieldId = field.replace('.', '_'); |
|
219 | var fieldId = field.replace('.', '_'); | |
220 | $('#tr_'+fieldId+' td.values .value').each(function(index) { |
|
220 | $('#tr_'+fieldId+' td.values .value').each(function(index) { | |
221 | if ($.inArray(index, indexes) >= 0) { |
|
221 | if ($.inArray(index, indexes) >= 0) { | |
222 | $(this).removeAttr('disabled'); |
|
222 | $(this).removeAttr('disabled'); | |
223 | $(this).parents('span').first().show(); |
|
223 | $(this).parents('span').first().show(); | |
224 | } else { |
|
224 | } else { | |
225 | $(this).val(''); |
|
225 | $(this).val(''); | |
226 | $(this).attr('disabled', true); |
|
226 | $(this).attr('disabled', true); | |
227 | $(this).parents('span').first().hide(); |
|
227 | $(this).parents('span').first().hide(); | |
228 | } |
|
228 | } | |
229 |
|
229 | |||
230 | if ($(this).hasClass('group')) { |
|
230 | if ($(this).hasClass('group')) { | |
231 | $(this).addClass('open'); |
|
231 | $(this).addClass('open'); | |
232 | } else { |
|
232 | } else { | |
233 | $(this).show(); |
|
233 | $(this).show(); | |
234 | } |
|
234 | } | |
235 | }); |
|
235 | }); | |
236 | } |
|
236 | } | |
237 |
|
237 | |||
238 | function toggleOperator(field) { |
|
238 | function toggleOperator(field) { | |
239 | var fieldId = field.replace('.', '_'); |
|
239 | var fieldId = field.replace('.', '_'); | |
240 | var operator = $("#operators_" + fieldId); |
|
240 | var operator = $("#operators_" + fieldId); | |
241 | switch (operator.val()) { |
|
241 | switch (operator.val()) { | |
242 | case "!*": |
|
242 | case "!*": | |
243 | case "*": |
|
243 | case "*": | |
244 | case "t": |
|
244 | case "t": | |
245 | case "ld": |
|
245 | case "ld": | |
246 | case "w": |
|
246 | case "w": | |
247 | case "lw": |
|
247 | case "lw": | |
248 | case "l2w": |
|
248 | case "l2w": | |
249 | case "m": |
|
249 | case "m": | |
250 | case "lm": |
|
250 | case "lm": | |
251 | case "y": |
|
251 | case "y": | |
252 | case "o": |
|
252 | case "o": | |
253 | case "c": |
|
253 | case "c": | |
254 | enableValues(field, []); |
|
254 | enableValues(field, []); | |
255 | break; |
|
255 | break; | |
256 | case "><": |
|
256 | case "><": | |
257 | enableValues(field, [0,1]); |
|
257 | enableValues(field, [0,1]); | |
258 | break; |
|
258 | break; | |
259 | case "<t+": |
|
259 | case "<t+": | |
260 | case ">t+": |
|
260 | case ">t+": | |
261 | case "><t+": |
|
261 | case "><t+": | |
262 | case "t+": |
|
262 | case "t+": | |
263 | case ">t-": |
|
263 | case ">t-": | |
264 | case "<t-": |
|
264 | case "<t-": | |
265 | case "><t-": |
|
265 | case "><t-": | |
266 | case "t-": |
|
266 | case "t-": | |
267 | enableValues(field, [2]); |
|
267 | enableValues(field, [2]); | |
268 | break; |
|
268 | break; | |
269 | case "=p": |
|
269 | case "=p": | |
270 | case "=!p": |
|
270 | case "=!p": | |
271 | case "!p": |
|
271 | case "!p": | |
272 | enableValues(field, [1]); |
|
272 | enableValues(field, [1]); | |
273 | break; |
|
273 | break; | |
274 | default: |
|
274 | default: | |
275 | enableValues(field, [0]); |
|
275 | enableValues(field, [0]); | |
276 | break; |
|
276 | break; | |
277 | } |
|
277 | } | |
278 | } |
|
278 | } | |
279 |
|
279 | |||
280 | function toggleMultiSelect(el) { |
|
280 | function toggleMultiSelect(el) { | |
281 | if (el.attr('multiple')) { |
|
281 | if (el.attr('multiple')) { | |
282 | el.removeAttr('multiple'); |
|
282 | el.removeAttr('multiple'); | |
283 | } else { |
|
283 | } else { | |
284 | el.attr('multiple', true); |
|
284 | el.attr('multiple', true); | |
285 | } |
|
285 | } | |
286 | } |
|
286 | } | |
287 |
|
287 | |||
288 | function submit_query_form(id) { |
|
288 | function submit_query_form(id) { | |
289 | selectAllOptions("selected_columns"); |
|
289 | selectAllOptions("selected_columns"); | |
290 | $('#'+id).submit(); |
|
290 | $('#'+id).submit(); | |
291 | } |
|
291 | } | |
292 |
|
292 | |||
293 | function showTab(name) { |
|
293 | function showTab(name) { | |
294 | $('div#content .tab-content').hide(); |
|
294 | $('div#content .tab-content').hide(); | |
295 | $('div.tabs a').removeClass('selected'); |
|
295 | $('div.tabs a').removeClass('selected'); | |
296 | $('#tab-content-' + name).show(); |
|
296 | $('#tab-content-' + name).show(); | |
297 | $('#tab-' + name).addClass('selected'); |
|
297 | $('#tab-' + name).addClass('selected'); | |
298 | return false; |
|
298 | return false; | |
299 | } |
|
299 | } | |
300 |
|
300 | |||
301 | function moveTabRight(el) { |
|
301 | function moveTabRight(el) { | |
302 | var lis = $(el).parents('div.tabs').first().find('ul').children(); |
|
302 | var lis = $(el).parents('div.tabs').first().find('ul').children(); | |
303 | var tabsWidth = 0; |
|
303 | var tabsWidth = 0; | |
304 | var i = 0; |
|
304 | var i = 0; | |
305 | lis.each(function(){ |
|
305 | lis.each(function(){ | |
306 | if ($(this).is(':visible')) { |
|
306 | if ($(this).is(':visible')) { | |
307 | tabsWidth += $(this).width() + 6; |
|
307 | tabsWidth += $(this).width() + 6; | |
308 | } |
|
308 | } | |
309 | }); |
|
309 | }); | |
310 | if (tabsWidth < $(el).parents('div.tabs').first().width() - 60) { return; } |
|
310 | if (tabsWidth < $(el).parents('div.tabs').first().width() - 60) { return; } | |
311 | while (i<lis.length && !lis.eq(i).is(':visible')) { i++; } |
|
311 | while (i<lis.length && !lis.eq(i).is(':visible')) { i++; } | |
312 | lis.eq(i).hide(); |
|
312 | lis.eq(i).hide(); | |
313 | } |
|
313 | } | |
314 |
|
314 | |||
315 | function moveTabLeft(el) { |
|
315 | function moveTabLeft(el) { | |
316 | var lis = $(el).parents('div.tabs').first().find('ul').children(); |
|
316 | var lis = $(el).parents('div.tabs').first().find('ul').children(); | |
317 | var i = 0; |
|
317 | var i = 0; | |
318 | while (i<lis.length && !lis.eq(i).is(':visible')) { i++; } |
|
318 | while (i<lis.length && !lis.eq(i).is(':visible')) { i++; } | |
319 | if (i>0) { |
|
319 | if (i>0) { | |
320 | lis.eq(i-1).show(); |
|
320 | lis.eq(i-1).show(); | |
321 | } |
|
321 | } | |
322 | } |
|
322 | } | |
323 |
|
323 | |||
324 | function displayTabsButtons() { |
|
324 | function displayTabsButtons() { | |
325 | var lis; |
|
325 | var lis; | |
326 | var tabsWidth = 0; |
|
326 | var tabsWidth = 0; | |
327 | var el; |
|
327 | var el; | |
328 | $('div.tabs').each(function() { |
|
328 | $('div.tabs').each(function() { | |
329 | el = $(this); |
|
329 | el = $(this); | |
330 | lis = el.find('ul').children(); |
|
330 | lis = el.find('ul').children(); | |
331 | lis.each(function(){ |
|
331 | lis.each(function(){ | |
332 | if ($(this).is(':visible')) { |
|
332 | if ($(this).is(':visible')) { | |
333 | tabsWidth += $(this).width() + 6; |
|
333 | tabsWidth += $(this).width() + 6; | |
334 | } |
|
334 | } | |
335 | }); |
|
335 | }); | |
336 | if ((tabsWidth < el.width() - 60) && (lis.first().is(':visible'))) { |
|
336 | if ((tabsWidth < el.width() - 60) && (lis.first().is(':visible'))) { | |
337 | el.find('div.tabs-buttons').hide(); |
|
337 | el.find('div.tabs-buttons').hide(); | |
338 | } else { |
|
338 | } else { | |
339 | el.find('div.tabs-buttons').show(); |
|
339 | el.find('div.tabs-buttons').show(); | |
340 | } |
|
340 | } | |
341 | }); |
|
341 | }); | |
342 | } |
|
342 | } | |
343 |
|
343 | |||
344 | function setPredecessorFieldsVisibility() { |
|
344 | function setPredecessorFieldsVisibility() { | |
345 | var relationType = $('#relation_relation_type'); |
|
345 | var relationType = $('#relation_relation_type'); | |
346 | if (relationType.val() == "precedes" || relationType.val() == "follows") { |
|
346 | if (relationType.val() == "precedes" || relationType.val() == "follows") { | |
347 | $('#predecessor_fields').show(); |
|
347 | $('#predecessor_fields').show(); | |
348 | } else { |
|
348 | } else { | |
349 | $('#predecessor_fields').hide(); |
|
349 | $('#predecessor_fields').hide(); | |
350 | } |
|
350 | } | |
351 | } |
|
351 | } | |
352 |
|
352 | |||
353 | function showModal(id, width) { |
|
353 | function showModal(id, width) { | |
354 | var el = $('#'+id).first(); |
|
354 | var el = $('#'+id).first(); | |
355 | if (el.length == 0 || el.is(':visible')) {return;} |
|
355 | if (el.length == 0 || el.is(':visible')) {return;} | |
356 | var title = el.find('h3.title').text(); |
|
356 | var title = el.find('h3.title').text(); | |
357 | el.dialog({ |
|
357 | el.dialog({ | |
358 | width: width, |
|
358 | width: width, | |
359 | modal: true, |
|
359 | modal: true, | |
360 | resizable: false, |
|
360 | resizable: false, | |
361 | dialogClass: 'modal', |
|
361 | dialogClass: 'modal', | |
362 | title: title |
|
362 | title: title | |
363 | }); |
|
363 | }); | |
364 | el.find("input[type=text], input[type=submit]").first().focus(); |
|
364 | el.find("input[type=text], input[type=submit]").first().focus(); | |
365 | } |
|
365 | } | |
366 |
|
366 | |||
367 | function hideModal(el) { |
|
367 | function hideModal(el) { | |
368 | var modal; |
|
368 | var modal; | |
369 | if (el) { |
|
369 | if (el) { | |
370 | modal = $(el).parents('.ui-dialog-content'); |
|
370 | modal = $(el).parents('.ui-dialog-content'); | |
371 | } else { |
|
371 | } else { | |
372 | modal = $('#ajax-modal'); |
|
372 | modal = $('#ajax-modal'); | |
373 | } |
|
373 | } | |
374 | modal.dialog("close"); |
|
374 | modal.dialog("close"); | |
375 | } |
|
375 | } | |
376 |
|
376 | |||
377 | function submitPreview(url, form, target) { |
|
377 | function submitPreview(url, form, target) { | |
378 | $.ajax({ |
|
378 | $.ajax({ | |
379 | url: url, |
|
379 | url: url, | |
380 | type: 'post', |
|
380 | type: 'post', | |
381 | data: $('#'+form).serialize(), |
|
381 | data: $('#'+form).serialize(), | |
382 | success: function(data){ |
|
382 | success: function(data){ | |
383 | $('#'+target).html(data); |
|
383 | $('#'+target).html(data); | |
384 | } |
|
384 | } | |
385 | }); |
|
385 | }); | |
386 | } |
|
386 | } | |
387 |
|
387 | |||
388 | function collapseScmEntry(id) { |
|
388 | function collapseScmEntry(id) { | |
389 | $('.'+id).each(function() { |
|
389 | $('.'+id).each(function() { | |
390 | if ($(this).hasClass('open')) { |
|
390 | if ($(this).hasClass('open')) { | |
391 | collapseScmEntry($(this).attr('id')); |
|
391 | collapseScmEntry($(this).attr('id')); | |
392 | } |
|
392 | } | |
393 | $(this).hide(); |
|
393 | $(this).hide(); | |
394 | }); |
|
394 | }); | |
395 | $('#'+id).removeClass('open'); |
|
395 | $('#'+id).removeClass('open'); | |
396 | } |
|
396 | } | |
397 |
|
397 | |||
398 | function expandScmEntry(id) { |
|
398 | function expandScmEntry(id) { | |
399 | $('.'+id).each(function() { |
|
399 | $('.'+id).each(function() { | |
400 | $(this).show(); |
|
400 | $(this).show(); | |
401 | if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) { |
|
401 | if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) { | |
402 | expandScmEntry($(this).attr('id')); |
|
402 | expandScmEntry($(this).attr('id')); | |
403 | } |
|
403 | } | |
404 | }); |
|
404 | }); | |
405 | $('#'+id).addClass('open'); |
|
405 | $('#'+id).addClass('open'); | |
406 | } |
|
406 | } | |
407 |
|
407 | |||
408 | function scmEntryClick(id, url) { |
|
408 | function scmEntryClick(id, url) { | |
409 | el = $('#'+id); |
|
409 | el = $('#'+id); | |
410 | if (el.hasClass('open')) { |
|
410 | if (el.hasClass('open')) { | |
411 | collapseScmEntry(id); |
|
411 | collapseScmEntry(id); | |
412 | el.addClass('collapsed'); |
|
412 | el.addClass('collapsed'); | |
413 | return false; |
|
413 | return false; | |
414 | } else if (el.hasClass('loaded')) { |
|
414 | } else if (el.hasClass('loaded')) { | |
415 | expandScmEntry(id); |
|
415 | expandScmEntry(id); | |
416 | el.removeClass('collapsed'); |
|
416 | el.removeClass('collapsed'); | |
417 | return false; |
|
417 | return false; | |
418 | } |
|
418 | } | |
419 | if (el.hasClass('loading')) { |
|
419 | if (el.hasClass('loading')) { | |
420 | return false; |
|
420 | return false; | |
421 | } |
|
421 | } | |
422 | el.addClass('loading'); |
|
422 | el.addClass('loading'); | |
423 | $.ajax({ |
|
423 | $.ajax({ | |
424 | url: url, |
|
424 | url: url, | |
425 | success: function(data){ |
|
425 | success: function(data){ | |
426 | el.after(data); |
|
426 | el.after(data); | |
427 | el.addClass('open').addClass('loaded').removeClass('loading'); |
|
427 | el.addClass('open').addClass('loaded').removeClass('loading'); | |
428 | } |
|
428 | } | |
429 | }); |
|
429 | }); | |
430 | return true; |
|
430 | return true; | |
431 | } |
|
431 | } | |
432 |
|
432 | |||
433 | function randomKey(size) { |
|
433 | function randomKey(size) { | |
434 | var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'); |
|
434 | var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'); | |
435 | var key = ''; |
|
435 | var key = ''; | |
436 | for (i = 0; i < size; i++) { |
|
436 | for (i = 0; i < size; i++) { | |
437 | key += chars[Math.floor(Math.random() * chars.length)]; |
|
437 | key += chars[Math.floor(Math.random() * chars.length)]; | |
438 | } |
|
438 | } | |
439 | return key; |
|
439 | return key; | |
440 | } |
|
440 | } | |
441 |
|
441 | |||
442 | // Can't use Rails' remote select because we need the form data |
|
442 | // Can't use Rails' remote select because we need the form data | |
443 | function updateIssueFrom(url) { |
|
443 | function updateIssueFrom(url) { | |
444 | $.ajax({ |
|
444 | $.ajax({ | |
445 | url: url, |
|
445 | url: url, | |
446 | type: 'post', |
|
446 | type: 'post', | |
447 | data: $('#issue-form').serialize() |
|
447 | data: $('#issue-form').serialize() | |
448 | }); |
|
448 | }); | |
449 | } |
|
449 | } | |
450 |
|
450 | |||
451 | function updateBulkEditFrom(url) { |
|
451 | function updateBulkEditFrom(url) { | |
452 | $.ajax({ |
|
452 | $.ajax({ | |
453 | url: url, |
|
453 | url: url, | |
454 | type: 'post', |
|
454 | type: 'post', | |
455 | data: $('#bulk_edit_form').serialize() |
|
455 | data: $('#bulk_edit_form').serialize() | |
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 | |||
468 | function observeSearchfield(fieldId, targetId, url) { |
|
468 | function observeSearchfield(fieldId, targetId, url) { | |
469 | $('#'+fieldId).each(function() { |
|
469 | $('#'+fieldId).each(function() { | |
470 | var $this = $(this); |
|
470 | var $this = $(this); | |
471 | $this.attr('data-value-was', $this.val()); |
|
471 | $this.attr('data-value-was', $this.val()); | |
472 | var check = function() { |
|
472 | var check = function() { | |
473 | var val = $this.val(); |
|
473 | var val = $this.val(); | |
474 | if ($this.attr('data-value-was') != val){ |
|
474 | if ($this.attr('data-value-was') != val){ | |
475 | $this.attr('data-value-was', val); |
|
475 | $this.attr('data-value-was', val); | |
476 | $.ajax({ |
|
476 | $.ajax({ | |
477 | url: url, |
|
477 | url: url, | |
478 | type: 'get', |
|
478 | type: 'get', | |
479 | data: {q: $this.val()}, |
|
479 | data: {q: $this.val()}, | |
480 | success: function(data){ $('#'+targetId).html(data); }, |
|
480 | success: function(data){ $('#'+targetId).html(data); }, | |
481 | beforeSend: function(){ $this.addClass('ajax-loading'); }, |
|
481 | beforeSend: function(){ $this.addClass('ajax-loading'); }, | |
482 | complete: function(){ $this.removeClass('ajax-loading'); } |
|
482 | complete: function(){ $this.removeClass('ajax-loading'); } | |
483 | }); |
|
483 | }); | |
484 | } |
|
484 | } | |
485 | }; |
|
485 | }; | |
486 | var reset = function() { |
|
486 | var reset = function() { | |
487 | if (timer) { |
|
487 | if (timer) { | |
488 | clearInterval(timer); |
|
488 | clearInterval(timer); | |
489 | timer = setInterval(check, 300); |
|
489 | timer = setInterval(check, 300); | |
490 | } |
|
490 | } | |
491 | }; |
|
491 | }; | |
492 | var timer = setInterval(check, 300); |
|
492 | var timer = setInterval(check, 300); | |
493 | $this.bind('keyup click mousemove', reset); |
|
493 | $this.bind('keyup click mousemove', reset); | |
494 | }); |
|
494 | }); | |
495 | } |
|
495 | } | |
496 |
|
496 | |||
497 | function observeProjectModules() { |
|
497 | function observeProjectModules() { | |
498 | var f = function() { |
|
498 | var f = function() { | |
499 | /* Hides trackers and issues custom fields on the new project form when issue_tracking module is disabled */ |
|
499 | /* Hides trackers and issues custom fields on the new project form when issue_tracking module is disabled */ | |
500 | if ($('#project_enabled_module_names_issue_tracking').attr('checked')) { |
|
500 | if ($('#project_enabled_module_names_issue_tracking').attr('checked')) { | |
501 | $('#project_trackers').show(); |
|
501 | $('#project_trackers').show(); | |
502 | }else{ |
|
502 | }else{ | |
503 | $('#project_trackers').hide(); |
|
503 | $('#project_trackers').hide(); | |
504 | } |
|
504 | } | |
505 | }; |
|
505 | }; | |
506 |
|
506 | |||
507 | $(window).load(f); |
|
507 | $(window).load(f); | |
508 | $('#project_enabled_module_names_issue_tracking').change(f); |
|
508 | $('#project_enabled_module_names_issue_tracking').change(f); | |
509 | } |
|
509 | } | |
510 |
|
510 | |||
511 | function initMyPageSortable(list, url) { |
|
511 | function initMyPageSortable(list, url) { | |
512 | $('#list-'+list).sortable({ |
|
512 | $('#list-'+list).sortable({ | |
513 | connectWith: '.block-receiver', |
|
513 | connectWith: '.block-receiver', | |
514 | tolerance: 'pointer', |
|
514 | tolerance: 'pointer', | |
515 | update: function(){ |
|
515 | update: function(){ | |
516 | $.ajax({ |
|
516 | $.ajax({ | |
517 | url: url, |
|
517 | url: url, | |
518 | type: 'post', |
|
518 | type: 'post', | |
519 | data: {'blocks': $.map($('#list-'+list).children(), function(el){return $(el).attr('id');})} |
|
519 | data: {'blocks': $.map($('#list-'+list).children(), function(el){return $(el).attr('id');})} | |
520 | }); |
|
520 | }); | |
521 | } |
|
521 | } | |
522 | }); |
|
522 | }); | |
523 | $("#list-top, #list-left, #list-right").disableSelection(); |
|
523 | $("#list-top, #list-left, #list-right").disableSelection(); | |
524 | } |
|
524 | } | |
525 |
|
525 | |||
526 | var warnLeavingUnsavedMessage; |
|
526 | var warnLeavingUnsavedMessage; | |
527 | function warnLeavingUnsaved(message) { |
|
527 | function warnLeavingUnsaved(message) { | |
528 | warnLeavingUnsavedMessage = message; |
|
528 | warnLeavingUnsavedMessage = message; | |
529 |
|
529 | |||
530 | $('form').submit(function(){ |
|
530 | $('form').submit(function(){ | |
531 | $('textarea').removeData('changed'); |
|
531 | $('textarea').removeData('changed'); | |
532 | }); |
|
532 | }); | |
533 | $('textarea').change(function(){ |
|
533 | $('textarea').change(function(){ | |
534 | $(this).data('changed', 'changed'); |
|
534 | $(this).data('changed', 'changed'); | |
535 | }); |
|
535 | }); | |
536 | window.onbeforeunload = function(){ |
|
536 | window.onbeforeunload = function(){ | |
537 | var warn = false; |
|
537 | var warn = false; | |
538 | $('textarea').blur().each(function(){ |
|
538 | $('textarea').blur().each(function(){ | |
539 | if ($(this).data('changed')) { |
|
539 | if ($(this).data('changed')) { | |
540 | warn = true; |
|
540 | warn = true; | |
541 | } |
|
541 | } | |
542 | }); |
|
542 | }); | |
543 | if (warn) {return warnLeavingUnsavedMessage;} |
|
543 | if (warn) {return warnLeavingUnsavedMessage;} | |
544 | }; |
|
544 | }; | |
545 | }; |
|
545 | }; | |
546 |
|
546 | |||
547 | function setupAjaxIndicator() { |
|
547 | function setupAjaxIndicator() { | |
548 |
|
548 | |||
549 | $('#ajax-indicator').bind('ajaxSend', function(event, xhr, settings) { |
|
549 | $('#ajax-indicator').bind('ajaxSend', function(event, xhr, settings) { | |
550 |
|
550 | |||
551 | if ($('.ajax-loading').length == 0 && settings.contentType != 'application/octet-stream') { |
|
551 | if ($('.ajax-loading').length == 0 && settings.contentType != 'application/octet-stream') { | |
552 | $('#ajax-indicator').show(); |
|
552 | $('#ajax-indicator').show(); | |
553 | } |
|
553 | } | |
554 | }); |
|
554 | }); | |
555 |
|
555 | |||
556 | $('#ajax-indicator').bind('ajaxStop', function() { |
|
556 | $('#ajax-indicator').bind('ajaxStop', function() { | |
557 | $('#ajax-indicator').hide(); |
|
557 | $('#ajax-indicator').hide(); | |
558 | }); |
|
558 | }); | |
559 | } |
|
559 | } | |
560 |
|
560 | |||
561 | function hideOnLoad() { |
|
561 | function hideOnLoad() { | |
562 | $('.hol').hide(); |
|
562 | $('.hol').hide(); | |
563 | } |
|
563 | } | |
564 |
|
564 | |||
565 | function addFormObserversForDoubleSubmit() { |
|
565 | function addFormObserversForDoubleSubmit() { | |
566 | $('form[method=post]').each(function() { |
|
566 | $('form[method=post]').each(function() { | |
567 | if (!$(this).hasClass('multiple-submit')) { |
|
567 | if (!$(this).hasClass('multiple-submit')) { | |
568 | $(this).submit(function(form_submission) { |
|
568 | $(this).submit(function(form_submission) { | |
569 | if ($(form_submission.target).attr('data-submitted')) { |
|
569 | if ($(form_submission.target).attr('data-submitted')) { | |
570 | form_submission.preventDefault(); |
|
570 | form_submission.preventDefault(); | |
571 | } else { |
|
571 | } else { | |
572 | $(form_submission.target).attr('data-submitted', true); |
|
572 | $(form_submission.target).attr('data-submitted', true); | |
573 | } |
|
573 | } | |
574 | }); |
|
574 | }); | |
575 | } |
|
575 | } | |
576 | }); |
|
576 | }); | |
577 | } |
|
577 | } | |
578 |
|
578 | |||
579 | function blockEventPropagation(event) { |
|
579 | function blockEventPropagation(event) { | |
580 | event.stopPropagation(); |
|
580 | event.stopPropagation(); | |
581 | event.preventDefault(); |
|
581 | event.preventDefault(); | |
582 | } |
|
582 | } | |
583 |
|
583 | |||
584 | $(document).ready(setupAjaxIndicator); |
|
584 | $(document).ready(setupAjaxIndicator); | |
585 | $(document).ready(hideOnLoad); |
|
585 | $(document).ready(hideOnLoad); | |
586 | $(document).ready(addFormObserversForDoubleSubmit); |
|
586 | $(document).ready(addFormObserversForDoubleSubmit); |
@@ -1,152 +1,168 | |||||
1 | # Redmine - project management software |
|
1 | # Redmine - project management software | |
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang |
|
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
6 | # as published by the Free Software Foundation; either version 2 |
|
6 | # as published by the Free Software Foundation; either version 2 | |
7 | # of the License, or (at your option) any later version. |
|
7 | # of the License, or (at your option) any later version. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU General Public License |
|
14 | # You should have received a copy of the GNU General Public License | |
15 | # along with this program; if not, write to the Free Software |
|
15 | # along with this program; if not, write to the Free Software | |
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 |
|
17 | |||
18 | require File.expand_path('../../test_helper', __FILE__) |
|
18 | require File.expand_path('../../test_helper', __FILE__) | |
19 |
|
19 | |||
20 | class AuthSourcesControllerTest < ActionController::TestCase |
|
20 | class AuthSourcesControllerTest < ActionController::TestCase | |
21 | fixtures :users, :auth_sources |
|
21 | fixtures :users, :auth_sources | |
22 |
|
22 | |||
23 | def setup |
|
23 | def setup | |
24 | @request.session[:user_id] = 1 |
|
24 | @request.session[:user_id] = 1 | |
25 | end |
|
25 | end | |
26 |
|
26 | |||
27 | def test_index |
|
27 | def test_index | |
28 | get :index |
|
28 | get :index | |
29 |
|
29 | |||
30 | assert_response :success |
|
30 | assert_response :success | |
31 | assert_template 'index' |
|
31 | assert_template 'index' | |
32 | assert_not_nil assigns(:auth_sources) |
|
32 | assert_not_nil assigns(:auth_sources) | |
33 | end |
|
33 | end | |
34 |
|
34 | |||
35 | def test_new |
|
35 | def test_new | |
36 | get :new |
|
36 | get :new | |
37 |
|
37 | |||
38 | assert_response :success |
|
38 | assert_response :success | |
39 | assert_template 'new' |
|
39 | assert_template 'new' | |
40 |
|
40 | |||
41 | source = assigns(:auth_source) |
|
41 | source = assigns(:auth_source) | |
42 | assert_equal AuthSourceLdap, source.class |
|
42 | assert_equal AuthSourceLdap, source.class | |
43 | assert source.new_record? |
|
43 | assert source.new_record? | |
44 |
|
44 | |||
45 | assert_select 'form#auth_source_form' do |
|
45 | assert_select 'form#auth_source_form' do | |
46 | assert_select 'input[name=type][value=AuthSourceLdap]' |
|
46 | assert_select 'input[name=type][value=AuthSourceLdap]' | |
47 | assert_select 'input[name=?]', 'auth_source[host]' |
|
47 | assert_select 'input[name=?]', 'auth_source[host]' | |
48 | end |
|
48 | end | |
49 | end |
|
49 | end | |
50 |
|
50 | |||
51 | def test_new_with_invalid_type_should_respond_with_404 |
|
51 | def test_new_with_invalid_type_should_respond_with_404 | |
52 | get :new, :type => 'foo' |
|
52 | get :new, :type => 'foo' | |
53 | assert_response 404 |
|
53 | assert_response 404 | |
54 | end |
|
54 | end | |
55 |
|
55 | |||
56 | def test_create |
|
56 | def test_create | |
57 | assert_difference 'AuthSourceLdap.count' do |
|
57 | assert_difference 'AuthSourceLdap.count' do | |
58 | post :create, :type => 'AuthSourceLdap', :auth_source => {:name => 'Test', :host => '127.0.0.1', :port => '389', :attr_login => 'cn'} |
|
58 | post :create, :type => 'AuthSourceLdap', :auth_source => {:name => 'Test', :host => '127.0.0.1', :port => '389', :attr_login => 'cn'} | |
59 | assert_redirected_to '/auth_sources' |
|
59 | assert_redirected_to '/auth_sources' | |
60 | end |
|
60 | end | |
61 |
|
61 | |||
62 | source = AuthSourceLdap.order('id DESC').first |
|
62 | source = AuthSourceLdap.order('id DESC').first | |
63 | assert_equal 'Test', source.name |
|
63 | assert_equal 'Test', source.name | |
64 | assert_equal '127.0.0.1', source.host |
|
64 | assert_equal '127.0.0.1', source.host | |
65 | assert_equal 389, source.port |
|
65 | assert_equal 389, source.port | |
66 | assert_equal 'cn', source.attr_login |
|
66 | assert_equal 'cn', source.attr_login | |
67 | end |
|
67 | end | |
68 |
|
68 | |||
69 | def test_create_with_failure |
|
69 | def test_create_with_failure | |
70 | assert_no_difference 'AuthSourceLdap.count' do |
|
70 | assert_no_difference 'AuthSourceLdap.count' do | |
71 | post :create, :type => 'AuthSourceLdap', :auth_source => {:name => 'Test', :host => '', :port => '389', :attr_login => 'cn'} |
|
71 | post :create, :type => 'AuthSourceLdap', :auth_source => {:name => 'Test', :host => '', :port => '389', :attr_login => 'cn'} | |
72 | assert_response :success |
|
72 | assert_response :success | |
73 | assert_template 'new' |
|
73 | assert_template 'new' | |
74 | end |
|
74 | end | |
75 | assert_error_tag :content => /host can't be blank/i |
|
75 | assert_error_tag :content => /host can't be blank/i | |
76 | end |
|
76 | end | |
77 |
|
77 | |||
78 | def test_edit |
|
78 | def test_edit | |
79 | get :edit, :id => 1 |
|
79 | get :edit, :id => 1 | |
80 |
|
80 | |||
81 | assert_response :success |
|
81 | assert_response :success | |
82 | assert_template 'edit' |
|
82 | assert_template 'edit' | |
83 |
|
83 | |||
84 | assert_select 'form#auth_source_form' do |
|
84 | assert_select 'form#auth_source_form' do | |
85 | assert_select 'input[name=?]', 'auth_source[host]' |
|
85 | assert_select 'input[name=?]', 'auth_source[host]' | |
86 | end |
|
86 | end | |
87 | end |
|
87 | end | |
88 |
|
88 | |||
89 | def test_edit_should_not_contain_password |
|
89 | def test_edit_should_not_contain_password | |
90 | AuthSource.find(1).update_column :account_password, 'secret' |
|
90 | AuthSource.find(1).update_column :account_password, 'secret' | |
91 |
|
91 | |||
92 | get :edit, :id => 1 |
|
92 | get :edit, :id => 1 | |
93 | assert_response :success |
|
93 | assert_response :success | |
94 | assert_select 'input[value=secret]', 0 |
|
94 | assert_select 'input[value=secret]', 0 | |
95 | assert_select 'input[name=dummy_password][value=?]', /x+/ |
|
95 | assert_select 'input[name=dummy_password][value=?]', /x+/ | |
96 | end |
|
96 | end | |
97 |
|
97 | |||
98 | def test_edit_invalid_should_respond_with_404 |
|
98 | def test_edit_invalid_should_respond_with_404 | |
99 | get :edit, :id => 99 |
|
99 | get :edit, :id => 99 | |
100 | assert_response 404 |
|
100 | assert_response 404 | |
101 | end |
|
101 | end | |
102 |
|
102 | |||
103 | def test_update |
|
103 | def test_update | |
104 | put :update, :id => 1, :auth_source => {:name => 'Renamed', :host => '192.168.0.10', :port => '389', :attr_login => 'uid'} |
|
104 | put :update, :id => 1, :auth_source => {:name => 'Renamed', :host => '192.168.0.10', :port => '389', :attr_login => 'uid'} | |
105 | assert_redirected_to '/auth_sources' |
|
105 | assert_redirected_to '/auth_sources' | |
106 |
|
106 | |||
107 | source = AuthSourceLdap.find(1) |
|
107 | source = AuthSourceLdap.find(1) | |
108 | assert_equal 'Renamed', source.name |
|
108 | assert_equal 'Renamed', source.name | |
109 | assert_equal '192.168.0.10', source.host |
|
109 | assert_equal '192.168.0.10', source.host | |
110 | end |
|
110 | end | |
111 |
|
111 | |||
112 | def test_update_with_failure |
|
112 | def test_update_with_failure | |
113 | put :update, :id => 1, :auth_source => {:name => 'Renamed', :host => '', :port => '389', :attr_login => 'uid'} |
|
113 | put :update, :id => 1, :auth_source => {:name => 'Renamed', :host => '', :port => '389', :attr_login => 'uid'} | |
114 | assert_response :success |
|
114 | assert_response :success | |
115 | assert_template 'edit' |
|
115 | assert_template 'edit' | |
116 | assert_error_tag :content => /host can't be blank/i |
|
116 | assert_error_tag :content => /host can't be blank/i | |
117 | end |
|
117 | end | |
118 |
|
118 | |||
119 | def test_destroy |
|
119 | def test_destroy | |
120 | assert_difference 'AuthSourceLdap.count', -1 do |
|
120 | assert_difference 'AuthSourceLdap.count', -1 do | |
121 | delete :destroy, :id => 1 |
|
121 | delete :destroy, :id => 1 | |
122 | assert_redirected_to '/auth_sources' |
|
122 | assert_redirected_to '/auth_sources' | |
123 | end |
|
123 | end | |
124 | end |
|
124 | end | |
125 |
|
125 | |||
126 | def test_destroy_auth_source_in_use |
|
126 | def test_destroy_auth_source_in_use | |
127 | User.find(2).update_attribute :auth_source_id, 1 |
|
127 | User.find(2).update_attribute :auth_source_id, 1 | |
128 |
|
128 | |||
129 | assert_no_difference 'AuthSourceLdap.count' do |
|
129 | assert_no_difference 'AuthSourceLdap.count' do | |
130 | delete :destroy, :id => 1 |
|
130 | delete :destroy, :id => 1 | |
131 | assert_redirected_to '/auth_sources' |
|
131 | assert_redirected_to '/auth_sources' | |
132 | end |
|
132 | end | |
133 | end |
|
133 | end | |
134 |
|
134 | |||
135 | def test_test_connection |
|
135 | def test_test_connection | |
136 | AuthSourceLdap.any_instance.stubs(:test_connection).returns(true) |
|
136 | AuthSourceLdap.any_instance.stubs(:test_connection).returns(true) | |
137 |
|
137 | |||
138 | get :test_connection, :id => 1 |
|
138 | get :test_connection, :id => 1 | |
139 | assert_redirected_to '/auth_sources' |
|
139 | assert_redirected_to '/auth_sources' | |
140 | assert_not_nil flash[:notice] |
|
140 | assert_not_nil flash[:notice] | |
141 | assert_match /successful/i, flash[:notice] |
|
141 | assert_match /successful/i, flash[:notice] | |
142 | end |
|
142 | end | |
143 |
|
143 | |||
144 | def test_test_connection_with_failure |
|
144 | def test_test_connection_with_failure | |
145 | AuthSourceLdap.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError.new("Something went wrong")) |
|
145 | AuthSourceLdap.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError.new("Something went wrong")) | |
146 |
|
146 | |||
147 | get :test_connection, :id => 1 |
|
147 | get :test_connection, :id => 1 | |
148 | assert_redirected_to '/auth_sources' |
|
148 | assert_redirected_to '/auth_sources' | |
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 |
@@ -1,55 +1,59 | |||||
1 | # Redmine - project management software |
|
1 | # Redmine - project management software | |
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang |
|
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
6 | # as published by the Free Software Foundation; either version 2 |
|
6 | # as published by the Free Software Foundation; either version 2 | |
7 | # of the License, or (at your option) any later version. |
|
7 | # of the License, or (at your option) any later version. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU General Public License |
|
14 | # You should have received a copy of the GNU General Public License | |
15 | # along with this program; if not, write to the Free Software |
|
15 | # along with this program; if not, write to the Free Software | |
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 |
|
17 | |||
18 | require File.expand_path('../../../test_helper', __FILE__) |
|
18 | require File.expand_path('../../../test_helper', __FILE__) | |
19 |
|
19 | |||
20 | class RoutingAuthSourcesTest < ActionController::IntegrationTest |
|
20 | class RoutingAuthSourcesTest < ActionController::IntegrationTest | |
21 | def test_auth_sources |
|
21 | def test_auth_sources | |
22 | assert_routing( |
|
22 | assert_routing( | |
23 | { :method => 'get', :path => "/auth_sources" }, |
|
23 | { :method => 'get', :path => "/auth_sources" }, | |
24 | { :controller => 'auth_sources', :action => 'index' } |
|
24 | { :controller => 'auth_sources', :action => 'index' } | |
25 | ) |
|
25 | ) | |
26 | assert_routing( |
|
26 | assert_routing( | |
27 | { :method => 'get', :path => "/auth_sources/new" }, |
|
27 | { :method => 'get', :path => "/auth_sources/new" }, | |
28 | { :controller => 'auth_sources', :action => 'new' } |
|
28 | { :controller => 'auth_sources', :action => 'new' } | |
29 | ) |
|
29 | ) | |
30 | assert_routing( |
|
30 | assert_routing( | |
31 | { :method => 'post', :path => "/auth_sources" }, |
|
31 | { :method => 'post', :path => "/auth_sources" }, | |
32 | { :controller => 'auth_sources', :action => 'create' } |
|
32 | { :controller => 'auth_sources', :action => 'create' } | |
33 | ) |
|
33 | ) | |
34 | assert_routing( |
|
34 | assert_routing( | |
35 | { :method => 'get', :path => "/auth_sources/1234/edit" }, |
|
35 | { :method => 'get', :path => "/auth_sources/1234/edit" }, | |
36 | { :controller => 'auth_sources', :action => 'edit', |
|
36 | { :controller => 'auth_sources', :action => 'edit', | |
37 | :id => '1234' } |
|
37 | :id => '1234' } | |
38 | ) |
|
38 | ) | |
39 | assert_routing( |
|
39 | assert_routing( | |
40 | { :method => 'put', :path => "/auth_sources/1234" }, |
|
40 | { :method => 'put', :path => "/auth_sources/1234" }, | |
41 | { :controller => 'auth_sources', :action => 'update', |
|
41 | { :controller => 'auth_sources', :action => 'update', | |
42 | :id => '1234' } |
|
42 | :id => '1234' } | |
43 | ) |
|
43 | ) | |
44 | assert_routing( |
|
44 | assert_routing( | |
45 | { :method => 'delete', :path => "/auth_sources/1234" }, |
|
45 | { :method => 'delete', :path => "/auth_sources/1234" }, | |
46 | { :controller => 'auth_sources', :action => 'destroy', |
|
46 | { :controller => 'auth_sources', :action => 'destroy', | |
47 | :id => '1234' } |
|
47 | :id => '1234' } | |
48 | ) |
|
48 | ) | |
49 | assert_routing( |
|
49 | assert_routing( | |
50 | { :method => 'get', :path => "/auth_sources/1234/test_connection" }, |
|
50 | { :method => 'get', :path => "/auth_sources/1234/test_connection" }, | |
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 |
@@ -1,130 +1,154 | |||||
1 | # Redmine - project management software |
|
1 | # Redmine - project management software | |
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang |
|
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
6 | # as published by the Free Software Foundation; either version 2 |
|
6 | # as published by the Free Software Foundation; either version 2 | |
7 | # of the License, or (at your option) any later version. |
|
7 | # of the License, or (at your option) any later version. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU General Public License |
|
14 | # You should have received a copy of the GNU General Public License | |
15 | # along with this program; if not, write to the Free Software |
|
15 | # along with this program; if not, write to the Free Software | |
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 |
|
17 | |||
18 | require File.expand_path('../../test_helper', __FILE__) |
|
18 | require File.expand_path('../../test_helper', __FILE__) | |
19 |
|
19 | |||
20 | class AuthSourceLdapTest < ActiveSupport::TestCase |
|
20 | class AuthSourceLdapTest < ActiveSupport::TestCase | |
21 | include Redmine::I18n |
|
21 | include Redmine::I18n | |
22 | fixtures :auth_sources |
|
22 | fixtures :auth_sources | |
23 |
|
23 | |||
24 | def setup |
|
24 | def setup | |
25 | end |
|
25 | end | |
26 |
|
26 | |||
27 | def test_create |
|
27 | def test_create | |
28 | a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName') |
|
28 | a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName') | |
29 | assert a.save |
|
29 | assert a.save | |
30 | end |
|
30 | end | |
31 |
|
31 | |||
32 | def test_should_strip_ldap_attributes |
|
32 | def test_should_strip_ldap_attributes | |
33 | a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName', |
|
33 | a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName', | |
34 | :attr_firstname => 'givenName ') |
|
34 | :attr_firstname => 'givenName ') | |
35 | assert a.save |
|
35 | assert a.save | |
36 | assert_equal 'givenName', a.reload.attr_firstname |
|
36 | assert_equal 'givenName', a.reload.attr_firstname | |
37 | end |
|
37 | end | |
38 |
|
38 | |||
39 | def test_replace_port_zero_to_389 |
|
39 | def test_replace_port_zero_to_389 | |
40 | a = AuthSourceLdap.new( |
|
40 | a = AuthSourceLdap.new( | |
41 | :name => 'My LDAP', :host => 'ldap.example.net', :port => 0, |
|
41 | :name => 'My LDAP', :host => 'ldap.example.net', :port => 0, | |
42 | :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName', |
|
42 | :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName', | |
43 | :attr_firstname => 'givenName ') |
|
43 | :attr_firstname => 'givenName ') | |
44 | assert a.save |
|
44 | assert a.save | |
45 | assert_equal 389, a.port |
|
45 | assert_equal 389, a.port | |
46 | end |
|
46 | end | |
47 |
|
47 | |||
48 | def test_filter_should_be_validated |
|
48 | def test_filter_should_be_validated | |
49 | set_language_if_valid 'en' |
|
49 | set_language_if_valid 'en' | |
50 |
|
50 | |||
51 | a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :attr_login => 'sn') |
|
51 | a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :attr_login => 'sn') | |
52 | a.filter = "(mail=*@redmine.org" |
|
52 | a.filter = "(mail=*@redmine.org" | |
53 | assert !a.valid? |
|
53 | assert !a.valid? | |
54 | assert_include "LDAP filter is invalid", a.errors.full_messages |
|
54 | assert_include "LDAP filter is invalid", a.errors.full_messages | |
55 |
|
55 | |||
56 | a.filter = "(mail=*@redmine.org)" |
|
56 | a.filter = "(mail=*@redmine.org)" | |
57 | assert a.valid? |
|
57 | assert a.valid? | |
58 | end |
|
58 | end | |
59 |
|
59 | |||
60 | if ldap_configured? |
|
60 | if ldap_configured? | |
61 | context '#authenticate' do |
|
61 | context '#authenticate' do | |
62 | setup do |
|
62 | setup do | |
63 | @auth = AuthSourceLdap.find(1) |
|
63 | @auth = AuthSourceLdap.find(1) | |
64 | @auth.update_attribute :onthefly_register, true |
|
64 | @auth.update_attribute :onthefly_register, true | |
65 | end |
|
65 | end | |
66 |
|
66 | |||
67 | context 'with a valid LDAP user' do |
|
67 | context 'with a valid LDAP user' do | |
68 | should 'return the user attributes' do |
|
68 | should 'return the user attributes' do | |
69 | attributes = @auth.authenticate('example1','123456') |
|
69 | attributes = @auth.authenticate('example1','123456') | |
70 | assert attributes.is_a?(Hash), "An hash was not returned" |
|
70 | assert attributes.is_a?(Hash), "An hash was not returned" | |
71 | assert_equal 'Example', attributes[:firstname] |
|
71 | assert_equal 'Example', attributes[:firstname] | |
72 | assert_equal 'One', attributes[:lastname] |
|
72 | assert_equal 'One', attributes[:lastname] | |
73 | assert_equal 'example1@redmine.org', attributes[:mail] |
|
73 | assert_equal 'example1@redmine.org', attributes[:mail] | |
74 | assert_equal @auth.id, attributes[:auth_source_id] |
|
74 | assert_equal @auth.id, attributes[:auth_source_id] | |
75 | attributes.keys.each do |attribute| |
|
75 | attributes.keys.each do |attribute| | |
76 | assert User.new.respond_to?("#{attribute}="), "Unexpected :#{attribute} attribute returned" |
|
76 | assert User.new.respond_to?("#{attribute}="), "Unexpected :#{attribute} attribute returned" | |
77 | end |
|
77 | end | |
78 | end |
|
78 | end | |
79 | end |
|
79 | end | |
80 |
|
80 | |||
81 | context 'with an invalid LDAP user' do |
|
81 | context 'with an invalid LDAP user' do | |
82 | should 'return nil' do |
|
82 | should 'return nil' do | |
83 | assert_equal nil, @auth.authenticate('nouser','123456') |
|
83 | assert_equal nil, @auth.authenticate('nouser','123456') | |
84 | end |
|
84 | end | |
85 | end |
|
85 | end | |
86 |
|
86 | |||
87 | context 'without a login' do |
|
87 | context 'without a login' do | |
88 | should 'return nil' do |
|
88 | should 'return nil' do | |
89 | assert_equal nil, @auth.authenticate('','123456') |
|
89 | assert_equal nil, @auth.authenticate('','123456') | |
90 | end |
|
90 | end | |
91 | end |
|
91 | end | |
92 |
|
92 | |||
93 | context 'without a password' do |
|
93 | context 'without a password' do | |
94 | should 'return nil' do |
|
94 | should 'return nil' do | |
95 | assert_equal nil, @auth.authenticate('edavis','') |
|
95 | assert_equal nil, @auth.authenticate('edavis','') | |
96 | end |
|
96 | end | |
97 | end |
|
97 | end | |
98 |
|
98 | |||
99 | context 'without filter' do |
|
99 | context 'without filter' do | |
100 | should 'return any user' do |
|
100 | should 'return any user' do | |
101 | assert @auth.authenticate('example1','123456') |
|
101 | assert @auth.authenticate('example1','123456') | |
102 | assert @auth.authenticate('edavis', '123456') |
|
102 | assert @auth.authenticate('edavis', '123456') | |
103 | end |
|
103 | end | |
104 | end |
|
104 | end | |
105 |
|
105 | |||
106 | context 'with filter' do |
|
106 | context 'with filter' do | |
107 | setup do |
|
107 | setup do | |
108 | @auth.filter = "(mail=*@redmine.org)" |
|
108 | @auth.filter = "(mail=*@redmine.org)" | |
109 | end |
|
109 | end | |
110 |
|
110 | |||
111 | should 'return user who matches the filter only' do |
|
111 | should 'return user who matches the filter only' do | |
112 | assert @auth.authenticate('example1','123456') |
|
112 | assert @auth.authenticate('example1','123456') | |
113 | assert_nil @auth.authenticate('edavis', '123456') |
|
113 | assert_nil @auth.authenticate('edavis', '123456') | |
114 | end |
|
114 | end | |
115 | end |
|
115 | end | |
116 | end |
|
116 | end | |
117 |
|
117 | |||
118 | def test_authenticate_should_timeout |
|
118 | def test_authenticate_should_timeout | |
119 | auth_source = AuthSourceLdap.find(1) |
|
119 | auth_source = AuthSourceLdap.find(1) | |
120 | auth_source.timeout = 1 |
|
120 | auth_source.timeout = 1 | |
121 | def auth_source.initialize_ldap_con(*args); sleep(5); end |
|
121 | def auth_source.initialize_ldap_con(*args); sleep(5); end | |
122 |
|
122 | |||
123 | assert_raise AuthSourceTimeoutException do |
|
123 | assert_raise AuthSourceTimeoutException do | |
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 | |
130 | end |
|
154 | end |
General Comments 0
You need to be logged in to leave comments.
Login now