Skip to content

Commit

Permalink
Merge branch 'master' of github.com:aio-libs/aiohttp_session
Browse files Browse the repository at this point in the history
  • Loading branch information
asvetlov committed Nov 20, 2015
2 parents d25bd59 + c0548a1 commit 9bff57b
Show file tree
Hide file tree
Showing 19 changed files with 328 additions and 162 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,5 @@ docs/_build/
# PyBuilder
target/

coverage
coverage
venv
30 changes: 8 additions & 22 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,36 +1,19 @@
language: python
python:
- 3.3
- 3.4

before_install:
- sudo add-apt-repository -y ppa:chris-lea/redis-server
- echo "deb http://ppa.launchpad.net/chris-lea/redis-server/ubuntu oneiric main" | sudo tee -a /etc/apt/sources.list
- sudo apt-get update -qq
# - sudo apt-get install redis-server -V;
- sudo apt-get install redis-server/oneiric -V --force-yes

- 3.5

install:
- pip install --upgrade setuptools
- pip install hiredis
- pip install pyflakes
- pip install pep8
- pip install coveralls --use-mirrors
- pip install coveralls
- pip install aiohttp
- pip install aioredis
- pip install pycrypto
- pip install cryptography
- python setup.py develop

before_script:
# main redis instance listening port & socket
- >
redis-server --daemonize yes
--pidfile ./redis-server.pid
--port $REDIS_PORT
--save ""
- redis-cli -p $REDIS_PORT PING

script:
- pyflakes aiohttp_session tests
- pep8 aiohttp_session tests
Expand All @@ -43,5 +26,8 @@ env:
global:
- REDIS_PORT=6379
matrix:
- PYTHONASYNCIODEBUG=1
- PYTHONASYNCIODEBUG=0
- PYTHONASYNCIODEBUG=x
- PYTHONASYNCIODEBUG=
services:
- redis-server
sudo: false
19 changes: 19 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
Changes
=======

0.3.0 (2015-11-20)
------------------

- Reflect aiohttp changes: minimum required Python version is 3.4.1

- Use explicit 'aiohttp_session' package

0.2.0 (2015-09-07)
------------------

- Add session.created property #14

- Replaced PyCrypto with crypthography library #16

0.1.2 (2015-08-07)
------------------

- Add manifest file #15

0.1.1 (2015-04-20)
------------------

Expand Down
9 changes: 9 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
include LICENSE
include CHANGES.txt
include README.rst
include Makefile
graft aiohttp_session
graft docs
graft tests
global-exclude *.pyc
prune docs/_build
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ Available session storages are:
encodes it via AES cipher. ``secrect_key`` is a ``bytes`` key for AES
encryption/decryption, the length should be 16 bytes.

Requires ``PyCrypto`` library::
Requires ``crypotgraphy`` library::

$ pip install aiohttp_session[pycrypto]
$ pip install aiohttp_session[secure]

* ``aiohttp_session.redis_storage.RedisStorage(redis_pool)`` -- stores
JSON-ed data into *redis*, keepeng into cookie only redis key
Expand Down
47 changes: 41 additions & 6 deletions aiohttp_session/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
import asyncio
from collections import MutableMapping
import json
import time

from aiohttp import web


__version__ = '0.1.1'
__version__ = '0.3.0'


class Session(MutableMapping):
Expand All @@ -20,13 +21,21 @@ def __init__(self, identity, *, data=None, new=False):
self._mapping = {}
self._identity = identity
self._new = new
if data is not None:
self._mapping.update(data)
created = data.get('created', None) if data else None
session_data = data.get('session', None) if data else None

if new or created is None:
self._created = int(time.time())
else:
self._created = created

if session_data is not None:
self._mapping.update(session_data)

def __repr__(self):
return '<{} [new:{}, changed:{}] {!r}>'.format(
return '<{} [new:{}, changed:{}, created:{}] {!r}>'.format(
self.__class__.__name__, self.new, self._changed,
self._mapping)
self.created, self._mapping)

@property
def new(self):
Expand All @@ -36,6 +45,22 @@ def new(self):
def identity(self):
return self._identity

@property
def created(self):
return self._created

@property
def empty(self):
return not bool(self._mapping)

@property
def max_age(self):
return self._max_age

@max_age.setter
def max_age(self, value):
self._max_age = value

def changed(self):
self._changed = True

Expand Down Expand Up @@ -143,6 +168,16 @@ def max_age(self):
def cookie_params(self):
return self._cookie_params

def _get_session_data(self, session):
if not session.empty:
data = {
'created': session.created,
'session': session._mapping
}
else:
data = {}
return data

@asyncio.coroutine
@abc.abstractmethod
def load_session(self, request):
Expand Down Expand Up @@ -188,5 +223,5 @@ def load_session(self, request):

@asyncio.coroutine
def save_session(self, request, response, session):
cookie_data = json.dumps(session._mapping)
cookie_data = json.dumps(self._get_session_data(session))
self.save_cookie(response, cookie_data)
43 changes: 16 additions & 27 deletions aiohttp_session/cookie_storage.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import asyncio
import json
import base64
from . import AbstractStorage, Session

from Crypto.Cipher import AES
from Crypto import Random
from cryptography import fernet

from . import AbstractStorage, Session


class EncryptedCookieStorage(AbstractStorage):
Expand All @@ -18,39 +18,28 @@ def __init__(self, secret_key, *, cookie_name="AIOHTTP_SESSION",
max_age=max_age, path=path, secure=secure,
httponly=httponly)

self._secret_key = secret_key
if len(self._secret_key) % AES.block_size != 0:
raise TypeError(
'Secret key must be a multiple of {} in length'.format(
AES.block_size))
self._fernet = fernet.Fernet(base64.urlsafe_b64encode(secret_key))

@asyncio.coroutine
def load_session(self, request):
cookie = self.load_cookie(request)
if cookie is None:
return Session(None, new=True)
else:
cookie = base64.b64decode(cookie)
iv = cookie[:AES.block_size]
data = cookie[AES.block_size:]
cipher = AES.new(self._secret_key, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(data)
data = json.loads(decrypted.decode('utf-8'))
data = json.loads(
self._fernet.decrypt(cookie.encode('utf-8')).decode('utf-8')
)
return Session(None, data=data, new=False)

@asyncio.coroutine
def save_session(self, request, response, session):
if not session._mapping:
if session.empty:
return self.save_cookie(response, session._mapping)
cookie_data = json.dumps(session._mapping).encode('utf-8')
if len(cookie_data) % AES.block_size != 0:
# padding with spaces to full blocks
to_pad = AES.block_size - (len(cookie_data) % AES.block_size)
cookie_data += b' ' * to_pad

iv = Random.new().read(AES.block_size)
cipher = AES.new(self._secret_key, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(cookie_data)
encrypted = iv + encrypted
b64coded = base64.b64encode(encrypted).decode('utf-8')
self.save_cookie(response, b64coded)

cookie_data = json.dumps(
self._get_session_data(session)
).encode('utf-8')
self.save_cookie(
response,
self._fernet.encrypt(cookie_data).decode('utf-8'),
)
3 changes: 2 additions & 1 deletion aiohttp_session/redis_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ def save_session(self, request, response, session):
else:
key = str(key)
self.save_cookie(response, key)
data = self._encoder(session._mapping)

data = self._encoder(self._get_session_data(session))
with (yield from self._redis) as conn:
max_age = self.max_age
expire = max_age if max_age is not None else 0
Expand Down
3 changes: 2 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,4 +330,5 @@
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/3': None,
'http://aiohttp.readthedocs.org/en/stable': None,
'http://aioredis.readthedocs.org/en/latest': None}
'http://aioredis.readthedocs.org/en/latest': None,
'http://cryptography.io/en/latest': None}
28 changes: 28 additions & 0 deletions docs/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,37 @@
.. glossary::

aioredis

:term:`asyncio` compatible Redis client library

http://aioredis.readthedocs.org/

asyncio

The library for writing single-threaded concurrent code using
coroutines, multiplexing I/O access over sockets and other
resources, running network clients and servers, and other
related primitives.

Reference implementation of :pep:`3156`

https://docs.python.org/3/library/asyncio.html

cryptography

The libary used for encrypting secure cookied session

https://cryptography.io

session

A namespace that is valid for some period of continual activity
that can be used to represent a user's interaction with a web
application.

sqlalchemy

The Python SQL Toolkit and Object Relational Mapper.

http://www.sqlalchemy.org/

0 comments on commit 9bff57b

Please sign in to comment.