sessions in secure cookies, session.connect(cookie_key='mypassword')
This commit is contained in:
@@ -1 +1 @@
|
|||||||
Version 2.1.1 (2012-10-15 21:49:38) dev
|
Version 2.1.1 (2012-10-16 06:15:40) dev
|
||||||
|
|||||||
@@ -136,12 +136,11 @@ class WebClient(object):
|
|||||||
raise error
|
raise error
|
||||||
|
|
||||||
# parse headers into cookies
|
# parse headers into cookies
|
||||||
|
self.cookies = {}
|
||||||
if 'set-cookie' in self.headers:
|
if 'set-cookie' in self.headers:
|
||||||
self.cookies = dict(
|
for item in self.headers['set-cookie'].split(','):
|
||||||
item[:item.find(';')].split('=') for item in \
|
key,value = item[:item.find(';')].split('=')
|
||||||
self.headers['set-cookie'].split(','))
|
self.cookies[key.strip()]=value.strip()
|
||||||
else:
|
|
||||||
self.cookies = {}
|
|
||||||
|
|
||||||
# check is a new session id has been issued, symptom of broken session
|
# check is a new session id has been issued, symptom of broken session
|
||||||
if self.session_regex is not None:
|
if self.session_regex is not None:
|
||||||
|
|||||||
+80
-81
@@ -23,7 +23,7 @@ from http import HTTP, redirect
|
|||||||
from fileutils import up
|
from fileutils import up
|
||||||
from serializers import json, custom_json
|
from serializers import json, custom_json
|
||||||
import settings
|
import settings
|
||||||
from utils import web2py_uuid
|
from utils import web2py_uuid, secure_dumps, secure_loads
|
||||||
from settings import global_settings
|
from settings import global_settings
|
||||||
import hashlib
|
import hashlib
|
||||||
import portalocker
|
import portalocker
|
||||||
@@ -39,11 +39,6 @@ import threading
|
|||||||
import hmac
|
import hmac
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
try:
|
|
||||||
from Crypto.Cipher import AES
|
|
||||||
except ImportError:
|
|
||||||
from contrib import aes as AES
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from gluon.contrib.minify import minify
|
from gluon.contrib.minify import minify
|
||||||
have_minify = True
|
have_minify = True
|
||||||
@@ -463,8 +458,8 @@ class Session(Storage):
|
|||||||
|
|
||||||
def connect(
|
def connect(
|
||||||
self,
|
self,
|
||||||
request,
|
request=None,
|
||||||
response,
|
response=None,
|
||||||
db=None,
|
db=None,
|
||||||
tablename='web2py_session',
|
tablename='web2py_session',
|
||||||
masterapp=None,
|
masterapp=None,
|
||||||
@@ -478,6 +473,8 @@ class Session(Storage):
|
|||||||
and it is used to determine a session prefix.
|
and it is used to determine a session prefix.
|
||||||
separate can be True and it is set to session_name[-2:]
|
separate can be True and it is set to session_name[-2:]
|
||||||
"""
|
"""
|
||||||
|
if request is None: request = current.request
|
||||||
|
if response is None: response = current.response
|
||||||
if separate == True:
|
if separate == True:
|
||||||
separate = lambda session_name: session_name[-2:]
|
separate = lambda session_name: session_name[-2:]
|
||||||
self._unlock(response)
|
self._unlock(response)
|
||||||
@@ -487,34 +484,32 @@ class Session(Storage):
|
|||||||
|
|
||||||
# Load session data from cookie
|
# Load session data from cookie
|
||||||
cookies = request.cookies
|
cookies = request.cookies
|
||||||
|
|
||||||
|
if response.session_id_name in cookies:
|
||||||
|
response.session_id = \
|
||||||
|
cookies[response.session_id_name].value
|
||||||
|
else:
|
||||||
|
response.session_id = None
|
||||||
|
|
||||||
if cookie_key:
|
if cookie_key:
|
||||||
|
response.session_storage_type = 'cookie'
|
||||||
response.session_cookie_key = cookie_key
|
response.session_cookie_key = cookie_key
|
||||||
response.session_cookie_key2 = hashlib.md5(cookie_key).digest()
|
response.session_cookie_hkey = hashlib.md5(cookie_key).hexdigest()
|
||||||
cookie_name = masterapp.lower()+'_session_data'
|
cookie_name = 'session_data_'+masterapp.lower()
|
||||||
response.session_cookie_name = cookie_name
|
response.session_cookie_name = cookie_name
|
||||||
if cookie_name in cookies:
|
if cookie_name in cookies:
|
||||||
cookie_value = cookies[cookie_name].value
|
cookie_value = cookies[cookie_name].value
|
||||||
cookie_parts = cookie_value.split(":")
|
session_data = secure_loads(cookie_value, cookie_key)
|
||||||
enc = cookie_parts[2]
|
if session_data:
|
||||||
cipher = AES.new(cookie_key)
|
|
||||||
decrypted = cipher.decrypt(base64.b64decode(enc)).rstrip('{')
|
|
||||||
check = hmac.new(response.session_cookie_key2,enc).hexdigest()
|
|
||||||
if cookie_parts[0] == check:
|
|
||||||
session_data = cPickle.loads(decrypted)
|
|
||||||
self.update(session_data)
|
self.update(session_data)
|
||||||
else:
|
elif not db:
|
||||||
return
|
response.session_storage_type = 'file'
|
||||||
|
|
||||||
if not db:
|
|
||||||
if global_settings.db_sessions is True \
|
if global_settings.db_sessions is True \
|
||||||
or masterapp in global_settings.db_sessions:
|
or masterapp in global_settings.db_sessions:
|
||||||
return
|
return
|
||||||
response.session_new = False
|
response.session_new = False
|
||||||
client = request.client and request.client.replace(':', '.')
|
client = request.client and request.client.replace(':', '.')
|
||||||
if response.session_id_name in cookies:
|
if response.session_id:
|
||||||
response.session_id = \
|
|
||||||
cookies[response.session_id_name].value
|
|
||||||
if regex_session_id.match(response.session_id):
|
if regex_session_id.match(response.session_id):
|
||||||
response.session_filename = \
|
response.session_filename = \
|
||||||
os.path.join(up(request.folder), masterapp,
|
os.path.join(up(request.folder), masterapp,
|
||||||
@@ -553,9 +548,9 @@ class Session(Storage):
|
|||||||
'sessions', response.session_id)
|
'sessions', response.session_id)
|
||||||
response.session_new = True
|
response.session_new = True
|
||||||
else:
|
else:
|
||||||
|
response.session_storage_type = 'db'
|
||||||
if global_settings.db_sessions is not True:
|
if global_settings.db_sessions is not True:
|
||||||
global_settings.db_sessions.add(masterapp)
|
global_settings.db_sessions.add(masterapp)
|
||||||
response.session_db = True
|
|
||||||
if response.session_file:
|
if response.session_file:
|
||||||
self._close(response)
|
self._close(response)
|
||||||
if settings.global_settings.web2py_runtime_gae:
|
if settings.global_settings.web2py_runtime_gae:
|
||||||
@@ -584,9 +579,7 @@ class Session(Storage):
|
|||||||
try:
|
try:
|
||||||
|
|
||||||
# Get session data out of the database
|
# Get session data out of the database
|
||||||
# Key comes from the cookie
|
(record_id, unique_key) = response.session_id.split(':')
|
||||||
key = cookies[response.session_id_name].value
|
|
||||||
(record_id, unique_key) = key.split(':')
|
|
||||||
if record_id == '0':
|
if record_id == '0':
|
||||||
raise Exception, 'record_id == 0'
|
raise Exception, 'record_id == 0'
|
||||||
# Select from database
|
# Select from database
|
||||||
@@ -602,13 +595,13 @@ class Session(Storage):
|
|||||||
record_id = None
|
record_id = None
|
||||||
unique_key = web2py_uuid()
|
unique_key = web2py_uuid()
|
||||||
session_data = {}
|
session_data = {}
|
||||||
response._dbtable_and_field = \
|
|
||||||
(response.session_id_name, table, record_id, unique_key)
|
|
||||||
response.session_id = '%s:%s' % (record_id, unique_key)
|
response.session_id = '%s:%s' % (record_id, unique_key)
|
||||||
|
response.session_db_table = table
|
||||||
|
response.session_db_record_id = record_id
|
||||||
|
response.session_db_unique_key = unique_key
|
||||||
rcookies = response.cookies
|
rcookies = response.cookies
|
||||||
rcookies[response.session_id_name] = response.session_id
|
rcookies[response.session_id_name] = response.session_id
|
||||||
rcookies[response.session_id_name]['path'] = '/'
|
rcookies[response.session_id_name]['path'] = '/'
|
||||||
self.__hash = hashlib.md5(str(self)).digest()
|
|
||||||
if self.flash:
|
if self.flash:
|
||||||
(response.flash, self.flash) = (self.flash, None)
|
(response.flash, self.flash) = (self.flash, None)
|
||||||
|
|
||||||
@@ -636,31 +629,38 @@ class Session(Storage):
|
|||||||
self._forget = True
|
self._forget = True
|
||||||
|
|
||||||
def _try_store_in_cookie(self, request, response):
|
def _try_store_in_cookie(self, request, response):
|
||||||
pad = lambda s: s + (32 - len(s) % 32) * '{'
|
if response.session_storage_type!='cookie': return False
|
||||||
data = cPickle.dumps(dict(self))
|
value = secure_dumps(dict(self),response.session_cookie_key)
|
||||||
cipher = AES.new(response.session_cookie_key)
|
|
||||||
encrypted_data = base64.b64encode(cipher.encrypt(pad(data)))
|
|
||||||
signature = hmac.new(response.session_cookie_key2,encrypted_data)\
|
|
||||||
.hexdigest()
|
|
||||||
value = signature+':'+encrypted_data
|
|
||||||
response.cookies[response.session_cookie_name] = value
|
response.cookies[response.session_cookie_name] = value
|
||||||
response.cookies[response.session_cookie_name]['path'] = '/'
|
response.cookies[response.session_cookie_name]['path'] = '/'
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _unchanged(self):
|
||||||
|
previous_session_hash = self.pop('_session_hash',None)
|
||||||
|
if not previous_session_hash and not self:
|
||||||
|
return True
|
||||||
|
session_pickled = cPickle.dumps(dict(self))
|
||||||
|
session_hash = hashlib.md5(session_pickled).hexdigest()
|
||||||
|
if previous_session_hash == session_hash:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self._session_hash = session_hash
|
||||||
|
return False
|
||||||
|
|
||||||
def _try_store_in_db(self, request, response):
|
def _try_store_in_db(self, request, response):
|
||||||
# don't save if file-based sessions, no session id, or session being forgotten
|
# don't save if file-based sessions,
|
||||||
if not response.session_db or not response.session_id or self._forget:
|
# no session id, or session being forgotten
|
||||||
return
|
# or no changes to session
|
||||||
|
if response.session_storage_type!='db' or not response.session_id \
|
||||||
|
or self._forget or self._unchanged():
|
||||||
|
return False
|
||||||
|
|
||||||
# don't save if no change to session
|
table = response.session_db_table
|
||||||
__hash = self.__hash
|
record_id = response.session_db_record_id
|
||||||
if __hash is not None:
|
unique_key = response.session_db_unique_key
|
||||||
del self.__hash
|
|
||||||
if __hash == hashlib.md5(str(self)).digest():
|
|
||||||
return
|
|
||||||
|
|
||||||
(record_id_name, table, record_id, unique_key) = \
|
dd = dict(locked=False,
|
||||||
response._dbtable_and_field
|
client_ip=request.client.replace(':','.'),
|
||||||
dd = dict(locked=False, client_ip=request.client.replace(':','.'),
|
|
||||||
modified_datetime=request.now,
|
modified_datetime=request.now,
|
||||||
session_data=cPickle.dumps(dict(self)),
|
session_data=cPickle.dumps(dict(self)),
|
||||||
unique_key=unique_key)
|
unique_key=unique_key)
|
||||||
@@ -668,41 +668,40 @@ class Session(Storage):
|
|||||||
table._db(table.id == record_id).update(**dd)
|
table._db(table.id == record_id).update(**dd)
|
||||||
else:
|
else:
|
||||||
record_id = table.insert(**dd)
|
record_id = table.insert(**dd)
|
||||||
response.cookies[response.session_id_name] = '%s:%s'\
|
|
||||||
% (record_id, unique_key)
|
|
||||||
response.cookies[response.session_id_name]['path'] = '/'
|
|
||||||
|
|
||||||
def _try_store_on_disk(self, request, response):
|
cookies, session_id_name = response.cookies, response.session_id_name
|
||||||
|
cookies[session_id_name] = '%s:%s' % (record_id, unique_key)
|
||||||
|
cookies[session_id_name]['path'] = '/'
|
||||||
|
return True
|
||||||
|
|
||||||
# don't save if sessions not not file-based
|
def _try_store_in_cookie_or_file(self, request, response):
|
||||||
if response.session_db:
|
return \
|
||||||
return
|
self._try_store_in_cookie(request,response) or \
|
||||||
|
self._try_store_in_file(request,response)
|
||||||
|
|
||||||
# don't save if no change to session
|
def _try_store_in_file(self, request, response):
|
||||||
__hash = self.__hash
|
if response.session_storage_type!='file':
|
||||||
if __hash is not None:
|
return False
|
||||||
del self.__hash
|
|
||||||
if __hash == hashlib.md5(str(self)).digest():
|
|
||||||
self._close(response)
|
|
||||||
return
|
|
||||||
|
|
||||||
if not response.session_id or self._forget:
|
try:
|
||||||
self._close(response)
|
if not response.session_id or self._forget or self._unchanged():
|
||||||
return
|
return False
|
||||||
|
|
||||||
if response.session_new:
|
if response.session_new:
|
||||||
# Tests if the session sub-folder exists, if not, create it
|
# Tests if the session sub-folder exists, if not, create it
|
||||||
session_folder = os.path.dirname(response.session_filename)
|
session_folder = os.path.dirname(response.session_filename)
|
||||||
if not os.path.exists(session_folder):
|
if not os.path.exists(session_folder):
|
||||||
os.mkdir(session_folder)
|
os.mkdir(session_folder)
|
||||||
response.session_file = open(response.session_filename, 'wb')
|
response.session_file = open(response.session_filename, 'wb')
|
||||||
portalocker.lock(response.session_file, portalocker.LOCK_EX)
|
portalocker.lock(response.session_file, portalocker.LOCK_EX)
|
||||||
response.session_locked = True
|
response.session_locked = True
|
||||||
|
|
||||||
if response.session_file:
|
if response.session_file:
|
||||||
cPickle.dump(dict(self), response.session_file)
|
cPickle.dump(dict(self), response.session_file)
|
||||||
response.session_file.truncate()
|
response.session_file.truncate()
|
||||||
|
finally:
|
||||||
self._close(response)
|
self._close(response)
|
||||||
|
return True
|
||||||
|
|
||||||
def _unlock(self, response):
|
def _unlock(self, response):
|
||||||
if response and response.session_file and response.session_locked:
|
if response and response.session_file and response.session_locked:
|
||||||
|
|||||||
+1
-1
@@ -558,7 +558,7 @@ def wsgibase(environ, responder):
|
|||||||
# this must be done after trying to commit database!
|
# this must be done after trying to commit database!
|
||||||
# ##################################################
|
# ##################################################
|
||||||
|
|
||||||
session._try_store_on_disk(request, response)
|
session._try_store_in_cookie_or_file(request, response)
|
||||||
|
|
||||||
if request.cid:
|
if request.cid:
|
||||||
if response.flash:
|
if response.flash:
|
||||||
|
|||||||
@@ -21,6 +21,14 @@ import os
|
|||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
|
import cPickle
|
||||||
|
import base64
|
||||||
|
|
||||||
|
try:
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
except ImportError:
|
||||||
|
from contrib import aes as AES
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from contrib.pbkdf2 import pbkdf2_hex
|
from contrib.pbkdf2 import pbkdf2_hex
|
||||||
HAVE_PBKDF2 = True
|
HAVE_PBKDF2 = True
|
||||||
@@ -94,6 +102,34 @@ DIGEST_ALG_BY_SIZE = {
|
|||||||
512/4: 'sha512',
|
512/4: 'sha512',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def pad(s,n=32,padchar='.'):
|
||||||
|
return s + (32 - len(s) % 32) * padchar
|
||||||
|
|
||||||
|
def secure_dumps(data,encryption_key,hash_key=None):
|
||||||
|
if not hash_key:
|
||||||
|
hash_key = hashlib.sha1(encryption_key).hexdigest()
|
||||||
|
dump = cPickle.dumps(data)
|
||||||
|
key = pad(encryption_key[:32])
|
||||||
|
cipher = AES.new(key,IV=key)
|
||||||
|
encrypted_data = base64.b16encode(cipher.encrypt(pad(dump)))
|
||||||
|
signature = hmac.new(hash_key,encrypted_data).hexdigest()
|
||||||
|
return signature+':'+encrypted_data
|
||||||
|
|
||||||
|
def secure_loads(data,encryption_key,hash_key=None):
|
||||||
|
if not hash_key:
|
||||||
|
hash_key = hashlib.sha1(encryption_key).hexdigest()
|
||||||
|
signature, encrypted_data = data.split(':',1)
|
||||||
|
actual_signature = hmac.new(hash_key,encrypted_data).hexdigest()
|
||||||
|
if signature!=actual_signature:
|
||||||
|
return None
|
||||||
|
key = pad(encryption_key[:32])
|
||||||
|
cipher = AES.new(key,IV=key)
|
||||||
|
try:
|
||||||
|
data = cipher.decrypt(base64.b16decode(encrypted_data))
|
||||||
|
data = data.rstrip(' ')
|
||||||
|
return cPickle.loads(data)
|
||||||
|
except (TypeError,cPickle.UnpicklingError):
|
||||||
|
return None
|
||||||
|
|
||||||
### compute constant CTOKENS
|
### compute constant CTOKENS
|
||||||
def initialize_urandom():
|
def initialize_urandom():
|
||||||
|
|||||||
Reference in New Issue
Block a user