class ASF::Mail
Convenience functions related to emails or mailing lists.
Public Class Methods
Source
# File lib/whimsy/asf/mail.rb, line 278 def self._autosubid(dom, list) return "#{list}@#{dom}" end
Convert dom, list to form used in mail_list_autosub.yml This changed in March 2022 from dom-list format
Source
# File lib/whimsy/asf/mail.rb, line 99 def self._cannot_sub self._load_auto() @auto[:disallowed] + @auto[:intrinsic] end
Source
# File lib/whimsy/asf/mail.rb, line 104 def self._cannot_unsub self._load_auto() @auto[:intrinsic] end
Source
# File lib/whimsy/asf/mail.rb, line 114 def self._chairs_allowed self._load_auto() @auto[:chairs] end
Source
# File lib/whimsy/asf/mail.rb, line 109 def self._committers_allowed self._load_auto() @auto[:committers] end
Source
# File lib/whimsy/asf/mail.rb, line 94 def self._deprecated self._load_auto() @auto[:deprecated] end
list of mailing lists that arenβt actively seeking new subscribers
Source
# File lib/whimsy/asf/mail.rb, line 315 def self._load_auto apmail_bin = File.join(ASF::Config[:puppet_data], 'apmail_bin') # Loaded by puppet auto_file = File.join(apmail_bin, 'mail_list_autosub.yml') auto_mtime = File.mtime(auto_file) # fetch this up front in case file updated during loading if not @auto or auto_mtime != @auto_mtime @auto = YAML.load_file(auto_file) @auto_mtime = auto_mtime end end
Load the auto-subscription file
Source
# File lib/whimsy/asf/mail.rb, line 326 def self._load_flags # flags for each mailing list @list_flags ||= File.join(ASF::Config[:subscriptions], 'list-flags') if not @flags or File.mtime(@list_flags) != @flags_mtime lists = [] File.open(@list_flags).each do |line| if line.match(/(?:^F:-([a-zA-Z]{26}) )?(\S+) (\S+)/) flags, dom, list = $1, $2, $3 next if list =~ /^infra-[a-z]$/ or (dom == 'incubator' and list == 'infra-dev') lists << [dom, list, flags || ''] else raise "Unexpected flags: #{line}" end end @flags = lists @flags_hash = lists.map{|d,l,f| ["#{l}@#{d}",f]}.to_h @flags_mtime = File.mtime(@list_flags) end end
Load the flags file
Source
# File lib/whimsy/asf/mail.rb, line 119 def self._members_allowed self._load_auto() @auto[:members] + @auto[:chairs] end
Source
# File lib/whimsy/asf/mail.rb, line 269 def self.archivelistid(dom, list) return "apachecon-#{list}" if dom == 'apachecon.com' return list if dom == 'apache.org' dom.sub('.apache.org', '-') + list end
Convert dom, list to form currently used in subreq.py
Source
# File lib/whimsy/asf/mail.rb, line 203 def self.canmod(ldap_pmcs, lidonly = true) allowed = [] parse_flags do |dom, list, _| autoid = _autosubid(dom, list) next if self._deprecated.include? autoid next if self._cannot_sub.include? autoid lid = archivelistid(dom, list) if ldap_pmcs.include? dom.sub('.apache.org', '') if lidonly allowed << lid else allowed << [dom, list, lid] end end end allowed end
which lists are available for automatic moderation via Whimsy? Params: ldap_pmcs: list of (P)PMC mail_list names to which the user belongs (as owner) lid_only: return lid instead of [dom,list,lid] Return: an array of entries: lid or [dom,list,lid]
Source
# File lib/whimsy/asf/mail.rb, line 142 def self.canread(listid, member=false, pmc_chair=false, ldap_pmcs=[]) flags = getflags(listid) return nil if flags.nil? # Not a known list return true unless isModSub?(flags) # subscription not needed return true if self._cannot_unsub.include? listid # must be system maintained, so assume OK return true if self._committers_allowed().include?(listid) return true if member # They can read anything return true if pmc_chair and self._chairs_allowed.include? listid return true if ldap_pmcs and ldap_pmcs.include? listid.split('@')[-1].sub('.apache.org', '') false end
Can a list be read by the person with the specified attributes? Note: this is different from cansub - not all readable lists can be self-subscribed
Params: listid: list@domain member: true if member pmc_chair: true if pmc_chair ldap_pmcs: list of (P)PMC mail_list names to which the user belongs Return true if person is allowed to read the list nil if the list is not known otherwise true
Source
# File lib/whimsy/asf/mail.rb, line 161 def self.cansub(member, pmc_chair, ldap_pmcs, lidonly = true) allowed = [] parse_flags do |dom, list, f| autoid = _autosubid(dom, list) next if self._deprecated.include? autoid next if self._cannot_sub.include? autoid lid = archivelistid(dom, list) cansub = false modsub = isModSub?(f) if not modsub # subs not moderated; allow all cansub = true elsif self._committers_allowed().include?(autoid) # always allowed cansub = true else # subs are moderated if member if list == 'private' or self._members_allowed.include?(autoid) cansub = true end elsif ldap_pmcs and list == 'private' and ldap_pmcs.include? dom.sub('.apache.org', '') cansub = true end if pmc_chair and self._chairs_allowed.include? autoid cansub = true end end if cansub if lidonly allowed << lid else allowed << [dom, list, lid] end end end allowed end
which lists are available for subscription via Whimsy? Params: member: true if member pmc_chair: true if pmc_chair ldap_pmcs: list of (P)PMC mail_list names to which the user belongs lid_only: return lid instead of [dom,list,lid] Return: an array of entries: lid or [dom,list,lid]
Source
# File lib/whimsy/asf/mail.rb, line 226 def self.configure # fetch overrides sendmail = ASF::Config.get(:sendmail) if sendmail # convert string keys to symbols options = Hash[sendmail.map {|key, value| [key.to_sym, value]}] # extract delivery method method = options.delete(:delivery_method).to_sym else # provide defaults that work on whimsy-vm* infrastructure. Since # procmail is configured with a self-signed certificate, verification # isn't a possibility method = :smtp options = {openssl_verify_mode: 'none'} end ::Mail.defaults do delivery_method method, options end end
common configuration for sending mail; loads :sendmail configuration from ~/.whimsy if available; otherwise default to disable openssl verification as that is what it required in order to work on the infrastructure provided whimsy-vm.
Source
# File lib/whimsy/asf/mail.rb, line 363 def self.getflags(listid) self._load_flags() @flags_hash[listid] end
get flags for list@domain; nil if not found
Source
# File lib/whimsy/asf/mail.rb, line 369 def self.isModSub?(flags) flags.include? 's' end
Do the flags indicate subscription moderation?
Source
# File lib/whimsy/asf/mail.rb, line 9 def self.listall # Note: no point currently caching the list in memory, as this method is only used by cgi scripts list = Hash.new{|h,k| h[k] = Set.new} # this creates a default Proc, which we remove later # load info from LDAP people = ASF::Person.preload(['mail', 'asf-altEmail', 'cn']) people.each do |person| (person.mail + person.alt_email).each do |mail| list[mail.downcase] << person # there may be more that one person associated with a single email end end # load all member emails in one pass ASF::Member.each do |id, text| Member.emails(text).each do |mail| list[mail.downcase] << Person.find(id) # this should find one of the pre-loads end end # load all ICLA emails in one pass ASF::ICLA.each do |icla| person = Person.find(icla.id) icla.emails.each do |email| list[email.downcase] << person end next if icla.noId? list["#{icla.id.downcase}@apache.org"] << person # is this needed? end list.default_proc = nil # no longer create an empty set when a key is missing list end
return a hash containing down-cased email names, together with a Set of people with whom they are associated. In almost all cases there is only a single associated person. The emails are sourced from LDAP, and optionally members.txt and iclas.txt
Source
# File lib/whimsy/asf/mail.rb, line 263 def self.listdom2listkey(listdom) list, dom = listdom.split('@') self.archivelistid(dom, list) end
Convert list@host.apache.org to host-list (listkey) style
Source
# File lib/whimsy/asf/mail.rb, line 353 def self.parse_flags(filter=nil) self._load_flags() @flags.each do |d, l, f| next if filter and f !~ filter yield [d, l, f] end end
parse the flags F:-aBcdeFgHiJklMnOpqrSTUVWXYz domain list Input: filter = RE to match against the flags, e.g. /s/ for subsmod Output: yields: domain, list, flags
Source
# File lib/whimsy/asf/mail.rb, line 55 def self.people_mails member_statuses = ASF::Member.member_statuses # cache the statuses. TODO: should be done in ASF.Member people = ASF::Person.preload(['cn', 'mail','asf-altEmail']) result = people.sort_by(&:id).map do |person| id = person.id mails = person.mail + person.alt_email + ["#{id}@apache.org"] entry = { id: id, name: person.public_name, mail: mails.map{|m| m.downcase}.uniq, } entry[:member] = true if member_statuses[id] entry end result end
return a list of people ids, their public-name, whether they are an ASF member, and email addresses
Source
# File lib/whimsy/asf/mail.rb, line 75 def self.people_mails_github(extra_mails) member_statuses = ASF::Member.member_statuses # cache the statuses. TODO: should be done in ASF.Member people = ASF::Person.preload(['cn', 'mail','asf-altEmail','githubUsername', 'asf-githubStringID']) result = people.sort_by(&:id).map do |person| id = person.id mails = person.mail + person.alt_email + extra_mails.fetch(id, []) + ["#{id}@apache.org"] { id: id, name: person.public_name, mail: mails.map{|m| m.downcase}.uniq, githubUsername: person.attrs['githubUsername'] || [], asf_githubStringID: person.attrs['asf-githubStringID']&.first || '', asf_member_status: member_statuses[person.id], } end result end
return a list of people ids, their public-name, whether they are an ASF member, and email addresses Also return GH settings @param extra_mails additional emails derived from members and iclas
Source
# File lib/whimsy/asf/mail.rb, line 250 def self.qmail_ids return [] unless File.exist? '/srv/subscriptions/qmail.ids' File.read('/srv/subscriptions/qmail.ids').split end
List of .qmail files that could clash with user ids (See: INFRA-14566)
Source
# File lib/whimsy/asf/mail.rb, line 48 def self.remove_email_suffix(email) # TODO: may need to tighten this to avoid ambiguities # e.g. selectively apply '-xxx' or '+xxx' removal, depending on domain email.downcase.sub(/[-+]\w+@/,'@') # drop suffix end
Allow for trailing +- suffix in emails. e.g. username-test@apache.org => username@apache.org username+test@gmail.com => username@gmail.com
Source
# File lib/whimsy/asf/mail.rb, line 258 def self.taken?(id) self.qmail_ids.include? id end
Is the id used by qmail? See also ASF::ICLA.taken?
Source
# File lib/whimsy/asf/mail.rb, line 291 def self.to_canonical(email) parts = email.split('@') if parts.length == 2 name, dom = parts return email if name.empty? || dom.empty? dom.downcase! dom = 'gmail.com' if dom == 'googlemail.com' # same mailbox if dom == 'gmail.com' return name.sub(/\+.*/, '').gsub('.', '').downcase + '@' + dom else # Effectively the same: dom = 'apache.org' if dom == 'minotaur.apache.org' # only downcase the domain (done above) return name + '@' + dom end end # Invalid; return input rather than failing return email end
Canonicalise an email address, removing aliases and ignored punctuation and downcasing the name if safe to do so
Currently only handles aliases for @gmail.com and @googlemail.com
All domains are converted to lower-case
The case of the name part is preserved since some providers may be case-sensitive Almost all providers ignore case in names, however that is not guaranteed
Source
# File lib/whimsy/asf/mail.rb, line 127 def self.unsubbable?(listid) !self._cannot_unsub.include? listid end
Is a list available for unsubscription via Whimsy? Params: listid (a@b) Return: true or false