class ASF::Person

An instance of this class represents a person. Data comes from a variety of sources: LDAP, asf--authorization-template, iclas.txt, members.txt, nominated-members.txt, and potential-member-watch-list.txt.

Constants

DOUBLE_PFX
SINGLE_PFX

surname prefixes

SUFFIXES

generational suffixes

Public Class Methods

[](id) click to toggle source

return person only if it actually exits

Calls superclass method ASF::Base::[]
# File lib/whimsy/asf/ldap.rb, line 740
def self.[](id)
  person = super
  person.attrs['dn'] ? person : nil
end
add(attrs) click to toggle source

add a new person to LDAP. Attrs must include uid, cn, and mail

# File lib/whimsy/asf/ldap.rb, line 910
def self.add(attrs)
  # convert keys to strings
  attrs = attrs.map {|key, value| [key.to_s, value]}.to_h

  # verify required arguments are present
  %w(uid cn mail).each do |required|
    unless attrs.include? required
      raise ArgumentError.new("missing attribute #{required}")
    end
  end

  availid = attrs['uid']

  # determine next uid and group, unless provided
  nextuid = attrs['uidNumber'] ||
    ASF::search_one(ASF::Person.base, 'uid=*', 'uidNumber').
      flatten.map(&:to_i).max + 1

  nextgid = attrs['gidNumber']
  unless nextgid
    nextgid = ASF::search_one(group_base, 'cn=*', 'gidNumber').
      flatten.map(&:to_i).max + 1

    # create new LDAP group
    entry = [
      mod_add('objectClass', ['posixGroup', 'top']),
      mod_add('cn', availid),
      mod_add('userPassword', '{crypt}*'),
      mod_add('gidNumber', nextgid.to_s),
    ]
  end

  # fixed attributes
  attrs.merge!({
    'uidNumber' => nextuid.to_s,
    'gidNumber' => nextuid.to_s,
    'asf-committer-email' => "#{availid}@apache.org",
    'objectClass' => %w(person top posixAccount organizationalPerson
                        inetOrgPerson asf-committer hostObject ldapPublicKey)
  })

  # defaults
  attrs['loginShell'] ||= '/usr/local/bin/bash'
  attrs['homeDirectory'] ||= File.join("/home", availid)
  attrs['host'] ||= "home.apache.org"
  attrs['asf-sascore'] ||= "10"

  # parse name
  attrs = ASF::Person.ldap_name(attrs['cn']).merge(attrs)

  # generate a password that is between 8 and 16 alphanumeric characters
  unless attrs['userPassword']
    while attrs['userPassword'].to_s.length < 8
      attrs['userPassword'] = SecureRandom.base64(12).gsub(/\W+/, '')
    end
  end

  ASF::LDAP.add("cn=#{availid},#{group_base}", entry)

  # create new LDAP person
  begin
    entry = attrs.map {|key, value| mod_add(key, value)}
    ASF::LDAP.add("uid=#{availid},#{base}", entry)
  rescue
    # don't leave an orphan group behind
    ASF::LDAP.delete("cn=#{availid},#{group_base}") rescue nil
    raise
  end

  # return person object with password filled in
  person = ASF::Person.find(availid)
  person.attrs['userPassword'] = [attrs['userPassword']]
  person
end
asciize(name, nonWord = '-') click to toggle source

Convert non-ASCII characters to equivalent ASCII optionally: replace any remaining non-word characters (e.g. '.' and space) with '-'

# File lib/whimsy/asf/person.rb, line 16
def self.asciize(name, nonWord = '-')
  if name.match %r{[^\x00-\x7F]} # at least one non-ASCII character present
    # digraphs.  May be culturally sensitive
    # Note that the combining accents require matching two characters
    name.gsub! %r{\u00df}, 'ss'
    name.gsub! %r{\u00e4|a\u0308}, 'ae' # 308 = combining diaeresis
    name.gsub! %r{\u00e5|a\u030a}, 'aa' # a with ring above: should this translate as 'a'?
    name.gsub! %r{\u00c5|A\u030a}, 'AA' # A with ring above: should this translate as 'A'?
    name.gsub! %r{\u00e6},         'ae' # small letter ae
    name.gsub! %r{\u00c6},         'AE' # large letter AE
    name.gsub! %r{\u00f1|n\u0303}, 'ny' # 303 = combining tilde
    name.gsub! %r{\u00d1|N\u0303}, 'NY' # 303 = combining tilde
    name.gsub! %r{\u00f6|o\u0308}, 'oe' # 308 = combining diaeresis
    name.gsub! %r{\u00d6|O\u0308}, 'OE' # 308 = combining diaeresis
    name.gsub! %r{\u00de},         'TH' # thorn
    name.gsub! %r{\u00fe},         'th' # thorn
    name.gsub! %r{\u00fc|u\u0308}, 'ue' # 308 = combining diaeresis
    name.gsub! %r{\u00dc|U\u0308}, 'UE' # 308 = combining diaeresis

    # latin 1
    name.gsub! %r{[\u00e0-\u00e3]}, 'a' # a with various accents
    name.gsub! %r{[\u00c0-\u00c3]}, 'A' # A with various accents
    name.gsub! %r{\u00e7},          'c' # c-cedilla
    name.gsub! %r{\u00c7},          'C' # C-cedilla
    name.gsub! %r{\u00f0},          'd' # eth
    name.gsub! %r{\u00d0},          'D' # eth
    name.gsub! %r{[\u00e8-\u00eb]}, 'e'
    name.gsub! %r{[\u00c8-\u00cb]}, 'E'
    name.gsub! %r{[\u00ec-\u00ef]}, 'i'
    name.gsub! %r{[\u00cc-\u00cf]}, 'I'
    name.gsub! %r{[\u00f2-\u00f5\u00f8]}, 'o'
    name.gsub! %r{[\u00d2-\u00d5\u00d8]}, 'O'
    name.gsub! %r{[\u00f9-\u00fb]}, 'u'
    name.gsub! %r{[\u00d9-\u00db]}, 'U'
    name.gsub! %r{[\u00fd\u00ff]},  'y'
    name.gsub! %r{[\u00dd\u0178]},  'Y'

    # Latin Extended-A
    name.gsub! %r{[\u0100\u0102\u0104]}, 'A'
    name.gsub! %r{[\u0101\u0103\u0105]}, 'a'
    name.gsub! %r{[\u0106\u0108\u010A\u010C]}, 'C'
    name.gsub! %r{[\u0107\u0109\u010B\u010D]}, 'c'
    name.gsub! %r{[\u010E\u0110]}, 'D'
    name.gsub! %r{[\u010F\u0111]}, 'd'
    name.gsub! %r{[\u0112\u0114\u0116\u0118\u011A]}, 'E'
    name.gsub! %r{[\u0113\u0115\u0117\u0119\u011B]}, 'e'
    name.gsub! %r{[\u014A]}, 'ENG'
    name.gsub! %r{[\u014B]}, 'eng'
    name.gsub! %r{[\u011C\u011E\u0120\u0122]}, 'G'
    name.gsub! %r{[\u011D\u011F\u0121\u0123]}, 'g'
    name.gsub! %r{[\u0124\u0126]}, 'H'
    name.gsub! %r{[\u0125\u0127]}, 'h'
    name.gsub! %r{[\u0128\u012A\u012C\u012E\u0130]}, 'I'
    name.gsub! %r{[\u0129\u012B\u012D\u012F\u0131]}, 'i'
    name.gsub! %r{[\u0132]}, 'IJ'
    name.gsub! %r{[\u0133]}, 'ij'
    name.gsub! %r{[\u0134]}, 'J'
    name.gsub! %r{[\u0135]}, 'j'
    name.gsub! %r{[\u0136]}, 'K'
    name.gsub! %r{[\u0137]}, 'k'
    name.gsub! %r{[\u0138]}, 'kra'
    name.gsub! %r{[\u0139\u013B\u013D\u013F\u0141]}, 'L'
    name.gsub! %r{[\u013A\u013C\u013E\u0140\u0142]}, 'l'
    name.gsub! %r{[\u0143\u0145\u0147]}, 'N'
    name.gsub! %r{[\u0144\u0146\u0148\u0149]}, 'n'
    name.gsub! %r{[\u014C\u014E\u0150]}, 'O'
    name.gsub! %r{[\u014D\u014F\u0151]}, 'o'
    name.gsub! %r{[\u0152]}, 'OE'
    name.gsub! %r{[\u0153]}, 'oe'
    name.gsub! %r{[\u0154\u0156\u0158]}, 'R'
    name.gsub! %r{[\u0155\u0157\u0159]}, 'r'
    name.gsub! %r{[\u015A\u015C\u015E\u0160]}, 'S'
    name.gsub! %r{[\u015B\u015D\u015F\u0161]}, 's'
    name.gsub! %r{[\u0162\u0164\u0166]}, 'T'
    name.gsub! %r{[\u0163\u0165\u0167]}, 't'
    name.gsub! %r{[\u0168\u016A\u016C\u016E\u0170\u0172]}, 'U'
    name.gsub! %r{[\u0169\u016B\u016D\u016F\u0171\u0173]}, 'u'
    name.gsub! %r{[\u0174]}, 'W'
    name.gsub! %r{[\u0175]}, 'w'
    name.gsub! %r{[\u0176\u0178]}, 'Y'
    name.gsub! %r{[\u0177]}, 'y'
    name.gsub! %r{[\u0179\u017B\u017D]}, 'Z'
    name.gsub! %r{[\u017A\u017C\u017E]}, 'z'

    # Latin Extended Additional
    # N.B. Only ones seen in iclas.txt are included here
    name.gsub! %r{\u1ea0},          'A' # A with combining dot below
    name.gsub! %r{\u1ea1},          'a' # a with combining dot below
    name.gsub! %r{\u1ec4},          'E' # E with circumflex and tilde
    name.gsub! %r{\u1ec5},          'e' # e with circumflex and tilde

    # remove unhandled combining diacritics (some combinations are handled above)
    name.gsub! %r{[\u0300-\u036f]}, ''
  end

  if nonWord
    # deal with any remaining non-word characters
    name.strip.gsub %r{[^\w]+}, nonWord if nonWord
  else
    name
  end
end
find_by_email(value) click to toggle source

find a Person by email address

# File lib/whimsy/asf/mail.rb, line 276
def self.find_by_email(value)
  person = Mail.list[value.downcase]
  return person if person
end
find_by_name(name) click to toggle source

Get a list of ids matching a name matches against 'cn', also 'givenName' and 'sn' if the former does not match returns an array of ids; may be empty Intended for use when matching a few people individually.

# File lib/whimsy/asf/ldap.rb, line 693
def self.find_by_name(name)
  res = listids("(cn=#{name})")
  if res.empty?
    parts = name.split(' ')
    res = listids("(&(givenName=#{parts[0]})(sn=#{parts[-1]}))")
  end
  res
end
find_by_name!(name) click to toggle source

Get id matching a name, or nil matches against 'cn', also 'givenName' and 'sn' if the former does not match returns a single id, or nil if there is not a unique match Intended for use when matching a few people individually.

# File lib/whimsy/asf/ldap.rb, line 706
def self.find_by_name!(name)
  res = find_by_name(name)
  return nil unless res.size == 1
  res.first
end
group_base() click to toggle source
# File lib/whimsy/asf/ldap.rb, line 673
def self.group_base
  'ou=people,' + ASF::Group.base
end
ldap_name(name) click to toggle source

parse a name into LDAP fields

# File lib/whimsy/asf/person.rb, line 139
def self.ldap_name(name)
  words = name.gsub(',', '').split(' ')
  result = {'cn' => name}
  result['title'] = words.shift if words.first == 'Dr.' or words.first == 'Dr'
  if words.last =~ /^Ph\.D\.?/
    title = words.pop # Always pop (||= short-circuits the pop)
    result['title'] ||= title
  end
  result['generationQualifier'] = words.pop if words.last =~ SUFFIXES
  result['givenName'] = words.shift # TODO does gn allow multiple words?
  # extract surnames like van Gogh etc
  if words.size >= 3 and DOUBLE_PFX.include? words[-3..-2].join(' ')
    result['sn'] = words[-3..-1].join(' ')
    result['unused'] = words[0..-4]
  elsif words.size >= 2 and SINGLE_PFX.include? words[-2]
    result['sn'] = words[-2..-1].join(' ')
    result['unused'] = words[0..-3]
  else
    result['sn'] = words.pop
    result['unused'] = words
  end
  result
end
list(filter='uid=*') click to toggle source

Obtain a list of people known to LDAP. LDAP filters may be used to retrieve only a subset.

# File lib/whimsy/asf/ldap.rb, line 679
def self.list(filter='uid=*')
  ASF.search_one(base, filter, 'uid').flatten.map {|uid| find(uid)}
end
listids(filter='uid=*') click to toggle source

Obtain a list of people (ids) known to LDAP. LDAP filters may be used to retrieve only a subset. Result is returned as a list of ids only.

# File lib/whimsy/asf/ldap.rb, line 685
def self.listids(filter='uid=*')
  ASF.search_one(base, filter, 'uid').flatten
end
member_nominees() click to toggle source

Return a hash of nominated members. Keys are ASF::Person objects, values are the nomination text.

# File lib/whimsy/asf/nominees.rb, line 9
def self.member_nominees
  begin
    return Hash[@member_nominees.to_a] if @member_nominees
  rescue
  end

  meetings = ASF::SVN['Meetings']
  nominations = Dir[File.join(meetings, '*', 'nominated-members.txt')].max

  # ensure non-UTF-8 chars don't cause a crash
  nominations = File.read(nominations).encode("utf-8", "utf-8", :invalid => :replace).split(/^\s*---+--\s*/)
  nominations.shift(2)

  nominees = {}
  nominations.each do |nomination|

    # leading space is swallowed by the split; match against
    # <NOMINATED PERSON'S APACHE ID> <PUBLIC NAME>
    id = nomination[/\A(\S+)/, 1]
    id ||= nomination[/^\s?\w+.*<(\S+)@apache.org>/, 1]
    id ||= nomination[/^\s?\w+.*\((\S+)@apache.org\)/, 1]
    id ||= nomination[/^\s?\w+.*\(([a-z]+)\)/, 1]

    next unless id

    nominees[find(id)] = nomination
  end

  @member_nominees = WeakRef.new(nominees)
  nominees
end
member_watch_list() click to toggle source

Return a hash of individuals in the member watch list. Keys are ASF::Person objects, values are the text from potential-member-watch-list.txt..

# File lib/whimsy/asf/watch.rb, line 8
def self.member_watch_list
  return @member_watch_list if @member_watch_list

  text = File.read File.join(ASF::SVN['foundation'], 'potential-member-watch-list.txt')

  nominations = text.scan(/^\s+\*\)\s+\w.*?\n\s*(?:---|\Z)/m)

  i = 0
  member_watch_list = {}
  nominations.each do |nomination|
    id = nil
    name = nomination[/\*\)\s+(.+?)\s+(\(|\<|$)/,1]
    id ||= nomination[/\*\)\s.+?\s\((.*?)\)/,1]
    id ||= nomination[/\*\)\s.+?\s<(.*?)@apache.org>/,1]

    unless id
      id = "notinavail_#{i+=1}"
      find(id).attrs['cn'] = name
    end

    member_watch_list[find(id)] = nomination
  end

  @member_watch_list = member_watch_list
end
preload(attributes, people=[]) click to toggle source

pre-fetch a given set of attributes, for a given list of people

# File lib/whimsy/asf/ldap.rb, line 713
def self.preload(attributes, people=[])
  list = Hash.new {|hash, name| hash[name] = find(name)}

  attributes = [attributes].flatten

  if people.empty? or people.length > 1000
    filter = "(|#{attributes.map {|attribute| "(#{attribute}=*)"}.join})"
  else
    filter = "(|#{people.map {|person| "(uid=#{person.name})"}.join})"
  end

  zero = Hash[attributes.map {|attribute| [attribute, nil]}]

  data = ASF.search_one(base, filter, attributes + ['uid'])
  data = Hash[data.map! {|hash| [list[hash['uid'].first], hash]}]
  data.each {|person, hash| person.attrs.merge!(zero.merge(hash))}

  if people.empty?
    (list.values - data.keys).each do |person|
      person.attrs.merge! zero
    end
  end

  list.values
end
remove(availid) click to toggle source

remove a person from LDAP

# File lib/whimsy/asf/ldap.rb, line 986
def self.remove(availid)
  ASF::LDAP.delete("cn=#{availid},#{group_base}")
  ASF::LDAP.delete("uid=#{availid},#{base}")
end
sortable_name(name) click to toggle source

rearrange line in an order suitable for sorting

# File lib/whimsy/asf/person.rb, line 123
def self.sortable_name(name)
  name = name.split.reverse
  suffix = (name.shift if name.first =~ SUFFIXES)
  suffix += ' ' + name.shift if name.first =~ SUFFIXES
  name << name.shift
  # name << name.shift if name.first=='van'
  name.last.sub! %r{^IJ}, 'Ij'
  name.unshift(suffix) if suffix
  name.map! {|word| asciize(word)}
  name.reverse.join(' ').downcase
end
stem_DRAFT(name) click to toggle source

DRAFT return name suitable for a filename stem Should normally be applied to the legal name

# File lib/whimsy/asf/person.rb, line 166
def self.stem_DRAFT(name)
  # need to split before
  name = name.gsub(',', ' ').split(/ +/).map {|n| n.gsub(%r{^(Dr|Jr|Sr|[A-Z])\.$}, '\1')}
  asciize(name.join('-')).downcase.chomp('-')
end

Public Instance Methods

active_emails() click to toggle source

Active emails: primary email address, alt email addresses, and member email addresses.

# File lib/whimsy/asf/mail.rb, line 296
def active_emails
  (mail + alt_email + member_emails).uniq
end
all_mail() click to toggle source

All known email addresses: includes active, obsolete, and apache.org email addresses. (But don't add notinavail@apache.org)

# File lib/whimsy/asf/mail.rb, line 302
def all_mail
  (active_emails + obsolete_emails + (id == 'notinavail' ? [] : ["#{id}@apache.org"])).uniq
end
alt_email() click to toggle source

list all of the alternative emails for this person

# File lib/whimsy/asf/ldap.rb, line 794
def alt_email
  attrs['asf-altEmail'] || []
end
asf_banned?() click to toggle source

determine if the person has asf-banned: yes. If scanning a large list, consider preloading the asf-banned attributes for these people.

# File lib/whimsy/asf/ldap.rb, line 778
def asf_banned?
  # No idea what this means (yet)
  attrs['asf-banned'] == 'yes'
end
asf_committer?() click to toggle source

Is this person listed in the committers LDAP group?

# File lib/whimsy/asf/ldap.rb, line 758
def asf_committer?
  ASF::Group.new('committers').include? self
end
asf_member?() click to toggle source

Returns true if this person is listed as an ASF member in either LDAP or members.txt. Note: LDAP includes infrastructure staff members that may not be ASF Members.

# File lib/whimsy/asf/person.rb, line 206
def asf_member?
  ASF::Member.status[name] or ASF.members.include? self
end
asf_officer_or_member?() click to toggle source

Returns true if this person is listed as an ASF member in either LDAP or members.txt or this person is listed as an PMC chair in LDAP.

# File lib/whimsy/asf/person.rb, line 213
def asf_officer_or_member?
  asf_member? or ASF.pmc_chairs.include? self
end
attrs() click to toggle source

list of LDAP attributes for this person, populated lazily upon first reference.

# File lib/whimsy/asf/ldap.rb, line 747
def attrs
  @attrs ||= LazyHash.new {ASF.search_one(base, "uid=#{name}").first}
end
auth() click to toggle source

return a list of ASF authorizations that contain this individual

# File lib/whimsy/asf/auth.rb, line 62
def auth
  @auths ||= ASF::Authorization.find_by_id(name)
end
banned?() click to toggle source

determine if the person is banned. If scanning a large list, consider preloading the loginShell attributes for these people.

# File lib/whimsy/asf/ldap.rb, line 764
def banned?
  # FreeBSD uses /usr/bin/false; Ubuntu uses /bin/false
  not attrs['loginShell'] or %w(/bin/false bin/nologin bin/no-cla).any? {|a| attrs['loginShell'].first.include? a}
end
committees() click to toggle source

list of LDAP committees that this individual is a member of TODO should this be deleted? It seems to be used partly as LDAP membership and partly as PMC membership (which were originally generally the same) If the former, then it disappears. If the latter, then it needs to be derived from project_owners filtered to keep only PMCs

# File lib/whimsy/asf/ldap.rb, line 818
    def committees
      # legacy LDAP entries
      committees = []
#      committees = weakref(:committees) do
#        Committee.list("member=uid=#{name},#{base}")
#      end

      # add in projects
      # Get list of project names where the person is an owner
      projects = self.projects.select {|prj| prj.owners.include? self}.map(&:name)
      committees += ASF::Committee.pmcs.select do |pmc|
        projects.include? pmc.name
      end

      # dedup
      committees.uniq
    end
createDate() click to toggle source
# File lib/whimsy/asf/person.rb, line 189
def createDate
  createTimestamp[0..7]
end
createTimestamp() click to toggle source

determine account creation date. Notes:

  • LDAP info is not accurate for dates prior to 2009. See person/override-dates.rb

  • createTimestamp isn't loaded by default (but can either be preloaded or fetched explicitly)

# File lib/whimsy/asf/person.rb, line 182
def createTimestamp
  result = @@create_date[name]
  result ||= attrs['createTimestamp'][0] rescue nil # in case not loaded
  result ||= ASF.search_one(base, "uid=#{name}", 'createTimestamp')[0][0]
  result
end
dn() click to toggle source

Designated Name from LDAP

# File lib/whimsy/asf/ldap.rb, line 870
def dn
  "uid=#{name},#{ASF::Person.base}"
end
groups() click to toggle source

list of LDAP groups that this individual is a member of

# File lib/whimsy/asf/ldap.rb, line 856
def groups
  weakref(:groups) do
    Group.list("memberUid=#{name}")
  end
end
icla() click to toggle source

ASF::ICLA information for this person.

# File lib/whimsy/asf/icla.rb, line 248
def icla
  @icla ||= ASF::ICLA.find_by_id(name)
end
icla=(icla) click to toggle source

setter for icla, should only be used by ASF::ICLA.preload

# File lib/whimsy/asf/icla.rb, line 253
def icla=(icla)
  @icla = icla
end
icla?() click to toggle source

does this individual have an ICLA on file?

# File lib/whimsy/asf/icla.rb, line 258
def icla?
  @icla || ICLA.availids.include?(name)
end
inactive?() click to toggle source

is the login marked as inactive?

# File lib/whimsy/asf/ldap.rb, line 784
def inactive?
  nologin? || asf_banned?
end
mail() click to toggle source

primary mail addresses

# File lib/whimsy/asf/ldap.rb, line 789
def mail
  attrs['mail'] || []
end
member_emails() click to toggle source

email addresses from members.txt

# File lib/whimsy/asf/member.rb, line 240
def member_emails
  ASF::Member.emails(members_txt)
end
member_name() click to toggle source

Person's name as found in members.txt

# File lib/whimsy/asf/member.rb, line 245
def member_name
  ASF::Member.get_name(members_txt) if members_txt
end
member_nomination() click to toggle source

Return the member nomination text for this individual

# File lib/whimsy/asf/nominees.rb, line 42
def member_nomination
  @member_nomination ||= Person.member_nominees[self]
end
member_watch() click to toggle source

This person's entry in potential-member-watch-list.txt.

# File lib/whimsy/asf/watch.rb, line 35
def member_watch
  text = Person.member_watch_list[self]
  if text
    text.sub!(/\A\s*\n/,'')
    text.sub!(/\n---\Z/,'')
  end
  text
end
members_txt(full = false) click to toggle source

text entry from members.txt. If full is true, this will also include the text delimiters.

# File lib/whimsy/asf/member.rb, line 227
def members_txt(full = false)
  prefix, suffix = " *) ", "\n\n" if full
  # Is the cached text still valid?
  unless @members_time == ASF::Member.mtime
    @members_txt = nil
  end
  # cache the text and its time (may be changed by the find operation)
  @members_txt ||= ASF::Member.find_text_by_id(id)
  @members_time = ASF::Member.mtime
  "#{prefix}#{@members_txt}#{suffix}" if @members_txt
end
method_missing(name, *args) click to toggle source

Allow arbitrary LDAP attibutes to be referenced as object properties. Example: ASF::Person.find('rubys').cn. Can also be used to modify an LDAP attribute.

Calls superclass method
# File lib/whimsy/asf/ldap.rb, line 877
def method_missing(name, *args)
  if name.to_s.end_with? '=' and args.length == 1
    return modify(name.to_s[0..-2], args)
  end

  return super unless args.empty?
  result = self.attrs[name.to_s]
  return super unless result

  if result.empty?
    return nil
  else
    result.map! do |value|
      value = value.dup.force_encoding('utf-8') if value.is_a? String
      value
    end

    if result.length == 1
      result.first
    else
      result
    end
  end
end
modify(attr, value) click to toggle source

update an LDAP attribute for this person. This needs to be run either inside or after ASF::LDAP.bind.

# File lib/whimsy/asf/ldap.rb, line 904
def modify(attr, value)
  ASF::LDAP.modify(self.dn, [ASF::Base.mod_replace(attr.to_s, value)])
  attrs[attr.to_s] = value
end
nologin?() click to toggle source

determine if the person has no login. If scanning a large list, consider preloading the loginShell attributes for these people.

# File lib/whimsy/asf/ldap.rb, line 771
def nologin?
  # FreeBSD uses /usr/bin/false; Ubuntu uses /bin/false
  not attrs['loginShell'] or %w(/bin/false bin/nologin bin/no-cla).any? {|a| attrs['loginShell'].first.include? a}
end
obsolete_emails() click to toggle source

List of inactive email addresses: currently only contains the address in iclas.txt if it is not contained in the list of active email addresses.

# File lib/whimsy/asf/mail.rb, line 284
def obsolete_emails
  return @obsolete_emails if @obsolete_emails

  result = []
  if icla && active_emails.none? {|mail| mail.downcase == icla.email.downcase}
    result << icla.email
  end
  @obsolete_emails = result
end
pgp_key_fingerprints() click to toggle source

list all of the PGP key fingerprints

# File lib/whimsy/asf/ldap.rb, line 799
def pgp_key_fingerprints
  attrs['asf-pgpKeyFingerprint'] || []
end
podlings() click to toggle source

list of Podlings that this individual is a member (owner) of

# File lib/whimsy/asf/ldap.rb, line 851
def podlings
  ASF::Podling.current.select {|pod| project_owners.map(&:name).include? pod.name}
end
project_owners() click to toggle source

list of LDAP projects that this individual is an owner of - i.e. on (P)PMC

# File lib/whimsy/asf/ldap.rb, line 844
def project_owners
  weakref(:project_owners) do
    Project.list("owner=uid=#{name},#{base}")
  end
end
projects() click to toggle source

list of LDAP projects that this individual is a member of

# File lib/whimsy/asf/ldap.rb, line 837
def projects
  weakref(:projects) do
    Project.list("member=uid=#{name},#{base}")
  end
end
public_name() click to toggle source

return person's public name, searching a variety of sources, starting with iclas.txt, then LDAP, and finally the archives.

# File lib/whimsy/asf/person.rb, line 195
def public_name
  return icla.name if icla
  cn = [attrs['cn']].flatten.first
  cn.force_encoding('utf-8') if cn.respond_to? :force_encoding
  return cn if cn
  ASF.search_archive_by_id(name)
end
reload!() click to toggle source

reload all attributes from LDAP

# File lib/whimsy/asf/ldap.rb, line 752
def reload!
  @attrs = nil
  attrs
end
services() click to toggle source

list of LDAP services that this individual is a member of

# File lib/whimsy/asf/ldap.rb, line 863
def services
  weakref(:services) do
    Service.listcns("member=#{dn}")
  end
end
sortable_name() click to toggle source

return public name in a sortable order (last name first)

# File lib/whimsy/asf/person.rb, line 173
def sortable_name
  Person.sortable_name(self.public_name)
end
ssh_public_keys() click to toggle source

list all of the ssh public keys

# File lib/whimsy/asf/ldap.rb, line 804
def ssh_public_keys
  attrs['sshPublicKey'] || []
end
urls() click to toggle source

list all of the personal URLs

# File lib/whimsy/asf/ldap.rb, line 809
def urls
  attrs['asf-personalURL'] || []
end