www/secretary/ldap-names.cgi (273 lines of code) (raw):

#!/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' ASF::ICLAFiles.update_cache({}) _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; cn should match Public 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 _ 'If the givenName or sn does not match part or all of the public name, the cell is light grey' _br _ 'If the Public Name has only a single word, givenName is marked as strike-thru as it should perhaps be dropped' _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 mismatches = 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'] _initials = parse['initials'] givenOK = ASF::Person.names_equivalent?(new_given, given) badGiven += 1 unless givenOK snOK = (new_sn == p.sn) badSN += 1 unless snOK icla = ASF::ICLA.find_by_id(p.uid) public_name = icla.name rescue nil next unless public_name # Don't check entries not in iclas.txt cnOK = (public_name == p.cn) pnames=public_name.gsub(%r{[(),]}, '').split # drop special characters that may terminate names missingGiven = given.split.any? {|one| ! (one == p.uid or pnames.include?(one) or pnames.any? {|pn| ASF::Person.names_equivalent?(pn, one)})} missingsn = p.sn.split.any? {|one| ! (one == p.uid or pnames.include? one or pnames.any? {|pn| ASF::Person.names_equivalent?(pn, one)})} if givenOK and snOK and cnOK and ! missingGiven and ! missingsn # all checks OK matches += 1 next end if 0 == (mismatches += 1) % 25 #repeat header every 25 lines _tr bgcolor: 'lightblue' do _td 'uid' _td "ICLA file" _td "iclas.txt real name" _td "iclas.txt public name" _td 'cn' _td 'givenName' _td 'Modify to?' _td 'sn' _td 'Modify to?' _td 'Unused' end end next if givenOK and skipSN #and unused.size == 0 claRef = icla.claRef if icla claRef ||= 'unknown' legal_name = icla.legal_name rescue '?' _tr do _td do _a p.uid, href: '/roster/committer/' + p.uid end _td do file = ASF::ICLAFiles.match_claRef(claRef) if file _a claRef, href: ASF::SVN.svnpath!('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 bgcolor: missingGiven ? 'lightgrey' : 'white' do if givenOK _ given elsif new_given == '' _del given # entry should be removed 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 bgcolor: missingsn ? 'lightgrey' : 'white' 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 _tr bgcolor: 'lightblue' do _td 'uid' _td "ICLA file" _td "iclas.txt real name" _td "iclas.txt public name" _td 'cn' _td 'givenName' _td 'Modify to?' _td 'sn' _td 'Modify to?' _td 'Unused' 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