Skip to content

Commit

Permalink
Handle withdrawal requests
Browse files Browse the repository at this point in the history
  • Loading branch information
sebbASF committed Sep 16, 2024
1 parent 9ad0b6c commit c84b906
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 51 deletions.
103 changes: 103 additions & 0 deletions lib/whimsy/asf/subreq.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#!/usr/bin/env ruby
$LOAD_PATH.unshift '/srv/whimsy/lib'

require 'wunderbar'
require 'whimsy/asf/config'
require 'whimsy/asf/svn'

module ASF
# Handle subscribe and unsubscribe requests
#
# This is a two part process (so the request can be shown to the user before committing if required)
# user = ASF::Person.new(person)
# request = ASF::Subreq.create_request(user, email, list)
# ASF::Subreq.queue_request(reqtype, request, wunderbar, options)
module Subreq
FORMAT_NUMBER = 3 # json format number

# Create the request hash
# Params:
# user: ASF::Person instance
# email: the email to unsubscribe
# list: listname@domain
# Returns: hash suitable for saving as a JSON file
def self.create_request(user, email, list)
{
version: FORMAT_NUMBER,
availid: user.id,
addr: email,
listkey: list,
member_p: user.asf_member?, # does not appear to be used
chair_p: ASF.pmc_chairs.include?(user), # does not appear to be used
}
end

# Queue the update request
# Params:
# - reqtype: 'sub'|'unsub'
# - request: hash generated by create_request
# - wbar: wunderbar context (or nil)
# - credentials: has containing credentials; may also contain options, e.g. verbose: true
def self.queue_request(reqtype, request, wbar, credentials)
raise Exception.new("Unexpected reqtype: #{reqtype}") unless %w{sub unsub}.include? reqtype

queue_url = ASF::SVN.svnpath!('subreq') # only this URL is defined
queue_url.sub! '/subreq', '/unsubreq' if reqtype == 'unsub'

# subreq/unsubreq now accept name@dom
# Keep the key for the file name
list = request[:listkey]
listkey = ASF::Mail.listdom2listkey(list)
userid = request[:availid]

content = JSON.pretty_generate(request) + "\n"

# Each user can only (un)subscribe once to each list in each timeslot
fn = "#{userid}-#{listkey}.json"

Dir.mktmpdir do |tmpdir|

if wbar
ASF::SVN.svn_!('checkout', [queue_url, tmpdir], wbar, credentials)
else
ASF::SVN.svn!('checkout', [queue_url, tmpdir], credentials)
end
path = File.join(tmpdir, fn)
if File.exist? path
File.write(path, content) # overwrite
else
File.write(path, content) # new
ASF::SVN.svn('add', path)
end

if reqtype == 'unsub'
message = "#{list} -= #{userid}"
else
message = "#{list} += #{userid}"
end

options = credentials.merge({msg: message})
if wbar
ASF::SVN.svn_!('commit', path, wbar, options)
else
ASF::SVN.svn!('commit', path, options)
end
end
return nil
end
end
end

if __FILE__ == $0
list = ARGV.shift or raise Exception.new('need list')
person = ARGV.shift or raise Exception.new('need id')
email = ARGV.shift or raise Exception.new('need email')
dryrun = ARGV.shift != 'nodryrun'
require 'whimsy/asf'
user = ASF::Person.new(person)
request = ASF::Subreq.create_request(user, email, list)
puts request
rc,err = ASF::Subreq.queue_request('unsub', request, nil, {dryrun: dryrun, verbose: true})
puts rc
p err
end
119 changes: 72 additions & 47 deletions www/roster/views/actions/memstat.json.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# get entry for @userid
require 'wunderbar'

user = ASF::Person.find(@userid)
entry = user.members_txt(true)
raise Exception.new("Unable to find member entry for #{@userid}") unless entry
Expand Down Expand Up @@ -56,55 +57,79 @@
[ASF::Member.normalize(text), extra]
end

############### INCOMPLETE #######################
# elsif @action == 'withdraw' # process withdrawal request (secretary only)
# # TODO
# # update LDAP - remove from members
# # unsubscribe from member only mailing lists:
# # - compare subscriptions against ASF::Mail.cansub
# Tempfile.create('withdraw') do |tempfile|
# ASF::SVN.multiUpdate_ members_txt, message, env, _ do |text|
# extra = []
# # remove user's entry
# unless text.sub! entry, '' # e.g. if the workspace was out of date
# raise Exception.new('Failed to remove existing entry -- try refreshing')
# end
# # save the entry to the archive
# File.write(tempfile, entry)
# tempfile.close
# extra << ['put' , tempfile.path, ASF::SVN.svnpath!('withdrawn', 'archive', "#{@userid}.txt")]

# # Find matching request for the id
# pathname, basename = ASF::WithdrawalRequestFiles.findpath(@userid, env)
# unless pathname
# raise Exception.new("Failed to find withdrawal pending file for #{@userid}")
# end

# # Move the request from pending - this should also work for directories
# extra << ['mv', pathname, ASF::SVN.svnpath!('withdrawn', basename)]
# [ASF::Member.normalize(text), extra]
# end
# ASF::WithdrawalRequestFiles.refreshnames(true, env) # update the listing if successful
# ASF::Mail.configure
# mail = Mail.new do
# from '[email protected]'
# to "#{USERNAME}<#{USERMAIL}>"
# subject "Acknowledgement of membership withdrawal from #{USERNAME}"
# text_part do
# body <<~EOD
# The membership withdrawal request that was registered for you has now been actioned.
# Your details have been removed from the membership roster.
# You have also been unsubscribed from members-only private email lists.
elsif @action == 'withdraw' # process withdrawal request (secretary only)
require 'whimsy/asf/subreq'

# TODO
# Check members.md for entry and report - how?

# unsubscribe from member only mailing lists:
all_mail = user.all_mail # their known emails
pmc_chair = ASF.pmc_chairs.include?(user)
# to which (P)PMCs do they belong?
ldap_pmcs = user.committees.map(&:mail_list)
ldap_pmcs += user.podlings.map(&:mail_list)
# What are their subscriptions?
subs = ASF::MLIST.subscriptions(all_mail)[:subscriptions]
subs += ASF::MLIST.digests(all_mail)[:digests]
# Check subscriptions for readability
subs.each do |list, email|
unless ASF::Mail.canread(list, false, pmc_chair, ldap_pmcs)
request = ASF::Subreq.create_request(user, email, list)
ASF::Subreq.queue_request('unsub', request, nil, {env: env})
end
end
# remove from LDAP member group
ASF::LDAP.bind(env.user, env.password) do
ASF::Group['member'].remove(user)
end

# Remove from members.txt
Tempfile.create('withdraw') do |tempfile|
ASF::SVN.multiUpdate_ members_txt, message, env, _, {verbose: true} do |text|
extra = []
# remove user's entry
unless text.sub! entry, '' # e.g. if the workspace was out of date
raise Exception.new('Failed to remove existing entry -- try refreshing')
end
# save the entry to the archive
File.write(tempfile, entry)
tempfile.close
extra << ['put' , tempfile.path, ASF::SVN.svnpath!('withdrawn', 'archive', "#{@userid}.txt")]

# Find matching request for the id
pathname, basename = ASF::WithdrawalRequestFiles.findpath(@userid, env)
unless pathname
raise Exception.new("Failed to find withdrawal pending file for #{@userid}")
end

# Move the request from pending - this should also work for directories
extra << ['mv', pathname, ASF::SVN.svnpath!('withdrawn', basename)]
[ASF::Member.normalize(text), extra]
end
ASF::WithdrawalRequestFiles.refreshnames(true, env) # update the listing if successful

# Send confirmation email
ASF::Mail.configure
mail = Mail.new do
from '[email protected]'
to "#{USERNAME}<#{USERMAIL}>"
subject "Acknowledgement of membership withdrawal from #{USERNAME}"
text_part do
body <<~EOD
The membership withdrawal request that was registered for you has now been actioned.
Your details have been removed from the membership roster.
You have also been unsubscribed from members-only private email lists.
# Warm Regards,
Warm Regards,
# Secretary, Apache Software Foundation
# [email protected]
# EOD
# end
# end
# mail.deliver!
# end
Secretary, Apache Software Foundation
[email protected]
EOD
end
end
mail.deliver!
end

elsif @action == 'rescind_withdrawal' # Secretary only
pathname, _basename = ASF::WithdrawalRequestFiles.findpath(@userid, env)
Expand Down
7 changes: 3 additions & 4 deletions www/roster/views/person/memstat.js.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,10 @@ def render
name: 'action', value: 'emeritus'
end
if committer.forms['withdrawal_request']
# INCOMPLETE
# _button.btn.btn_primary 'move to withdrawn',
# name: 'action', value: 'withdraw'
_button.btn.btn_primary 'process withdrawal',
name: 'action', value: 'withdraw'
_button.btn.btn_primary 'rescind withdrawal request',
name: 'action', value: 'rescind_withdrawal'
name: 'action', value: 'rescind_withdrawal'
end
end
end # end _form
Expand Down

0 comments on commit c84b906

Please sign in to comment.