├── .asf.yaml ├── .github └── workflows │ ├── linting.yml │ └── type-tests.yml ├── .gitignore ├── LICENSE ├── NOTICE ├── README.md ├── doap_steve.rdf ├── monitoring ├── README.txt ├── monitoring-check.pl ├── nstv-rank.py ├── stv_tool.py ├── yna-recompute-summary │ ├── README │ ├── count.pl │ ├── display-dups.pl │ ├── driver.sh │ └── generate-issues-list.sh └── yna-summary.pl ├── pelicanconf.yaml ├── pylintrc ├── pysteve ├── GETTING_STARTED.txt ├── LICENSE ├── NOTICE ├── README.md ├── REST_API_README.txt ├── cli │ ├── load_election.py │ ├── mkelection.py │ ├── setup.py │ └── tally.py ├── docker │ └── Dockerfile ├── htpasswd-example ├── httpd.conf ├── lib │ ├── __init__.py │ ├── backends │ │ ├── __init__.py │ │ ├── es.py │ │ ├── files.py │ │ └── sqlite.py │ ├── constants.py │ ├── election.py │ ├── form.py │ ├── gateway.py │ ├── plugins │ │ ├── __init__.py │ │ ├── ap.py │ │ ├── cop.py │ │ ├── dh.py │ │ ├── fic.py │ │ ├── fpp.py │ │ ├── mntv.py │ │ ├── stv.py │ │ └── yna.py │ ├── response.py │ └── voter.py ├── pyproject.toml ├── standalone.py ├── steve.cfg ├── tests │ └── test.py └── www │ ├── cgi-bin │ ├── rest_admin.py │ └── rest_voter.py │ ├── htdocs │ ├── admin │ │ ├── add_issue.html │ │ ├── close.html │ │ ├── create_election.html │ │ ├── edit_basedata.html │ │ ├── edit_election.html │ │ ├── edit_issue.html │ │ ├── index.html │ │ ├── invite.html │ │ ├── reopen.html │ │ └── tally.html │ ├── ballot_ap.html │ ├── ballot_cop.html │ ├── ballot_dh.html │ ├── ballot_fic.html │ ├── ballot_fpp.html │ ├── ballot_mntv.html │ ├── ballot_stv.html │ ├── ballot_yna.html │ ├── bulk_yna.html │ ├── css │ │ ├── images │ │ │ └── ui-icons_454545_256x240.png │ │ ├── jquery-ui.css │ │ └── steve_interactive.css │ ├── election.html │ ├── favicon.ico │ ├── images │ │ ├── ballot_bg.png │ │ ├── dragleft.png │ │ ├── dragright.png │ │ ├── icon_add.png │ │ ├── icon_close.png │ │ ├── icon_delete.png │ │ ├── icon_edit.png │ │ ├── icon_invite.png │ │ ├── icon_view.png │ │ ├── steve_large.png │ │ ├── steve_logo.png │ │ ├── steve_spinner.gif │ │ ├── target.png │ │ ├── vote_a.png │ │ ├── vote_n.png │ │ └── vote_y.png │ ├── index.html │ ├── js │ │ ├── jquery-ui.js │ │ ├── jquery.js │ │ ├── steve_ap.js │ │ ├── steve_cop.js │ │ ├── steve_dh.js │ │ ├── steve_monitor.js │ │ ├── steve_rest.js │ │ └── steve_stv.js │ ├── monitor.html │ └── request_link.html │ └── wsgi │ ├── rest_admin.py │ └── rest_voter.py ├── requirements.txt ├── setup.py ├── site ├── css │ └── steve.css ├── images │ ├── front-splash.png │ ├── logo-bright.png │ ├── logo.svg │ ├── steve.png │ └── tat.png ├── js │ └── empty ├── pages │ ├── community.md │ ├── demo.md │ ├── dev │ │ ├── index.html │ │ └── source-code.mdtext │ ├── documentation.md │ ├── downloads.md │ ├── index.md │ ├── privacy-policy.md │ └── vote_types.md ├── test-build.sh └── theme │ └── templates │ ├── index.html │ └── page.html ├── steve_logo.psd ├── stv9_template.txt ├── stv_background ├── meekm.pdf └── stvpas.pas ├── v3 ├── README.md ├── bin │ └── .placeholder ├── queries.yaml ├── requirements.txt ├── schema.sql ├── server │ ├── main.py │ ├── static │ │ └── .placeholder │ └── templates │ │ └── .placeholder ├── steve │ ├── __init__.py │ ├── crypto.py │ ├── db.py │ ├── election.py │ └── vtypes │ │ ├── __init__.py │ │ ├── stv.py │ │ └── yna.py └── test │ ├── check_coverage.py │ ├── populate_v2_stv.sh │ └── run_stv.py ├── whatif.py ├── whatif.rb └── www ├── cgi-bin ├── cast-vote.pl └── redirect.pl ├── conf ├── httpd.conf ├── magic └── mime.types └── htdocs ├── cast-style.css ├── favicon.ico ├── images ├── ballot_bg.png ├── dragleft.png ├── dragright.png └── target.png ├── index.html ├── steve_interactive.css └── steve_interactive.js /.asf.yaml: -------------------------------------------------------------------------------- 1 | # set some values on github.com/apache/steve 2 | github: 3 | description: "Apache STeVe -- a set of voting tools" 4 | homepage: https://steve.apache.org 5 | labels: 6 | - python 7 | - voting 8 | - stv 9 | features: 10 | issues: true 11 | 12 | # We will publish our site using Pelican, via the asf-site branch 13 | pelican: 14 | whoami: trunk 15 | target: asf-site 16 | -------------------------------------------------------------------------------- /.github/workflows/linting.yml: -------------------------------------------------------------------------------- 1 | name: Linting 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**/*.py' 7 | - '**/linting.yml' 8 | - 'pylintrc' 9 | - 'requirements.txt' 10 | 11 | pull_request: 12 | 13 | workflow_dispatch: 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | test: 20 | runs-on: ubuntu-latest 21 | strategy: 22 | fail-fast: false 23 | max-parallel: 1 24 | matrix: 25 | python-version: [3.12] 26 | steps: 27 | - uses: actions/checkout@master 28 | with: 29 | persist-credentials: false 30 | - name: Set up Python ${{ matrix.python-version }} 31 | uses: actions/setup-python@v5 32 | with: 33 | python-version: ${{ matrix.python-version }} 34 | - name: Install dependencies 35 | run: | 36 | python -m pip install -r requirements.txt 37 | python -m pip install pylint 38 | - name: Testing with pylint 39 | run: | 40 | pylint pysteve -d W0311 # enable later 41 | -------------------------------------------------------------------------------- /.github/workflows/type-tests.yml: -------------------------------------------------------------------------------- 1 | name: Type Tests 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**/type-tests.yml' 7 | - '**/*.py' 8 | - 'requirements.txt' 9 | 10 | pull_request: 11 | 12 | workflow_dispatch: 13 | 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | max-parallel: 1 20 | matrix: 21 | python-version: [3.12] 22 | steps: 23 | - uses: actions/checkout@master 24 | with: 25 | persist-credentials: false 26 | - name: Set up Python ${{ matrix.python-version }} 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | - name: Install dependencies 31 | run: | 32 | python -m pip install -r requirements.txt 33 | python -m pip install mypy 34 | python3 -m pip install types-PyYAML 35 | python3 -m pip install types-requests 36 | - name: Type testing with mypy 37 | run: | 38 | # exclude duplicate module names under wsgi; also standalone.py print syntax error as they cause failure 39 | # Need to re-enable var-annotated later 40 | mypy --cache-dir /tmp/ --ignore-missing-imports \ 41 | --exclude pysteve/www/wsgi --exclude pysteve/standalone.py \ 42 | --disable-error-code var-annotated \ 43 | pysteve 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | *.komodo* 7 | *.kpf 8 | *.log 9 | *.pid 10 | *.pyc 11 | *.swp 12 | *.*.swp 13 | .*.*.swo 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | docs/_build 18 | dropin.cache 19 | eggs/ 20 | include/ 21 | /lib/ 22 | man/ 23 | parts/ 24 | share/ 25 | .coverage 26 | .idea 27 | .pip_cache 28 | .project 29 | .pydevproject 30 | .tox 31 | /.settings 32 | /.metadata 33 | .Python 34 | 35 | # Installer logs 36 | pip-log.txt 37 | 38 | # Unit test / coverage reports 39 | .coverage 40 | .tox 41 | 42 | #Translations 43 | *.mo 44 | 45 | #Mr Developer 46 | .mr.developer.cfg 47 | 48 | # intellij project files 49 | *.ipr 50 | *.iml 51 | *.iws 52 | .idea 53 | # all target dirs 54 | target/ 55 | output/ 56 | .DS_Store 57 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Apache Steve 2 | Copyright 2012-2015 The Apache Software Foundation 3 | 4 | This product includes software developed at 5 | The Apache Software Foundation (http://www.apache.org/). 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Apache Steve 2 | 3 | Apache Steve is software to conduct a vote using the STV (Single 4 | Transferrable Vote) and other voting algorithms. The tool grew out of the voting 5 | system used to elect the Apache Software Foundation Board of 6 | Directors. 7 | 8 | Read more about STV at 9 | http://en.wikipedia.org/wiki/Single_transferable_vote 10 | 11 | ## Getting Started 12 | 13 | Getting Started documentation can be found at: http://steve.apache.org/demo.html 14 | Otherwise, come to the #steve channel on the Slack workspace at the-asf.slack.com, 15 | and we'd be glad to help you. 16 | 17 | 18 | ## Documentation 19 | 20 | Documentation may be found at http://steve.apache.org/documentation.html 21 | 22 | Contributions to the documentation are very welcome. 23 | 24 | ## Mailing Lists 25 | 26 | Discussion about Steve takes place on the following mailing lists: 27 | 28 | dev@steve.apache.org - About using Steve and developing Steve 29 | 30 | Notification on all code changes are sent to the following mailing list: 31 | 32 | commits@steve.apache.org 33 | 34 | The mailing lists are open to anyone and publicly archived. 35 | 36 | You can subscribe the mailing lists by sending a message to 37 | -subscribe@steve.apache.org (for example 38 | dev-subscribe@steve...). To unsubscribe, send a message to 39 | -unsubscribe@steve.apache.org. For more instructions, send a 40 | message to -help@steve.apache.org. 41 | 42 | Additional mailing list details may be found at 43 | http://steve.apache.org/support.html 44 | 45 | ## Issue Tracker 46 | 47 | If you encounter errors in Steve or want to suggest an improvement or a new 48 | feature, please visit the Steve issue tracker at 49 | https://issues.apache.org/jira/browse/STEVE 50 | 51 | ## Implemented changes 52 | 53 | Implemented changes can be found at: 54 | https://github.apache.org/apache/steve/ 55 | -------------------------------------------------------------------------------- /doap_steve.rdf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 24 | 25 | 2014-06-13 26 | 27 | Apache Steve 28 | 29 | 30 | STV Voting Tools 31 | Apache STeVe is a collection of online voting tools, used by the ASF, to handle STV and other voting methods. 32 | 33 | 34 | 35 | Perl 36 | Python 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | Jim Jagielski 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /monitoring/README.txt: -------------------------------------------------------------------------------- 1 | Included are some helpful tools/scripts to make the 2 | vote monitor's job easier. 3 | 4 | When votes are closed, each monitor will get, via EMail, 5 | a final tally (list) of all votes cast, with hash ID and 6 | timestamp. The list is time-ordered as well, so the later- 7 | cast ballots are closer to the bottom of the tally. 8 | 9 | For STV, we use voter/stv_tool.py to process the vote results: 10 | 11 | ./stv_tool.py raw_votes.txt 12 | 13 | where `raw_votes.txt` is the emailed set of votes. For example, 14 | see Meetings/.../raw_board_votes.txt. Lines other than votes 15 | are ignored, and the votes are assumed to be time-ordered to 16 | ensure that only the latest vote by each voter is considered. 17 | 18 | We also support OpenSTV (www.openstv.org) or the deprecated VoteMain 19 | (http://sourceforge.net/projects/votesystem) systems. Most modern 20 | tools use the blt format. 21 | 22 | Simply feed as input the STV-tally email (typically used for the board 23 | elections) and direct the output to 'outputFile' (or whatever 24 | you'd like): 25 | 26 | ./nstv-rank.py raw_votes.txt > outputFile 27 | 28 | After installing Voting Systems Toolbox, you can execute the 'VoteMain' 29 | program as 30 | 31 | java -cp Vote-0-4.jar VoteMain -system stv-meek -seats 9 outputFile 32 | or 33 | java -cp Quick_STV_1_2.jar VoteMain -system stv-meek -seats 9 outputFile 34 | 35 | where outputFile is the output of nstv-rank.py above. 36 | 37 | Using blt-oriented STV tools, such as OpenSTV: 38 | 39 | ./nstv-rank.py -b raw_votes.txt > outputFile.blt 40 | 41 | and load in the blt file to OpenSTV. Please note the ASF uses Meek STV with: 42 | 43 | Precision: 6 44 | Threshold: Droop | Dynamic | Fractional 45 | 46 | ---- 47 | 48 | For simple YNA votes (Yes / No / Abstain), we have 49 | 50 | yna-summary.pl 51 | 52 | which does the checks for you. 53 | 54 | This script is smart enough that you can actually concat 55 | *all* the final vote tallies for yna elections into one big 56 | file, and it will pull out the issue name and the results 57 | for each issue. 58 | 59 | ./yna-summary.pl all30tally.txt 60 | 61 | yna-summary.pl will only honor the most recent vote cast by each voter. 62 | 63 | 64 | ---- 65 | 66 | Also see monitoring-check.pl to ensure the incoming votes are from 67 | legitimate voters. 68 | -------------------------------------------------------------------------------- /monitoring/monitoring-check.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # ============================================================ 3 | # SCRIPT NAME: check 4 | # 5 | # USAGE: check authorizedVotersFile actualVotersFile 6 | # 7 | # Checks whether the elements listed in 'actualVotersFile' are 8 | # contained in authorizedVotersFile 9 | # 10 | # Both files are expected to contain the hash of voters 11 | # 12 | 13 | open(AUTH, "$ARGV[0]"); 14 | 15 | # Have @auth contain the valid voters 16 | @auth = (); 17 | while() { 18 | #chop $_; 19 | push(@auth, $_); 20 | #print $_; 21 | } 22 | 23 | # Now check against the votes 24 | 25 | open(VOTES, "$ARGV[1]"); 26 | 27 | while() { 28 | 29 | $result = isInAuth($_); 30 | unless($result) { 31 | print "Voter [$_] is not in list of valid voters"; 32 | } 33 | } 34 | 35 | # =================================== 36 | sub isInAuth() { 37 | $voter = $_[0]; 38 | 39 | foreach $v (@auth) { 40 | if($voter =~ $v) { 41 | return 1; 42 | } 43 | } 44 | return 0; 45 | } 46 | # ============================================================ 47 | 48 | -------------------------------------------------------------------------------- /monitoring/nstv-rank.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # ============================================================ 3 | # 4 | # nstv-rank.py: 5 | # Usage: nstv-rank.py [-h] [-b] [-o filename] inputfile(s) 6 | # -h: Usage info (help) 7 | # -b: Generate BLT STV data format file 8 | # -o : Output file (default stdout) 9 | # 10 | # This script is designed to accept as input data in 11 | # the form as returned from the ASF Voter Tool 'Final 12 | # Tally' report email and generate output in the format 13 | # to be used by various STV ballot-counting software. 14 | # 15 | # The script allows you to use as input the full Email 16 | # message of the final tally; it takes care of correctly 17 | # handling duplicate votes (accepting only the last one) 18 | # as well as non-vote lines. Vote lines MUST have the 19 | # following format: 20 | # 21 | # [2006/06/13 18:22:30] 0422f389874d7a8ecfd2fabea0a152ed lagd 22 | # [2006/06/13 18:23:15] 9c58d57506a17c83b9d4cc8c0ed4a31e lgej 23 | # [2006/06/13 18:28:27] 9db55691a072f88d79968eed8297dc90 fcaki 24 | # [2006/06/13 19:01:13] a6d1332887e40af1dd2405cd2abf1e77 glfj 25 | # [2006/06/13 19:22:45] a9472aa4f7df905173acd8fe2d8edcb2 jkc 26 | # [2006/06/13 20:42:33] adc9914bb9112d8da4cddd6353e09ef1 eagk 27 | # 28 | # NOTE: All this depends on the format of the Final Tally 29 | # Email not changing. The assumptions are that the vote 30 | # list is ALWAYS in correct chronological order, with newer 31 | # votes after older ones. 32 | # 33 | # By default, it prints out a file in the format expected by 34 | # Voting Systems Toolbox (http://sourceforge.net/projects/votesystem): 35 | # 36 | # 32d4a49fbe6f1ee8a4f6a47f45fd5bbf,g,c,j,e,k,i,f,d,h 37 | # 04e53da3b92cd9a36fd2e9eba91915f8,i,c,j,e,l,a,g,d,b 38 | # 5e3bea51a6eef2d09722069f0c6178a5,a,c,j,g,l,h 39 | # f58528624ce66abe6ee8039e5d6a40f0,c,e,h,j,i,g,b,d,f,a,k,l 40 | # 41 | # It can also print out a file in BLT format, via the '-b' argument. 42 | ## 43 | #### VoteMain instructions #### 44 | # After installing Voting Systems Toolbox, you can execute the 'VoteMain' 45 | # program as 46 | # 47 | # java -cp Vote-0-4.jar VoteMain -system stv-meek -seats 9 outputFile 48 | # 49 | # where outputFile is the result of this script (rank). 50 | # 51 | # The output of 'VoteMain' program is the result of the elections. 52 | # 53 | # Note that the 'VoteMain' program can detect duplicate votes, as well as votes 54 | # with incorrect labels. 55 | ## 56 | #### OpenSTV instructions #### 57 | # OpenSTV (http://stv.sourceforge.net/) is a newer and better 58 | # maintained STV counting codebase. It requires the use of BLT 59 | # input files. 60 | # 61 | # The ASF uses Meek STV with: 62 | # Precision: 6 63 | # Threshold: Droop | Dynamic | Fractional 64 | # 65 | 66 | import getopt 67 | import os.path 68 | import sys 69 | import re 70 | import string 71 | import ConfigParser 72 | 73 | def read_nominees(votefile): 74 | ini_fname = os.path.join(os.path.dirname(votefile), 75 | 'board_nominations.ini') 76 | 77 | config = ConfigParser.ConfigParser() 78 | config.read(ini_fname) 79 | try: 80 | return dict(config.items('nominees')) 81 | except: 82 | print >> sys.stderr, "Error processing input file: " + ini_fname 83 | print >> sys.stderr, " Goodbye!" 84 | sys.exit(2) 85 | 86 | def usage(error=None): 87 | print >> sys.stderr, "nstv-rank.py:" 88 | print >> sys.stderr, " Usage: nstv-rank.py [-h] [-b] [-o filename] inputfile(s)" 89 | print >> sys.stderr, " -h: This info" 90 | print >> sys.stderr, " -b: Generate BLT STV data format file" 91 | print >> sys.stderr, " -o : Output file (default stdout)" 92 | if error: 93 | print >> sys.stderr, "Error: " + error 94 | 95 | def read_votes(args): 96 | votes = { } 97 | vote_pat = re.compile(r'\[.{19}\]\s+([\w\d]{32})\s+([a-z]{1,26})', re.I) 98 | for fname in args: 99 | try: 100 | for line in open(fname): 101 | line = string.strip(line) 102 | vote = vote_pat.search(line) 103 | if vote: 104 | votes[vote.group(1)] = vote.group(2) 105 | except: 106 | print >> sys.stderr, "Error processing input file: " + fname 107 | print >> sys.stderr, " Goodbye!" 108 | sys.exit(2) 109 | return votes 110 | 111 | def print_tally(args, output, blt): 112 | votes = read_votes(args) 113 | nominees = read_nominees(args[0]) 114 | nomkeys = sorted(nominees) 115 | numseats = 9 116 | 117 | if output: 118 | try: 119 | sys.stdout = open(a, 'w') 120 | except: 121 | print >> sys.stderr, "Cannot open output file: " + a 122 | print >> sys.stderr, " Goodbye!" 123 | sys.exit(2) 124 | 125 | if blt: 126 | numcands = len(nomkeys) 127 | print "%d %d" % (numcands, numseats) 128 | for id in votes.keys(): 129 | line = [ ] 130 | for vote in votes[id]: 131 | value = ord(vote) - ord('a') + 1 132 | line.append(str(value)) 133 | line = "1 "+ " ".join(line) + " 0" 134 | print line 135 | print "0" 136 | for id in nomkeys: 137 | print '"%s"' % nominees[id] 138 | print '"ASF STV Board Election"' 139 | 140 | else: 141 | print "rank order" 142 | line = [ ] 143 | for id in nomkeys: 144 | line.append("%20.20s" % nominees[id]) 145 | line = "NAME, " + ", ".join(line) 146 | print line 147 | line = [ ] 148 | for id in nomkeys: 149 | line.append("%20.20s" % id) 150 | line = "LABEL," + ", ".join(line) 151 | print line 152 | for id in votes.keys(): 153 | line = id + "," + ",".join(votes[id]) 154 | print line 155 | 156 | 157 | if __name__ == '__main__': 158 | 159 | try: 160 | opts, args = getopt.getopt(sys.argv[1:], "ho:b") 161 | except getopt.GetoptError, err: 162 | print str(err) 163 | usage() 164 | sys.exit(2) 165 | output = None 166 | blt = False 167 | for o, a in opts: 168 | if o == "-b": 169 | blt = True 170 | elif o == "-h": 171 | usage() 172 | sys.exit() 173 | elif o == "-o": 174 | output = a 175 | if len(args) > 0: 176 | print_tally(args, output, blt) 177 | else: 178 | usage("No input file") 179 | sys.exit(2) 180 | -------------------------------------------------------------------------------- /monitoring/yna-recompute-summary/README: -------------------------------------------------------------------------------- 1 | These scripts parse a maildir that contains (among other things) the 2 | notification mails sent by voter@ after each and every vote, and generate: 3 | 4 | * For multiple votes with the same hash, list them 5 | (because no one implemented date-based choice of the most recent vote yet) 6 | 7 | * Display the results (modulo those warnings) 8 | 9 | These can then be compared with the tallies sent by the voter script upon 10 | close_issue. 11 | 12 | 13 | INSTRUCTIONS: 14 | 15 | * Unpack a maildir containing the vote mails somewhere 16 | * Run driver.sh with /path/to/maildir as argv[1] 17 | 18 | * driver.sh is the only one marked 'executable' in the repository. That's intentional. 19 | -------------------------------------------------------------------------------- /monitoring/yna-recompute-summary/count.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use strict; 3 | 4 | my $maildir = (shift or '/tmp/votem/wd'); 5 | die "argv[1] = /PATH/TO/MAILDIR" unless -d $maildir; 6 | 7 | while (<>) { 8 | my $issuename = $_; 9 | chomp $issuename; 10 | my $resultsfile = ".results.$issuename"; 11 | unlink $resultsfile or die "unlink: $!" 12 | if -e $resultsfile; 13 | open my $grep, "-|", "grep -Rl 'Subject: $issuename' $maildir/" or die "open: $!"; 14 | while (defined (my $fname = <$grep>)) { 15 | chomp $fname; 16 | system("grep 'vote: ' $fname >> $resultsfile") == 0 or die "system($?): $!"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /monitoring/yna-recompute-summary/display-dups.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use strict; 3 | 4 | my $label = shift // "(stdin)"; 5 | my %votes; 6 | my %result; 7 | 8 | while (<>) { 9 | my (undef, $hash, undef, $vote) = split; 10 | $votes{$hash} .= substr($vote, 0, 1); 11 | $result{$vote}++; 12 | } 13 | 14 | while (my ($h, $v) = each %votes) { 15 | print "WARNING: $label: '$h' voted '$v'\n" if $v =~ /../; 16 | } 17 | 18 | my $yesno = ($result{yes} > $result{no}) ? "YES" : "NO"; 19 | my $margin = ($result{yes} - $result{no}); 20 | 21 | print "RESULTS: $label: $yesno (margin=$margin; +$result{yes}, -$result{no}, =$result{abstain})\n"; 22 | warn "Unknown keys found" if grep { ! /^(yes|no|abstain)$/ } keys %result; 23 | -------------------------------------------------------------------------------- /monitoring/yna-recompute-summary/driver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | maildir=${1-/tmp/votem/wd/} 4 | [ -d "$maildir" ] || exit 1 5 | 6 | touch .results. 7 | rm .results.* 8 | sh ./generate-issues-list.sh $maildir | perl ./count.pl $maildir 9 | for i in .results.*; do 10 | cat < $i | sort | uniq > $i.sorted 11 | mv $i.sorted $i 12 | done 13 | for i in .results.*; do perl ./display-dups.pl $i < $i; done 14 | -------------------------------------------------------------------------------- /monitoring/yna-recompute-summary/generate-issues-list.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | maildir=${1-/tmp/votem/wd/} 4 | [ -d "$maildir" ] || exit 1 5 | 6 | grep -R -h '^Subject:' $maildir \ 7 | | grep -v 'vote on' \ 8 | | perl -lne 'print if s/^Subject: (members\d{6}-\d{8}-)(\S*)/\1\2/' \ 9 | | sort | uniq 10 | -------------------------------------------------------------------------------- /monitoring/yna-summary.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # ============================================================ 3 | # 4 | # SCRIPT NAME: yna-summary 5 | # 6 | # This script takes in files in the form sent by the 7 | # voter tool final summary Email 8 | # 9 | # e.g 10 | # Issue members200606-20060613-foobar is now closed. The summary is not yet implemented. 11 | # 12 | # Here are the raw collected results -- remember to remove old votes from 13 | # those with duplicate entries before counting them. 14 | # 15 | # [2006/06/13 18:22:30] 0422f389874d7a8ecfd2fabea0a152ed yes 16 | # [2006/06/13 18:23:15] 9c58d57506a17c83b9d4cc8c0ed4a31e abstain 17 | # [2006/06/13 18:28:27] 9db55691a072f88d79968eed8297dc90 no 18 | # [2006/06/13 19:01:13] a6d1332887e40af1dd2405cd2abf1e77 yes 19 | # [2006/06/13 19:22:45] a9472aa4f7df905173acd8fe2d8edcb2 yes 20 | # [2006/06/13 20:42:33] adc9914bb9112d8da4cddd6353e09ef1 abstain 21 | # 22 | # ... 23 | # 24 | # And produces a summary of total votes, the number of 25 | # dups and the vote summaries themselves. 26 | # 27 | # It is very dependent on the format of the Final Tally 28 | # Email that comes from the voter tool and is 29 | # designed to work on the full email message. 30 | # 31 | # This means you can concat all the final tallies 32 | # together in one big file and this tool will 33 | # correctly parse the whole shebang for you. 34 | # 35 | 36 | open(INPUT, "$ARGV[0]"); 37 | 38 | while() { 39 | chomp; 40 | # Assumes standard format of closed email 41 | if (/Issue ([^-]*)-[^-]*-(\w*) is now closed/) { 42 | $issuename = "$1 - $2"; 43 | %votes = (); 44 | $dups{$issuename} = 0; 45 | $yes = 0; $no = 0; $abstain = 0; $total = 0; 46 | next; 47 | } 48 | # [2006/06/13 17:54:23] 4db08a9e058fa8e4742f1f05cd32e409 yes 49 | if (/\[.{19}\]\s([\w\d]{32})\s([a-z]{1,12})/) { 50 | if ($votes{$1} ne "") { 51 | $dups{$issuename}++; 52 | } 53 | $votes{$1} = $2; 54 | next; 55 | } 56 | if (/Current file digests:/) { 57 | foreach $id (keys %votes) { 58 | $total++; 59 | #print "$id, $votes{$id} \n"; 60 | if ($votes{$id} eq 'yes') { $yes++; } 61 | if ($votes{$id} eq 'no') { $no++; } 62 | if ($votes{$id} eq 'abstain') { $abstain++; } 63 | } 64 | if ($yes > $no) { 65 | $elected = "(elected)"; 66 | } else { 67 | $elected = "(NOT ELECTED)"; 68 | } 69 | print "Issue: $issuename: Total: $total ($dups{$issuename}), Yes: $yes, No: $no, Abstain: $abstain $elected\n"; 70 | next; 71 | } 72 | } 73 | 74 | # ============================================================ 75 | -------------------------------------------------------------------------------- /pelicanconf.yaml: -------------------------------------------------------------------------------- 1 | site: 2 | name: Apache STeVe 3 | description: Apache STeVe 4 | domain: steve.apache.org 5 | logo: images/logo.png 6 | repository: https://github.com/apache/steve/blob/trunk/site 7 | trademarks: Apache, the Apache feather logo, and "Project" are trademarks or registered trademarks 8 | 9 | theme: theme 10 | 11 | content: 12 | pages: pages 13 | static_dirs: 14 | - css 15 | - images 16 | - js 17 | -------------------------------------------------------------------------------- /pysteve/GETTING_STARTED.txt: -------------------------------------------------------------------------------- 1 | Very Quick Start Guide: 2 | 3 | - svn co https://svn.apache.org/repos/asf/steve/trunk/pysteve/ 4 | - Edit steve.cfg to suit your needs (karma, DB backend etc) 5 | - IF you choose ElasticSearch as backend, install the python module (pip install elasticsearch) 6 | - OR IF you choose files as your backend, run setup.py in the CLI directory. 7 | Beware that using the file backend is currently only intended for development purposes. 8 | - EITHER: Edit httpd.conf, add it to your existing httpd configuration 9 | - Set up authorization using htpasswd for admins, monitors etc 10 | - OR: run python standalone.py (remember to edit auth karma inside it) 11 | - Go to http://steve.yourdomain.foo/admin and set up an election 12 | - Start voting! 13 | -------------------------------------------------------------------------------- /pysteve/NOTICE: -------------------------------------------------------------------------------- 1 | Apache Steve 2 | Copyright 2012-2015 The Apache Software Foundation 3 | 4 | This product includes software developed at 5 | The Apache Software Foundation (http://www.apache.org/). 6 | 7 | This product includes software developed by the jQuery Foundation 8 | 9 | -------------------------------------------------------------------------------- /pysteve/README.md: -------------------------------------------------------------------------------- 1 | ![Logo](www/htdocs/images/steve_large.png) 2 | 3 | ============================================================= 4 | Welcome to Apache STeVe 5 | ============================================================= 6 | 7 | Apache STeVe is software to conduct a vote using the STV (Single Transferrable 8 | Vote) and other voting algorithms. The tool grew out of the voting system used 9 | to elect the Apache Software Foundation Board of Directors. 10 | 11 | Read more about STV at 12 | http://en.wikipedia.org/wiki/Single_transferable_vote 13 | 14 | Getting Started 15 | =============== 16 | Getting Started documentation is at: steve.apache.org/demo.html. 17 | 18 | You are welcome to join the `#steve` channel in `the-asf` Slack workspace to discuss STeVe, ask questions, and make suggestions. 19 | 20 | 21 | License (see also LICENSE.txt) 22 | ============================== 23 | Collective work: Copyright 2012-2016 The Apache Software Foundation. 24 | 25 | Licensed to the Apache Software Foundation (ASF) under one or more 26 | contributor license agreements. See the NOTICE file distributed with 27 | this work for additional information regarding copyright ownership. 28 | The ASF licenses this file to You under the Apache License, Version 2.0 29 | (the "License"); you may not use this file except in compliance with 30 | the License. You may obtain a copy of the License at 31 | 32 | http://www.apache.org/licenses/LICENSE-2.0 33 | 34 | Unless required by applicable law or agreed to in writing, software 35 | distributed under the License is distributed on an "AS IS" BASIS, 36 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 37 | See the License for the specific language governing permissions and 38 | limitations under the License. 39 | 40 | 41 | Documentation 42 | ============= 43 | Documentation may be found at https://steve.apache.org/documentation.html 44 | 45 | Contributions to the documentation are very welcome. 46 | 47 | Mailing Lists 48 | ============= 49 | Discussion about STeVe takes place on the following mailing lists: 50 | 51 | dev@steve.apache.org - About using STeVe and developing STeVe 52 | 53 | Notification on all code changes are sent to the following mailing list: 54 | 55 | commits@steve.apache.org 56 | 57 | The mailing lists are open to anyone and publicly archived. 58 | 59 | You can subscribe the mailing lists by sending a message to 60 | -subscribe@steve.apache.org (for example 61 | dev-subscribe@steve...). To unsubscribe, send a message to 62 | -unsubscribe@steve.apache.org. For more instructions, send a 63 | message to -help@steve.apache.org. 64 | 65 | Additional mailing list details may be found at 66 | http://steve.apache.org/support.html 67 | 68 | Issue Tracker 69 | ============= 70 | If you encounter errors in STeVe or want to suggest an improvement or a new 71 | feature, please visit the STeVe issue tracker at 72 | https://issues.apache.org/jira/browse/STEVE 73 | 74 | Implemented changes 75 | =================== 76 | Implemented changes can be found at: 77 | https://svn.apache.org/viewvc/steve/ 78 | 79 | 80 | -------------------------------------------------------------------------------- /pysteve/cli/load_election.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one or more 4 | # contributor license agreements. See the NOTICE file distributed with 5 | # this work for additional information regarding copyright ownership. 6 | # The ASF licenses this file to You under the Apache License, Version 2.0 7 | # (the "License"); you may not use this file except in compliance with 8 | # the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | # 20 | # Load/create an election with a set of issues specified in a .yaml file 21 | # 22 | ### NOTE: 23 | # relies on using .netrc for user/pass. Cuz not gonna bother doing better. 24 | # 25 | 26 | import sys 27 | import logging 28 | 29 | import yaml # pip install PyYAML 30 | import requests # pip install requests 31 | import easydict 32 | 33 | LOGGER = logging.getLogger(__name__) 34 | 35 | 36 | def main(y_fname): 37 | cfg = easydict.EasyDict(yaml.safe_load(open(y_fname))) 38 | 39 | s = requests.Session() 40 | 41 | eid = cfg.election.eid 42 | LOGGER.info(f'ELECTION: {eid}') 43 | issues = ensure_election(s, cfg) 44 | 45 | # Remove all the issues, so we can load the new/current set. 46 | for issue in issues: 47 | iid = issue['id'] 48 | print(f'DELETING: {iid}') 49 | r = s.get(f'{cfg.config.endpoint}/delete/{eid}/{iid}') 50 | if r.status_code != 200: 51 | # Something is wrong. 52 | LOGGER.error(f'UNKNOWN: {r.status_code}') 53 | LOGGER.debug(f'BODY: {r.text}') 54 | raise Exception 55 | 56 | # Load all the defined issues. 57 | for idx, issue in enumerate(cfg['issues']): 58 | iid = f'issue-{idx}' 59 | print(f'CREATING: {iid}: {issue}') 60 | 61 | payload = { 62 | 'title': issue['title'], 63 | 'description': issue['description'], 64 | 'type': issue['type'], 65 | ### not needed for YNA. fix as needed 66 | #'candidates': [ ], 67 | } 68 | r = s.post(f'{cfg.config.endpoint}/create/{eid}/{iid}', 69 | data=payload) 70 | if r.status_code != 201: 71 | # Something is wrong. 72 | LOGGER.error(f'UNKNOWN: {r.status_code}') 73 | LOGGER.debug(f'BODY: {r.text}') 74 | raise Exception 75 | 76 | LOGGER.debug(r.json()) 77 | 78 | 79 | def ensure_election(s, cfg): 80 | eid = cfg.election.eid 81 | r = s.get(f'{cfg.config.endpoint}/view/{eid}') 82 | LOGGER.debug(f'HEADERS: {r.request.headers}') 83 | if r.status_code == 200: 84 | # The election has already been created, so return its issues. 85 | j = r.json() 86 | LOGGER.debug(f'BODY: {j}') 87 | return j['issues'] 88 | if r.status_code != 404: 89 | # Something is wrong. 90 | LOGGER.error(f'UNKNOWN: {r.status_code}') 91 | LOGGER.debug(f'BODY: {r.text}') 92 | raise Exception 93 | 94 | # Got a 404 saying the election doesn't exist. So create it. 95 | payload = { 96 | 'title': cfg.election.title, 97 | 'owner': cfg.config.user, 98 | 'monitors': ','.join(cfg.election.monitors), 99 | 100 | ### Below are optional. Skip for now. 101 | #'starts': d, 102 | #'ends': e, 103 | #'open': f 104 | } 105 | 106 | ### use a PreparedRequest so we can debug the URL used 107 | url = f'{cfg.config.endpoint}/setup/{eid}' 108 | #req = requests.Request('POST', url, data=payload) 109 | #prepped = req.prepare() 110 | #LOGGER.debug(f'HEADERS: {prepped.headers}') 111 | 112 | r = s.post(url, data=payload) 113 | LOGGER.debug(f'HEADERS: {r.request.headers}') 114 | if r.status_code != 201: 115 | # Something is wrong. 116 | LOGGER.error(f'UNKNOWN: {r.status_code}') 117 | LOGGER.debug(f'BODY: {r.text}') 118 | raise Exception 119 | 120 | LOGGER.debug(r.json()) 121 | 122 | # We created a new election. It has no issues. 123 | return [ ] 124 | 125 | 126 | if __name__ == '__main__': 127 | ### TODO: fancy arg parsing 128 | logging.basicConfig(level=logging.DEBUG) 129 | main(sys.argv[1]) 130 | -------------------------------------------------------------------------------- /pysteve/cli/mkelection.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env/python 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one or more 4 | # contributor license agreements. See the NOTICE file distributed with 5 | # this work for additional information regarding copyright ownership. 6 | # The ASF licenses this file to You under the Apache License, Version 2.0 7 | # (the "License"); you may not use this file except in compliance with 8 | # the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | import os, sys, random, time 19 | 20 | path = os.path.abspath(os.getcwd() + "/..") 21 | sys.path.append(path) 22 | 23 | version = 2 24 | if sys.hexversion < 0x03000000: 25 | import ConfigParser as configparser 26 | else: 27 | import configparser 28 | version = 3 29 | 30 | config = configparser.RawConfigParser() 31 | config.read('../steve.cfg') 32 | 33 | homedir = config.get("general", "homedir") 34 | 35 | from lib import election 36 | import argparse 37 | 38 | parser = argparse.ArgumentParser(description='Command line options.') 39 | parser.add_argument('--id', dest='id', type=str, nargs=1, 40 | help='Election ID: If defined, attempt to create an election using this as the election ID (OPTIONAL)') 41 | parser.add_argument('--owner', required=True, dest='owner', nargs=1, 42 | help='Sets the owner of this election, as according to steve.cfg [REQUIRED]') 43 | parser.add_argument('--title', required=True, dest='title', nargs=1, 44 | help='Sets the title (name) of the election [REQUIRED]') 45 | parser.add_argument('--monitors', dest='monitors', nargs=1, 46 | help='Comma-separated list of email addresses to use for monitoring (OPTIONAL)') 47 | parser.add_argument('--public', dest='public', action='store_true', 48 | help='If set, create the election as a public (open) election where anyone can vote (OPTIONAL)') 49 | 50 | args = parser.parse_args() 51 | eid = args.id[0] if args.id and len(args.id) > 0 else None 52 | if not eid: 53 | eid = ("%08x" % int(time.time() * random.randint(1,999999999999)))[0:8] 54 | print("Creating new election with ID %s" % eid) 55 | monitors = [] 56 | if args.monitors: 57 | monitors = args.monitors[0].split(",") 58 | if not config.has_option("karma", args.owner[0]): 59 | print("Sorry, I could not find '%s' in the karma list in steve.cfg!" % args.owner[0]) 60 | sys.exit(-1) 61 | else: 62 | election.createElection(eid, args.title[0], args.owner[0], monitors, 0, 0, args.public) 63 | 64 | print("Election created!") 65 | print("Election ID: %s" % eid) 66 | print("Election Admin URL: %s/edit_election.html?%s" % (config.get("general", "rooturl"), eid)) 67 | -------------------------------------------------------------------------------- /pysteve/cli/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env/python 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one or more 4 | # contributor license agreements. See the NOTICE file distributed with 5 | # this work for additional information regarding copyright ownership. 6 | # The ASF licenses this file to You under the Apache License, Version 2.0 7 | # (the "License"); you may not use this file except in compliance with 8 | # the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | import os, sys 19 | 20 | path = os.path.abspath(os.getcwd() + "/..") 21 | sys.path.append(path) 22 | 23 | version = 2 24 | if sys.hexversion < 0x03000000: 25 | import ConfigParser as configparser 26 | else: 27 | import configparser 28 | version = 3 29 | 30 | print("Reading steve.cfg") 31 | config = configparser.RawConfigParser() 32 | config.read('../steve.cfg') 33 | 34 | homedir = config.get("general", "homedir") 35 | 36 | print("Attempting to set up STeVe directories...") 37 | 38 | if os.path.isdir(homedir): 39 | print("Creating election folder") 40 | if os.path.exists(homedir + "/issues"): 41 | print("Election folder already exists, nothing to do here..") 42 | sys.exit(-1) 43 | else: 44 | try: 45 | os.mkdir(homedir + "/issues") 46 | print("All done!") 47 | except Exception as err: 48 | print("Could not create dir: %s" % err) 49 | else: 50 | print("Home dir (%s) does not exist, please create it!" % homedir) 51 | sys.exit(-1) -------------------------------------------------------------------------------- /pysteve/cli/tally.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | """ Tallying program for pySTeVe """ 20 | import sys, json, os 21 | 22 | version = 2 23 | if sys.hexversion < 0x03000000: 24 | import ConfigParser as configparser 25 | else: 26 | import configparser 27 | version = 3 28 | path = os.path.abspath(os.getcwd() + "/../") # Get parent dir, so we can snag 'lib' from there 29 | 30 | sys.path.append(path) 31 | sys.path.append(os.path.basename(sys.argv[0])) 32 | 33 | # Fetch config (hack, hack, hack) 34 | config = configparser.RawConfigParser() 35 | config.read(path + '/steve.cfg') 36 | 37 | # Some quick paths 38 | homedir = config.get("general", "homedir") 39 | 40 | # Import the goodness 41 | from lib import election 42 | 43 | 44 | 45 | import argparse 46 | 47 | parser = argparse.ArgumentParser(description='Command line options.') 48 | parser.add_argument('-e', '--election', dest='election', type=str, help='Election to load') 49 | parser.add_argument('-i', '--issue', dest='issue', type=str, help='Issue to load') 50 | parser.add_argument('-f', '--file', dest='vfile', type=str, help='Monitor file to load. Used by monitors instead of specifying election and issue') 51 | parser.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Enable verbose logging", default=False) 52 | args = parser.parse_args() 53 | 54 | baseData = None 55 | issueData = None 56 | votes = None 57 | 58 | if args.election and args.issue: 59 | issueData = election.getIssue(args.election, args.issue) 60 | baseData = election.getBasedata(args.election) 61 | votes = election.getVotes(args.election, args.issue) 62 | elif args.vfile: 63 | with open(args.vfile, "r") as f: 64 | js = json.loads(f.read()) 65 | f.close() 66 | issueData = js['issue'] 67 | baseData = js['base'] 68 | votes = js['votes'] 69 | else: 70 | parser.print_help() 71 | 72 | 73 | if baseData and issueData: 74 | if not votes or len(votes) == 0: 75 | print("No votes have been cast yet, cannot tally") 76 | sys.exit(-1) 77 | else: 78 | tally, prettyprint = election.tally(votes, issueData) 79 | if args.verbose and tally.get('debug'): 80 | print("------\nDebug:\n------") 81 | for line in tally['debug']: 82 | print(line) 83 | print("----------") 84 | print("Base data:") 85 | print("----------") 86 | print("Election: %s" % baseData['title']) 87 | print("Issue: %s" % issueData['title']) 88 | print("Votes cast: %u" % len(votes)) 89 | print("\n-----------------\nElection results:\n-----------------") 90 | print(prettyprint) 91 | else: 92 | print("No such election or issue!") 93 | sys.exit(-1) -------------------------------------------------------------------------------- /pysteve/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # Dockerfile to build pySTeVe container images 3 | # Based on Debian 4 | ############################################################ 5 | 6 | # Set base images 7 | FROM debian 8 | FROM elasticsearch 9 | 10 | MAINTAINER Daniel Gruno 11 | 12 | # Update aptitude repo data 13 | RUN apt-get update 14 | 15 | # Install base packages 16 | RUN apt-get install -y apache2 subversion python-pip 17 | RUN pip install elasticsearch 18 | 19 | 20 | # Download pySTeVe 21 | RUN svn co https://svn.apache.org/repos/asf/steve/trunk/pysteve/ /var/www/steve 22 | 23 | # Copy libs 24 | RUN cp -R /var/www/steve/lib /var/www/steve/www/cgi-bin/lib 25 | 26 | 27 | # Add httpd config 28 | RUN rm /etc/apache2/sites-enabled/*.conf 29 | ADD https://svn.apache.org/repos/asf/steve/trunk/pysteve/httpd.conf /etc/apache2/sites-enabled/000-default.conf 30 | 31 | 32 | # Start ElasticSearch 33 | EXPOSE 9200 9300 34 | RUN service elasticsearch start && sleep 30 && service elasticsearch status 35 | 36 | # Enable mod_cgi 37 | RUN a2enmod cgi 38 | 39 | # Expose port for httpd 40 | EXPOSE 80 41 | 42 | # Set default container startup sequence 43 | ENTRYPOINT service elasticsearch start && service apache2 start && bash 44 | -------------------------------------------------------------------------------- /pysteve/htpasswd-example: -------------------------------------------------------------------------------- 1 | admin:$apr1$TsvlU5.b$P8Pa..abaRCZcu/35suT3/ 2 | -------------------------------------------------------------------------------- /pysteve/httpd.conf: -------------------------------------------------------------------------------- 1 | # Sample httpd configuration for Steve's Python REST API test 2 | # see http://stv.website/steve/voter/view/foo for an example. 3 | 4 | 5 | DocumentRoot /var/www/steve/www/htdocs 6 | ServerName stv.website 7 | DirectoryIndex index.html 8 | 9 | Require all granted 10 | Options +ExecCGI 11 | AddHandler cgi-script .py 12 | 13 | 14 | # REST API 15 | ScriptAlias /steve/admin /var/www/steve/www/cgi-bin/rest_admin.py 16 | ScriptAlias /steve/voter /var/www/steve/www/cgi-bin/rest_voter.py 17 | 18 | # HTML generators 19 | ScriptAlias /steve/ballot /var/www/steve/www/cgi-bin/html_ballot.py 20 | ScriptAlias /steve/election /var/www/steve/www/cgi-bin/html_election.py 21 | 22 | # AcceptPathInfo On ensures that /steve/admin/foo etc go to /steve/admin 23 | AcceptPathInfo On 24 | 25 | 26 | AuthType Basic 27 | AuthName "STeVe administration" 28 | 29 | # CHANGE THIS TO YOUR REAL ACCESS FILE/SCHEME 30 | AuthBasicProvider file 31 | AuthUserFile /var/www/steve/htpasswd-example 32 | Require valid-user 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /pysteve/lib/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | """ 18 | Stuff 19 | """ -------------------------------------------------------------------------------- /pysteve/lib/backends/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | """Database backends""" 18 | 19 | __all__ = ['files','es', 'sqlite'] -------------------------------------------------------------------------------- /pysteve/lib/constants.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | import hashlib 19 | VOTE_TYPES = ( 20 | 21 | ) 22 | 23 | DB_TYPES = ( 24 | 25 | ) 26 | 27 | def hexdigest(data: str, method=hashlib.sha224): 28 | """Wrapper for hashlib.sha224().hexdigest that handles encoding""" 29 | return method(data.encode("utf-8")).hexdigest() 30 | 31 | def appendVote(*types): 32 | """ Append a new type of voting to the list""" 33 | global VOTE_TYPES 34 | for t in types: 35 | found = False 36 | for v in VOTE_TYPES: 37 | if v['key'] == t['key']: 38 | found = True 39 | break 40 | if not found: 41 | VOTE_TYPES += (t,) 42 | 43 | def appendBackend(t, c): 44 | """Append a new database backend""" 45 | global DB_TYPES 46 | found = False 47 | for b in DB_TYPES: 48 | if b.get('id') == t: 49 | found = True 50 | break 51 | if not found: 52 | DB_TYPES += ( { 53 | 'id': t, 54 | 'constructor': c 55 | },) 56 | 57 | 58 | def initBackend(config): 59 | # Set up DB backend 60 | backend = None 61 | 62 | if config.has_option("database", "disabled") and config.get("database", "disabled") == "true": 63 | return 64 | dbtype = config.get("database", "dbsys") 65 | for b in DB_TYPES: 66 | if b.get('id') == dbtype: 67 | backend = b['constructor'](config) 68 | break 69 | 70 | if not backend: 71 | raise Exception("Unknown database backend: %s" % dbtype) 72 | return backend 73 | 74 | # For vote types with N number of seats/spots, this value denotes 75 | # the max number of useable types to display via the API 76 | MAX_NUM = 10 -------------------------------------------------------------------------------- /pysteve/lib/form.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | import os, sys 18 | import cgi 19 | 20 | ctype, pdict = cgi.parse_header(os.environ['CONTENT_TYPE'] if 'CONTENT_TYPE' in os.environ else "") 21 | if ctype == 'multipart/form-data': 22 | pdict['boundary'] = bytes(pdict['boundary'], "utf-8") 23 | xform = cgi.parse_multipart(sys.stdin, pdict, encoding="utf-8") 24 | else: 25 | xform = cgi.FieldStorage(); 26 | 27 | 28 | def getvalue(key): 29 | try: 30 | val = str("".join(xform.get(key, ""))) 31 | if val == "": 32 | val = None 33 | except: 34 | val = xform.getvalue(key) 35 | if val: 36 | return val.replace("<", "<") 37 | else: 38 | return None 39 | -------------------------------------------------------------------------------- /pysteve/lib/gateway.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | 4 | import os 5 | 6 | def uid(): 7 | """Sample user gateway function. Returns the basic auth username as UID""" 8 | return os.environ['REMOTE_USER'] if 'REMOTE_USER' in os.environ else None 9 | -------------------------------------------------------------------------------- /pysteve/lib/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | """ 18 | CORE VOTE PLUGINS: 19 | 20 | yna: Yes/No/Abstain 21 | stv: Single Transferable Vote 22 | dh: D'Hondt (Jefferson) Voting 23 | fpp: First Past the Post (Presidential elections) 24 | mntv: Multiple Non-Transferable Votes 25 | cop: Candidate or Party Voting 26 | fic: First in Class Voting 27 | ap: Apache PMC Style voting 28 | """ 29 | 30 | __all__ = ['yna','stv','dh','fpp','mntv','cop','fic','ap'] -------------------------------------------------------------------------------- /pysteve/lib/plugins/ap.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | """ ASF PMC style voting plugin """ 18 | from lib import constants 19 | 20 | def tallyAP(votes, issue): 21 | """ Simple YNA tallying 22 | :param votes: The JSON object from $issueid.json.votes 23 | :return: dict with y,n,a,by,bn numbers as well as pretty-printed version 24 | """ 25 | y = n = a = 0 26 | by = bn = 0 27 | # For each vote cast, tally it 28 | for vote in votes.values(): 29 | if vote == 'y': 30 | y += 1 31 | elif vote == 'n': 32 | n += 1 33 | elif vote == 'a': 34 | a += 1 35 | elif vote == 'by': 36 | by += 1 37 | elif vote == 'bn': 38 | bn += 1 39 | else: 40 | raise Exception("Invalid vote found in votes db!") 41 | 42 | js = { 43 | 'votes': len(votes), 44 | 'yes': y, 45 | 'no': n, 46 | 'abstain': a, 47 | 'binding_yes': by, 48 | 'binding_no': bn 49 | } 50 | 51 | return js, """ 52 | Yes: %4u 53 | No: %4u 54 | Abstain: %4u 55 | Binding Yes: %4u 56 | Binding No: %4u 57 | """ % (y,n,a,by,bn) 58 | 59 | 60 | def validateAP(vote, issue): 61 | "Tries to validate a vote, returns why if not valid, None otherwise" 62 | letters = ['y','n','a', 'by', 'bn'] 63 | if len(vote) >= 3 or not vote in letters: 64 | return "Invalid vote. Accepted votes are: %s" % ", ".join(letters) 65 | return None 66 | 67 | 68 | # Verification process 69 | def verifyAP(basedata, issueID, voterID, vote, uid): 70 | "Invalidate a binding vote if not allowed to cast such" 71 | if vote.startswith('b'): 72 | # Simple check example: if not apache committer, discard vote if binding 73 | if not uid.endswith("@apache.org"): 74 | raise Exception("You are not allowed to cast a binding vote!") 75 | 76 | 77 | constants.appendVote( 78 | { 79 | 'key': "ap", 80 | 'description': "PMC Style vote (YNA with binding votes)", 81 | 'category': 'ap', 82 | 'validate_func': validateAP, 83 | 'vote_func': verifyAP, 84 | 'tally_func': tallyAP 85 | }, 86 | ) -------------------------------------------------------------------------------- /pysteve/lib/plugins/dh.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | """ D'Hondt (Jefferson) Based Voting Plugin """ 18 | import re 19 | 20 | from lib import constants 21 | 22 | def validateDH(vote, issue): 23 | "Tries to validate a vote, returns why if not valid, None otherwise" 24 | letters = [chr(i) for i in range(ord('a'), ord('a') + len(issue['candidates']))] 25 | if len(vote) > 1: 26 | return "Vote may only contain one letter!" 27 | for char in vote: 28 | if char not in letters: 29 | return "Invalid characters in vote. Accepted are: %s" % ", ".join(letters) 30 | return None 31 | 32 | 33 | def tallyDH(votes, issue): 34 | m = re.match(r"dh(\d+)", issue['type']) 35 | if not m: 36 | raise Exception("Not a D'Hondt vote!") 37 | 38 | numseats = int(m.group(1)) 39 | candidates = [] 40 | for c in issue['candidates']: 41 | candidates.append(c['name']) 42 | 43 | 44 | debug = [] 45 | 46 | # Set up letters for mangling 47 | letters = [chr(i) for i in range(ord('a'), ord('a') + len(candidates))] 48 | cc = "".join(letters) 49 | 50 | # Set up seats won 51 | winners = [] 52 | 53 | # Set up vote matrix 54 | matrix = {} 55 | for key in votes: 56 | vote = votes[key] 57 | if not vote in matrix: 58 | matrix[vote] = [0,1] 59 | matrix[vote][0] += 1 60 | 61 | # Start counting 62 | while len(winners) < numseats: 63 | m = [] 64 | for c in matrix: 65 | quotient = (matrix[c][0]/matrix[c][1]) 66 | m.append(quotient) 67 | for c in matrix: 68 | quotient = (matrix[c][0]/matrix[c][1]) 69 | if quotient == max(m): 70 | winners.append(c) 71 | matrix[c][1] += 1 72 | break 73 | 74 | # Compile list of winner names 75 | winnernames = [] 76 | for c in winners: 77 | i = ord(c) - ord('a') 78 | winnernames.append(candidates[i]) 79 | 80 | # Return the data 81 | return { 82 | 'votes': len(votes), 83 | 'winners': winners, 84 | 'winnernames': winnernames, 85 | }, """ 86 | Winners: 87 | - %s 88 | """ % "\n - ".join(winnernames) 89 | 90 | 91 | constants.appendVote ( 92 | { 93 | 'key': "dh1", 94 | 'description': "D'Hondt Election with 1 seat", 95 | 'category': 'dh', 96 | 'validate_func': validateDH, 97 | 'vote_func': None, 98 | 'tally_func': tallyDH 99 | }, 100 | ) 101 | 102 | # Add ad nauseam 103 | for i in range(2,constants.MAX_NUM+1): 104 | constants.appendVote ( 105 | { 106 | 'key': "dh%u" % i, 107 | 'description': "D'Hondt Election with %u seats" % i, 108 | 'category': 'dh', 109 | 'validate_func': validateDH, 110 | 'vote_func': None, 111 | 'tally_func': tallyDH 112 | }, 113 | ) -------------------------------------------------------------------------------- /pysteve/lib/plugins/fic.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | """ First in Class (FiC) Based Voting Plugin """ 18 | import re, heapq, operator 19 | 20 | from lib import constants 21 | 22 | def validateFIC(vote, issue): 23 | "Tries to validate a vote, returns why if not valid, None otherwise" 24 | m = re.match(r"fic(\d+)", issue['type']) 25 | if not m: 26 | return "Not an FiC vote!" 27 | numseats = int(m.group(1)) 28 | letters = [chr(i) for i in range(ord('a'), ord('a') + len(issue['candidates']))] 29 | if len(vote) > numseats: 30 | return "Vote contains too many candidates!" 31 | for char in vote: 32 | if char not in letters: 33 | return "Invalid characters in vote. Accepted are: %s" % ", ".join(letters) 34 | return None 35 | 36 | 37 | def tallyFIC(votes, issue): 38 | m = re.match(r"fic(\d+)", issue['type']) 39 | if not m: 40 | raise Exception("Not an FiC vote!") 41 | 42 | numseats = int(m.group(1)) 43 | candidates = [] 44 | for c in issue['candidates']: 45 | candidates.append(c['name']) 46 | 47 | 48 | debug = [] 49 | 50 | # Set up letters for mangling 51 | letters = [chr(i) for i in range(ord('a'), ord('a') + len(candidates))] 52 | cc = "".join(letters) 53 | 54 | # Set up seats won 55 | winners = [] 56 | 57 | # Set up vote matrix 58 | matrix = {} 59 | for key in votes: 60 | vote = votes[key] 61 | i = 0 62 | for letter in vote: 63 | if not letter in matrix: 64 | matrix[letter] = 0 65 | matrix[letter] += numseats - i 66 | i += 1 67 | 68 | 69 | # Start counting 70 | sorted_matrix = sorted(matrix.items(), key=operator.itemgetter(1)) 71 | bignums = heapq.nlargest(numseats, matrix.values()) 72 | winners = [l[0] for l in sorted_matrix if matrix[l[0]] in bignums] 73 | 74 | # Compile list of winner names 75 | winnernames = [] 76 | x = 0 77 | for c in winners: 78 | i = ord(c) - ord('a') 79 | winnernames.append("%s (%u points)" % ( candidates[i], bignums[x])) 80 | x+=1 81 | 82 | # Return the data 83 | return { 84 | 'votes': len(votes), 85 | 'winners': winners, 86 | 'winnernames': winnernames, 87 | }, """ 88 | Winners: 89 | - %s 90 | """ % "\n - ".join(winnernames) 91 | 92 | 93 | constants.appendVote( 94 | { 95 | 'key': "fic1", 96 | 'description': "First in Class Votes with 1 point max", 97 | 'category': 'fic', 98 | 'validate_func': validateFIC, 99 | 'vote_func': None, 100 | 'tally_func': tallyFIC 101 | }, 102 | ) 103 | 104 | # Add ad nauseam 105 | for i in range(2,constants.MAX_NUM+1): 106 | constants.appendVote ( 107 | { 108 | 'key': "fic%u" % i, 109 | 'description': "First in Class Votes with %u points max" % i, 110 | 'category': 'fic', 111 | 'validate_func': validateFIC, 112 | 'vote_func': None, 113 | 'tally_func': tallyFIC 114 | }, 115 | ) -------------------------------------------------------------------------------- /pysteve/lib/plugins/fpp.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | """ FPP (First Past the Post) Based Voting Plugin """ 18 | 19 | from lib import constants 20 | 21 | def validateFPP(vote, issue): 22 | "Tries to validate a vote, returns why if not valid, None otherwise" 23 | letters = [chr(i) for i in range(ord('a'), ord('a') + len(issue['candidates']))] 24 | if len(vote) > 1: 25 | return "Vote may only contain one letter!" 26 | for char in vote: 27 | if char not in letters: 28 | return "Invalid characters in vote. Accepted are: %s" % ", ".join(letters) 29 | return None 30 | 31 | 32 | def tallyFPP(votes, issue): 33 | candidates = [] 34 | for c in issue['candidates']: 35 | candidates.append(c['name']) 36 | 37 | 38 | debug = [] 39 | matrix = {} 40 | 41 | # Set up counting matrix 42 | for key in votes: 43 | vote = votes[key] 44 | matrix[vote] = (matrix[vote] if vote in matrix else 0) + 1 45 | 46 | l = [] 47 | for x in matrix: 48 | l.append(matrix[x]) 49 | 50 | cc = [] 51 | for x in matrix: 52 | if matrix[x] == max(l): 53 | cc.append(x) 54 | winners = [] 55 | winnernames = [] 56 | 57 | for c in cc: 58 | i = ord(c) - ord('a') 59 | winners.append(c) 60 | winnernames.append(candidates[i]) 61 | 62 | 63 | # Return the data 64 | js = { 65 | 'votes': len(votes), 66 | 'winners': winners, 67 | 'winnernames': winnernames, 68 | 'winnerpct': ((1.00*max(l)/len(votes))*100) if len(votes) > 0 else 0.00, 69 | 'tie': True if len(winners) > 1 else False 70 | } 71 | 72 | return js, """ 73 | Winners: 74 | - %s 75 | """ % "\n - ".join(["%s (%f%%)" % (n,js['winnerpct']) for n in winnernames]) 76 | 77 | 78 | constants.appendVote ( 79 | { 80 | 'key': "fpp", 81 | 'description': "First Past the Post (FPP) Election", 82 | 'category': 'fpp', 83 | 'validate_func': validateFPP, 84 | 'vote_func': None, 85 | 'tally_func': tallyFPP 86 | }, 87 | ) 88 | -------------------------------------------------------------------------------- /pysteve/lib/plugins/mntv.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | """ Multiple Non-Transferable Vote (MNTV) Based Voting Plugin """ 18 | import re, heapq 19 | 20 | from lib import constants 21 | 22 | def validateMNTV(vote, issue): 23 | "Tries to validate a vote, returns why if not valid, None otherwise" 24 | m = re.match(r"mntv(\d+)", issue['type']) 25 | if not m: 26 | return "Not an MNTV vote!" 27 | numseats = int(m.group(1)) 28 | letters = [chr(i) for i in range(ord('a'), ord('a') + len(issue['candidates']))] 29 | if len(vote) > numseats: 30 | return "Vote contains too many candidates!" 31 | for char in vote: 32 | if char not in letters: 33 | return "Invalid characters in vote. Accepted are: %s" % ", ".join(letters) 34 | return None 35 | 36 | 37 | def tallyMNTV(votes, issue): 38 | m = re.match(r"mntv(\d+)", issue['type']) 39 | if not m: 40 | raise Exception("Not an MNTV vote!") 41 | 42 | numseats = int(m.group(1)) 43 | candidates = [] 44 | for c in issue['candidates']: 45 | candidates.append(c['name']) 46 | 47 | 48 | debug = [] 49 | 50 | # Set up letters for mangling 51 | letters = [chr(i) for i in range(ord('a'), ord('a') + len(candidates))] 52 | cc = "".join(letters) 53 | 54 | # Set up seats won 55 | winners = [] 56 | 57 | # Set up vote matrix 58 | matrix = {} 59 | for key in votes: 60 | vote = votes[key] 61 | for letter in vote: 62 | if not letter in matrix: 63 | matrix[letter] = 0 64 | matrix[letter] += 1 65 | 66 | # Start counting 67 | winners = [l for l in matrix if matrix[l] in heapq.nlargest(numseats, matrix.values())] 68 | 69 | # Compile list of winner names 70 | winnernames = [] 71 | for c in winners: 72 | i = ord(c) - ord('a') 73 | winnernames.append(candidates[i]) 74 | 75 | # Return the data 76 | return { 77 | 'votes': len(votes), 78 | 'winners': winners, 79 | 'winnernames': winnernames, 80 | }, """ 81 | Winners: 82 | - %s 83 | """ % "\n - ".join(winnernames) 84 | 85 | 86 | constants.appendVote ( 87 | { 88 | 'key': "mntv1", 89 | 'description': "Multiple Non-Transferable Votes with 1 seat", 90 | 'category': 'mntv', 91 | 'validate_func': validateMNTV, 92 | 'vote_func': None, 93 | 'tally_func': tallyMNTV 94 | }, 95 | ) 96 | 97 | # Add ad nauseam 98 | for i in range(2,constants.MAX_NUM+1): 99 | constants.appendVote ( 100 | { 101 | 'key': "mntv%u" % i, 102 | 'description': "Multiple Non-Transferable Votes with %u seats" % i, 103 | 'category': 'mntv', 104 | 'validate_func': validateMNTV, 105 | 'vote_func': None, 106 | 'tally_func': tallyMNTV 107 | }, 108 | ) -------------------------------------------------------------------------------- /pysteve/lib/plugins/yna.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | """ Simple YNA voting plugin """ 18 | from lib import constants 19 | 20 | def tallyYNA(votes, issue): 21 | """ Simple YNA tallying 22 | :param votes: The JSON object from $issueid.json.votes 23 | :return: y,n,a as numbers 24 | """ 25 | y = n = a = 0 26 | for vote in votes.values(): 27 | if vote == 'y': 28 | y += 1 29 | elif vote == 'n': 30 | n += 1 31 | elif vote == 'a': 32 | a += 1 33 | else: 34 | raise Exception("Invalid vote found!") 35 | 36 | return { 37 | 'votes': len(votes), 38 | 'yes': y, 39 | 'no': n, 40 | 'abstain': a 41 | }, """ 42 | Yes: %4u 43 | No: %4u 44 | Abstain: %4u 45 | """ % (y,n,a) 46 | 47 | def validateYNA(vote, issue): 48 | "Tries to validate a vote, returns why if not valid, None otherwise" 49 | letters = ['y','n','a'] 50 | if len(vote) != 1 or not vote in letters: 51 | return "Invalid vote. Accepted votes are: %s" % ", ".join(letters) 52 | return None 53 | 54 | constants.appendVote ( 55 | { 56 | 'key': "yna", 57 | 'description': "YNA (Yes/No/Abstain) vote", 58 | 'category': 'yna', 59 | 'validate_func': validateYNA, 60 | 'vote_func': None, 61 | 'tally_func': tallyYNA 62 | }, 63 | ) -------------------------------------------------------------------------------- /pysteve/lib/response.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one or more 4 | # contributor license agreements. See the NOTICE file distributed with 5 | # this work for additional information regarding copyright ownership. 6 | # The ASF licenses this file to You under the Apache License, Version 2.0 7 | # (the "License"); you may not use this file except in compliance with 8 | # the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | import json 19 | 20 | responseCodes = { 21 | 200: 'Okay', 22 | 201: 'Created', 23 | 206: 'Partial content', 24 | 304: 'Not Modified', 25 | 400: 'Bad Request', 26 | 403: 'Access denied', 27 | 404: 'Not Found', 28 | 410: 'Gone', 29 | 500: 'Server Error' 30 | } 31 | 32 | def respond(code, js): 33 | c = responseCodes[code] if code in responseCodes else "Unknown Response Code(?)" 34 | out = json.dumps(js, indent=4) 35 | print("Status: %u %s\r\nContent-Type: application/json\r\nCache-Control: no-cache\r\nContent-Length: %u\r\n" % (code, c, len(out))) 36 | print(out) 37 | 38 | def wsgirespond(start_response, code, js): 39 | c = responseCodes[code] if code in responseCodes else "Unknown Response Code(?)" 40 | out = json.dumps(js, indent=4) 41 | start_response("%u %s" % (code, c), [ 42 | ("Content-Type", "application/json"), ("Cache-Control", "no-cache"), ("Content-Length", "%u" % len(out)) 43 | ]) 44 | return out -------------------------------------------------------------------------------- /pysteve/lib/voter.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | import hashlib, random, os, time 18 | try: 19 | from __main__ import config 20 | except: 21 | import ConfigParser as configparser 22 | config = configparser.RawConfigParser() 23 | config.read("%s/../../../steve.cfg" % (os.path.dirname(__file__))) 24 | 25 | # SMTP Lib 26 | import smtplib 27 | from smtplib import SMTPException 28 | 29 | from lib import constants, election 30 | 31 | backend = constants.initBackend(config) 32 | 33 | def get(election, basedata, uid): 34 | xhash = constants.hexdigest(basedata['hash'] + uid, method=hashlib.sha512) 35 | return backend.voter_get_uid(election, xhash) 36 | 37 | 38 | def add(election, basedata, PID): 39 | uid = constants.hexdigest("%s%s%s%s" % (PID, basedata['hash'], time.time(), random.randint(1,99999999))) 40 | xhash = constants.hexdigest(basedata['hash'] + uid, method=hashlib.sha512) 41 | backend.voter_add(election, PID, xhash) 42 | return uid, xhash 43 | 44 | def remove(election, basedata, UID): 45 | backend.voter_remove(election, UID) 46 | 47 | 48 | def hasVoted(election, issue, uid): 49 | # Cut away .json endings if found. This is seemingly only used with the file-based db backend. 50 | # TODO: Test if this is still needed. 51 | if issue.endswith(".json"): 52 | issue = issue[:-5] 53 | return backend.voter_has_voted(election, issue, uid) 54 | 55 | def ballots(): 56 | try: 57 | from lib import gateway 58 | uid = gateway.uid() 59 | return backend.voter_ballots(uid) if uid else {} 60 | except: 61 | return {} 62 | 63 | def regenerate(election, basedata, xhash): 64 | try: 65 | from lib import gateway 66 | uid = gateway.uid() 67 | valid = backend.ballot_scrub(election, xhash) 68 | if valid: 69 | ballot, xhash = add(election, basedata, uid) 70 | return { 71 | 'election': election, 72 | 'ballot': ballot 73 | } 74 | else: 75 | return { 76 | 'error': "Not a valid ballot!" 77 | } 78 | except: 79 | return {'error': "No suitable gateway mechanism found"} 80 | 81 | def email(rcpt, subject, message): 82 | sender = config.get("email", "sender") 83 | signature = config.get("email", "signature") 84 | receivers = [rcpt] 85 | # py 2 vs 3 conversion 86 | if type(message) is bytes: 87 | message = message.decode('utf-8', errors='replace') 88 | msg = u"""From: %s 89 | To: %s 90 | Subject: %s 91 | 92 | %s 93 | 94 | With regards, 95 | %s 96 | -- 97 | Powered by Apache STeVe - https://steve.apache.org 98 | """ % (sender, rcpt, subject, message, signature) 99 | msg = msg.encode('utf-8', errors='replace') 100 | try: 101 | smtpObj = smtplib.SMTP(config.get("email", "mta")) 102 | smtpObj.sendmail(sender, receivers, msg) 103 | except SMTPException: 104 | raise Exception("Could not send email - SMTP server down?") 105 | 106 | -------------------------------------------------------------------------------- /pysteve/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "steve" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Apache STeVe "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.12" 10 | asfpy = "0.52" 11 | 12 | [build-system] 13 | requires = ["poetry-core"] 14 | build-backend = "poetry.core.masonry.api" 15 | -------------------------------------------------------------------------------- /pysteve/standalone.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one or more 4 | # contributor license agreements. See the NOTICE file distributed with 5 | # this work for additional information regarding copyright ownership. 6 | # The ASF licenses this file to You under the Apache License, Version 2.0 7 | # (the "License"); you may not use this file except in compliance with 8 | # the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | """ Buggy standalone server for testing out pySTeVe """ 19 | 20 | portno = 8080 21 | 22 | 23 | import BaseHTTPServer 24 | import CGIHTTPServer 25 | from SocketServer import ThreadingMixIn 26 | import cgitb 27 | import base64 28 | import os 29 | import sys, traceback 30 | 31 | cgitb.enable() 32 | server = BaseHTTPServer.HTTPServer 33 | 34 | handler = CGIHTTPServer.CGIHTTPRequestHandler 35 | server_address = ("", portno) 36 | handler.cgi_directories = ["/www/cgi-bin"] 37 | handler.cgi_info = {} 38 | 39 | path = os.path.abspath(os.getcwd()) 40 | 41 | 42 | # EDIT THIS OR SOME SUCH!!!! 43 | karma = { 44 | 'admin': 'demo' 45 | } 46 | 47 | def doTraceBack(): 48 | exc_type, exc_value, exc_traceback = sys.exc_info() 49 | traceback.print_tb(exc_traceback, limit=1, file=sys.stdout) 50 | traceback.print_exception(exc_type, exc_value, exc_traceback, 51 | limit=2, file=sys.stdout) 52 | traceback.print_exc() 53 | 54 | 55 | class pysteveHTTPHandler(handler): 56 | 57 | cgi_directories = ["/www/cgi-bin"] 58 | 59 | def do_AUTHHEAD(self): 60 | print("send header") 61 | self.send_response(401) 62 | self.send_header('WWW-Authenticate', 'Basic realm=\"STeVe Administration\"') 63 | self.send_header('Content-type', 'text/html') 64 | self.end_headers() 65 | 66 | 67 | def do_GET(self): 68 | try: 69 | print(self.path) 70 | if self.path.startswith("/steve/admin"): 71 | if self.headers.getheader('Authorization') == None: 72 | self.do_AUTHHEAD() 73 | self.wfile.write('no auth header received') 74 | return 75 | else: 76 | authed = False 77 | auth = self.headers.getheader('Authorization')[6:] 78 | arr = base64.decodestring(auth).split(":", 2) 79 | if len(arr) == 2: 80 | name = arr[0] 81 | password= arr[1] 82 | if karma.get(name) and karma[name] == password: 83 | authed = True 84 | if not authed: 85 | self.do_AUTHHEAD() 86 | self.wfile.write('Wrong user or pass received') 87 | return 88 | path_info = self.path.replace("/steve/admin", "", 1) 89 | os.chdir(path + "/www/cgi-bin") 90 | self.cgi_info = ("/", "rest_admin.py" + path_info) 91 | self.run_cgi() 92 | return 93 | elif self.path.startswith("/steve/voter"): 94 | path_info = self.path.replace("/steve/voter", "", 1) 95 | os.chdir(path + "/www/cgi-bin") 96 | self.cgi_info = ("/", "rest_voter.py" + path_info) 97 | self.run_cgi() 98 | return 99 | else: 100 | os.chdir(path) 101 | self.path = "/www/htdocs" + self.path 102 | print(self.path) 103 | handler.do_GET(self) 104 | 105 | except Exception as err: 106 | doTraceBack() 107 | 108 | def do_POST(self): 109 | self.do_GET() #Same diff, eh... 110 | 111 | class ThreadedHTTPServer(ThreadingMixIn, server): 112 | """Moomins live here""" 113 | 114 | 115 | if __name__ == '__main__': 116 | server = ThreadedHTTPServer(('', portno), pysteveHTTPHandler) 117 | print("Running at http://youriphere:%u/ ..." % portno) 118 | server.serve_forever() -------------------------------------------------------------------------------- /pysteve/steve.cfg: -------------------------------------------------------------------------------- 1 | [general] 2 | 3 | homedir: /home/voter 4 | rooturl: http://demo.stv.website 5 | lurk: yes 6 | 7 | 8 | [database] 9 | # Change dbsys to "file" to use file-based db 10 | # Change dbsys to "elasticsearch" to use ES instead of file-based db 11 | dbsys: sqlite 12 | 13 | [sqlite] 14 | database: /opt/steve/voting.db 15 | 16 | #[elasticsearch] 17 | #host: localhost 18 | #uri: 19 | #secure: false 20 | #port: 9200 21 | 22 | 23 | [karma] 24 | admin: 5 25 | chairman: 4 26 | monitorperson: 3 27 | 28 | 29 | [email] 30 | sender: no-reply@demo.stv.website 31 | signature: Apache STeVe 32 | mta: localhost 33 | -------------------------------------------------------------------------------- /pysteve/tests/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one or more 4 | # contributor license agreements. See the NOTICE file distributed with 5 | # this work for additional information regarding copyright ownership. 6 | # The ASF licenses this file to You under the Apache License, Version 2.0 7 | # (the "License"); you may not use this file except in compliance with 8 | # the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | # Jump to parent dir for imports 20 | import sys 21 | import os.path 22 | import ConfigParser as configparser 23 | import argparse 24 | 25 | parser = argparse.ArgumentParser(description='Command line options.') 26 | parser.add_argument('--nodb', dest='nodb', action='store_true', 27 | help="Only perform library test, don't use any database") 28 | args = parser.parse_args() 29 | 30 | 31 | sys.path.append( 32 | os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 33 | 34 | # Fetch config 35 | config = configparser.RawConfigParser() 36 | config.read(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)) + '/steve.cfg') 37 | 38 | # Some quick paths 39 | homedir = config.get("general", "homedir") 40 | if args.nodb: 41 | config.set("database", "disabled", "true") 42 | 43 | from lib import constants 44 | 45 | crashed = 0 46 | failed = 0 47 | okay = 0 48 | 49 | print("Testing vote types...") 50 | 51 | 52 | # Validation functions inside vote types 53 | for t in constants.VOTE_TYPES: 54 | sys.stdout.write("Testing %s's validation function with an invalid vote..." % t['key']) 55 | try: 56 | # All vote functions should reject this vote as invalid 57 | if t['validate_func']("blablabla", {'type': t['key'], 'candidates': ['candidate 1', 'candidate 2']}) != None: 58 | print("Okay") 59 | okay += 1 60 | else: 61 | print("Borked?") 62 | failed += 1 63 | 64 | # All except COP votes will accept this 'a' vote 65 | if t['key'].find("cop") == -1: 66 | sys.stdout.write("Testing %s's validation function with a valid vote..." % t['key']) 67 | if t['validate_func']("a", {'type': t['key'], 'candidates': ['candidate 1', 'candidate 2']}) == None: 68 | print("Okay") 69 | okay += 1 70 | else: 71 | print("Borked?") 72 | failed += 1 73 | 74 | except Exception as err: 75 | print("CRASHED: %s" % err) 76 | failed += 0 77 | 78 | 79 | print("\n\n--------------------------") 80 | print("%4u tests were successful" % okay) 81 | print("%4u tests failed" % failed) 82 | print("%4u tests crashed python" % crashed) 83 | print("--------------------------") 84 | 85 | if crashed > 0: 86 | sys.exit(-2) 87 | if failed > 0: 88 | sys.exit(-1) 89 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/admin/add_issue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Apache STeVe: Add issue 10 | 11 | 12 | 13 | 14 | 18 |

19 | 20 |

21 |
22 | Back to election editing front page 23 |

Add a new issue:

24 |

25 | Not sure what the various voting methods are or how they work? 26 | Check out our vote FAQ! 27 |

28 |

29 |
30 | Base data 31 |
32 |
Issue ID:
33 |
34 |
35 |
36 |
Issue title:
37 |
38 |
39 |
40 |
Issue type:
41 | 45 |
46 |
47 |
48 |
Description of issue:
49 | 50 |
51 | 61 | 68 |
69 |   70 | 71 |
72 |
73 |
74 |

75 | Powered by Apache STeVe. 76 | Copyright 2016, the Apache Software Foundation. 77 | Licensed under the Apache License 2.0 78 |

79 | 80 | 81 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/admin/close.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Apache STeVe: Close election 10 | 11 | 12 | 13 | 14 | 15 |
16 |

17 | 18 |

19 |
20 | Back to election editing front page 21 |

Close election:

22 | 23 |

24 | Are you sure you wish to mark this elecion as closed? 25 | If you do, no further votes can be cast!

26 | Be advised: When you close the election, the monitors will be advised and have their final checksums sent. 27 |

28 | Close election! 29 |

30 | 31 |
32 | 33 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/admin/create_election.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Apache STeVe: Create a new election 10 | 11 | 12 | 13 | 14 | 15 |
16 |

17 | 18 |

19 |
20 | Back to adminstration front page 21 |

Create a new election:

22 |
23 |
24 | Base data 25 |
26 |
ID of election:
27 | You can enter your own EID if you like, for instance foo-election-2015.
28 |
29 |
30 |
Name of election:
31 | This is the official title of the election
32 |
33 |
34 |
Election type:
35 | 39 |
40 |
41 |
Owner of election (UID):
42 | Owner of the election, as found in the karma section of steve.cfg
43 |
44 |
45 |
Election monitors (emails):
46 | Comma-separated list of email addresses.
This CANNOT be changed once an election has been created.
47 |
48 |
49 |
50 | Optional information 51 |
52 |
Date election opens:
53 |
56 |
57 |
58 |
Date election ends:
59 |
62 |
63 |
64 |
65 | 66 |
67 |
68 |
69 |

70 | Powered by Apache STeVe. 71 | Copyright 2016, the Apache Software Foundation. 72 | Licensed under the Apache License 2.0 73 |

74 | 75 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/admin/edit_basedata.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Apache STeVe: Edit election base data 10 | 11 | 12 | 13 | 14 | 15 |
16 |

17 | 18 |

19 |
20 | Back to election editing front page 21 |

Edit election basedata:

22 | 23 | 24 |
25 |
26 | Loading issue, please wait... 27 |
28 | 29 |
30 |

31 | Powered by Apache STeVe. 32 | Copyright 2016, the Apache Software Foundation. 33 | Licensed under the Apache License 2.0 34 |

35 | 36 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/admin/edit_election.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Apache STeVe: Edit election 10 | 11 | 12 | 13 | 14 | 15 |
16 |

17 | 18 |

19 |
20 | Back to adminstration front page 21 |

Edit an election:

22 | 23 |
24 | General options 25 | 28 | 29 | Invite people 30 | 31 | 32 | 35 | 36 | Add issue 37 | 38 | 39 | 41 | 42 | Close election 43 | 44 | 45 | 47 | 48 | Edit base data 49 | 50 | 51 | 53 | 54 | View election 55 | 56 | 57 | 59 | 60 | Tally votes 61 | 62 | 63 | 65 | 66 | Cue monitors 67 | 68 |
69 |
70 |
71 |

Issues in this election (click to edit):

72 |
    73 | 74 |
75 |
76 | 77 |
78 |

79 | Powered by Apache STeVe. 80 | Copyright 2016, the Apache Software Foundation. 81 | Licensed under the Apache License 2.0 82 |

83 | 84 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/admin/edit_issue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Apache STeVe: Edit an issue 10 | 11 | 12 | 13 | 14 | 15 |
16 |

17 | 18 |

19 |
20 | Back to election editing front page 21 | Delete issue 22 |

Edit an issue:

23 |

24 | Not sure what the various voting methods are or how they work? 25 | Check out our vote FAQ! 26 |

27 | 28 |
29 |
30 | Loading issue, please wait... 31 |
32 | 33 |
34 |

35 | Powered by Apache STeVe. 36 | Copyright 2016, the Apache Software Foundation. 37 | Licensed under the Apache License 2.0 38 |

39 | 40 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/admin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Apache STeVe: Administration 10 | 11 | 12 | 13 | 14 | 15 |
16 |

17 | 18 |

19 |
20 |

STeVe administration

21 | Create a new election
22 |
23 | 24 |
25 |
26 |

27 | Powered by Apache STeVe. 28 | Copyright 2016, the Apache Software Foundation. 29 | Licensed under the Apache License 2.0 30 |

31 | 32 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/admin/invite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 54 | Apache STeVe: Invite people 55 | 56 | 57 | 58 | 59 | 60 |

61 | 62 |

63 |
64 | Back to election editing 65 |
66 | Invite one or more users to this election 67 |
68 |
Invitation type:
69 | 73 |
74 |
75 |
Invitation type:
76 | 79 |
80 |
81 |
Email address(es) to invite
(one per line, use email proxypersonname for proxy votes):
82 | 83 |
84 |
85 | 86 |
87 |
88 |

89 | Powered by Apache STeVe. 90 | Copyright 2016, the Apache Software Foundation. 91 | Licensed under the Apache License 2.0 92 |

93 | 94 | 95 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/admin/reopen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Apache STeVe: Reopen election 10 | 11 | 12 | 13 | 14 | 15 |
16 |

17 | 18 |

19 |
20 | Back to election editing front page 21 |

Reopen election:

22 | 23 |

24 | Are you sure you wish to mark this election as reopened? 25 |



26 | Reopen election! 27 |

28 | 29 |
30 | 31 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/admin/tally.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Apache STeVe: Tally votes 10 | 11 | 12 | 13 | 14 | 15 |
16 |

17 | 18 |

19 |
20 | Back to election editing front page 21 |

Tally votes:

22 | 23 |
24 |
25 | Loading issues, please wait... 26 |
27 | 28 |
29 | 30 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/ballot_ap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Apache STeVe: ASF PMC style vote 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |

20 | 21 |

22 | Back to election front page 23 |
24 |

25 | This is a standard ASF PMC vote with binding and non-binding votes. 26 | If you agree with the motion put forward, vote Yes. If you disagree, vote No. 27 | If you neither agree nor disagree, you can mark your ballot as blank by clicking on Abstain. F 28 | or some elections where the number of votes 29 | are important, abstaining on a vote you have no preference for/against can help reach quorum. 30 |

31 |
32 | 33 |
34 | Loading issue, please wait... 35 |
36 |
37 |
38 |

39 | Powered by Apache STeVe. 40 | Copyright 2016, the Apache Software Foundation. 41 | Licensed under the Apache License 2.0 42 |

43 | 44 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/ballot_cop.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Apache STeVe: Candidate or Party vote 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |

20 | 21 |

22 | Back to election front page 23 |
24 |

25 | This is a standard Candidate or Party vote. 26 | To vote, click on the candidate or party of your choice and then click on Cast vote. 27 | Should you reconsider later, you can always recast your vote for as long as the election remains open. 28 |

29 |
30 | 31 |
32 | Loading issue, please wait... 33 |
34 |
35 |
36 |

37 | Powered by Apache STeVe. 38 | Copyright 2016, the Apache Software Foundation. 39 | Licensed under the Apache License 2.0 40 |

41 | 42 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/ballot_dh.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Apache STeVe: D'Hondt vote 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |

20 | 21 |

22 | Back to election front page 23 |

24 |
25 |

26 | This is a standard D'Hondt (Jefferson) based vote with N candidates and 27 | N seats available. Select the candidate/party you would like to vote for and click Cast vote to cast your vote. 28 | You can change your vote as often as you like, should you reconsider your choice. 29 |

30 |
31 | 32 |
33 | Loading issue, please wait... 34 |
35 |
36 |
37 |

38 | Powered by Apache STeVe. 39 | Copyright 2016, the Apache Software Foundation. 40 | Licensed under the Apache License 2.0 41 |

42 | 43 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/ballot_fic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Apache STeVe: FiC Voting 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |

18 | 19 |

20 | Back to election front page 21 |
22 |

23 |

24 | This is a First in Class Vote ballot with N candidates and 25 | N points available. 26 | All the nominees are placed in random order on the candidate list. 27 | If the canidate has prepared a statement, you can view it by clicking on the 28 | statement link to the right of the candidate's name. 29 |

30 |

31 | How to vote:
32 | Drag a candidate from the candidate list to the ballot box to place them in 33 | the vote. Place the person you wish to give the highest score at the top, 34 | then the person with the second highest score and so on. 35 | You can rearrange your votes as you see fit, by dragging 36 | candidates up/down on the ballot box list. You may only votes for as many 37 | canidates as there are points to give out. If you want to 38 | remove a single candidate from your ballot box, simply drag the 39 | canidate back to the list of remaining candidates to the left. 40 |

41 |
42 | 43 |
44 | Loading issues, please wait... 45 |
46 |
47 |
48 |

49 | Powered by Apache STeVe. 50 | Copyright 2016, the Apache Software Foundation. 51 | Licensed under the Apache License 2.0 52 |

53 | 54 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/ballot_fpp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Apache STeVe: D'Hondt vote 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |

20 | 21 |

22 | Back to election front page 23 |

24 |
25 |

26 | This is a standard First Past the Post election. The person/party with the most votes wins the election. 27 | To vote, click on the candidate of your choice and then click on Cast vote. 28 | Should you reconsider later, you can always recast your vote for as long as the election remains open. 29 |

30 |
31 | 32 |
33 | Loading issue, please wait... 34 |
35 |
36 |
37 |

38 | Powered by Apache STeVe. 39 | Copyright 2016, the Apache Software Foundation. 40 | Licensed under the Apache License 2.0 41 |

42 | 43 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/ballot_mntv.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Apache STeVe: MNTV Voting 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |

18 | 19 |

20 | Back to election front page 21 |
22 |

23 |

24 | This is a Multiple Non-Transferable Vote ballot with N candidates and 25 | N seats available. 26 | All the nominees are placed in random order on the candidate list. 27 | If the canidate has prepared a statement, you can view it by clicking on the 28 | statement link to the right of the candidate's name. 29 |

30 |

31 | How to vote:
32 | Drag a candidate from the candidate list to the ballot box to place them in 33 | the vote. You can rearrange your votes as you see fit, by dragging 34 | candidates up/down on the ballot box list. You may only votes for as many 35 | canidates as there are seats. If you want to 36 | remove a single candidate from your ballot box, simply drag the 37 | canidate back to the list of remaining candidates to the left. 38 |

39 |
40 | 41 |
42 | Loading issues, please wait... 43 |
44 |
45 |
46 |

47 | Powered by Apache STeVe. 48 | Copyright 2016, the Apache Software Foundation. 49 | Licensed under the Apache License 2.0 50 |

51 | 52 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/ballot_stv.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Apache STeVe: STV Voting 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |

18 | 19 |

20 | Back to election front page 21 |
22 |

23 |

24 | This is a ballot with N candidates and 25 | N seats available. 26 | The red line denotes the cutaway, should all your choices be voted in. 27 | All the nominees are placed in random order on the candidate list. 28 | If the candidate has prepared a statement, you can view it by clicking on the 29 | statement link to the right of the candidate's name. 30 |

31 |

32 | How to vote:
33 | Drag a candidate from the candidate list to the ballot box to place them in 34 | the vote. ORDER MATTERS: Put your candidates in the order of your preference, 35 | with the first preference at the top, the second below that and so on. 36 | You can rearrange your votes as you see fit, by dragging 37 | candidates up/down on the ballot box list. You may place as many candidates 38 | in the ballot box as you see fit. If you want to 39 | remove a single candidate from your ballot box, simply drag the 40 | candidate back to the list of remaining candidates to the left. 41 |

42 |
43 | 44 |
45 | Loading issues, please wait... 46 |
47 |
48 |
49 |

50 | Powered by Apache STeVe. 51 | Copyright 2016, the Apache Software Foundation. 52 | Licensed under the Apache License 2.0 53 |

54 | 55 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/ballot_yna.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Apache STeVe: YNA vote 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |

20 | 21 |

22 | Back to election front page 23 |
24 |

25 | This is a standard YNA (Yes/No/Abstain) vote. If you agree with the motion put forward, vote Yes. If you disagree, vote No. 26 | If you neither agree nor disagree, you can mark your ballot as blank by clicking on Abstain. For some elections where the number of votes 27 | are important, abstaining on a vote you have no preference for/against can help reach quorum. 28 |

29 |
30 | 31 |
32 | Loading issue, please wait... 33 |
34 |
35 |
36 |

37 | Powered by Apache STeVe. 38 | Copyright 2016, the Apache Software Foundation. 39 | Licensed under the Apache License 2.0 40 |

41 | 42 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/bulk_yna.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Apache STeVe: Bulk voting 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

19 | 20 |

21 | Back to election front page 22 |
23 |

24 | Here you can perform a quick vote for each of the yes/no issues in this 25 | election. Click on yes, no or abstain to cast your vote. Your vote is cast 26 | and registered once you click the respective button, and can be changed at 27 | any time, as long as the election is still open. When you vote, an icon will 28 | appear next to the vote options to indicate the vote that was recorded by 29 | the system. 30 |

31 |

When you click on a button, your vote is registered immediately. There is no 'done' button here.
Also note that this page only lists Yes/No/Abstain issues. There may be other issues in need of your vote on the main election page.

32 |
33 | 34 |
35 | Loading issues, please wait... 36 |
37 |
38 |
39 |

40 | Powered by Apache STeVe. 41 | Copyright 2016, the Apache Software Foundation. 42 | Licensed under the Apache License 2.0 43 |

44 | 45 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/css/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/pysteve/www/htdocs/css/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /pysteve/www/htdocs/election.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Apache STeVe: Election viewer 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

19 | 20 |

21 |
22 | 23 |
24 | 25 |
26 | Loading issues, please wait... 27 |
28 |
29 |
30 |

31 | Powered by Apache STeVe. 32 | Copyright 2016, the Apache Software Foundation. 33 | Licensed under the Apache License 2.0 34 |

35 | 36 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/pysteve/www/htdocs/favicon.ico -------------------------------------------------------------------------------- /pysteve/www/htdocs/images/ballot_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/pysteve/www/htdocs/images/ballot_bg.png -------------------------------------------------------------------------------- /pysteve/www/htdocs/images/dragleft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/pysteve/www/htdocs/images/dragleft.png -------------------------------------------------------------------------------- /pysteve/www/htdocs/images/dragright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/pysteve/www/htdocs/images/dragright.png -------------------------------------------------------------------------------- /pysteve/www/htdocs/images/icon_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/pysteve/www/htdocs/images/icon_add.png -------------------------------------------------------------------------------- /pysteve/www/htdocs/images/icon_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/pysteve/www/htdocs/images/icon_close.png -------------------------------------------------------------------------------- /pysteve/www/htdocs/images/icon_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/pysteve/www/htdocs/images/icon_delete.png -------------------------------------------------------------------------------- /pysteve/www/htdocs/images/icon_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/pysteve/www/htdocs/images/icon_edit.png -------------------------------------------------------------------------------- /pysteve/www/htdocs/images/icon_invite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/pysteve/www/htdocs/images/icon_invite.png -------------------------------------------------------------------------------- /pysteve/www/htdocs/images/icon_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/pysteve/www/htdocs/images/icon_view.png -------------------------------------------------------------------------------- /pysteve/www/htdocs/images/steve_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/pysteve/www/htdocs/images/steve_large.png -------------------------------------------------------------------------------- /pysteve/www/htdocs/images/steve_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/pysteve/www/htdocs/images/steve_logo.png -------------------------------------------------------------------------------- /pysteve/www/htdocs/images/steve_spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/pysteve/www/htdocs/images/steve_spinner.gif -------------------------------------------------------------------------------- /pysteve/www/htdocs/images/target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/pysteve/www/htdocs/images/target.png -------------------------------------------------------------------------------- /pysteve/www/htdocs/images/vote_a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/pysteve/www/htdocs/images/vote_a.png -------------------------------------------------------------------------------- /pysteve/www/htdocs/images/vote_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/pysteve/www/htdocs/images/vote_n.png -------------------------------------------------------------------------------- /pysteve/www/htdocs/images/vote_y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/pysteve/www/htdocs/images/vote_y.png -------------------------------------------------------------------------------- /pysteve/www/htdocs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Apache STeVe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

13 | 14 |

15 | 16 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/js/steve_ap.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one or more 3 | contributor license agreements. See the NOTICE file distributed with 4 | this work for additional information regarding copyright ownership. 5 | The ASF licenses this file to You under the Apache License, Version 2.0 6 | (the "License"); you may not use this file except in compliance with 7 | the License. You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | function displayIssueAP(code, response, state) { 19 | election_data = response 20 | var obj = document.getElementById('preloaderWrapper') 21 | obj.setAttribute("id", "ynavote") 22 | if (code != 200) { 23 | obj.innerHTML = "

Could not load issue:

" + response.message + "

"; 24 | } else { 25 | obj.innerHTML = "" 26 | 27 | var title = document.createElement('h2') 28 | title.innerHTML = response.issue.title; 29 | obj.appendChild(title) 30 | 31 | obj.appendChild(keyvaluepairText("nominatedby", "Put forward (nominated) by:", response.issue.nominatedby)) 32 | obj.appendChild(keyvaluepairText("seconds", "Seconded by:", response.issue.seconds.length > 0 ? response.issue.seconds.join(", ") : "no-one" )) 33 | 34 | var desc = document.createElement('pre') 35 | desc.setAttribute("class", "statement") 36 | desc.innerHTML = response.issue.description 37 | obj.appendChild(desc) 38 | 39 | var outer = document.createElement('div') 40 | outer.setAttribute("class", "issueListItemWide") 41 | 42 | var byes = document.createElement('input') 43 | byes.setAttribute("type", "button") 44 | byes.setAttribute("value", "Binding Yes (+1)") 45 | byes.setAttribute("class", "btn-green") 46 | byes.setAttribute("style", "float: right;"); 47 | byes.setAttribute("onclick", "castSingleVote('by');") 48 | 49 | var yes = document.createElement('input') 50 | yes.setAttribute("type", "button") 51 | yes.setAttribute("value", "Yes (+1)") 52 | yes.setAttribute("class", "btn-green") 53 | yes.setAttribute("style", "float: right;"); 54 | yes.setAttribute("onclick", "castSingleVote('y');") 55 | 56 | var no = document.createElement('input') 57 | no.setAttribute("type", "button") 58 | no.setAttribute("value", "No (-1)") 59 | no.setAttribute("class", "btn-red") 60 | no.setAttribute("style", " float: right;"); 61 | no.setAttribute("onclick", "castSingleVote('n');") 62 | 63 | var bno = document.createElement('input') 64 | bno.setAttribute("type", "button") 65 | bno.setAttribute("value", "Binding No (-1)") 66 | bno.setAttribute("class", "btn-red") 67 | bno.setAttribute("style", " float: right;"); 68 | bno.setAttribute("onclick", "castSingleVote('bn');") 69 | 70 | var abstain = document.createElement('input') 71 | abstain.setAttribute("type", "button") 72 | abstain.setAttribute("value", "Abstain (0)") 73 | abstain.setAttribute("class", "btn-yellow") 74 | abstain.setAttribute("style", "float: right;"); 75 | abstain.setAttribute("onclick", "castSingleVote('a');") 76 | 77 | var p = document.createElement('p') 78 | p.innerHTML = "Cast your vote by clicking on the respective button below. You may recast your vote as many time as you like, should you reconsider." 79 | 80 | obj.appendChild(p) 81 | outer.appendChild(bno) 82 | outer.appendChild(no) 83 | outer.appendChild(abstain) 84 | outer.appendChild(yes) 85 | outer.appendChild(byes) 86 | 87 | obj.appendChild(outer) 88 | } 89 | } 90 | 91 | function loadIssue(election, issue, uid, callback) { 92 | 93 | var messages = ["Herding cats...", "Shaving yaks...", "Shooing some cows away...", "Fetching election data...", "Loading issues..."] 94 | if (!election || !uid) { 95 | var l = document.location.search.substr(1).split("/"); 96 | election = l[0]; 97 | issue = l.length > 1 ? l[l.length-2] : ""; 98 | uid = l.length > 2 ? l[l.length-1] : ""; 99 | } 100 | if (step == -1) { 101 | getJSON("/steve/voter/view/" + election + "/" + issue + "?uid=" + uid, [election, issue, uid], callback) 102 | } 103 | 104 | var obj = document.getElementById('preloader'); 105 | step++; 106 | if (!election_data && obj) { 107 | if (step % 2 == 1) obj.innerHTML = messages[parseInt(Math.random()*messages.length-0.01)] 108 | } else if (obj && (step % 2 == 1)) { 109 | obj.innerHTML = "Ready..!" 110 | } 111 | if (step % 2 == 1) { 112 | obj.style.transform = "translate(0,0)" 113 | } else if (obj) { 114 | obj.style.transform = "translate(0,-500%)" 115 | } 116 | if (!election_data|| (step % 2 == 0) ) { 117 | window.setTimeout(loadElection, 750, election, uid, callback); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/monitor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Apache STeVe: Election monitor 7 | 8 | 9 | 10 | 11 | 12 |
13 |

14 | 15 |

16 |
17 | 18 |

Election monitor:

19 |

20 | Leave this page open during the entire election. There is no need to refresh the page, it does that by itself.
21 | If you disconnect from the Internet or hibernate your machine (or close this page), it will resume once the connection is re-established, but 22 | should you decide to do this, some debug data could theoretically be lost.
23 |

24 | 25 |

Timestamps shown on this page are browser-local (your computer's timezone).

26 | 27 |
28 |
29 | Loading issues, please wait... 30 |
31 | 32 |
33 |

34 | Powered by Apache STeVe. 35 | Copyright 2016, the Apache Software Foundation. 36 | Licensed under the Apache License 2.0 37 |

38 | 39 | -------------------------------------------------------------------------------- /pysteve/www/htdocs/request_link.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 24 | Apache STeVe: Request link 25 | 26 | 27 | 28 | 29 | 30 |

31 | 32 |

33 |
34 |

Request vote link:

35 |

36 | In open elections, anyone can request a voter ID and participate. 37 | To receive a voter ID, please enter your email address in the field below and click 'Request link'. 38 | An email will be sent to you with your own personalized voter ID. 39 | With this ID, you may vote as many times as you wish on any of the issues in this election, 40 | however only the last vote you cast on any given issue will be your final vote. 41 |

42 |
43 |
Email address:
44 |
45 | 46 | 47 |
48 |
49 | 50 |
51 |

52 | Powered by Apache STeVe. 53 | Copyright 2016, the Apache Software Foundation. 54 | Licensed under the Apache License 2.0 55 |

56 | 57 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | argparse 2 | nose 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # 20 | 21 | from io import open 22 | import subprocess 23 | import sys 24 | 25 | from setuptools import find_packages, setup, Command, os 26 | 27 | 28 | VERSION = '1.0' 29 | 30 | 31 | class doc(Command): 32 | description = 'generate or test documentation' 33 | user_options = [('test', 't', 'run doctests instead of generating documentation')] 34 | boolean_options = ['test'] 35 | 36 | def initialize_options(self): 37 | self.test = False 38 | 39 | def finalize_options(self): 40 | pass 41 | 42 | def run(self): 43 | if self.test: 44 | path = 'docs/build/doctest' 45 | mode = 'doctest' 46 | else: 47 | path = 'docs/build/%s' % VERSION 48 | mode = 'html' 49 | 50 | try: 51 | os.makedirs(path) 52 | except OSError: 53 | pass 54 | 55 | status = subprocess.call(['sphinx-build', '-E', '-b', mode, '-d', 'docs/build/doctrees', 'docs/source', path]) 56 | 57 | if status: 58 | raise RuntimeError('documentation step "%s" failed' % (mode,)) 59 | 60 | sys.stdout.write('\nDocumentation step "%s" performed, results here:\n' 61 | ' %s/\n' % (mode, path)) 62 | 63 | 64 | class test(Command): 65 | description = 'run nosetests' 66 | user_options = [('verbose', 'v', 'run nosetests with -v option')] 67 | boolean_options = ['verbose'] 68 | 69 | def initialize_options(self): 70 | self.verbose = False 71 | 72 | def finalize_options(self): 73 | pass 74 | 75 | def run(self): 76 | if self.verbose: 77 | verbose = '-v' 78 | else: 79 | verbose = '' 80 | 81 | status = subprocess.call(['nosetests', verbose]) 82 | 83 | if status: 84 | raise RuntimeError('nosetests step failed') 85 | 86 | 87 | with open('requirements.txt') as f: 88 | install_requires = f.read().splitlines() 89 | 90 | tests_requires = install_requires + [ 91 | 'nose >= 1.2', 92 | ] 93 | 94 | setup( 95 | name='apache-steve', 96 | version=VERSION, 97 | url='https://svn.apache.org/repos/asf/steve', 98 | license='Apache Software License (http://www.apache.org/licenses/LICENSE-2.0)', 99 | author='Apache Software Foundation', 100 | author_email='dev@steve.apache.org', 101 | description='Apache Steve', 102 | # don't ever depend on refcounting to close files anywhere else 103 | long_description=open('README', encoding='utf-8').read(), 104 | 105 | # scripts=["bin/votegroup"], 106 | 107 | # namespace_packages=['steve'], 108 | package_dir={'': 'lib'}, 109 | packages=find_packages('lib'), 110 | 111 | zip_safe=False, 112 | platforms='any', 113 | install_requires=install_requires, 114 | 115 | tests_require=tests_requires, 116 | test_suite='nose.collector', 117 | 118 | classifiers=[ 119 | 'Intended Audience :: Developers', 120 | 'Intended Audience :: System Administrators', 121 | 'License :: OSI Approved :: Apache Software License', 122 | 'Operating System :: OS Independent', 123 | 'Programming Language :: Python', 124 | 'Programming Language :: Python :: 2.6', 125 | 'Programming Language :: Python :: 2.7', 126 | 'Topic :: Software Development :: Libraries :: Python Modules' 127 | ], 128 | cmdclass={'doc': doc, 'test': test}, 129 | ) 130 | -------------------------------------------------------------------------------- /site/css/steve.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | background-color: #F4F4F4; 4 | font-family: sans-serif; 5 | height: 100%; 6 | min-height: 100%; 7 | } 8 | 9 | #main_splash { 10 | width: 1200px; 11 | background-image: url(../images/front-splash.png); 12 | background-position-y: 48px; 13 | background-repeat: no-repeat; 14 | margin: 0 auto; 15 | background-color: #FFF; 16 | box-shadow: 0px 0px 3px #0006; 17 | position: relative; 18 | padding-top: 700px; 19 | } 20 | 21 | #main { 22 | width: 1200px; 23 | min-height: 100%; 24 | margin: 0 auto; 25 | background-color: #FFF; 26 | box-shadow: 0px 0px 3px #0006; 27 | position: relative; 28 | } 29 | 30 | nav { 31 | position: absolute; 32 | top: 0px; 33 | text-align: right; 34 | width: calc(100% - 20px); 35 | margin-right: 20px; 36 | background-image: url(../images/logo-bright.png); 37 | background-size: 100px; 38 | background-repeat: no-repeat; 39 | background-position: 5px 5px; 40 | } 41 | 42 | nav > ul { 43 | list-style: none; 44 | display: inline-block; 45 | } 46 | nav > ul > li { 47 | display: inline-block; 48 | color: #444; 49 | font-weight: bold; 50 | text-transform: uppercase; 51 | margin-left: 24px; 52 | font-size: 0.8rem; 53 | } 54 | 55 | nav > ul > li > a { 56 | color: #444; 57 | font-width: bold; 58 | text-decoration: none; 59 | } 60 | 61 | nav > ul > li > a:hover { 62 | color: #222; 63 | font-width: bold; 64 | text-decoration: underline; 65 | } 66 | 67 | #contents { 68 | padding: 20px; 69 | } 70 | 71 | #main > #contents { 72 | padding-top: 80px; 73 | } 74 | 75 | footer { 76 | border-top: 1px solid #3336; 77 | font-size: 0.75rem; 78 | text-align: left; 79 | padding: 16px; 80 | } 81 | -------------------------------------------------------------------------------- /site/images/front-splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/site/images/front-splash.png -------------------------------------------------------------------------------- /site/images/logo-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/site/images/logo-bright.png -------------------------------------------------------------------------------- /site/images/steve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/site/images/steve.png -------------------------------------------------------------------------------- /site/images/tat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/site/images/tat.png -------------------------------------------------------------------------------- /site/js/empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/site/js/empty -------------------------------------------------------------------------------- /site/pages/community.md: -------------------------------------------------------------------------------- 1 | Title: Community and Support 2 | 3 | Simple support. 4 | 5 | ## Mailing Lists 6 | 7 | * [Users](mailto:user@steve.apache.org) ([Subscribe](mailto:user-subscribe@steve.apache.org), [Unsubscribe](mailto:user-unsubscribe@steve.apache.org)) 8 | * [Developers](mailto:dev@steve.apache.org) ([Subscribe](mailto:dev-subscribe@steve.apache.org), [Unsubscribe](mailto:dev-unsubscribe@steve.apache.org)) 9 | * [Commits](mailto:commits@steve.apache.org) ([Subscribe](mailto:commits-subscribe@steve.apache.org), [Unsubscribe](mailto:commits-unsubscribe@steve.apache.org)) 10 | * [Issues](mailto:issues@steve.apache.org) ([Subscribe](mailto:issues-subscribe@steve.apache.org), [Unsubscribe](mailto:issues-unsubscribe@steve.apache.org)) 11 | 12 | 13 | 14 | ## Archives 15 | * [users](http://steve.markmail.org/search/?q=#query:%20list%3Aorg.apache.steve.user) 16 | * [development](http://steve.markmail.org/search/?q=#query:%20list%3Aorg.apache.steve.dev) 17 | * [commits](http://steve.markmail.org/search/?q=#query:%20list%3Aorg.apache.steve.commits) 18 | * [issues](http://steve.markmail.org/search/?q=#query:%20list%3Aorg.apache.steve.issues) 19 | 20 | 21 | ## JIRA 22 | 23 | * [STEVE](https://issues.apache.org/jira/browse/STEVE) 24 | 25 | 26 | ## Contributing 27 | 28 | Contributing to the project is a great way to support the community. Some great links for getting involved 29 | 30 | - [Source Code](dev/source-code.html) 31 | - [Contribution Tips](dev/contribution-tips.html) 32 | - [Developer Documentation](dev/index.html) 33 | - [IRC](http://webchat.freenode.net/?channels=apache-steve): #apache-steve on freenode. 34 | 35 | ## PMC Members 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 |
Jim Jagielski PMC Member/Committer
Chris Mattmann PMC Member/Committer
Sam Ruby PMC Member/Committer
Greg Stein PMC Member/Committer
Alan Cabrera PMC Member/Committer
Daniel Gruno PMC Member/Committer (chair)
Sean KellyPMC Member/Committer
Rich BowenPMC Member/Committer
73 | 74 | ## Committers 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 |
Pierre SmitsCommitter
84 | -------------------------------------------------------------------------------- /site/pages/demo.md: -------------------------------------------------------------------------------- 1 | Title: Quickstart Guide 2 | 3 | **TODO**: rework this for our new git-based repository. 4 | 5 | ---- 6 | 7 | ## pySTeVe 8 | 9 | ### Quickstart Guide 10 | 11 | * `svn co https://svn.apache.org/repos/asf/steve/trunk/pysteve/` 12 | * Edit `steve.cfg` to suit your needs (karma, DB backend etc) 13 | * IF you choose ElasticSearch as backend, install the python module (pip install elasticsearch) 14 | * OR IF you choose files as your backend, run setup.py in the CLI directory. 15 | * Edit `httpd.conf`, add it to your existing httpd configuration 16 | * Set up authorization using htpasswd for admins, monitors etc 17 | * Go to `http://steve.example.org/admin` and set up an election 18 | * Start voting! 19 | 20 | 21 | ### Building a Docker image 22 | 23 | You can also build pySTeVe as a Docker image using the Dockerfile locate in the `docker` directory: 24 | 25 | * `svn co https://svn.apache.org/repos/asf/steve/trunk/pysteve/` 26 | * `docker build -t pysteve docker/` 27 | * `docker run -i -p 127.0.0.1:80:80 pysteve` 28 | * Navigate to `http://localhost/admin` to set up stuff, using the default credentials (admin/demo) 29 | -------------------------------------------------------------------------------- /site/pages/dev/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /site/pages/dev/source-code.mdtext: -------------------------------------------------------------------------------- 1 | Title: Source Code 2 | 3 | ## pySTeVe 4 | PySTeVe can be browsed via the [web interface](https://svn.apache.org/viewvc/steve/trunk/pysteve/). 5 | 6 | You can check out the source code at: [https://svn.apache.org/repos/asf/steve/trunk/pysteve/](https://svn.apache.org/repos/asf/steve/trunk/pysteve/): 7 | 8 | 9 | $svn co https://svn.apache.org/repos/asf/steve/trunk/pysteve/ 10 | 11 | 12 | PySTeVe requires no building/compiling, but does require a setup, as explained on the [try it out](/demo.html) page. 13 | 14 | 15 | 16 | 17 | ## Older sources 18 | You can browse the source code via the [web interface](http://svn.apache.org/viewvc/steve/trunk/) 19 | . 20 | 21 | You can download (aka checkout) the sources of Apache STeVe with 22 | Subversion client using the following URL [http://svn.apache.org/repos/asf/steve/trunk](http://svn.apache.org/repos/asf/steve/trunk) 23 | . 24 | 25 | Performing the checkout from the command line using the subversion client 26 | is as easy as executing the following command: 27 | > $ svn co [http://svn.apache.org/repos/asf/steve/trunk](http://svn.apache.org/repos/asf/steve/trunk) 28 | 29 | If you are experiencing problems with errors like "400 Bad Request 30 | (http://svn.apache.org)", try using: 31 | > $ svn co [https://svn.apache.org/repos/asf/steve/trunk](https://svn.apache.org/repos/asf/steve/trunk) 32 | 33 | 34 | # Continuous integration 35 | 36 | Apache STeVe continuous integration relies on [Apache Buildbot](http://ci.apache.org/). 37 | All builders are available [from the page](http://ci.apache.org/builders). 38 | 39 | * [Apache STeVe under Ubuntu](http://ci.apache.org/builders/steve-trunk-ubuntu) 40 | * [Apache STeVe under Windows](http://ci.apache.org/builders/steve-trunk-win) 41 | 42 | 43 | # Building 44 | 45 | *TODO fill in* 46 | 47 | -------------------------------------------------------------------------------- /site/pages/documentation.md: -------------------------------------------------------------------------------- 1 | Title: Index 2 | 3 | * [List of implemented vote types](vote_types.html) 4 | * [Getting started with pySTeVe](demo.html) 5 | -------------------------------------------------------------------------------- /site/pages/downloads.md: -------------------------------------------------------------------------------- 1 | title: Downloads 2 | 3 | Apache STeVe has not produced any releases yet. Please ask on our dev@ list. 4 | -------------------------------------------------------------------------------- /site/pages/index.md: -------------------------------------------------------------------------------- 1 | Title: Home 2 | 3 | 4 | Apache STeVe is the ASF's Python-based voting system that the 5 | Foundation uses to handle things like 6 | [voting in our new Board of Directors](https://www.apache.org/foundation/governance/meetings#boardvoting) 7 | 8 | 9 | Apache STeVe supports many types of voting, including (but not limited to): 10 | 11 | * [Single Transferable Votes](vote_types.html#stv) 12 | * [Single-motion voting (Yes/No/Abstain)](vote_types.html#yna) 13 | * [D'Hondt (Jefferson) style voting](vote_types.html#dh) 14 | * [Multiple Non-Transferable Votes](vote_types.html#mntv) 15 | * [First Past the Post (presidential elections)](vote_types.html#fpp) 16 | * [Candidate or Party voting with preferential trickle-down](vote_types.html#cop) 17 | * [First in Class (N-x-points based voting)](vote_types.html#fic) 18 | * [Apache-style Single-motion Voting](vote_types.html#ap) 19 | 20 | 21 | It also supports multiple database backends: 22 | 23 | * File-based 24 | * ElasticSearch 25 | * More to come... 26 | -------------------------------------------------------------------------------- /site/pages/privacy-policy.md: -------------------------------------------------------------------------------- 1 | Title: Privacy Policy 2 | 3 | Information about your use of this website is collected using server access logs and a tracking cookie. The collected information consists of: 4 | 5 | * The IP address from which you access the website 6 | * The type of browser and operating system you use to access our site 7 | * The date and time you access our site 8 | * The pages you visit 9 | * The addresses of pages from where you followed a link to our site 10 | 11 | 12 | We gather part of this information using a tracking cookie set by 13 | the [Google Analytics](http://www.google.com/analytics/) 14 | service and handled by Google, as described in 15 | their [privacy policy](http://www.google.com/policies/privacy/). 16 | See your browser documentation for instructions on how to 17 | disable the cookie if you prefer not to share this data with Google. 18 | 19 | We use the gathered information to help us make our site more useful 20 | to visitors and to better understand how and when our site is used. We 21 | do not track or collect personally identifiable information or 22 | associate the data we collect with any personally identifiable information 23 | from other sources. 24 | 25 | By using this website, you consent to the collection of this data in 26 | the manner and for the purposes described above. 27 | -------------------------------------------------------------------------------- /site/pages/vote_types.md: -------------------------------------------------------------------------------- 1 | Title: Vote Types 2 | 3 | 4 |

Single motion voting (Yes/No/Abstain)

5 | 6 | This is a simple tally vote. Voters can vote either Yes or No on an 7 | issue, or they can abstain. Votes are tallied, and the result is 8 | presented. It is up to the election committee to interpret the result. 9 | 10 | ---- 11 | 12 |

Apache-style Single motion voting (Yes/No/Abstain with binding votes)

13 | 14 | This is a simple tally vote. Voters can vote either Yes or No on an 15 | issue, or they can abstain; however, certain people (committee members, 16 | for instance) may cast binding votes whereas others may only cast 17 | non-binding votes. Votes are tallied, and the result is presented. It 18 | is up to the election committee to interpret the result (for example, are there sufficient 'binding' votes to match any requirement that has been set, regardless of the total number of 'yes' and 'no' votes?). 19 | 20 | ---- 21 | 22 |

First Past the Post (presidential election style)

23 | 24 | FPP is a voting system with multiple candidates. The candidate with 25 | the most votes wins, regardless of whether they received more than 26 | half the votes. 27 | 28 | ---- 29 | 30 | 31 |

Single Transferable Vote

32 | 33 | The single transferable vote (STV) system is designed to achieve 34 | proportional representation through ranked voting in multi-seat 35 | elections. It does so by allowing every voter one vote, that is 36 | transferable to other candidates based on necessity of votes and the 37 | preference of the voter. Thus, if a candidate in an election is voted 38 | in (or in case of a tie), excess votes for that candidate are allocated to other candidates 39 | according to the preference of the voter. STV is designed to minimize 40 | 'wasted votes' in an election by reallocating votes (and thus the 41 | wishes of the voters) proportionally to the priority they stated. 42 | 43 | See the 44 | [Wikipedia article on STV voting](https://en.wikipedia.org/wiki/Single_transferable_vote#Voting) 45 | for more insight into how STV works. 46 | 47 | For calculating results, we use Meek's Method with a quota derived from 48 | the Droop Quota, but with implementation changes such as those 49 | proposed by New Zealand. See 50 | [this paper](http://svn.apache.org/repos/asf/steve/trunk/stv_background/meekm.pdf) 51 | for details. 52 | 53 | ---- 54 | 55 |

D'Hondt (Jefferson) Voting

56 | 57 | The D'Hondt method, also known as the Jefferson method, is a *highest 58 | average* method for calculating proportional representation of parties 59 | at an election. In essence, this is done by calculating a quotient 60 | per party for each number of seats available and finding the highest 61 | values. The quotient is determined as `V/(s+1)` where `V` is the 62 | number of votes received and `s` is the number of seats won. Thus, for 63 | each party, the quotient is calculated for the number of seats 64 | available: 65 | 66 | #### Example result for election with 4 seats: 67 | 68 | | Party | Votes | 1 seat | 2 seats | 3 seats | 4 seats | seats won | 69 | |-------|-------|--------|---------|---------|---------|-----------| 70 | | Gnomes | 25,000 | 25,000/(0+1) = 25,000 | 25,000/(1+1) = 12,500 | 25,000/(2+1) = 8,333 | 25,000/(3+1) = 6,250 | 2 | 71 | | Elves | 15,000 | 15,000/(0+1) = 15,000 | 15,000/(1+1) = 7,500 | 15,000/(2+1) = 5,000 | 15,000/(3+1) = 3,750 | 1 | 72 | | Dwarves | 10,000 | 10,000/(0+1) = 10,000 | 10,000/(1+1) = 5,000 | 10,000/(2+1) = 3,333 | 10,000/(3+1) = 2,500 | 1 | 73 | 74 | 75 | For more information on the D'Hondt Method, see 76 | [this Wikipedia article](https://en.wikipedia.org/wiki/D'Hondt_method) 77 | 78 | ---- 79 | 80 |

Candidate or Party voting with preferential trickle-down

81 | 82 | This information is not yet available. 83 | 84 | ---- 85 | 86 |

First in Class (N-x-points based voting)

87 | 88 | This information is not yet available 89 | 90 | ---- 91 | 92 |

Multiple Non-Transferable Votes

93 | 94 | Multiple Non-Transferable Voting (MNTV) is a group of voting systems in which voters elect several representatives at once, with each voter having more than one vote. MNTV uses multi-member electoral 'districts' or some other term for dividing the eligible voters into coherent groups, or only one district which contains all voters, which is used to provide at-large representation. 95 | -------------------------------------------------------------------------------- /site/test-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Run a local Pelican build of the site, for dev/test. 4 | # 5 | # REQUIRED ENV VARS: 6 | # 7 | # BUILDSITE=/path/to/infrastructure-pelican/bin/buildsite.py 8 | # LIBCMARKDIR=/path/to/cmark-gfm.0.28.3.gfm.12/lib 9 | # 10 | # ### DOCCO on using infra-pel/bin/build-cmark.sh for the lib/ 11 | # 12 | # ALSO NEEDED 13 | # $ pip3 install ezt pelican 14 | # 15 | 16 | THIS_SCRIPT=`realpath "$0"` 17 | SITE_DIR=`dirname "$THIS_SCRIPT"` 18 | ROOT_DIR=`dirname "$SITE_DIR"` 19 | #echo $ROOT_DIR 20 | 21 | cd "$SITE_DIR" 22 | 23 | # export BUILDSITE=~/src/asf/infra-pelican/bin/buildsite.py 24 | # export LIBCMARKDIR=~/src/asf/infra/tools/bintray/cmark-gfm.0.28.3.gfm.12-libs 25 | 26 | $BUILDSITE dir --output=/tmp/steve-site.$$ "--yaml-dir=$ROOT_DIR" "--content-dir=$SITE_DIR" 27 | -------------------------------------------------------------------------------- /site/theme/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Apache STeVe 7 | 8 | 9 |
10 | 20 |
21 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tincidunt hendrerit ornare. Curabitur ac sem eget orci condimentum convallis vitae sed nisl. Nullam cursus luctus ipsum, et viverra leo interdum ac. Duis in ante efficitur, tincidunt sapien dictum, tempus risus. Ut et convallis ante. Donec sagittis congue nisl, ac maximus eros hendrerit at. In vulputate at ex et imperdiet. Morbi in ligula risus. Aliquam nec efficitur sem. Nam sodales maximus purus, nec blandit odio interdum quis.

22 | 23 |

Ut eu interdum lorem, eu auctor lectus. Duis vitae imperdiet orci, ac condimentum neque. Nam sit amet hendrerit nisi. Sed ut tortor justo. Donec suscipit placerat dolor, in bibendum sem cursus eu. Aenean elementum dictum neque. Nam ullamcorper pretium nibh, imperdiet rhoncus odio aliquam in. Donec dapibus nulla nec lacinia scelerisque. Morbi non suscipit ipsum, id lobortis dolor. Sed pretium libero non tellus finibus tincidunt. Aenean ultricies lacus tortor, eget dictum est laoreet non. Fusce volutpat augue ut velit scelerisque, quis imperdiet erat viverra. Maecenas pretium scelerisque mi condimentum efficitur. Donec commodo dapibus lectus at rutrum. Donec sed ultrices tellus.

24 | 25 |

Curabitur venenatis dui eget nunc fermentum placerat. Fusce semper turpis eu cursus vulputate. Duis non ex ut metus luctus porttitor quis eget magna. Mauris tristique pellentesque tortor, congue semper justo lacinia euismod. In luctus fermentum metus in posuere. Donec auctor ipsum vel tortor lobortis lobortis. Proin gravida purus mi, vel ornare nibh congue eu. Phasellus ipsum diam, vestibulum eget pharetra non, eleifend non sem.

26 |
27 |

28 | 29 |

30 |
31 |
32 | Copyright© 2013-2022 the Apache Software Foundation. Licensed under the Apache License, Version 2.0 | Privacy Policy
33 | Apache STeVe, Apache, the Apache feather logo, and the Apache STeVe project logo are trademarks of The Apache Software Foundation. 34 | All other marks mentioned may be trademarks or registered trademarks of their respective owners. 35 |
36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /site/theme/templates/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ page.title }} - {{SITENAME}} 7 | 8 | 9 |
10 | 20 |
21 |

22 | {% if article %} 23 | {{ article.title }} 24 | {% else %} 25 | {{ page.title }} 26 | {% endif %} 27 |

28 | {% if article %} 29 | {{ article.content }} 30 | {% else %} 31 | {{ page.content }} 32 | {% endif %} 33 |
34 |
35 | Copyright© 2013-2022 the Apache Software Foundation. Licensed under the Apache License, Version 2.0 | Privacy Policy
36 | Apache STeVe, Apache, the Apache feather logo, and the Apache STeVe project logo are trademarks of The Apache Software Foundation. 37 | All other marks mentioned may be trademarks or registered trademarks of their respective owners. 38 |
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /steve_logo.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/steve_logo.psd -------------------------------------------------------------------------------- /stv9_template.txt: -------------------------------------------------------------------------------- 1 | 2 | The following candidates have been nominated for the nine 3 | available positions of Director within the Board of Directors of 4 | The Apache Software Foundation, as defined by Article V of the 5 | ASF Bylaws. In order to maximize effective representation 6 | of all members of the ASF, the 2002-2003 Board of Directors 7 | has resolved that such an election will be decided according 8 | to the Single Transferable Vote (STV) election process, as 9 | described below. 10 | 11 | STV: 12 | Board elections are performed via Single Transferable Vote (STV). Each 13 | voter lists all the people they want to see elected in order of 14 | preference. Excess votes for people who have already reached their quota 15 | (i.e., enough votes to ensure election) and also for the candidate with 16 | the least votes are redistributed to lower-numbered choices. This process 17 | is repeated until the required number are elected. Please see: 18 | 19 | http://en.wikipedia.org/wiki/Single_transferable_vote 20 | 21 | Election results are calculated using Meek's method for STV: 22 | 23 | http://en.wikipedia.org/wiki/Counting_Single_Transferable_Votes#Meek.27s_method 24 | 25 | The key idea to keep in mind is that the ordering of your votes 26 | is crucial. Those who you *really* want to be elected should 27 | be at the beginning/start of the list. For example: 28 | 29 | Aragorn, Frodo, Bilbo, Sauron, Gandalf, Treebeard, Gollum, Gimli 30 | 31 | means that you really want Aragorn to be elected (he's 32 | your primary preferred person), followed by Frodo (you want 33 | Frodo on the board more than you want Bilbo and Sauron, but 34 | not as much as you want Aragorn), followed by Bilbo, etc... 35 | 36 | There are 9 slots available; you should select all the candidates you 37 | want to be on the board, in the order in which you prefer them. You can 38 | select less than 9 people, and you can also select more than 9 people. 39 | 40 | More information can be found at: foundation:/voter 41 | 42 | --- 43 | Nominations: 44 | 45 | Label Name 46 | 47 | 48 | 49 | Candidate nomination and position statements can be found at: 50 | 51 | https://svn.apache.org/repos/private/foundation/Meetings/YYYYMMDD/board_ballot_YYYY_MM.txt 52 | https://svn.apache.org/repos/private/foundation/Meetings/YYYYMMDD/board_nominations.txt 53 | 54 | -------------------------------------------------------------------------------- /stv_background/meekm.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/stv_background/meekm.pdf -------------------------------------------------------------------------------- /v3/bin/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/v3/bin/.placeholder -------------------------------------------------------------------------------- /v3/queries.yaml: -------------------------------------------------------------------------------- 1 | queries: 2 | salt_issue: UPDATE ISSUES SET salt = ? WHERE _ROWID_ = ? 3 | salt_person: UPDATE PERSON SET salt = ? WHERE _ROWID_ = ? 4 | open_election: UPDATE METADATA SET salt = ?, opened_key = ? 5 | close_election: UPDATE METADATA SET closed = 1 6 | add_issue: | 7 | INSERT INTO ISSUES VALUES (?, ?, ?, ?, ?, ?) 8 | ON CONFLICT DO UPDATE SET 9 | title=excluded.title, 10 | description=excluded.description, 11 | type=excluded.type, 12 | kv=excluded.kv 13 | add_person: | 14 | INSERT INTO PERSON VALUES (?, ?, ?, ?) 15 | ON CONFLICT DO UPDATE SET 16 | name=excluded.name, 17 | email=excluded.email 18 | delete_issue: DELETE FROM ISSUES WHERE iid = ? 19 | delete_person: DELETE FROM PERSON WHERE pid = ? 20 | add_vote: INSERT INTO VOTES VALUES (NULL, ?, ?, ?, ?) 21 | has_voted: | 22 | SELECT 1 FROM VOTES 23 | WHERE person_token = ? AND issue_token = ? 24 | LIMIT 1 25 | metadata: SELECT * FROM METADATA 26 | issues: SELECT * FROM ISSUES ORDER BY iid 27 | person: SELECT * FROM PERSON ORDER BY pid 28 | get_issue: SELECT * FROM ISSUES WHERE iid = ? 29 | get_person: SELECT * FROM PERSON WHERE pid = ? 30 | by_issue: SELECT * FROM VOTES WHERE issue_token = ? ORDER BY _ROWID_ 31 | -------------------------------------------------------------------------------- /v3/requirements.txt: -------------------------------------------------------------------------------- 1 | passlib 2 | argon2-cffi 3 | cryptography 4 | 5 | # strictly speaking, optional: 6 | coverage 7 | -------------------------------------------------------------------------------- /v3/schema.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | /* ### TBD DOCCO. */ 21 | 22 | /* ### $ sqlite3 testing.db < schema.sql 23 | ### 24 | ### OR: 25 | ### >>> import sqlite3 26 | ### >>> conn = sqlite3.connect('testing.db') 27 | ### >>> conn.executescript(open('schema.sql').read()) 28 | ### 29 | ### ? maybe: conn.commit() and/or conn.close() ... the DML statements 30 | ### don't seem to require full closure of connection. 31 | */ 32 | 33 | 34 | /* --------------------------------------------------------------------- */ 35 | 36 | /* Various metadata about the Election contained in this database. 37 | Only one row will exist. 38 | 39 | An Election has three states: 40 | 41 | 1. Editable. The election is being set up. Issues and persons of 42 | record can be added, edited, and deleted. The Election's title 43 | may be changed (EID is fixed, however). 44 | DEFINITION: salt and opened_key are NULL. closed is n/a. 45 | 46 | 2. Open. The election is now open for voting. 47 | DEFINITION: salt and opened_key are NOT NULL. closed is NULL or 0. 48 | 49 | 3. Closed. The election is closed. 50 | DEFINITION: salt and opened_key are NOT NULL. closed is 1. 51 | */ 52 | CREATE TABLE METADATA ( 53 | 54 | /* The Election ID. This value might be replicated in the 55 | filesystem holding this database. To remain independent of 56 | application file choices, the ID is stored here. */ 57 | eid TEXT PRIMARY KEY NOT NULL, 58 | 59 | /* Title of this election. */ 60 | title TEXT NOT NULL, 61 | 62 | /* ### if we have monitors, they go here. */ 63 | /* ### maybe add an owner? */ 64 | 65 | /* A salt value to use for hashing this Election. 16 bytes. 66 | This will be NULL until the Election is opened. */ 67 | salt BLOB, 68 | 69 | /* If this Election has been opened for voting, then we store 70 | the OpenedKey here to avoid recomputing. 32 bytes. 71 | This will be NULL until the Election is opened. */ 72 | opened_key BLOB, 73 | 74 | /* Has this election been closed? NULL or 0 for not-closed (see 75 | SALT and OPENED_KEY to determine if the election has been 76 | opened). 1 for closed (implies it was opened). */ 77 | closed INTEGER 78 | 79 | ) STRICT; 80 | 81 | /* --------------------------------------------------------------------- */ 82 | 83 | /* The set of issues to vote upon in this Election. */ 84 | CREATE TABLE ISSUES ( 85 | 86 | /* The Issue ID, matching [-a-zA-Z0-9]+ */ 87 | iid TEXT PRIMARY KEY NOT NULL, 88 | 89 | /* Simple one-line title for this issue. */ 90 | title TEXT NOT NULL, 91 | 92 | /* An optional, longer description of the issue. */ 93 | description TEXT, 94 | 95 | /* The type of this issue's vote mechanism (eg. yna, stv, ...). This 96 | is one of an enumerated set of values. 97 | ### see for the enumeration. */ 98 | type TEXT NOT NULL, 99 | 100 | /* Per-type set of key/value pairs specifying additional data. This 101 | value is JSON-formatted */ 102 | kv TEXT, 103 | 104 | /* A salt value to use for hashing this Issue. 16 bytes. 105 | This will be NULL until the Election is opened. */ 106 | salt BLOB 107 | 108 | ) STRICT; 109 | 110 | /* --------------------------------------------------------------------- */ 111 | 112 | /* The set of people "on record" for this Election. Only these people 113 | may vote. */ 114 | CREATE TABLE PERSON ( 115 | 116 | /* An id assigned to the person (eg. an LDAP username). */ 117 | pid TEXT PRIMARY KEY NOT NULL, 118 | 119 | /* Optional human-readable name for this person. */ 120 | name TEXT, 121 | 122 | /* How to contact this person (ie. to send a ballot link). */ 123 | email TEXT NOT NULL, 124 | 125 | /* A salt value to use for hashing this Person. 16 bytes. 126 | This will be NULL until the Election is opened. */ 127 | salt BLOB 128 | 129 | ) STRICT; 130 | 131 | /* --------------------------------------------------------------------- */ 132 | 133 | /* The registered votes, once the Election has been opened. Note that 134 | duplicates of (person, issue) may occur, as re-voting is allowed. Only 135 | the latest is used. */ 136 | CREATE TABLE VOTES ( 137 | 138 | /* The key is auto-incrementing to provide a record of insert-order, 139 | so that we have an ordering to find the "most recent" when 140 | re-voting on an issue. 141 | Note: an integer primary key is an alias for _ROWID_. */ 142 | vid INTEGER PRIMARY KEY AUTOINCREMENT, 143 | 144 | /* A hashed token representing a single Person. 32 bytes. */ 145 | person_token BLOB NOT NULL, 146 | 147 | /* A hashed token representing an issue. 32 bytes. */ 148 | issue_token BLOB NOT NULL, 149 | 150 | /* A binary value used to salt the token's encryption. 16 bytes. */ 151 | salt BLOB NOT NULL, 152 | 153 | /* An encrypted form of the vote. */ 154 | token BLOB NOT NULL 155 | 156 | ) STRICT; 157 | 158 | CREATE INDEX I_BY_PERSON ON VOTES (person_token); 159 | CREATE INDEX I_BY_ISSUE ON VOTES (issue_token); 160 | 161 | /* --------------------------------------------------------------------- */ 162 | -------------------------------------------------------------------------------- /v3/server/main.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/v3/server/main.py -------------------------------------------------------------------------------- /v3/server/static/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/v3/server/static/.placeholder -------------------------------------------------------------------------------- /v3/server/templates/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/v3/server/templates/.placeholder -------------------------------------------------------------------------------- /v3/steve/__init__.py: -------------------------------------------------------------------------------- 1 | # TBD 2 | -------------------------------------------------------------------------------- /v3/steve/crypto.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # ### TBD docco 18 | # 19 | # 20 | 21 | import base64 22 | import random 23 | 24 | import passlib.hash # note that .argon2 is proxy in this pkg 25 | import passlib.utils # for the RNG, to create Salt values 26 | 27 | import cryptography.fernet 28 | 29 | # All salt values will be 16 bytes in length. After base64 encoding, they 30 | # will be represented with 22 characters. 31 | SALT_LEN = 16 32 | 33 | 34 | def gen_salt() -> bytes: 35 | "Generate bytes to be used as a salt, for hashing." 36 | return passlib.utils.getrandbytes(passlib.utils.rng, SALT_LEN) 37 | 38 | 39 | def gen_opened_key(edata: bytes, salt: bytes) -> bytes: 40 | "Generate the OpenedKey for this election." 41 | return _hash(edata, salt) 42 | 43 | 44 | def gen_token(opened_key: bytes, value: str, salt: bytes) -> bytes: 45 | "Generate a person or issue token." 46 | return _hash(opened_key + value.encode(), salt) 47 | 48 | 49 | ### fix return type, to be a tuple 50 | def create_vote(person_token: bytes, 51 | issue_token: bytes, 52 | votestring: str) -> bytes: 53 | "Create a vote tuple, to record the VOTESTRING." 54 | salt = gen_salt() 55 | key = _hash(person_token + issue_token, salt) 56 | b64key = base64.urlsafe_b64encode(key) 57 | f = cryptography.fernet.Fernet(b64key) 58 | return salt, f.encrypt(votestring.encode()) 59 | 60 | 61 | def decrypt_votestring(person_token: bytes, 62 | issue_token: bytes, 63 | salt: bytes, 64 | token: bytes) -> str: 65 | "Decrypt TOKEN into a VOTESTRING." 66 | key = _hash(person_token + issue_token, salt) 67 | b64key = base64.urlsafe_b64encode(key) 68 | f = cryptography.fernet.Fernet(b64key) 69 | return f.decrypt(token).decode() 70 | 71 | 72 | def _hash(data: bytes, salt: bytes) -> bytes: 73 | "Apply our desired hashing function." 74 | ph = passlib.hash.argon2.using(type='d', salt=salt) 75 | h = ph.hash(data) 76 | return base64.standard_b64decode(h.split('$')[-1] + '==') 77 | 78 | 79 | def shuffle(x): 80 | "Ensure we use the strongest RNG available for shuffling." 81 | return random.shuffle(x, passlib.utils.rng.random) 82 | -------------------------------------------------------------------------------- /v3/steve/db.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # ---- 18 | # 19 | # Convenience wrapper for working with a SQLite database. 20 | # 21 | # This wrapper has several primary purposes: 22 | # 23 | # 1. Easily create a cursor for each statement that might be 24 | # executed by the application. 25 | # 2. Remember the specific string object for those statements, 26 | # and re-use them in cursor.execute() for better performance. 27 | # 3. Rows fetched with SELECT statements are wrapped into a 28 | # namedtuple() instance, such that columns can be easily 29 | # accessed as attributes or numerically indexed as a tuple, 30 | # 31 | 32 | import sqlite3 33 | import collections 34 | import functools 35 | 36 | 37 | class DB: 38 | 39 | def __init__(self, fname): 40 | 41 | def row_factory(cursor, row): 42 | "Possibly apply namedtuple() to the returned row." 43 | return self.factories.get(cursor, lambda *row: row)(*row) 44 | 45 | # Note: isolation_level=None means autocommit mode. 46 | self.conn = sqlite3.connect(fname, isolation_level=None) 47 | self.conn.row_factory = row_factory 48 | 49 | # CURSOR : FACTORY 50 | self.factories = { } 51 | 52 | def _cursor_for(self, statement): 53 | return self.conn.cursor(functools.partial(NamedTupleCursor, 54 | statement)) 55 | 56 | def add_query(self, table, query): 57 | "Return a cursor to use for this QUERY against TABLE." 58 | 59 | # The query must select all columns. 60 | assert query[:9].lower() == 'select * ' 61 | 62 | # Get all column names for TABLE. 63 | cur = self.conn.execute(f'select * from {table} limit 1') 64 | names = [ info[0] for info in cur.description ] 65 | 66 | # We don't need the results, but cannot leave the cursor hanging, 67 | # as it establishes a lock on this table. This likely closes as 68 | # this method exits, but let's not rely upon that. 69 | cur.close() 70 | 71 | # Create a factory for turning rows into namedtuples. 72 | factory = collections.namedtuple(f'row_factory_{len(self.factories)}', 73 | names, rename=True, 74 | module=DB.__module__) 75 | 76 | # Register the row-wrapper factory for this cursor. 77 | cursor = self._cursor_for(query) 78 | self.factories[cursor] = factory 79 | return cursor 80 | 81 | def add_statement(self, statement): 82 | "Return a cursor for use with a DML SQL statement." 83 | 84 | # Note: rows should not be returned for this statement, and 85 | # (thus) the row_factory should not be called. If called, the 86 | # original row will be returned. 87 | return self._cursor_for(statement) 88 | 89 | 90 | class NamedTupleCursor(sqlite3.Cursor): 91 | 92 | def __init__(self, statement, *args, **kw): 93 | super().__init__(*args, **kw) 94 | self.statement = statement 95 | 96 | def perform(self, params=()): 97 | "Perform the statement with PARAMs, or prepare the query." 98 | 99 | # Use the exact same STATEMENT each time. Python's SQLite module 100 | # caches the parsed statement, if the string is the same object. 101 | self.execute(self.statement, params) 102 | 103 | def first_row(self, params=()): 104 | "Helper method to fetch the first row of a query." 105 | self.perform(params) 106 | row = self.fetchone() 107 | _ = self.fetchall() # run the cursor to completion; should be empty 108 | return row 109 | -------------------------------------------------------------------------------- /v3/steve/vtypes/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # NOTE: not using a dynamic system. Just define/import what we know. 19 | TYPES = { 'yna', 'stv' } 20 | 21 | from . import yna 22 | from . import stv 23 | 24 | 25 | def vtype_module(vtype): 26 | "Return the vote type's module." 27 | assert vtype in TYPES 28 | 29 | return globals()[vtype] 30 | -------------------------------------------------------------------------------- /v3/steve/vtypes/stv.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # ---- 18 | # 19 | # ### TBD: DOCCO 20 | # 21 | 22 | import os.path 23 | import importlib 24 | 25 | # Where can we find the stv_tool module? 26 | STV_RELPATH = '../../../monitoring/stv_tool.py' 27 | 28 | 29 | def load_stv(): 30 | pathname = os.path.join(os.path.dirname(__file__), STV_RELPATH) 31 | spec = importlib.util.spec_from_file_location('stv_tool', pathname) 32 | module = importlib.util.module_from_spec(spec) 33 | spec.loader.exec_module(module) 34 | return module 35 | 36 | 37 | # load_stv() loads the module (again) on each call. Each one is separate, 38 | # with (eg.) a distinct VERBOSE flag. Note that sys.modules is completely 39 | # uninvolved in this process. Thus, let's load this once. (this is 40 | # effectively a fancy "import" statement) 41 | stv_tool = load_stv() 42 | 43 | 44 | def tally(votestrings, kv, names=None): 45 | "Run the STV tally process." 46 | 47 | # NOTE: the NAMES parameter is usually not passed, but is available 48 | # to compare operation against custom ordering of NAMES in the 49 | # LABELMAP. This function takes a specific approach, which differs 50 | # from historical orderings. 51 | 52 | # kv['labelmap'] should be: LABEL: NAME 53 | # for example: { 'a': 'John Doe', } 54 | labelmap = kv['labelmap'] 55 | 56 | seats = kv['seats'] 57 | 58 | # Remap all votestrings from a string sequence of label characters, 59 | # into a sequence of NAMEs. 60 | votes = [[labelmap[c] for c in v] for v in votestrings] 61 | 62 | # NOTE: it is important that the names are sorted, to create a 63 | # reproducible list of names. Callers may specify a custom ordering. 64 | if names is None: 65 | names = sorted(labelmap.values()) 66 | results = stv_tool.run_stv(names, votes, seats) 67 | 68 | human = '\n'.join( 69 | f'{c.name:40}{" " if c.status == stv_tool.ELECTED else " not "}elected' 70 | for c in results.l 71 | ) 72 | data = { 'raw': results, } 73 | return human, data 74 | -------------------------------------------------------------------------------- /v3/steve/vtypes/yna.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # ---- 18 | # 19 | # ### TBD: DOCCO 20 | # 21 | 22 | # The votestring will be lower-cased. What is the result? 23 | YES = { 'y', 'yes', '1', 'true', } 24 | NO = { 'n', 'no', '0', 'false', } 25 | # "abstain" is any other (non-affirmative) value. 26 | 27 | 28 | def tally(votestrings, kv): 29 | y = n = a = 0 30 | for v in votestrings: 31 | if v in YES: 32 | y += 1 33 | elif v in NO: 34 | n += 1 35 | else: 36 | a += 1 37 | 38 | human = f'''\ 39 | Yes: {y:#4} 40 | No: {n:#4} 41 | Abstain: {a:#4}''' 42 | 43 | return human, {'y': y, 'n': n, 'a': a,} 44 | -------------------------------------------------------------------------------- /v3/test/check_coverage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # 20 | # ---- 21 | # 22 | # ### TBD: DOCCO 23 | # 24 | 25 | 26 | import sys 27 | import os.path 28 | import sqlite3 29 | import json 30 | 31 | import coverage 32 | 33 | # Ensure that we can import the "steve" package. 34 | THIS_DIR = os.path.realpath(os.path.dirname(__file__)) 35 | PARENT_DIR = os.path.dirname(THIS_DIR) 36 | sys.path.insert(0, PARENT_DIR) 37 | 38 | TESTING_DB = os.path.join(THIS_DIR, 'covtest.db') 39 | SCHEMA_FILE = os.path.join(PARENT_DIR, 'schema.sql') 40 | 41 | 42 | def touch_every_line(): 43 | "A minimal test to run each line in the 'steve' package." 44 | 45 | # Do the import *WITHIN* the coverage test. 46 | import steve.election 47 | 48 | eid = steve.election.new_eid() 49 | 50 | # Start the election, and open it. 51 | try: 52 | os.remove(TESTING_DB) 53 | except OSError: 54 | pass 55 | conn = sqlite3.connect(TESTING_DB) 56 | conn.executescript(open(SCHEMA_FILE).read()) 57 | conn.execute('INSERT INTO METADATA' 58 | f' VALUES ("{eid}", "title", NULL, NULL, NULL)') 59 | conn.commit() 60 | 61 | # Ready to load up the Election and exercise it. 62 | e = steve.election.Election(TESTING_DB) 63 | 64 | _ = e.get_metadata() # while EDITABLE 65 | 66 | e.add_person('alice', 'Alice', 'alice@example.org') 67 | e.add_person('bob', None, 'bob@example.org') 68 | e.add_person('carlos', 'Carlos', 'carlos@example.org') 69 | e.add_person('david', None, 'david@example.org') 70 | _ = e.list_persons() 71 | e.delete_person('david') 72 | _ = e.get_person('alice') 73 | 74 | e.add_issue('a', 'issue A', None, 'yna', None) 75 | e.add_issue('b', 'issue B', None, 'stv', { 76 | 'seats': 3, 77 | 'labelmap': { 78 | 'a': 'Alice', 79 | 'b': 'Bob', 80 | 'c': 'Carlos', 81 | 'd': 'David', 82 | 'e': 'Eve', 83 | }, 84 | }) 85 | _ = e.list_issues() 86 | e.add_issue('c', 'issue C', None, 'yna', None) 87 | e.delete_issue('c') 88 | _ = e.get_issue('a') 89 | 90 | e.open() 91 | _ = e.get_metadata() # while OPEN 92 | e.add_vote('alice', 'a', 'y') 93 | e.add_vote('bob', 'a', 'n') 94 | e.add_vote('carlos', 'a', 'a') # use each of Y/N/A 95 | e.add_vote('alice', 'b', 'bc') 96 | e.add_vote('bob', 'b', 'ad') 97 | _ = e.has_voted_upon('alice') 98 | _ = e.is_tampered() 99 | 100 | e.close() 101 | _ = e.get_metadata() # while CLOSED 102 | _ = e.tally_issue('a') 103 | _ = e.tally_issue('b') 104 | 105 | 106 | def main(): 107 | cov = coverage.Coverage( 108 | data_file=None, branch=True, config_file=False, 109 | source_pkgs=['steve'], messages=True, 110 | ) 111 | cov.start() 112 | 113 | try: 114 | touch_every_line() 115 | finally: 116 | cov.stop() 117 | 118 | cov.report(file=sys.stdout) 119 | cov.html_report(directory='covreport') 120 | 121 | 122 | if __name__ == '__main__': 123 | main() 124 | -------------------------------------------------------------------------------- /v3/test/populate_v2_stv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # 20 | 21 | # 22 | # Gather "all" known STV voting processing into a reference directory, 23 | # with full debug output. These files can then be used as a comparison 24 | # to future developments on STV tooling, to ensure consistency. 25 | # 26 | 27 | if test "$1" = ""; then echo "USAGE: $0 MEETINGS_DIR"; exit 1; fi 28 | MEETINGS_DIR="$1" 29 | 30 | REFERENCE_DIR="v2-stv-ref" 31 | mkdir "$REFERENCE_DIR" || /bin/true 32 | 33 | V3_DIR="v3-stv" 34 | mkdir "$V3_DIR" || /bin/true 35 | 36 | THIS_FILE=`realpath $0` 37 | THIS_DIR=`dirname "$THIS_file"` 38 | #echo $THIS_FILE 39 | STV_TOOL=`realpath "$THIS_DIR/../../monitoring/stv_tool.py"` 40 | echo $STV_TOOL 41 | V3_TOOL="${THIS_DIR}/run_stv.py" 42 | 43 | #echo "ls $MEETINGS_DIR/*/raw_board_votes.txt" 44 | 45 | for v in `ls $MEETINGS_DIR/*/raw_board_votes.txt`; do 46 | #echo $v 47 | DATE=`echo $v | sed -n 's#.*/\([0-9]*\)/.*#\1#p'` 48 | echo $DATE 49 | "$STV_TOOL" -v "$v" > "$REFERENCE_DIR/$DATE" 50 | 51 | MTG_DIR=`dirname "$v"` 52 | #echo $MTG_DIR 53 | "$V3_TOOL" "${MTG_DIR}" > "${V3_DIR}/$DATE" 54 | done 55 | -------------------------------------------------------------------------------- /v3/test/run_stv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # 20 | # ---- 21 | # 22 | # ### TBD: DOCCO 23 | # USAGE: run_stv.py .../Meetings/yyyymmdd 24 | # 25 | 26 | import sys 27 | import os.path 28 | 29 | # Ensure that we can import the "steve" package. 30 | THIS_DIR = os.path.realpath(os.path.dirname(__file__)) 31 | sys.path.insert(0, os.path.dirname(THIS_DIR)) 32 | import steve.vtypes.stv 33 | 34 | # The stv module loads the stv_tool module. Tweak it. 35 | stv_tool = steve.vtypes.stv.stv_tool 36 | stv_tool.VERBOSE = True 37 | 38 | 39 | def main(mtgdir): 40 | rawfile = os.path.join(mtgdir, 'raw_board_votes.txt') 41 | labelfile = os.path.join(mtgdir, 'board_nominations.ini') 42 | 43 | assert os.path.exists(rawfile) 44 | assert os.path.exists(labelfile) 45 | 46 | labelmap = stv_tool.read_labelmap(labelfile) 47 | votes = stv_tool.read_votefile(rawfile).values() 48 | 49 | # Construct a label-sorted list of names from the labelmap. 50 | names = [name for _, name in sorted(labelmap.items())] 51 | 52 | kv = { 53 | 'labelmap': labelmap, 54 | 'seats': 9, 55 | } 56 | 57 | # NOTE: for backwards-compat, the tally() function accepts a 58 | # list of names with caller-defined sorting. 59 | human, _ = steve.vtypes.stv.tally(votes, kv, names) 60 | 61 | # For the comparison purposes: 62 | print(human) 63 | print('Done!') 64 | 65 | 66 | if __name__ == '__main__': 67 | main(sys.argv[1]) 68 | -------------------------------------------------------------------------------- /whatif.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | ##### 4 | # Licensed to the Apache Software Foundation (ASF) under one or more 5 | # contributor license agreements. See the NOTICE file distributed with 6 | # this work for additional information regarding copyright ownership. 7 | # The ASF licenses this file to You under the Apache License, Version 2.0 8 | # (the "License"); you may not use this file except in compliance with 9 | # the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | ##### 19 | 20 | # Run alternate voting scenarios: vary the number of seats, remove candidates, 21 | # or do a run-off with only the specified candidates. Candidate names can 22 | # be specified using either their first name, last name, or full name without 23 | # any spaces (independent of case). Examples: 24 | # 25 | # Examples: 26 | # whatif.py ../Meetings/20110712/raw_board_votes.txt 10 27 | # whatif.py ../Meetings/20110712/raw_board_votes.txt -LawrenceRosen 28 | # whatif.py ../Meetings/20110712/raw_board_votes.txt 1 kulp noirin geir chris 29 | 30 | import os.path 31 | import sys 32 | import re 33 | 34 | sys.path.append(os.path.join(os.path.dirname(__file__), 'monitoring')) 35 | import stv_tool 36 | 37 | def usage(): 38 | print('Usage: %s [-v] RAW_VOTES_FILE [seats] [-]name...' % scriptname) 39 | sys.exit(1) 40 | 41 | if __name__ == '__main__': 42 | scriptname = sys.argv.pop(0) 43 | 44 | if sys.argv and sys.argv[0] == '-v': 45 | stv_tool.VERBOSE = True 46 | sys.argv.pop(0) 47 | 48 | # extract required vote file argument, and load votes from it 49 | if not sys.argv or not os.path.exists(sys.argv[0]): usage() 50 | votefile = sys.argv.pop(0) 51 | names, votes = stv_tool.load_votes(votefile) 52 | 53 | # extract optional number of seats argument 54 | if sys.argv and sys.argv[0].isdigit(): 55 | seats = int(sys.argv.pop(0)) 56 | else: 57 | seats = 9 58 | 59 | # extract an alias list of first, last, and joined names 60 | alias = {} 61 | for name in names: 62 | lname = re.sub(r'[^\w ]', '', name.lower()) 63 | alias[lname.replace(' ', '')] = name 64 | for part in lname.split(' '): 65 | alias[part] = name 66 | 67 | # validate input 68 | for arg in sys.argv: 69 | if arg.lstrip('-').lower() not in alias: 70 | sys.stderr.write('invalid selection: %s\n' % arg) 71 | usage() 72 | 73 | if not sys.argv: 74 | # no changes to the candidates running 75 | pass 76 | elif sys.argv[0][0] == '-': 77 | # remove candidates from vote 78 | for name in sys.argv: names.remove(alias[name.lstrip('-').lower()]) 79 | else: 80 | # only include specified candidates 81 | # NOTE: order is important, so sort the names for repeatability 82 | names = [ alias[n.lower()] for n in sorted(sys.argv) ] 83 | 84 | # Trim the raw votes based on cmdline params. Eliminate votes that 85 | # are not for one of the allowed names. Do not include voters who 86 | # did not vote for anybody [post-trimming]. 87 | trimmed = [ ] 88 | for voteseq in votes: 89 | newseq = [ v for v in voteseq if v in names ] 90 | if newseq: 91 | trimmed.append(newseq) 92 | 93 | # run the vote 94 | candidates = stv_tool.run_stv(names, trimmed, seats) 95 | candidates.print_results() 96 | print('Done!') 97 | -------------------------------------------------------------------------------- /www/cgi-bin/cast-vote.pl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/www/cgi-bin/cast-vote.pl -------------------------------------------------------------------------------- /www/cgi-bin/redirect.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -T 2 | ##### 3 | # Licensed to the Apache Software Foundation (ASF) under one or more 4 | # contributor license agreements. See the NOTICE file distributed with 5 | # this work for additional information regarding copyright ownership. 6 | # The ASF licenses this file to You under the Apache License, Version 2.0 7 | # (the "License"); you may not use this file except in compliance with 8 | # the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | ##### 18 | 19 | # 20 | # redirect.pl - simple redirect CGI script that changes the referer header 21 | # 22 | 23 | use strict; 24 | use warnings; 25 | use CGI; 26 | use CGI::Carp qw/fatalsToBrowser/; 27 | 28 | my $q = CGI->new; 29 | my $uri = $q->param("uri") or die "Can't find uri param"; 30 | print $q->header; 31 | print < 33 | 34 | 35 | 36 | 37 | Redirecting to $uri ... 38 | 39 | 40 | EOT 41 | exit; 42 | -------------------------------------------------------------------------------- /www/htdocs/cast-style.css: -------------------------------------------------------------------------------- 1 | textarea { width: 80%; height: 50% } 2 | h1 { font-size: 14pt } 3 | h2 { font-size: 12pt } 4 | body { font-size: 10pt } 5 | #issue {text-align:left; height:50%; width:80%; overflow:auto; border:solid 1px; padding: 8px} 6 | -------------------------------------------------------------------------------- /www/htdocs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/www/htdocs/favicon.ico -------------------------------------------------------------------------------- /www/htdocs/images/ballot_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/www/htdocs/images/ballot_bg.png -------------------------------------------------------------------------------- /www/htdocs/images/dragleft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/www/htdocs/images/dragleft.png -------------------------------------------------------------------------------- /www/htdocs/images/dragright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/www/htdocs/images/dragright.png -------------------------------------------------------------------------------- /www/htdocs/images/target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/www/htdocs/images/target.png -------------------------------------------------------------------------------- /www/htdocs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Apache Voting Using the STeVe Web Interface 4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

Types of Votes

17 | 18 |
    19 |
  • yna - yes, no, abstain
  • 20 |
  • stv1-9 - Single Transferable Vote for 1-9 Slots
  • 21 |
  • select1-9 - Select (Unordered) Preference for 1-9 Slots
  • 22 |
23 | 24 |

Casting a Vote in STeVe

25 | 26 |

27 | You can access the VOTE URL directly at https://vote.apache.org/cast/$issue/$hash 28 |

29 |

or use the following form

30 |

31 |

32 | 33 | 34 | 35 | 36 |
Issue
Hash
37 |
38 |

39 |

40 | On success, you see a ballot containing a form to fill 41 | out for your actual vote. 42 |

43 | 44 |

How the STeVe Web Interface Works

45 | 46 |

47 | First, after you supply your LDAP username and password, STeVe validates the URL 48 | and determines your voter email address. If you are 49 | doing a GET, the web interface displays the proper ballot for 50 | the issue. If you are doing a POST, the web interface treats that 51 | as an attempt to vote on the issue and validates your vote before 52 | passing it along to the vote tool. 53 |

54 |

55 | The web interface is REST compliant, so you can determine the outcome 56 | of your cast vote by looking at the resulting HTTP response status code. 57 | If the status code is 200 your vote was successfully cast; otherwise it 58 | will be 500. This means you can vote using the web interface with 59 | standard web-client tools like curl if you so desire. The POST should 60 | contain a single parameter: "vote", whose corresponding value will be 61 | your actual vote on the issue. 62 |

63 | 64 |

Additional References

65 | 66 | 75 | 76 | 77 | 78 | 79 | 80 | 84 | 85 | -------------------------------------------------------------------------------- /www/htdocs/steve_interactive.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/steve/14288373e8db8832fd566f0d82fd879b21ef47c7/www/htdocs/steve_interactive.js --------------------------------------------------------------------------------