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
MINIMUM_USER_UID
NAMEHASH
SINGLE_PFX

surname prefixes

SUFFIXES

generational suffixes

VALID_ATTRS

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 783
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 1037
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']
  if nextuid
    raise ArgumentError.new("gidNumber #{gidNumber} != uidNumber #{uidNumber}") unless attrs['gidNumber'] == nextuid
  else
    nextuid = next_uidNumber
  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'] ||= '/bin/bash' # as per asfpy.ldap
  attrs['homeDirectory'] ||= File.join('/home', availid)
  attrs['host'] ||= 'home.apache.org'
  attrs['asf-sascore'] ||= '10'

  # parse name if sn has not been provided (givenName is optional)
  attrs = ASF::Person.ldap_name(attrs['cn']).merge(attrs) unless attrs['sn']

  # user is expected to use id.apache.org to set their initial password
  attrs['userPassword'] = '{CRYPT}*' # invalid password (I assume)

  # create new LDAP person
  entry = attrs.map {|key, value| mod_add(key, value)}
  ASF::LDAP.add("uid=#{availid},#{base}", entry)

  # return person object; they must use id.apache.org to reset the password
  person = ASF::Person.find(availid)
  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 = '-')
  # Should agree with asciize.js.rb
  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\u01A1]}, '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\u01B0]}, '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
dn(name) click to toggle source
# File lib/whimsy/asf/ldap.rb, line 920
def self.dn(name)
  "uid=#{name},#{ASF::Person.base}"
end
find_by_email(value) click to toggle source

find a Person by email address

# File lib/whimsy/asf/mail.rb, line 330
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 736
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 749
def self.find_by_name!(name)
  res = find_by_name(name)
  return nil unless res.size == 1
  res.first
end
ldap_name(name) click to toggle source

parse a name into LDAP fields

# File lib/whimsy/asf/person.rb, line 140
def self.ldap_name(name)
  words = name.gsub(',', '').split(' ')
  result = {'cn' => name}
  result['title'] = words.shift if words.first == 'Dr.' or words.first == 'Dr'
  result['initials'] = []
  while words.first =~ %r{^[A-Z]\.$}
    result['initials'] << words.shift
  end
  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 if words.size > 1 # don't use remaining word as it must be sn
  # TODO givenName can have multiple entries
  # 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
ldap_parse_cn_DRAFT(cn, familyFirst) click to toggle source

extract sn and givenName from cn (needed for LDAP entries) returns sn, [givenName,…] Note that givenName is returned as an array (may be empty). This is because givenName is an optional attribute which may appear multiple times. It remains to be seen whether we want to create multiple attributes, or whether it is more appropriate to add at most one attribute containing all the givenName values. [The array can be joined to produce a single value]. DRAFT version: not for general use yet Does not handle multi-word family names or honorifics etc

# File lib/whimsy/asf/person.rb, line 178
def self.ldap_parse_cn_DRAFT(cn, familyFirst)
  words = cn.split(' ')
  if familyFirst
    sn = words.shift
  else
    sn = words.pop
  end
  return sn, words
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 722
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 728
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
names_equivalent?(one, two) click to toggle source
# File lib/whimsy/asf/person.rb, line 216
def self.names_equivalent?(one, two)
  # index cannot be -1, cannot match if missing entries are set to -1 or nil
  return one == two ||
    (NAMEHASH[one] || -1) == NAMEHASH[two] ||
    one.start_with?("#{two} ") || two.start_with?("#{one} ") ||
    (NAMEHASH[one.split(' ').first] || -1) == NAMEHASH[two.split(' ').first]
end
next_uidNumber(count=1) click to toggle source

Optionally return several free values as an array

# File lib/whimsy/asf/ldap.rb, line 1007
def self.next_uidNumber(count=1)
  raise ArgumentError.new "Count: #{count} is less than 1!" if count < 1
  numbers = ASF.search_one(ASF::Person.base, 'uid=*', ['uidNumber', 'gidNumber']).
    map{|i| u=i['uidNumber'];g=i['gidNumber']; u == g ? u : [u,g]}.flatten.map(&:to_i).
    select{|i| i >= MINIMUM_USER_UID}.uniq.sort.lazy
  enum = Enumerator.new do |output|
    last = MINIMUM_USER_UID - 1 # in case no valid entries exist
    loop do
        curr = numbers.next
        if curr <= last + 1
          last = curr
        else
            (last+1..curr-1).each {|i| output << i}
            last = curr
        end
    end
    # in case we ran off the end...
    loop do
      last = last+1
      output << last
    end
  end
  if (count == 1)
    enum.first
  else
    enum.take(count)
  end
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 756
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 1089
def self.remove(availid)
  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 124
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 227
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 353
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 359
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 843
def alt_email
  attrs['asf-altEmail'] || []
end
arg_empty?(arg) click to toggle source

is argument an empty string on its own or in a singleton array?

# File lib/whimsy/asf/ldap.rb, line 986
def arg_empty?(arg)
  return arg.empty? || (arg.is_a?(Array) && arg.size == 1 && arg.first.empty?)
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 827
def asf_banned?
  # No idea what this means (yet)
  attrs['asf-banned']&.first == 'yes'
end
asf_chair_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 274
def asf_chair_or_member?
  asf_member? or ASF.pmc_chairs.include? self
end
asf_committer?() click to toggle source

Is this person listed in the committers LDAP group?

# File lib/whimsy/asf/ldap.rb, line 807
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 267
def asf_member?
  ASF::Member.status[name] or ASF.members.include? self
end
asf_member_status() click to toggle source

Returns ASF membership status according to members.txt: :current if this person is listed as an ASF member :emeritus if this person is listed as an Emeritus ASF member :deceased if this person is listed as a Deceased ASF member nil otherwise (i.e. does not appear in members.txt)

# File lib/whimsy/asf/person.rb, line 283
def asf_member_status
  ASF::Member.member_status name
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 796
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 64
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 813
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 867
def committees
  # legacy LDAP entries
  committees = []

  # 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 250
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 243
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 916
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 902
def groups
  weakref(:groups) do
    Group.list("memberUid=#{name}")
  end
end
hasLDAP?() click to toggle source

override the base version which does not work as it relies on search by ‘cn’ == id

# File lib/whimsy/asf/ldap.rb, line 790
def hasLDAP?
  !attrs['dn'].nil?
end
icla() click to toggle source

ASF::ICLA information for this person.

# File lib/whimsy/asf/icla.rb, line 311
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 316
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 321
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 833
def inactive?
  nologin? || asf_banned?
end
mail() click to toggle source

primary mail addresses

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

email addresses from members.txt

# File lib/whimsy/asf/member.rb, line 378
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 383
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 365
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 attributes 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 959
def method_missing(name, *args)
  sname = name.to_s
  if sname.end_with? '=' and args.length == 1
    return modify(name[0..-2], args)
  end

  return super unless args.empty?
  return super unless VALID_ATTRS.include? sname
  result = self.attrs[sname]

  if result.nil? || 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 992
def modify(attr, value)
  # OK to remove the attribute? Only support givenName for now...
  if attr == 'givenName' and arg_empty?(value)
    ASF::LDAP.modify(self.dn, [ASF::Base.mod_delete(attr.to_s, nil)])
    attrs.delete(attr.to_s) # remove the cached entry
  else
    ASF::LDAP.modify(self.dn, [ASF::Base.mod_replace(attr.to_s, value)])
    # attributes are expected to be arrays
    attrs[attr.to_s] = value.is_a?(String) ? [value] : value
  end
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 820
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 338
def obsolete_emails
  return @obsolete_emails if @obsolete_emails

  result = []
  if icla
    icla.emails.each do |email|
      active_emails.none? {|mail| mail.downcase == email.downcase}
      result << email
    end
  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 848
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 897
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 890
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 883
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 256
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 801
def reload!
  @attrs = nil
  attrs
end
secretary?() click to toggle source

Is this person in the secretary team?

# File lib/whimsy/asf/person.rb, line 288
def secretary?
  ASF::Service.find('asf-secretary').members.include? self
end
services() click to toggle source

list of LDAP services that this individual is a member of

# File lib/whimsy/asf/ldap.rb, line 909
def services
  weakref(:services) do
    Service.listcns("(|(member=#{dn})(memberUid=#{name}))")
  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 234
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 853
def ssh_public_keys
  attrs['sshPublicKey'] || []
end
treasurer?() click to toggle source

Is this person in the treasurer team?

# File lib/whimsy/asf/person.rb, line 293
def treasurer?
  ASF::AuthGroup.find('treasurer').members.include? self
end
urls() click to toggle source

list all of the personal URLs

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