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):
Create a cookie for each user that persists between browser sessions (I am aware this is insecure as a method of preventing multiple votes. That's fine.)
Allow a single vote to be placed per day, per user.
Give a new vote to a user once 24 hours has elapsed.
Prevent all voting if day of week = saturday or sunday (this should be trivial with the use of a datetime() check placed prior to any cookie-checking logic.
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!
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 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.