pythonsessioncookiespyramidvoting

Implementing a "one vote per day" system in pyramid based on cookies


Background:

I have the core functionality of a very simple vote-based site setup and working well in pyramid utilizing a sqlite database. The last requirement for this application is to allow only one vote per day, per user. It has been specified that this must be done via cookies, and that no users shall be allowed to vote on Saturdays or Sundays.

I am currently using UnencryptedCookieSessionFactoryConfig for session management and to handle flash messages.

Question:

I've identified that I need the following functionality, but can't determine what modules of pyramid might provide it (or if I should be looking elsewhere):

Additional Info:

My current db schema is as follows, and must stay this way:

create table if not exists games (
    id integer primary key autoincrement,
    title char(100) not null,
    owned bool not null,
    created char(40) not null
);

create table if not exists votes (
    gameId integer,
    created char(40) not null,
    FOREIGN KEY(gameId) REFERENCES games(id)
);

and the current vote function is as follows:

@view_config(route_name='usevote')
def usevote_view(request):
    game_id = int(request.matchdict['id'])
    request.db.execute('insert into votes (gameId,created) values (?,?)',
                (game_id,now))
    request.db.commit()
    request.session.flash('Your vote has been counted. You can vote again in 24 hours.')
    return HTTPFound(location=request.route_url('list'))

Thanks!


Solution

  • session data on cookies only

    To integrate cookie sessions on pyramid, take a look on pyramid_beaker

    To guarantee integrity only using cookies (and avoid the user poking into the cookie data), you should use an encrypted cookie (take a look into the Session Based Cookie and the Encryption Options).

    Your main configuration will look somewhat like this:

    [app:main]
    ...
    session.type = cookie
    session.key = SESSION
    session.encrypt_key = R9RD9qx7uzcybJt1iBzeMoohyDUbZAnFCyfkWfxOoX8s5ay3pM
    session.validate_key = pKs3JDwWiJmt0N0wQjJIqdG5c1XsHSlauM6T2DfB8FqOifsWZN
    ...
    

    The session.key is just the name of the cookie. Change for whatever you want

    The session.encrypt_key and session.validate_key above are just examples of big random strings. You should generate them yourself and keep them private.

    Also, to encrypt the cookies properly you will need an AES cipher implementation. Installing pycrypto should do it:

    pip install pycryto
    

    Also your main function that creates the wsgi application should be changed to something like this:

    from pyramid_beaker import session_factory_from_settings
    ...
    
    def main(global_config, **settings):
        ...
        config = Configurator(settings=settings)
        ...
        config.set_session_factory(session_factory_from_settings(settings))
    

    Now you can store the cookie data directly into the client browser and avoid data tampering. The simple solution to solve your problem is setting this cookie to never expire, storing the date of the last time he voted inside it and check based on what day is today and what day did he last voted

    the main issue

    The main problem now is dealing with users that delete the cookie, use another browser or simple use the browser's incognito window (chrome) or private navigation (firefox). This user appears to be a new user to your system and thus can vote again.

    IMO to solve that you will need to have a server side control or penalize the user in a way that deleting the cookie will actually make his life harder to the point that deleting the cookie to gain a vote is not desirable anymore.

    Security is not about perfect unhackable systems, but building systems that the cost to bypass it is actually higher than the benefit of doing it.