whimsical-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ru...@apache.org
Subject [whimsy] 01/11: original version of submit-account-request.rb
Date Thu, 04 Aug 2016 12:59:59 GMT
This is an automated email from the ASF dual-hosted git repository.

rubys pushed a commit to branch master
in repository https://git-dual.apache.org/repos/asf/whimsy.git

commit 3d3e1cc5318f02ca0f1df5eadca0bcbcc92d949a
Author: Sam Ruby <rubys@intertwingly.net>
AuthorDate: Wed Aug 3 15:17:31 2016 -0400

    original version of submit-account-request.rb
    
    copied from https://s.apache.org/FrTq
    
    Runs slowly on https://id.apache.org/acreq/ for reasons unknown.
    
    Uses old versions of dependencies.  And all users are updating a common
    checkout of data from svn.  Intent is to modernize the code, and in the
    process identify and fix the performance issue.
---
 www/officers/acreq.cgi | 458 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 458 insertions(+)

diff --git a/www/officers/acreq.cgi b/www/officers/acreq.cgi
new file mode 100755
index 0000000..7ee9e5e
--- /dev/null
+++ b/www/officers/acreq.cgi
@@ -0,0 +1,458 @@
+#!/usr/bin/ruby
+#
+#   Just for demo purposes at the moment.  Builds a properly formatted
+#   and validated new-account-reqs.txt entry based on web input.  With
+#   the proper browser and with jquery installed, this will do full client
+#   side validation.  Server-side validation will also be done.
+#
+#   In demo mode, this script simply shows the formatted line that would
+#   be added to the file and the email to be sent.  In non-demo mode, it
+#   actually appends the line to the file and issue a svn commit, returns
+#   the messages produced by the commit (if any) in the response, and
+#   sends an email to root (copying the relevant pmc private list) of the
+#   request.
+#
+# Prereqs:
+#
+#   * svn checkout of infra/infrastructure/trunk and foundation/officers
+#   * Web server with the ability to run cgi (Apache httpd recommended)
+#   * Ruby 1.8.x or later
+#   * cgi-spa and mail gems ([sudo] gem install cgi-spa mail)
+#   * (optional) jQuery http://code.jquery.com/jquery.min.js
+#
+# Installation instructions:
+#
+#  ruby submit-account-request.rb --install=/var/www
+#
+#    1) Specify a path that supports cgi, like public-html or Sites.
+#    2) Tailor the paths and smtp settings in the generated
+#       submit-account-request.cgi as necessary.
+#    3) Download jQuery from the link above into either the directory
+#       containing the CGI or in the DOCUMENT_ROOT for the web server
+#
+# Execution instructions:
+#
+#   Point your web browser at your generated cgi script.  For best results,
+#   use a browser that implements HTML5 form validation.
+
+$SAFE=1
+
+require 'rubygems'
+require 'cgi-spa'
+require 'mail'
+require 'date'
+require 'open3'
+require 'pathname'
+require 'rexml/document'
+require 'net/http'
+
+
+# List of unix groups that do NOT correspond to PMCs
+NON_PMC_UNIX_GROUPS = %w(
+  apsite
+  audit
+  board
+  committers
+  concom
+  db-site
+  incubator-site
+  member
+)
+
+SVN = "/usr/bin/svn"
+
+# get up to date...
+`#{SVN} cleanup #{INFRA}/acreq #{OFFICERS} #{APMAIL}/bin`
+`#{SVN} revert -R #{INFRA}/acreq`
+unless `#{SVN} status -q #{INFRA}/acreq`.empty?
+  raise "acreq/ working copy is dirty"
+end
+`#{SVN} update --ignore-externals #{INFRA}/acreq #{OFFICERS} #{APMAIL}/bin`
+
+REQUESTS = "#{INFRA}/acreq/new-account-reqs.txt"
+
+# grab the current list of PMCs from ldap
+pmcs = `/usr/local/bin/list_unix_group.pl`.chomp.split("\n") - NON_PMC_UNIX_GROUPS
+
+# grab the list of podling mailing lists from apmail
+podlings = REXML::Document.new(
+    Net::HTTP.get_response(URI.parse 'http://incubator.apache.org/podlings.xml').body
+  ).root.elements.collect { |x| x.attributes['status'] == 'current' && x.attributes['resource']
}.select { |x| x }.sort
+
+# grab the list of iclas that have no ids assigned
+query_string = CGI::parse ENV['QUERY_STRING']
+if query_string.has_key? 'fulllist'
+  iclas = Hash[*File.read("#{OFFICERS}/iclas.txt").
+    scan(/^notinavail:.*?:(.*?):(.*?):Signed CLA/).flatten.reverse]
+else
+  oldrev = \
+    `#{SVN} log --incremental -q -r HEAD:0 -l300 -- #{OFFICERS}/iclas.txt`.
+    split("\n")[-1].split()[0][1..-1].to_i
+  iclas = Hash[*`#{SVN} diff -r #{oldrev}:HEAD -- #{OFFICERS}/iclas.txt`.
+    scan(/^[+]notinavail:.*?:(.*?):(.*?):Signed CLA/).flatten.reverse]
+end
+
+# grab the list of userids that have been assigned (for validation purposes)
+taken = File.read("#{OFFICERS}/iclas.txt").scan(/^(\w+?):/).flatten.sort.uniq
+
+# add the list of userids that are pending
+taken += File.read(REQUESTS).scan(/^(\w.*?);/).flatten
+
+# add member ids that do not have ICLAs
+taken += %w(andi andrei arved dgaudet pcs rasmus ssb zeev)  
+
+# add list of ids that match ones with embedded hyphens, e.g. an-selm (INFRA-7390)
+taken += %w(an james jean rgb soc swaroop)  
+
+# add list of tokens that could be mistaken for names
+taken += %w(r rw)  
+
+# get a list of pending new account requests (by email)
+pending = File.read(REQUESTS).scan(/^\w.*?;.*?;(.*?);/).flatten
+
+# remove pending email addresses from the selection list
+pending.each {|email| iclas.delete email}
+
+# HTML output
+$cgi.html do |x| 
+  x.head do
+    x.meta :charset => 'utf-8'
+    x.title 'Submit ASF Account Request'
+
+    x.style! <<-'EOF'
+      label {width: 6em; float: left}
+      legend {background: #141; color: #DFD; padding: 0.4em}
+      fieldset {background: #EFE; width: 28em}
+      fieldset div {clear: both; padding: 0.4em 0 0 1.5em}
+      input,textarea {width: 3in}
+      select {width: 3.06in}
+      input[type=checkbox] {margin-left: 6em; width: 1em}
+      input[type=submit] {margin-top: 0.5em; margin-left: 3em; width: 8em}
+      .error {margin: 1em; padding: 1em; background: red; color: white}
+      .stdout {background-color: yellow; margin: 0}
+      .stderr {background-color: red; color: white; margin: 0}
+      pre.email {background-color: #BDF; padding: 1em 3em; border-radius: 1em}
+    EOF
+
+    ENV['SCRIPT_FILENAME'] =~ /(.*)/
+    sf = $1
+    sf.untaint
+    
+    ENV['DOCUMENT_ROOT'] =~ /(.*)/
+    dr = $1
+    dr.untaint
+    
+    if (Pathname(sf).dirname + 'jquery.min.js').exist?
+      src = 'jquery.min.js'
+    elsif (Pathname(dr) + 'jquery.min.js').exist?
+      src = '/jquery.min.js'
+    else
+     src =  'https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js'
+    end
+
+    x.script '', :src => src
+
+    scriptSrc = <<-EOF
+      $(function() {
+
+        // if name changes, change email to match
+        $('#name').change(function() {
+          $("option:selected").each(function() {
+            var email = $(this).attr('data-email');
+            if (email) $('#email').val(email);
+          });
+        });
+
+        // if email changes, change name to match
+        $('#email').change(function() {
+          $("option:selected").each(function() {
+            var name = $(this).attr('data-name');
+            if (name) $('#name').val(name);
+          });
+        });
+
+        // if user changes, validate that the id is available
+        $('#user').focus().blur(function() {
+          if ($.inArray($(this).val(),#{taken.to_json}) != -1) {
+            this.setCustomValidity('userid is not available');
+          } else {
+            this.setCustomValidity('');
+          }
+        });
+
+        // if pmc is incubator, enable podling, else disable and clear podling
+        $('#pmc').change(function() {
+          if ($(this).val() == 'incubator') {
+            $('#podling').removeAttr('disabled', 'disabled');
+          } else {
+            $('#podling').attr('disabled', 'disabled')[0].
+              selectedIndex = -1;
+          }
+        });
+
+        // allow selected fields to be set based on parameters passed
+        if (#{$param.user.to_s.inspect} != '')
+          $('#user').val(#{$param.user.to_s.inspect});
+        $('#email').val(#{$param.email.to_s.inspect}).trigger('change');
+        $('#pmc').val(#{$param.pmc.to_s.inspect}).trigger('change').
+          attr('required', 'required');
+        $('#podling').val(#{$param.podling.to_s.inspect});
+        if (#{$param.votelink.to_s.inspect} != '')
+          $('#votelink').val(#{$param.votelink.to_s.inspect});
+      });
+    EOF
+    x.script scriptSrc, :type => "text/javascript" 
+  end
+
+  x.body do
+    x.form :method=>'post' do
+      x.fieldset do
+        x.legend 'ASF New Account Request'
+
+        x.div do
+          x.label 'User ID', :for=>"user"
+          x.input :name=>"user", :id=>"user", :autofocus => "autofocus",
+            :type=>"text", :required => "required",
+            :pattern => '^[a-z][-a-z0-9_]+$' # useridvalidationpattern dup
+        end
+
+        x.div do
+          x.label 'Name', :for=>"name"
+          x.select :name=>"name", :id=>"name", :required => "required" do
+            x.option '', :value => ''
+            iclas.invert.to_a.sort.each do |name, email|
+              x.option name, :value => name, 'data-email' => email
+            end
+          end
+        end
+
+        x.div do
+          x.label 'Email', :for=>"email"
+          x.select :name=>"email", :id=>"email", :required => "required" do
+            x.option '', :value => ''
+            iclas.to_a.sort_by {|email, name| email.downcase}.
+              each do |email, name|
+              x.option email.downcase, :value => email, 'data-name' => name
+            end
+          end
+        end
+
+        x.div do
+          x.label 'PMC', :for=>"pmc"
+          x.select :name=>"pmc", :id=>"pmc" do
+            x.option '', :value => ''
+            pmcs.each do |pmc| 
+              x.option pmc, {:value => pmc}
+            end
+          end
+        end
+
+        x.div do
+          x.label 'Podling', :for=>"podling"
+          x.select :name=>"podling", :id=>"podling" do
+            x.option '', :value => ''
+            podlings.each do |podling| 
+              x.option podling, {:value => podling}
+            end
+          end
+        end
+
+        x.div do
+          x.label 'Vote Link', :for=>"votelink"
+          x.input :name=>"votelink", :id=>"votelink", :type=>"text",
+            :pattern => '.*://.*|.*@.*'
+        end
+
+        x.div do
+          x.label 'Comments', :for=>"comments"
+          x.textarea "", :name=>"comments", :id=>"comments" 
+        end
+
+        x.input :type=>"submit", :value=>"Submit"
+      end
+    end
+
+    if $HTTP_POST
+      # server side validation
+      if pending.include? $param.email
+        x.div "Account request already pending for #{$param.email}", :class => 'error'
+      elsif taken.include? $param.user
+        x.div "UserID #{$param.user} is not available", :class => 'error'
+      elsif $param.user !~ /^[a-z][a-z0-9_]+$/ # useridvalidationpattern dup (disallow '-'
in names because of INFRA-7390)
+        x.div "Invalid userID #{$param.user}", :class => 'error'
+      elsif $param.user.length > 16
+        # http://forums.freebsd.org/showthread.php?t=14636
+        x.div "UserID #{$param.user} is too long (max 16)", :class => 'error'
+      elsif $param.pmc !~ /^[0-9a-z-]+$/
+        x.div "Unsafe PMC #{$param.pmc}", :class => 'error'
+      elsif $param.podling and $param.podling !~ /^[0-9a-z-]*$/
+        x.div "Unsafe podling name #{$param.podling}", :class => 'error'
+      elsif not iclas.include? $param.email
+        x.div "No ICLA on record for #{$param.email}", :class => 'error'
+      elsif not iclas[$param.email] == $param.name
+        x.div "Name #{$param.name} does not match name on ICLA", :class => 'error'
+      elsif not pmcs.include? $param.pmc
+        x.div "Unrecognized PMC name #{$param.pmc}", :class => 'error'
+      else
+
+        # verb tense to be used in messages
+        tobe = 'to be ' if DEMO_MODE
+
+        # capture submitter information
+        ENV['REMOTE_USER'] =~ /(\w+)/
+        submitter_id = $1
+        submitter_id.untaint
+        
+        submitter_name = 
+          File.read("#{OFFICERS}/iclas.txt")[/^#{submitter_id}:.*?:(.*?):/,1]
+        submitter_name.untaint
+        
+        # build the line to be added
+        line = "#{$param.user};#{$param.name};#{$param.email};#{$param.pmc};" +
+          "#{$param.pmc};#{Date.today.strftime('%m-%d-%Y')};yes;yes;no;"
+
+        # determine the requesting party and cc_list
+        $param.pmc =~ /([\w.-]+)/
+        requestor = $1
+        requestor.untaint
+        cc_list = ["private@#{$param.pmc}.apache.org".untaint]
+        if requestor == 'incubator' and not $param.podling.empty?
+          if File.read("#{APMAIL}/bin/.archives").include? "incubator-#{$param.podling}-private"
+            cc_list << "#{$param.podling}-private@#{$param.pmc}.apache.org".untaint
+          else
+            cc_list << "private@#{$param.podling}.#{$param.pmc}.apache.org".untaint
+          end
+          requestor = "#{$param.podling}@incubator".untaint
+        end
+        cc_list << "<#{$param.email}>".untaint # TODO: add $param.name RFC822-escaped
+
+        # build the mail to be sent
+        mail = Mail.new do
+          if submitter_name
+            from  "#{submitter_name} <#{submitter_id}@apache.org>"
+          else
+            from  "#{submitter_id}@apache.org"
+          end
+          return_path "root@apache.org"
+          to      "root@apache.org"
+          cc      cc_list
+          subject "[FORM] Account Request - #{requestor}: #{$param.name}"
+
+          ENV['REMOTE_ADDR'] =~ /(\w[\w.-]+)/
+          ra = $1
+          ra.untaint
+
+          body <<-EOF.gsub(/^ {12}/, '').gsub(/(Vote reference:)?\n\s+\n/, "\n\n")
+            Prospective userid: #{$param.user}
+            Full name: #{$param.name}
+            Forwarding email address: #{$param.email}
+
+            Vote reference:
+              #{$param.votelink.gsub('mail-search.apache.org/pmc/', 'mail-search.apache.org/members/')}
+
+            #{$param.comments}
+
+            -- 
+            Submitted by https://#{ENV['HTTP_HOST']}#{ENV['REQUEST_URI'].split('?').first}
+            From #{`/usr/bin/host #{ra}`.chomp}
+            Using #{ENV['HTTP_USER_AGENT']}
+          EOF
+        end
+
+        unless DEMO_MODE
+          # deliver the email.  Done first as undeliverable mail stops
+          # the process
+          begin
+            mail.deliver!
+          rescue Exception => exception
+            x.pre exception.inspect, :class => 'error'
+            tobe = 'would have been '
+          end
+        end
+
+        unless tobe
+          # Update the new-account-reqs file...
+          requests = File.read(REQUESTS)
+          File.open(REQUESTS, 'w') do |file|
+            file.write("#{requests}#{line}\n")
+          end
+
+          # and commit the change ...
+          command = "#{SVN} commit #{INFRA}/acreq/new-account-reqs.txt -m " + 
+            "#{requestor} account request by #{submitter_id}".inspect
+          x.h2 'Commit messages'
+          Open3.popen3(command) do |pin, pout, perr|
+            [
+              Thread.new do
+                x.p pout.readline.chomp, :class=>'stdout' until pout.eof?
+              end,
+              Thread.new do
+                x.p perr.readline.chomp, :class=>'stderr' until perr.eof?
+              end,
+              Thread.new do
+                pin.close
+              end
+            ].each {|thread| thread.join}
+          end
+        end
+
+        # report on status
+        x.h2 "New entry #{tobe}added:"
+        x.pre line
+        x.h2 "Mail #{tobe}sent:"
+        x.pre mail.to_s, :class => 'email'
+      end
+    end
+
+    unless $HTTP_POST
+      x.p do
+        if query_string.has_key? 'fulllist'
+          x.span 'This page shows all ICLAs ever received.  Click here to'
+          x.a 'show only ICLAs received recently', :href => '?'
+          x.span '.'
+        else
+          x.span 'This page shows only ICLAs received recently.  Click here to'
+          x.a 'choose from the full list of ICLA submitters', :href => '?fulllist=1'
+          x.span '.'
+        end
+      end
+    end
+  end
+end
+
+__END__
+# Doesn't actually have any effect !?  The one in the .rb file has an effect.
+$SAFE = 1
+
+# tailor these lines as necessary
+INFRA = '..'
+APMAIL = '../../apmail'
+OFFICERS = '../../foundation/officers'
+
+# uncomment the next line if you have installed gems in a non-standard location
+# ENV['GEM_PATH'] = '/prefix/install-dir'
+
+require 'rubygems'
+require 'mail'
+
+# customize the delivery method
+Mail.defaults do
+  # probably will work out of the box on ASF hardware
+  delivery_method :sendmail
+
+### For comparison, here's how to connect to gmail
+# delivery_method :smtp,
+#   :address =>        "smtp.gmail.com",
+#   :port =>           587, 
+#   :domain =>         "apache.org",
+#   :authentication => "plain",
+#   :user_name =>      "username",
+#   :password =>       "password",
+#   :enable_starttls_auto => true
+end
+
+# this should be pretty self evident
+DEMO_MODE = true
+
+# potentially useful when installed on a personal machine
+# ENV['REMOTE_USER'] ||= `/usr/bin/whoami`.chomp

-- 
To stop receiving notification emails like this one, please contact
"commits@whimsical.apache.org" <commits@whimsical.apache.org>.

Mime
View raw message