Example #1
0
def select(request):
    """Renders the candidate selection list.

    The link to the voting page for each candidate contains an identifier
    which is the result of encrypting the candidate number with a random
    session key. The main benefit from this is that the chosen candidate can
    not be identified from the used URL. This allows us to use GET requests
    instead of POST requests without having to worry about leaking information
    in server logs and browser history.

    :param request: The currently active request.
    :type request: :py:class:`pyramid.request.Request`

    :rtype: dict
    """
    # Deco Grid positions for the candidate columns.
    positions = '0 1:3 2:3'.split()
    session = DBSession()
    log = logging.getLogger('nuvavaalit')
    # Disable caching
    request.add_response_callback(disable_caching)

    # Require authentication.
    voter = authenticated_user(request)
    if voter is None:
        log.warn('Unauthenticated attempt to select candidates.')
        raise HTTPForbidden()

    # User should vote only once.
    if voter.has_voted():
        log.warn('User "{}" attempted to select candidates after voting.'.format(voter.username))
        return exit_voting(request)

    query = session.query(Candidate)\
                .filter(Candidate.number != Candidate.EMPTY_CANDIDATE)\
                .order_by(Candidate.number)

    candidates = []
    for candidate in query.all():
        candidates.append({
            'name': candidate.fullname(),
            'number': candidate.number,
            'vote_url': route_url('vote', request, id=encrypt(str(candidate.number), request.session['encryption_key'])),
            'image_url': request.static_url('nuvavaalit:views/templates/static/images/candidates/{}.jpg'.format(candidate.number)),
        })

    return {
        'candidates': split_candidates(candidates, len(positions)),
        'positions': cycle(positions),
        'columns': len(positions),
        'empty_vote_url': route_url('vote', request, id=encrypt(str(Candidate.EMPTY_CANDIDATE), request.session['encryption_key'])),
        'empty_vote_number': Candidate.EMPTY_CANDIDATE,
    }
Example #2
0
def login(request):
    """Renders a login form and logs in a user if given the correct
    credentials.

    :param request: The currently active request.
    :type request: :py:class:`pyramid.request.Request`
    """
    session = DBSession()
    log = logging.getLogger('nuvavaalit')
    request.add_response_callback(disable_caching)
    error = None

    if 'form.submitted' in request.POST:
        username = request.POST['username']

        if request.session.get_csrf_token() != request.POST.get('csrf_token'):
            log.warn('CSRF attempt at {}.'.format(request.url))
            raise HTTPForbidden(u'CSRF attempt detected.')
        else:
            user = session.query(Voter).filter_by(username=username).first()
            password = request.POST['password']

            if user is not None and user.check_password(password):
                if user.has_voted():
                    log.warn('User {} attempted to log in after already voting.'.format(user.username))
                    if request.statsd:
                        statsd.increment('login.voted')
                    return exit_voting(request)
                else:
                    headers = remember(request, user.username)
                    # Generate an encryption key for the duration of the session.
                    request.session['encryption_key'] = session_key()
                    log.info('Successful login for "{}".'.format(user.username))
                    if request.statsd:
                        statsd.increment('login.success')
                    return HTTPFound(location=route_url('select', request), headers=headers)

            error = u'Tunnistautuminen epäonnistui. Kokeile tunnistautua uudelleen!'
            if request.statsd:
                statsd.increment('login.failure')
            log.warn('Failed login attempt for {}'.format(request.POST.get('username').encode('utf-8')))

    return {
        'action_url': route_url('login', request),
        'csrf_token': request.session.get_csrf_token(),
        'error': error,
    }
Example #3
0
def vote(request):
    """Renders the voting form for the selected candidate and processes the
    vote.

    A valid vote must meet all of the following criteria:

        * The voter must be authenticated.

        * The voter must not have voted previously.

        * The candidate must be the one chosen in the previous step (See
          :py:func:`select`).

        * The CSRF token included in the form must be valid.

    :param request: The currently active request.
    :type request: :py:class:`pyramid.request.Request`

    :rtype: dict
    """
    error = False
    session = DBSession()
    voter = authenticated_user(request)
    log = logging.getLogger('nuvavaalit')
    request.add_response_callback(disable_caching)
    localizer = get_localizer(request)

    # The user must be authenticated at this time
    if voter is None:
        log.warn('Unauthenticated attempt to vote.')
        raise HTTPForbidden()

    # The user may vote only once
    if voter.has_voted():
        log.warn('User "{}" attempted to vote a second time.'.format(voter.username))
        return exit_voting(request)

    # Find the selected candidate
    try:
        number = int(decrypt(request.matchdict['id'], request.session['encryption_key']))
    except (ValueError, TypeError):
        log.warn('Candidate number decryption failed')
        raise HTTPNotFound

    candidate = session.query(Candidate)\
            .filter(Candidate.number == number)\
            .first()

    if candidate is None:
        log.warn('User "{}" attempted to vote for a non-existing candidate "{}".'.format(
            voter.username, number))
        raise HTTPNotFound

    # Handle voting
    if 'vote' in request.POST:

        if request.session.get_csrf_token() != request.POST.get('csrf_token'):
            log.warn('CSRF attempt at: {0}.'.format(request.url))
            error = True
        elif request.POST['vote'].strip() == str(number):

            session.add(Vote(candidate))
            session.add(VotingLog(voter))

            log.info('Stored vote cast by "{}".'.format(voter.username))
            if request.statsd:
                statsd.increment('vote.success')
            return exit_voting(request)
        else:
            error = True

    if request.statsd and error:
        statsd.increment('vote.error')

    options = {
        'action_url': request.path_url,
        'select_url': route_url('select', request),
        'candidate': {
            'number': candidate.number,
            'name': candidate.fullname() if not candidate.is_empty() else _(u'Tyhjä'),
            },
        'voter': {
            'fullname': voter.fullname(),
        },
        'error': error,
        'csrf_token': request.session.get_csrf_token(),
        'unload_confirmation': localizer.translate(
            _(u'Et ole vielä äänestänyt. Oletko varma, että haluat poistua sivulta?')),
    }

    return options