Patch #4755 » ldap-auto-groups.patch
app/models/auth_source_ldap.rb (Arbeitskopie) | ||
---|---|---|
33 | 33 |
|
34 | 34 |
def authenticate(login, password) |
35 | 35 |
return nil if login.blank? || password.blank? |
36 |
attrs = get_attributes(login) |
|
37 |
dn = attrs.first.delete(:dn) unless attrs.nil? |
|
38 |
# authenticate user |
|
39 |
ldap_con = initialize_ldap_con(dn, password) # add ", login" for fake LDAP tests |
|
40 |
return nil unless ldap_con.bind |
|
41 |
# return user's attributes |
|
42 |
logger.debug "Authentication successful for '#{login}'" if logger && logger.debug? |
|
43 |
attrs |
|
44 |
rescue Net::LDAP::LdapError => text |
|
45 |
raise "LdapError: " + text |
|
46 |
end |
|
47 |
|
|
48 |
def get_attributes(login) |
|
49 |
return nil if login.blank? |
|
36 | 50 |
attrs = [] |
37 | 51 |
# get user's DN |
38 |
ldap_con = initialize_ldap_con(self.account, self.account_password) |
|
52 |
ldap_con = initialize_ldap_con(self.account, self.account_password) # add ", login" for fake LDAP tests
|
|
39 | 53 |
login_filter = Net::LDAP::Filter.eq( self.attr_login, login ) |
40 | 54 |
object_filter = Net::LDAP::Filter.eq( "objectClass", "*" ) |
55 |
# Only ask for attributes that will be used |
|
56 |
query_attrs = ['dn'] |
|
57 |
query_attrs << [self.attr_firstname, self.attr_lastname, self.attr_mail] if onthefly_register? |
|
58 |
query_attrs << [self.attr_groups] unless self.attr_groups.blank? |
|
59 |
query_attrs << [self.attr_groups2] unless self.attr_groups2.blank? |
|
41 | 60 |
dn = String.new |
42 | 61 |
ldap_con.search( :base => self.base_dn, |
43 | 62 |
:filter => object_filter & login_filter, |
44 |
# only ask for the DN if on-the-fly registration is disabled
|
|
45 |
:attributes=> (onthefly_register? ? ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail] : ['dn'])) do |entry|
|
|
63 |
:attributes=> query_attrs) do |entry|
|
|
64 |
logger.debug "yielded"
|
|
46 | 65 |
dn = entry.dn |
47 | 66 |
attrs = [:firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname), |
48 | 67 |
:lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname), |
49 | 68 |
:mail => AuthSourceLdap.get_attr(entry, self.attr_mail), |
50 |
:auth_source_id => self.id ] if onthefly_register? |
|
69 |
:group_names => build_names(AuthSourceLdap.get_attr_list(entry, self.attr_groups), |
|
70 |
AuthSourceLdap.get_attr_list(entry, self.attr_groups2)), |
|
71 |
:auth_source_id => self.id ] if onthefly_register? |
|
51 | 72 |
end |
52 | 73 |
return nil if dn.empty? |
53 | 74 |
logger.debug "DN found for #{login}: #{dn}" if logger && logger.debug? |
54 |
# authenticate user |
|
55 |
ldap_con = initialize_ldap_con(dn, password) |
|
56 |
return nil unless ldap_con.bind |
|
57 |
# return user's attributes |
|
58 |
logger.debug "Authentication successful for '#{login}'" if logger && logger.debug? |
|
59 |
attrs |
|
75 |
attrs.first[:dn] = dn |
|
76 |
attrs |
|
60 | 77 |
rescue Net::LDAP::LdapError => text |
61 | 78 |
raise "LdapError: " + text |
62 | 79 |
end |
63 | 80 | |
81 |
def build_names(names, names2) |
|
82 |
all = [] |
|
83 |
names.each do |n| |
|
84 |
all << Group.shorten_lastname(self.group_prefix, n) |
|
85 |
end |
|
86 |
names2.each do |n| |
|
87 |
all << Group.shorten_lastname(self.group_prefix, n) |
|
88 |
end |
|
89 |
names.each do |n| |
|
90 |
names2.each do |n2| |
|
91 |
all << Group.shorten_lastname(self.group_prefix, n + self.group_separator + n2) |
|
92 |
end |
|
93 |
end if self.cross_product |
|
94 |
all |
|
95 |
end |
|
96 | ||
64 | 97 |
# test the connection to the LDAP |
65 | 98 |
def test_connection |
66 | 99 |
ldap_con = initialize_ldap_con(self.account, self.account_password) |
... | ... | |
81 | 114 |
end |
82 | 115 |
end |
83 | 116 |
|
84 |
def initialize_ldap_con(ldap_user, ldap_password) |
|
85 |
options = { :host => self.host, |
|
86 |
:port => self.port, |
|
87 |
:encryption => (self.tls ? :simple_tls : nil) |
|
88 |
} |
|
89 |
options.merge!(:auth => { :method => :simple, :username => ldap_user, :password => ldap_password }) unless ldap_user.blank? && ldap_password.blank? |
|
90 |
Net::LDAP.new options |
|
117 |
# By passing an optional third parameter (the login name), the fake classes |
|
118 |
# below will be used (more comfortable for debugging). Anyone logging in |
|
119 |
# with a login matching "firstname.lastname" is considered to be in this |
|
120 |
# fake LDAP, any password matches. Some groups will also be set |
|
121 |
def initialize_ldap_con(ldap_user, ldap_password, fake = nil) |
|
122 |
if (fake != nil) |
|
123 |
FakeLdapCon.new fake |
|
124 |
else |
|
125 |
options = { :host => self.host, |
|
126 |
:port => self.port, |
|
127 |
:encryption => (self.tls ? :simple_tls : nil) |
|
128 |
} |
|
129 |
options.merge!(:auth => { :method => :simple, :username => ldap_user, :password => ldap_password }) unless ldap_user.blank? && ldap_password.blank? |
|
130 |
Net::LDAP.new options |
|
131 |
end |
|
91 | 132 |
end |
92 | 133 |
|
93 | 134 |
def self.get_attr(entry, attr_name) |
... | ... | |
95 | 136 |
entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name] |
96 | 137 |
end |
97 | 138 |
end |
139 | ||
140 |
def self.get_attr_list(entry, attr_name) |
|
141 |
if !attr_name.blank? |
|
142 |
entry[attr_name].is_a?(Array) ? entry[attr_name] : [entry[attr_name]] |
|
143 |
end |
|
144 |
end |
|
98 | 145 |
end |
146 | ||
147 |
class FakeLdapCon |
|
148 |
def initialize(first_dot_last) |
|
149 |
parts = first_dot_last.split('.') |
|
150 |
@first = parts[0] |
|
151 |
@last = parts[1] |
|
152 |
end |
|
153 |
|
|
154 |
def first |
|
155 |
@first |
|
156 |
end |
|
157 |
|
|
158 |
def last |
|
159 |
@last |
|
160 |
end |
|
161 |
|
|
162 |
def to_s |
|
163 |
"FakeLdap(#{@first}.#{last})" |
|
164 |
end |
|
165 |
|
|
166 |
def search(query, &block) |
|
167 |
yield FakeLdapEntry.new(@first, @last) unless @last.blank? |
|
168 |
end |
|
169 |
|
|
170 |
def bind |
|
171 |
!@last.blank? |
|
172 |
end |
|
173 |
end |
|
174 | ||
175 |
class FakeLdapEntry |
|
176 |
def initialize(first, last) |
|
177 |
@first = first |
|
178 |
@last = last |
|
179 |
end |
|
180 |
|
|
181 |
def dn |
|
182 |
@first + "." + @last |
|
183 |
end |
|
184 |
|
|
185 |
def givenName |
|
186 |
@first |
|
187 |
end |
|
188 |
|
|
189 |
def sn |
|
190 |
@last |
|
191 |
end |
|
192 |
|
|
193 |
def mail |
|
194 |
self.dn + "@example.org" |
|
195 |
end |
|
196 |
|
|
197 |
def ou |
|
198 |
[@first, @last, "everyone"] |
|
199 |
end |
|
200 |
|
|
201 |
def businessCategory |
|
202 |
@first.length.odd? ? "S" : "A" |
|
203 |
end |
|
204 |
|
|
205 |
def [](entry) |
|
206 |
self.send(entry) |
|
207 |
end |
|
208 |
end |
app/models/user.rb (Arbeitskopie) | ||
---|---|---|
49 | 49 |
|
50 | 50 |
attr_accessor :password, :password_confirmation |
51 | 51 |
attr_accessor :last_before_login_on |
52 |
attr_accessor :group_names |
|
52 | 53 |
# Prevents unauthorized assignments |
53 | 54 |
attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids |
54 | 55 |
|
... | ... | |
102 | 103 |
return nil if !user.active? |
103 | 104 |
if user.auth_source |
104 | 105 |
# user has an external authentication method |
105 |
return nil unless user.auth_source.authenticate(login, password) |
|
106 |
attrs = user.auth_source.authenticate(login, password) |
|
107 |
logger.debug attrs.inspect |
|
108 |
return nil unless attrs = user.auth_source.authenticate(login, password) |
|
109 |
user.group_names = attrs.first[:group_names] |
|
110 |
user.refresh_group_memberships |
|
106 | 111 |
else |
107 | 112 |
# authentication with local password |
108 | 113 |
return nil unless User.hash_password(password) == user.hashed_password |
... | ... | |
117 | 122 |
if user.save |
118 | 123 |
user.reload |
119 | 124 |
logger.info("User '#{user.login}' created from the LDAP") if logger |
125 |
user.refresh_group_memberships |
|
120 | 126 |
end |
121 | 127 |
end |
122 | 128 |
end |
... | ... | |
134 | 140 |
token = tokens.first |
135 | 141 |
if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active? |
136 | 142 |
token.user.update_attribute(:last_login_on, Time.now) |
143 |
token.user.refresh_group_memberships if token.user.auth_source |
|
137 | 144 |
token.user |
138 | 145 |
end |
139 | 146 |
end |
... | ... | |
302 | 309 |
false |
303 | 310 |
end |
304 | 311 |
end |
305 |
|
|
312 |
|
|
306 | 313 |
def self.current=(user) |
307 | 314 |
@current_user = user |
308 | 315 |
end |
... | ... | |
321 | 328 |
end |
322 | 329 |
anonymous_user |
323 | 330 |
end |
331 | ||
332 |
def refresh_group_memberships |
|
333 |
return nil if !self.auth_source |
|
334 |
# (re-)import the information from LDAP if necessary |
|
335 |
if group_names.nil? |
|
336 |
attrs = self.auth_source.get_attributes(self.login) |
|
337 |
if attrs.nil? |
|
338 |
logger.error("Refreshing group memberships not possible for #{self.login}") |
|
339 |
return nil |
|
340 |
end |
|
341 |
self.group_names = attrs.first[:group_names] |
|
342 |
end |
|
343 |
# Add user to groups she is not in yet |
|
344 |
prefixed_names = {} |
|
345 |
self.group_names.each do |name| |
|
346 |
prefixed_names[name] = true |
|
347 |
group = Group.find_or_create_by_lastname(name) |
|
348 |
if !group.users.exists?(self) |
|
349 |
group.auth_source_id = self.auth_source_id # Mark as LDAP-maintained |
|
350 |
group.users << self |
|
351 |
group.save |
|
352 |
end |
|
353 |
end |
|
354 |
# Remove user from groups she is no longer in yet; remove empty groups |
|
355 |
gg = Group.find(:all, :conditions => ["auth_source_id = ?", self.auth_source_id]).each do |g| |
|
356 |
g.users.delete(self) if g.users.exists?(self) && !prefixed_names.key?(g.lastname) |
|
357 |
Group.destroy(g.id) if g.users.count == 0 |
|
358 |
end |
|
359 |
true |
|
360 |
end |
|
324 | 361 |
|
325 | 362 |
protected |
326 | 363 |
|
app/models/auth_source.rb (Arbeitskopie) | ||
---|---|---|
17 | 17 | |
18 | 18 |
class AuthSource < ActiveRecord::Base |
19 | 19 |
has_many :users |
20 |
has_many :groups |
|
20 | 21 |
|
21 | 22 |
validates_presence_of :name |
22 | 23 |
validates_uniqueness_of :name |
... | ... | |
46 | 47 |
end |
47 | 48 |
return nil |
48 | 49 |
end |
50 | ||
51 |
def self.import(login) |
|
52 |
auth = get_data(login) |
|
53 |
logger.debug("auth is #{auth.class.to_s}") |
|
54 |
if auth && auth.size == 1 |
|
55 |
a = auth.first |
|
56 |
a.each { |key, value| logger.debug("#{key} => #{value}") } |
|
57 |
a.delete(:dn) |
|
58 |
user = User.new(a) |
|
59 |
user.login = login |
|
60 |
user.language = Setting.default_language |
|
61 |
user.admin = false # Just to be sure |
|
62 |
if user.save |
|
63 |
logger.debug("successful created") |
|
64 |
user.refresh_group_memberships |
|
65 |
return user |
|
66 |
else |
|
67 |
logger.debug("failed to create") |
|
68 |
return nil |
|
69 |
end |
|
70 |
else |
|
71 |
logger.debug("User not found among those sources available for on-the-fly creation") |
|
72 |
return nil |
|
73 |
end |
|
74 |
end |
|
75 | ||
76 |
private |
|
77 | ||
78 |
# Try to import a user not yet registered against available sources |
|
79 |
def self.get_data(login) |
|
80 |
AuthSource.find(:all, :conditions => ["onthefly_register=?", true]).each do |source| |
|
81 |
begin |
|
82 |
logger.debug "Importing '#{login}' from '#{source.name}'" if logger && logger.debug? |
|
83 |
logger.debug "Using class #{source.class.to_s}" if logger && logger.debug? |
|
84 |
attrs = source.get_attributes(login) |
|
85 |
rescue => e |
|
86 |
logger.error "Error during import: #{e.message}" |
|
87 |
attrs = nil |
|
88 |
end |
|
89 |
return attrs if attrs |
|
90 |
end |
|
91 |
return nil |
|
92 |
end |
|
49 | 93 |
end |
app/models/group.rb (Arbeitskopie) | ||
---|---|---|
24 | 24 |
validates_presence_of :lastname |
25 | 25 |
validates_uniqueness_of :lastname, :case_sensitive => false |
26 | 26 |
validates_length_of :lastname, :maximum => 30 |
27 |
|
|
27 |
|
|
28 |
# Remove slash- or space-separated entities until it fits into the maxlength |
|
29 |
# If the last part is too long, just take the ending maxlength chars |
|
30 |
def self.shorten_lastname(prefix, lastname) |
|
31 |
save = lastname |
|
32 |
while !lastname.nil? && lastname.length+prefix.length > 30 |
|
33 |
dummy, lastname = lastname.split(/[ \/]/, 2) |
|
34 |
end |
|
35 |
lastname = save[-30+prefix.length,30] if lastname.nil? || lastname.empty? |
|
36 |
prefix + lastname |
|
37 |
end |
|
38 |
|
|
28 | 39 |
def to_s |
29 | 40 |
lastname.to_s |
30 | 41 |
end |
... | ... | |
45 | 56 |
:conditions => ["#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids]).each(&:destroy) |
46 | 57 |
end |
47 | 58 |
end |
59 |
|
|
60 |
def size_and_updated_by_string |
|
61 |
updated_by = updated_by_string |
|
62 |
if updated_by == nil |
|
63 |
return users.size |
|
64 |
else |
|
65 |
return "#{users.size} (#{updated_by_string})" |
|
66 |
end |
|
67 |
end |
|
68 | ||
69 |
def updated_by_string |
|
70 |
source_id = auth_source_id.to_i |
|
71 |
if source_id == 0 |
|
72 |
return nil |
|
73 |
else |
|
74 |
source = AuthSource.find_by_id(source_id) |
|
75 |
if source == nil |
|
76 |
return l(:text_group_updated_by_unknown, :id => source_id) |
|
77 |
else |
|
78 |
return l(:text_group_updated_by, :name => source.name, :type => source.auth_method_name) |
|
79 |
end |
|
80 |
end |
|
81 |
end |
|
48 | 82 |
end |
app/controllers/members_controller.rb (Arbeitskopie) | ||
---|---|---|
24 | 24 |
members = [] |
25 | 25 |
if params[:member] && request.post? |
26 | 26 |
attrs = params[:member].dup |
27 |
# When no user is selected but the name does match a user |
|
28 |
# in LDAP, which has not yet been imported, then go and import the |
|
29 |
# user from LDAP and add it to the project. Multiple names may be |
|
30 |
# separated by whitespace. |
|
31 |
if (! attrs.has_key?(:user_ids) && ! params[:principal_search].empty?) |
|
32 |
attrs[:user_ids] = [] |
|
33 |
newUser = nil |
|
34 |
params[:principal_search].split.each do |login| |
|
35 |
newUser = AuthSource.import(login) |
|
36 |
if newUser |
|
37 |
logger.info("Imported AuthSource as #{newUser}") |
|
38 |
else |
|
39 |
newUser = User.first(:conditions => ["login = ?", login]) |
|
40 |
end |
|
41 |
attrs[:user_ids] << newUser.id if newUser |
|
42 |
logger.debug("Would join entries #{attrs[:user_ids].inspect}") |
|
43 |
end |
|
44 |
end |
|
27 | 45 |
if (user_ids = attrs.delete(:user_ids)) |
28 | 46 |
user_ids.each do |user_id| |
29 | 47 |
members << Member.new(attrs.merge(:user_id => user_id)) |
app/controllers/auth_sources_controller.rb (Arbeitskopie) | ||
---|---|---|
72 | 72 |
end |
73 | 73 |
redirect_to :action => 'list' |
74 | 74 |
end |
75 |
|
|
76 |
def refresh_groups |
|
77 |
success_count = 0 |
|
78 |
error_users = [] |
|
79 |
User.active.find(:all, :conditions => ['auth_source_id = ?', params[:id]]).each do |u| |
|
80 |
if u.refresh_group_memberships.nil? |
|
81 |
error_users << u.login |
|
82 |
else |
|
83 |
success_count = success_count + 1 |
|
84 |
end |
|
85 |
end |
|
86 |
if (error_users.size == 0) |
|
87 |
flash[:notice] = l(:text_group_refreshed, :count => success_count) |
|
88 |
else |
|
89 |
flash[:error] = l(:text_group_refresh_failed, :errors => error_users.size, :failures => error_users.join(", "), :success => success_count) |
|
90 |
end |
|
91 |
redirect_to :action => 'list' |
|
92 |
end |
|
75 | 93 | |
76 | 94 |
def destroy |
77 | 95 |
@auth_source = AuthSource.find(params[:id]) |
app/views/auth_sources/list.rhtml (Arbeitskopie) | ||
---|---|---|
20 | 20 |
<td align="center"><%= source.host %></td> |
21 | 21 |
<td align="center"><%= source.users.count %></td> |
22 | 22 |
<td class="buttons"> |
23 |
<%= link_to l(:button_refresh), :action => 'refresh_groups', :id => source unless source.attr_groups.blank? %> |
|
23 | 24 |
<%= link_to l(:button_test), :action => 'test_connection', :id => source %> |
24 | 25 |
<%= link_to l(:button_delete), { :action => 'destroy', :id => source }, |
25 | 26 |
:method => :post, |
app/views/auth_sources/_form.rhtml (Arbeitskopie) | ||
---|---|---|
39 | 39 | |
40 | 40 |
<p><label for="auth_source_attr_mail"><%=l(:field_mail)%></label> |
41 | 41 |
<%= text_field 'auth_source', 'attr_mail', :size => 20 %></p> |
42 | ||
43 |
<p><label for="auth_source_attr_groups"><%=l(:field_groups)%></label> |
|
44 |
<%= text_field 'auth_source', 'attr_groups', :size => 20 %></p> |
|
45 | ||
46 |
<p><label for="auth_source_attr_groups2"><%=l(:field_groups2)%></label> |
|
47 |
<%= text_field 'auth_source', 'attr_groups2', :size => 20 %></p> |
|
48 | ||
42 | 49 |
</fieldset> |
50 | ||
51 |
<fieldset class="box"><legend><%=l(:label_group_option_plural)%></legend> |
|
52 |
<p><label for="auth_source_group_prefix"><%=l(:field_prefix)%></label> |
|
53 |
<%= text_field 'auth_source', 'group_prefix', :size => 20 %></p> |
|
54 | ||
55 |
<p><label for="auth_source_cross_product"><%=l(:field_cross_product)%></label> |
|
56 |
<%= check_box 'auth_source', 'cross_product' %></p> |
|
57 | ||
58 |
<p><label for="auth_source_group_separator"><%=l(:field_group_separator)%></label> |
|
59 |
<%= text_field 'auth_source', 'group_separator', :size => 20 %></p> |
|
60 |
</fieldset> |
|
61 | ||
43 | 62 |
<!--[eoform:auth_source]--> |
44 | 63 |
app/views/groups/index.html.erb (Arbeitskopie) | ||
---|---|---|
13 | 13 |
</tr></thead> |
14 | 14 |
<tbody> |
15 | 15 |
<% @groups.each do |group| %> |
16 |
<tr class="<%= cycle 'odd', 'even' %>"> |
|
16 |
<tr class="<%= cycle 'odd', 'even' %><%= group.auth_source_id.to_i == 0 ? '' : ' auto-group' %>">
|
|
17 | 17 |
<td><%= link_to h(group), :action => 'edit', :id => group %></td> |
18 |
<td align="center"><%= group.users.size %></td>
|
|
18 |
<td align="left"><%= group.size_and_updated_by_string %></td>
|
|
19 | 19 |
<td class="buttons"><%= link_to l(:button_delete), group, :confirm => l(:text_are_you_sure), :method => :delete, :class => 'icon icon-del' %></td> |
20 | 20 |
</tr> |
21 | 21 |
<% end %> |
app/views/groups/_form.html.erb (Arbeitskopie) | ||
---|---|---|
1 | 1 |
<%= error_messages_for :group %> |
2 | 2 | |
3 | 3 |
<div class="box tabular"> |
4 |
<p><%= f.text_field :lastname, :label => :field_name %></p> |
|
4 |
<p><%= f.text_field :lastname, :label => :field_name, :disabled => (@group.auth_source_id.to_i > 0) %> <%= @group.updated_by_string || "" %></p>
|
|
5 | 5 |
<% @group.custom_field_values.each do |value| %> |
6 | 6 |
<p><%= custom_field_tag_with_label :group, value %></p> |
7 | 7 |
<% end %> |
config/locales/en.yml (Arbeitskopie) | ||
---|---|---|
276 | 276 |
field_content: Content |
277 | 277 |
field_group_by: Group results by |
278 | 278 |
field_sharing: Sharing |
279 |
field_groups: Organisation group |
|
280 |
field_prefix: Group name prefix |
|
281 |
field_groups2: Function group |
|
282 |
field_cross_product: Include combined function/organisation groups |
|
283 |
field_group_separator: Function/group separator |
|
279 | 284 |
|
280 | 285 |
setting_app_title: Application title |
281 | 286 |
setting_app_subtitle: Application subtitle |
... | ... | |
743 | 748 |
label_api_access_key: API access key |
744 | 749 |
label_missing_api_access_key: Missing an API access key |
745 | 750 |
label_api_access_key_created_on: "API access key created {{value}} ago" |
751 |
label_group_option_plural: Grouping options |
|
746 | 752 |
|
747 | 753 |
button_login: Login |
748 | 754 |
button_submit: Submit |
... | ... | |
787 | 793 |
button_quote: Quote |
788 | 794 |
button_duplicate: Duplicate |
789 | 795 |
button_show: Show |
796 |
button_refresh: Refresh groups |
|
790 | 797 |
|
791 | 798 |
status_active: active |
792 | 799 |
status_registered: registered |
... | ... | |
853 | 860 |
text_wiki_page_destroy_children: "Delete child pages and all their descendants" |
854 | 861 |
text_wiki_page_reassign_children: "Reassign child pages to this parent page" |
855 | 862 |
text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?" |
856 |
|
|
863 |
text_group_updated_by: "maintained by {{type}} authentication source {{name}}" |
|
864 |
text_group_refreshed: "Groups for {{count}} active users refreshed" |
|
865 |
text_group_refresh_failed: "Group refresh for {{errors}} failed: {{failures}} (updating for {{success}} active users successful)" |
|
866 |
|
|
857 | 867 |
default_role_manager: Manager |
858 | 868 |
default_role_developper: Developer |
859 | 869 |
default_role_reporter: Reporter |
config/locales/de.yml (Arbeitskopie) | ||
---|---|---|
279 | 279 |
field_default_value: Standardwert |
280 | 280 |
field_comments_sorting: Kommentare anzeigen |
281 | 281 |
field_parent_title: Übergeordnete Seite |
282 |
field_groups: Organisation für Gruppen |
|
283 |
field_prefix: Präfix für Gruppennamen |
|
284 |
field_groups2: Funktion für Gruppen |
|
285 |
field_cross_product: Erzeuge kombinierte Organisations-/Funktionsgruppen |
|
286 |
field_group_separator: Trenner zwischen Organisation und Funktion |
|
282 | 287 |
|
283 | 288 |
setting_app_title: Applikations-Titel |
284 | 289 |
setting_app_subtitle: Applikations-Untertitel |
... | ... | |
693 | 698 |
label_generate_key: Generieren |
694 | 699 |
label_issue_watchers: Beobachter |
695 | 700 |
label_example: Beispiel |
701 |
label_group_option_plural: Gruppenoptionen |
|
696 | 702 |
|
697 | 703 |
button_login: Anmelden |
698 | 704 |
button_submit: OK |
... | ... | |
732 | 738 |
button_update: Aktualisieren |
733 | 739 |
button_configure: Konfigurieren |
734 | 740 |
button_quote: Zitieren |
741 |
button_refresh: Gruppenzugehörigkeit aktualisieren |
|
735 | 742 |
|
736 | 743 |
status_active: aktiv |
737 | 744 |
status_registered: angemeldet |
... | ... | |
781 | 788 |
text_email_delivery_not_configured: "Der SMTP-Server ist nicht konfiguriert und Mailbenachrichtigungen sind ausgeschaltet.\nNehmen Sie die Einstellungen für Ihren SMTP-Server in config/email.yml vor und starten Sie die Applikation neu." |
782 | 789 |
text_repository_usernames_mapping: "Bitte legen Sie die Zuordnung der Redmine-Benutzer zu den Benutzernamen der Commit-Log-Meldungen des Projektarchivs fest.\nBenutzer mit identischen Redmine- und Projektarchiv-Benutzernamen oder -E-Mail-Adressen werden automatisch zugeordnet." |
783 | 790 |
text_diff_truncated: '... Dieser Diff wurde abgeschnitten, weil er die maximale Anzahl anzuzeigender Zeilen überschreitet.' |
791 |
text_group_updated_by: "verwaltet durch {{type}}-Anbindung {{name}}" |
|
792 |
text_group_refreshed: "Gruppenzugehörigkeit von {{count}} aktiven Benutzern aktualisiert" |
|
793 |
text_group_refresh_failed: "Gruppenzugehörigkeit der folgenden {{errors}} Benutzer fehlgeschlagen: {{failures}} (Aktualisierung für {{success}} aktive Benutzer erfolgreich)" |
|
784 | 794 |
|
785 | 795 |
default_role_manager: Manager |
786 | 796 |
default_role_developper: Entwickler |
db/schema.rb (Arbeitskopie) | ||
---|---|---|
9 | 9 |
# |
10 | 10 |
# It's strongly recommended to check this file into your version control system. |
11 | 11 | |
12 |
ActiveRecord::Schema.define(:version => 20091227112908) do
|
|
12 |
ActiveRecord::Schema.define(:version => 20100207220329) do
|
|
13 | 13 | |
14 | 14 |
create_table "attachments", :force => true do |t| |
15 | 15 |
t.integer "container_id", :default => 0, :null => false |
... | ... | |
43 | 43 |
t.string "attr_mail", :limit => 30 |
44 | 44 |
t.boolean "onthefly_register", :default => false, :null => false |
45 | 45 |
t.boolean "tls", :default => false, :null => false |
46 |
t.string "attr_groups", :limit => 30, :default => "", :null => false |
|
47 |
t.string "group_prefix", :limit => 30, :default => "_", :null => false |
|
48 |
t.string "attr_groups2", :limit => 30, :default => "", :null => false |
|
49 |
t.string "group_separator", :limit => 30, :default => ":", :null => false |
|
50 |
t.boolean "cross_product", :default => false, :null => false |
|
46 | 51 |
end |
47 | 52 | |
48 | 53 |
add_index "auth_sources", ["id", "type"], :name => "index_auth_sources_on_id_and_type" |
... | ... | |
473 | 478 | |
474 | 479 |
add_index "users", ["auth_source_id"], :name => "index_users_on_auth_source_id" |
475 | 480 |
add_index "users", ["id", "type"], :name => "index_users_on_id_and_type" |
481 |
add_index "users", [nil], :name => "index_users_on_lower_login" |
|
476 | 482 | |
477 | 483 |
create_table "versions", :force => true do |t| |
478 | 484 |
t.integer "project_id", :default => 0, :null => false |
db/migrate/20100207220329_extend_ldap_groups.rb (Revision 67) | ||
---|---|---|
1 |
class ExtendLdapGroups < ActiveRecord::Migration |
|
2 |
def self.up |
|
3 |
add_column :auth_sources, :attr_groups2, :string, :limit => 30, :default => "", :null => false |
|
4 |
add_column :auth_sources, :group_separator, :string, :limit => 30, :default => ":", :null => false |
|
5 |
add_column :auth_sources, :cross_product, :boolean, :default => false, :null => false |
|
6 |
end |
|
7 | ||
8 |
def self.down |
|
9 |
remove_column :auth_sources, :attr_groups2 |
|
10 |
remove_column :auth_sources, :group_separator |
|
11 |
remove_column :auth_sources, :cross_product |
|
12 |
end |
|
13 |
end |
db/migrate/20100204211355_add_ldap_group_support.rb (Revision 67) | ||
---|---|---|
1 |
class AddLdapGroupSupport < ActiveRecord::Migration |
|
2 |
def self.up |
|
3 |
add_column :auth_sources, :attr_groups, :string, :limit => 30, :default => "", :null => false |
|
4 |
add_column :auth_sources, :group_prefix, :string, :limit => 30, :default => "_", :null => false |
|
5 |
end |
|
6 | ||
7 |
def self.down |
|
8 |
remove_column :auth_sources, :attr_groups |
|
9 |
remove_column :auth_sources, :group_prefix |
|
10 |
end |
|
11 |
end |