blob: ce15604080b852aabced0b9d09266bc322c54ff9 [file] [log] [blame]
#!/usr/bin/env ruby
=begin
Check LDAP names: cn, sn, givenName
givenName and sn should match the output from ASF::Person.ldap_name
=end
$LOAD_PATH.unshift '/srv/whimsy/lib'
require 'whimsy/asf'
require 'wunderbar/script'
require 'ruby2js/filter/functions'
_html do
_style %{
table {border-collapse: collapse}
table, th, td {border: 1px solid black}
td {padding: 3px 6px}
tr:hover td {background-color: #FF8}
th {background-color: #a0ddf0}
}
if @updates
##################################################################
# Apply Updates #
##################################################################
_h2_ 'Applying updates'
updates = JSON.parse(@updates)
# update LDAP
unless updates.empty?
unless ASF::Service.find('asf-secretary').members.include? ASF::Person.new($USER)
print "Status: 401 Unauthorized\r\n"
print "WWW-Authenticate: Basic realm=\"Secretary\"\r\n\r\n"
exit
end
_pre 'ldapmodify', class: '_stdin'
begin
ASF::LDAP.bind($USER, $PASSWORD) do
updates.each do |id, names|
person = ASF::Person.new(id)
_pre person.dn, class: '_stdout'
_pre names.inspect, class: '_stdout'
names.each do |k,v|
begin
person.modify k,v
rescue => e
_pre "Failed: #{e}", class: '_stdout'
end
end
end
end
_pre 'Completed', class: '_stdout'
rescue => e
_pre updates.inspect, class: '_stdout'
_pre "Failed: #{e}", class: '_stdout'
end
end
else
_h1 'LDAP people name checks'
_p do
_ 'LDAP sn and givenName must match the result of ASF::Person.ldap_name'
_br
_ 'The table below show the differences, if any.'
_br
_ 'If the cn does not match the public name, the cell is light grey'
_br
_ 'The Modify? columns show suggested fixes. If the name is non-italic then the suggestion is likely correct; italicised suggestions may be wrong/unnecessary.'
_br
_ 'The suggested name is considered correct if:'
_ul do
_li 'The existing field value matches the uid (never initialised) or the cn (single name)'
_li 'The existing field is missing'
_li 'AND there are no parts of the cn unused'
end
end
_h2_ 'Instructions:'
_ul do
_li 'Double click a Modify? column to copy the contents one column to its left (cannot copy ???).'
_li 'When done, click "Commit Changes" (at the bottom of the page).'
end
skipSN = ARGV.shift == 'skipSN' # skip entries with only bad SN
# prefetch LDAP data
people = ASF::Person.preload(%w(uid cn sn givenName loginShell))
matches = 0
badGiven = 0
badSN = 0
# prefetch ICLA data
ASF::ICLA.preload
_table do
# Must agree with columnNames below
_tr do
_th 'uid'
_th "ICLA file"
_th "iclas.txt real name"
_th "iclas.txt public name"
_th 'cn'
_th 'givenName'
_th 'Modify?'
_th 'sn'
_th 'Modify?'
_th 'Unused'
end
people.sort_by(&:name).each do |p|
next if p.banned?
next if p.name == 'apldaptest'
given = p.givenName rescue '---' # some entries have not set this up
parse = ASF::Person.ldap_name(p.cn)
new_given = parse['givenName']
new_sn = parse['sn']
unused = parse['unused']
givenOK = (new_given == given)
badGiven += 1 unless givenOK
snOK = (new_sn == p.sn)
badSN += 1 unless snOK
if givenOK and snOK # all checks OK
matches += 1
next
end
next if givenOK and skipSN #and unused.size == 0
icla = ASF::ICLA.find_by_id(p.uid)
claRef = icla.claRef if icla
claRef ||= 'unknown'
legal_name = icla.legal_name rescue '?'
public_name = icla.name rescue '?'
_tr do
_td do
_a p.uid, href: '/roster/committer/' + p.uid
end
_td do
file = ASF::ICLAFiles.match_claRef(claRef.untaint)
if file
_a claRef, href: "https://svn.apache.org/repos/private/documents/iclas/#{file}"
else
_ claRef
end
end
_td legal_name
_td public_name
if p.cn == public_name
_td p.cn
else
_td bgcolor: 'lightgrey' do
_ p.cn
end
end
_td do
if givenOK
_ given
else
_em given
end
end
_td! copyAble: 'true' do
if givenOK
_ ''
else
if unused.size == 0 and (given == p.uid or given == '---' or given == p.cn)
_ new_given # likely to be correct
else
_em new_given # less likely
end
end
end
_td do
if snOK
_ p.sn
else
_em p.sn
end
end
_td! copyAble: 'true' do
if snOK
_ ''
else
if unused.size == 0 and (p.sn == p.uid or p.sn == p.cn)
_ new_sn
else
_em new_sn
end
end
end
_td unused.join(' ')
end
end
end
_p do
_ "Total: #{people.size} Matches: #{matches} GivenBad: #{badGiven} SNBad: #{badSN}"
end
####################################################################
# Form used to submit changes #
####################################################################
_form_ method: 'post' do
_input type: 'hidden', name: 'updates'
_input type: 'submit', value: 'Commit Changes', disabled: true
end
end
####################################################################
# Client side logic #
####################################################################
_script do
# enable submit button only when there are modifications
def enable_submit()
button = document.querySelector('input[type=submit]')
modified = document.querySelectorAll('td.modified')
button.disabled = (modified.length == 0)
end
Array(document.getElementsByTagName('td')).each do |td|
next unless td.getAttribute('copyAble') == 'true'
# double-click: copy to previous cell
td.addEventListener(:dblclick) do |event|
cell = event.target
cell = cell.parentNode if cell.nodeName != 'TD'
txt = cell.textContent
return if txt == '???'
row = cell.parentNode
col = cell.cellIndex
pes = row.children[col-1]
pes.innerText = txt
pes.classList.add 'modified'
enable_submit()
end
end
# capture modifications when button is pressed
document.querySelector('input[type=submit]').addEventListener(:click) do
updates = {}
# Must agree with number of columns in the main table above
columnNames = %w(uid icla_file legal_name public_name cn givenName newGiven sn newSN unused)
Array(document.querySelectorAll('td.modified')).each do |td|
id = td.parentNode.firstElementChild.textContent.strip()
updates[id] ||= {}
updates[id][columnNames[td.cellIndex]] = td.textContent
end
document.querySelector('form input').value = JSON.stringify(updates)
end
# force submit state on initial load (i.e., disable submit button)
enable_submit()
end
end