Patch #4755 » ldap-auto-groups2.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 |
|