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  | 
|