def __init__(self, secret, cookie_name='auth', secure=False, max_age=None, httponly=False, path="/", domains=None, timeout=None, reissue_time=None, debug=False, hashalg='sha512', ): self.domains = domains self.cookie = CookieHelper( secret, 'usingnamespace-auth', cookie_name, secure=secure, max_age=max_age, httponly=httponly, path=path, domains=domains, hashalg=hashalg, ) self.debug = debug
def __init__(self, secret, salt='paildocket-auth', cookie_name='auth', secure=False, http_only=False, path='/', domains=None, hashalg='sha512', timeout=None, reissue_time=None, max_age=None, debug=False): """ :param secret: Secret for the secure cookie generator. :param salt: Salt for collision protection (but don't use the same secret anyway). :param cookie_name: Name of the auth ticket cookie. :param secure: Only send the cookie over a secure connection. :param http_only: Set HttpOnly flag on the cookie to prevent access by JavaScript (on conforming browsers). :param path: The path for the cookie. :param domains: The domains for the cookie. :param hashalg: The hashing algorithm to use for the cookie signature. :param timeout: The maximum age of the ticket, in seconds. When this amount of time passes after the ticket is created, the ticket will no longer be valid. :param reissue_time: The number of seconds before an authentication token cookie is reissued. If provided, must be less than `timeout`. :param max_age: The maximum age of the cookie in the browser, in seconds. If provided, must be greater than `timeout` and `reissue_time`. :param debug: If true, log verbosely. """ if secure: raise NotImplementedError if reissue_time: if not timeout or reissue_time >= timeout: raise ValueError('reissue_time must be less than timeout') if max_age: if not timeout or timeout >= max_age: raise ValueError('max_age must be greater than timeout') if not reissue_time or reissue_time >= max_age: raise ValueError('max_age must be greater than reissue_time') self.cookie = SignedCookieProfile( secret, salt, cookie_name, secure=secure, httponly=http_only, path=path, domains=domains, hashalg=hashalg, max_age=max_age, ) self.reissue_time = reissue_time self.timeout = timeout self.debug = debug
def __init__( self, secret, cookie_name='auth', secure=False, max_age=None, httponly=False, path="/", domains=None, timeout=None, reissue_time=None, debug=False, hashalg='sha512', ): self.domains = domains self.cookie = CookieHelper( secret, 'alexandria-auth', cookie_name, secure=secure, max_age=max_age, httponly=httponly, path=path, domains=domains, hashalg=hashalg, ) self.debug = debug
def _adapter_for(name, profile): return match( Profile, { SignedProfile: (lambda config, secret, salt: SignedCookieProfile( secret, salt, name, **config)), UnsignedProfile: (lambda config: CookieProfile(name, **config)) }, profile)
def __init__( self, secret, cookie_name="auth", secure=False, max_age=None, httponly=False, path="/", domains=None, timeout=None, reissue_time=None, debug=False, hashalg="sha512", ): self.domains = domains self.cookie = CookieHelper( secret, "alexandria-auth", cookie_name, secure=secure, max_age=max_age, httponly=httponly, path=path, domains=domains, hashalg=hashalg, ) self.debug = debug
def factory(_context, request): """Return a AuthCookieService instance for the passed context and request.""" cookie = SignedCookieProfile( # This value is set in `h.auth` at the moment secret=request.registry.settings["h_auth_cookie_secret"], salt="authsanity", cookie_name="auth", secure=False, max_age=30 * 24 * 3600, # 30 days httponly=True, ) return AuthCookieService( request.db, user_service=request.find_service(name="user"), cookie=cookie.bind(request), )
def __init__(self, context, request): self.domains = domains if self.domains is None: self.domains = [] self.domains.append(request.domain) self.cookie = SignedCookieProfile( secret, 'authsanity', cookie_name, secure=secure, max_age=max_age, httponly=httponly, path=path, domains=domains, hashalg=hashalg, ) # Bind the cookie to the current request self.cookie = self.cookie.bind(request)
def test_it(self, pyramid_request, SignedCookieProfile, AuthCookieService, user_service): pyramid_request.registry.settings[ "h_auth_cookie_secret"] = sentinel.cookie_secret cookie_service = factory(sentinel.context, pyramid_request) SignedCookieProfile.assert_called_once_with( secret=pyramid_request.registry.settings["h_auth_cookie_secret"], salt="authsanity", cookie_name="auth", secure=False, max_age=2592000, httponly=True, ) SignedCookieProfile.return_value.bind.assert_called_once_with( pyramid_request) AuthCookieService.assert_called_once_with( pyramid_request.db, user_service=user_service, cookie=SignedCookieProfile.return_value.bind.return_value, ) assert cookie_service == AuthCookieService.return_value
class CookieAuthSource(object): vary = ['Cookie'] def __init__(self, context, request): self.domains = domains if self.domains is None: self.domains = [] self.domains.append(request.domain) self.cookie = SignedCookieProfile( secret, 'authsanity', cookie_name, secure=secure, max_age=max_age, httponly=httponly, path=path, domains=domains, hashalg=hashalg, ) # Bind the cookie to the current request self.cookie = self.cookie.bind(request) def get_value(self): val = self.cookie.get_value() if val is None: return [None, None] return val def headers_remember(self, value): return self.cookie.get_headers(value, domains=self.domains) def headers_forget(self): return self.cookie.get_headers('', max_age=0)
class AuthPolicy(object): def _log(self, msg, methodname, request): logger = request.registry.queryUtility(IDebugLogger) if logger: cls = self.__class__ classname = cls.__module__ + "." + cls.__name__ methodname = classname + "." + methodname logger.debug(methodname + ": " + msg) def __init__( self, secret, cookie_name="auth", secure=False, max_age=None, httponly=False, path="/", domains=None, timeout=None, reissue_time=None, debug=False, hashalg="sha512", ): self.domains = domains self.cookie = CookieHelper( secret, "alexandria-auth", cookie_name, secure=secure, max_age=max_age, httponly=httponly, path=path, domains=domains, hashalg=hashalg, ) self.debug = debug def unauthenticated_userid(self, request): """ No support for the unauthenticated userid """ return None def authenticated_userid(self, request): """ Return the authenticated userid or ``None``.""" try: return request.state["auth"]["userinfo"].id except: pass result = self.cookie.bind(request).get_value() self.debug and self._log("Got result from cookie: %s" % (result,), "authenticated_userid", request) class UserInfo(object): def __init__(self): self.id = None self.auth = {} self.user = None self.ticket = None userinfo = UserInfo() request.state["auth"] = {} request.state["auth"]["userinfo"] = userinfo if result: request.state["auth"]["principal"] = result["principal"] request.state["auth"]["ticket"] = result["ticket"] request.state["auth"]["tokens"] = result["tokens"] ticket = self.find_user_ticket(request) if ticket is None: return None userinfo.id = ticket.user.email userinfo.user = ticket.user userinfo.ticket = ticket return userinfo.id else: return None def find_user_ticket(self, request): """ Return the user object if valid for the ticket or ``None``.""" auth = request.state.get("auth", {}) ticket = auth.get("ticket", "") principal = auth.get("principal", "") if not ticket or not principal: return None ticket = UserTickets.find_ticket_userid(request.dbsession, ticket, principal) if ticket is None: self.debug and self._log("No ticket found", "find_user_ticket", request) self.cookie.set_cookies(request.response, "", max_age=0) return ticket def effective_principals(self, request): """ A list of effective principals derived from request. This will return a list of principals including, at least, :data:`pyramid.security.Everyone`. If there is no authenticated userid, or the ``callback`` returns ``None``, this will be the only principal: .. code-block:: python return [Everyone] """ debug = self.debug effective_principals = [Everyone] userid = self.authenticated_userid(request) if userid is None: debug and self._log( "authenticated_userid returned %r; returning %r" % (userid, effective_principals), "effective_principals", request, ) return effective_principals groups = [] # Get the groups here ... effective_principals.append(Authenticated) effective_principals.append(userid) effective_principals.extend(groups) debug and self._log( "returning effective principals: %r" % (effective_principals,), "effective_principals", request ) return effective_principals def remember(self, request, principal, tokens=None, **kw): """ Accepts the following kw args: ``max_age=<int-seconds>`` Return a list of headers which will set appropriate cookies on the response. """ debug = self.debug hashalg = "sha256" digestmethod = lambda string=b"": hashlib.new(hashalg, string) value = {} value["principal"] = principal value["ticket"] = ticket = digestmethod(urandom(32)).hexdigest() value["tokens"] = tokens if tokens is not None else [] user = request.dbsession.query(User).filter(User.email == principal).first() if user is None: raise ValueError("Invalid principal provided") debug and self._log("Remember user: %s, ticket: %s" % (user.email, value["ticket"]), "remember", request) ticket = value["ticket"] remote_addr = request.environ["REMOTE_ADDR"] if "REMOTE_ADDR" in request.environ else None user.tickets.append(UserTickets(ticket=ticket, remote_addr=remote_addr)) if self.domains is None: self.domains = [] self.domains.append(request.domain) return self.cookie.get_headers(value, domains=self.domains) def forget(self, request): """ A list of headers which will delete appropriate cookies.""" debug = self.debug user = request.user if user.ticket: debug and self._log( "forgetting user: %s, removing ticket: %s" % (user.id, user.ticket.ticket), "forget", request ) request.dbsession.delete(user.ticket) return self.cookie.get_headers("", max_age=0)
def __init__(self, request): self._cookie = CookieHelper( secret, salt, cookie_name, secure=secure, max_age=max_age, httponly=httponly, path=path, domains=domain, hashalg=hashalg, ) self._session_id = None self.request = request reg = request.registry plug = reg.queryUtility(IPlugSession) if plug is None: raise RuntimeError( 'Unable to find any registered IPlugSession') now = time.time() created = renewed = now new = True value = None state = {} # Get the session_id self._session_id = self._cookie.bind(request).get_value() if self._session_id is not None: try: sess_val = plug.loads(self, request) value = serializer.loads(bytes_(sess_val)) except ValueError: value = None # Cleanup the session, since it failed to deserialize plug.clear(self, request) self._session_id = None if value is not None: try: rval, cval, sval = value renewed = float(rval) created = float(cval) state = sval new = False except (TypeError, ValueError): # value failed to unpack properly or renewed was not # a numeric type so we'll fail deserialization here state = {} # Clean up the session since it failed to unpack plug.clear(self, request) self._session_id = None if self._timeout is not None: if now - renewed > self._timeout: # expire the session because it was not renewed # before the timeout threshold state = {} # Session expired, cleanup this session plug.clear(self, request) self._session_id = None # Generate a new session id if self._session_id is None: self._generate_new_id() self.created = created self.accessed = renewed self.renewed = renewed self.new = new self._plug = plug dict.__init__(self, state)
class AuthPolicy(object): def _log(self, msg, methodname, request): logger = request.registry.queryUtility(IDebugLogger) if logger: cls = self.__class__ classname = cls.__module__ + '.' + cls.__name__ methodname = classname + '.' + methodname logger.debug(methodname + ': ' + msg) def __init__( self, secret, cookie_name='auth', secure=False, max_age=None, httponly=False, path="/", domains=None, timeout=None, reissue_time=None, debug=False, hashalg='sha512', ): self.domains = domains self.cookie = CookieHelper( secret, 'alexandria-auth', cookie_name, secure=secure, max_age=max_age, httponly=httponly, path=path, domains=domains, hashalg=hashalg, ) self.debug = debug def unauthenticated_userid(self, request): """ No support for the unauthenticated userid """ return None def authenticated_userid(self, request): """ Return the authenticated userid or ``None``.""" try: return request.state['auth']['userinfo'].id except: pass result = self.cookie.bind(request).get_value() self.debug and self._log('Got result from cookie: %s' % (result, ), 'authenticated_userid', request) class UserInfo(object): def __init__(self): self.id = None self.auth = {} self.user = None self.ticket = None userinfo = UserInfo() request.state['auth'] = {} request.state['auth']['userinfo'] = userinfo if result: request.state['auth']['principal'] = result['principal'] request.state['auth']['ticket'] = result['ticket'] request.state['auth']['tokens'] = result['tokens'] ticket = self.find_user_ticket(request) if ticket is None: return None userinfo.id = ticket.user.email userinfo.user = ticket.user userinfo.ticket = ticket return userinfo.id else: return None def find_user_ticket(self, request): """ Return the user object if valid for the ticket or ``None``.""" auth = request.state.get('auth', {}) ticket = auth.get('ticket', '') principal = auth.get('principal', '') if not ticket or not principal: return None ticket = UserTickets.find_ticket_userid(request.dbsession, ticket, principal) if ticket is None: self.debug and self._log('No ticket found', 'find_user_ticket', request) self.cookie.set_cookies(request.response, '', max_age=0) return ticket def effective_principals(self, request): """ A list of effective principals derived from request. This will return a list of principals including, at least, :data:`pyramid.security.Everyone`. If there is no authenticated userid, or the ``callback`` returns ``None``, this will be the only principal: .. code-block:: python return [Everyone] """ debug = self.debug effective_principals = [Everyone] userid = self.authenticated_userid(request) if userid is None: debug and self._log( 'authenticated_userid returned %r; returning %r' % (userid, effective_principals), 'effective_principals', request) return effective_principals groups = [] # Get the groups here ... effective_principals.append(Authenticated) effective_principals.append(userid) effective_principals.extend(groups) debug and self._log( 'returning effective principals: %r' % (effective_principals, ), 'effective_principals', request) return effective_principals def remember(self, request, principal, tokens=None, **kw): """ Accepts the following kw args: ``max_age=<int-seconds>`` Return a list of headers which will set appropriate cookies on the response. """ debug = self.debug hashalg = 'sha256' digestmethod = lambda string=b'': hashlib.new(hashalg, string) value = {} value['principal'] = principal value['ticket'] = ticket = digestmethod(urandom(32)).hexdigest() value['tokens'] = tokens if tokens is not None else [] user = request.dbsession.query(User).filter( User.email == principal).first() if user is None: raise ValueError('Invalid principal provided') debug and self._log( 'Remember user: %s, ticket: %s' % (user.email, value['ticket']), 'remember', request) ticket = value['ticket'] remote_addr = request.environ[ 'REMOTE_ADDR'] if 'REMOTE_ADDR' in request.environ else None user.tickets.append(UserTickets(ticket=ticket, remote_addr=remote_addr)) if self.domains is None: self.domains = [] self.domains.append(request.domain) return self.cookie.get_headers(value, domains=self.domains) def forget(self, request): """ A list of headers which will delete appropriate cookies.""" debug = self.debug user = request.user if user.ticket: debug and self._log( 'forgetting user: %s, removing ticket: %s' % (user.id, user.ticket.ticket), 'forget', request) request.dbsession.delete(user.ticket) return self.cookie.get_headers('', max_age=0)
class PluggableSession(dict): """ Dictionary-like session object """ # configuration parameters _cookie_on_exception = set_on_exception _timeout = timeout _reissue_time = reissue_time # dirty flag _dirty = False def __init__(self, request): self._cookie = CookieHelper( secret, salt, cookie_name, secure=secure, max_age=max_age, httponly=httponly, path=path, domains=domain, hashalg=hashalg, ) self._session_id = None self.request = request reg = request.registry plug = reg.queryUtility(IPlugSession) if plug is None: raise RuntimeError('Unable to find any registered IPlugSession') now = time.time() created = renewed = now new = True value = None state = {} # Get the session_id self._session_id = self._cookie.bind(request).get_value() if self._session_id is not None: try: sess_val = plug.loads(self, request) value = serializer.loads(bytes_(sess_val)) except ValueError: value = None # Cleanup the session, since it failed to deserialize plug.clear(self, request) self._session_id = None if value is not None: try: rval, cval, sval = value renewed = float(rval) created = float(cval) state = sval new = False except (TypeError, ValueError): # value failed to unpack properly or renewed was not # a numeric type so we'll fail deserialization here state = {} # Clean up the session since it failed to unpack plug.clear(self, request) self._session_id = None if self._timeout is not None: if now - renewed > self._timeout: # expire the session because it was not renewed # before the timeout threshold state = {} # Session expired, cleanup this session plug.clear(self, request) self._session_id = None # Generate a new session id if self._session_id is None: self._generate_new_id() self.created = created self.accessed = renewed self.renewed = renewed self.new = new self._plug = plug dict.__init__(self, state) # ISession methods def changed(self): if not self._dirty: self._dirty = True def save_session_callback(request, response): self._save_session(response) self.request = None # explicitly break cycle for gc self.request.add_response_callback(save_session_callback) def invalidate(self): self._plug.clear(self, self.request) self._generate_new_id() now = time.time() self.created = self.renewed = now self.new = True self.clear() # non-modifying dictionary methods get = manage_accessed(dict.get) __getitem__ = manage_accessed(dict.__getitem__) items = manage_accessed(dict.items) values = manage_accessed(dict.values) keys = manage_accessed(dict.keys) __contains__ = manage_accessed(dict.__contains__) __len__ = manage_accessed(dict.__len__) __iter__ = manage_accessed(dict.__iter__) if not PY3: iteritems = manage_accessed(dict.iteritems) itervalues = manage_accessed(dict.itervalues) iterkeys = manage_accessed(dict.iterkeys) has_key = manage_accessed(dict.has_key) # modifying dictionary methods clear = manage_changed(dict.clear) update = manage_changed(dict.update) setdefault = manage_changed(dict.setdefault) pop = manage_changed(dict.pop) popitem = manage_changed(dict.popitem) __setitem__ = manage_changed(dict.__setitem__) __delitem__ = manage_changed(dict.__delitem__) # flash API methods @manage_changed def flash(self, msg, queue='', allow_duplicate=True): storage = self.setdefault('_f_' + queue, []) if allow_duplicate or (msg not in storage): storage.append(msg) @manage_changed def pop_flash(self, queue=''): storage = self.pop('_f_' + queue, []) return storage @manage_accessed def peek_flash(self, queue=''): storage = self.get('_f_' + queue, []) return storage # CSRF API methods @manage_changed def new_csrf_token(self): token = text_(binascii.hexlify(os.urandom(20))) self['_csrft_'] = token return token @manage_accessed def get_csrf_token(self): token = self.get('_csrft_', None) if token is None: token = self.new_csrf_token() return token # non-API methods def _save_session(self, response): if not self._cookie_on_exception: exception = getattr(self.request, 'exception', None) if exception is not None: # dont set a cookie during exceptions return False sess_val = native_( serializer.dumps( (self.accessed, self.created, dict(self)) ) ) self._plug.dumps(self, self.request, sess_val) self._cookie.set_cookies(response, self._session_id) return True def _generate_new_id(self): self._session_id = text_(binascii.hexlify(os.urandom(20)))
def __init__(self, request): self._cookie = CookieHelper( secret, salt, cookie_name, secure=secure, max_age=max_age, httponly=httponly, path=path, domains=domain, hashalg=hashalg, ) self._session_id = None self.request = request reg = request.registry plug = reg.queryUtility(IPlugSession) if plug is None: raise RuntimeError('Unable to find any registered IPlugSession') now = time.time() created = renewed = now new = True value = None state = {} # Get the session_id self._session_id = self._cookie.bind(request).get_value() if self._session_id is not None: try: sess_val = plug.loads(self, request) value = serializer.loads(bytes_(sess_val)) except ValueError: value = None # Cleanup the session, since it failed to deserialize plug.clear(self, request) self._session_id = None if value is not None: try: rval, cval, sval = value renewed = float(rval) created = float(cval) state = sval new = False except (TypeError, ValueError): # value failed to unpack properly or renewed was not # a numeric type so we'll fail deserialization here state = {} # Clean up the session since it failed to unpack plug.clear(self, request) self._session_id = None if self._timeout is not None: if now - renewed > self._timeout: # expire the session because it was not renewed # before the timeout threshold state = {} # Session expired, cleanup this session plug.clear(self, request) self._session_id = None # Generate a new session id if self._session_id is None: self._generate_new_id() self.created = created self.accessed = renewed self.renewed = renewed self.new = new self._plug = plug dict.__init__(self, state)
class AuthPolicy(object): def _log(self, msg, methodname, request): logger = request.registry.queryUtility(IDebugLogger) if logger: cls = self.__class__ classname = cls.__module__ + '.' + cls.__name__ methodname = classname + '.' + methodname logger.debug(methodname + ': ' + msg) def __init__(self, secret, cookie_name='auth', secure=False, max_age=None, httponly=False, path="/", domains=None, timeout=None, reissue_time=None, debug=False, hashalg='sha512', ): self.domains = domains self.cookie = CookieHelper( secret, 'usingnamespace-auth', cookie_name, secure=secure, max_age=max_age, httponly=httponly, path=path, domains=domains, hashalg=hashalg, ) self.debug = debug def unauthenticated_userid(self, request): """ The userid key within the auth_tkt cookie.""" result = self.cookie.bind(request).get_value() self.debug and self._log('Got result from cookie: %s' % (result,), 'unauthenticated_userid', request) if result: principal = result['principal'] if _clean_principal(principal) is None: self.debug and self._log('use of principal %r is disallowed by any ' 'built-in Pyramid security policy, returning None' % principal) return None auth = {'principal': principal} if 'tokens' in result: auth['tokens'] = result['tokens'] if 'auth_ticket' in result: auth['ticket'] = result['auth_ticket'] request.state['auth'] = auth return principal def authenticated_userid(self, request): """ Return the authenticated userid or ``None``.""" userid = request.user.id return userid def find_user_ticket(self, request): """ Return the user object if valid for the ticket or ``None``.""" auth = request.state.get('auth', {}) ticket = auth.get('ticket', '') principal = auth.get('principal', '') if not ticket or not principal: return None ticket = UserTickets.find_ticket_userid(ticket, principal) if ticket is None: self.debug and self._log('No ticket found', 'find_user_ticket', request) self.cookie.set_cookies(request.response, '', max_age=0) return ticket def effective_principals(self, request): """ A list of effective principals derived from request. This will return a list of principals including, at least, :data:`pyramid.security.Everyone`. If there is no authenticated userid, or the ``callback`` returns ``None``, this will be the only principal: .. code-block:: python return [Everyone] """ debug = self.debug effective_principals = [Everyone] userid = self.authenticated_userid(request) if userid is None: debug and self._log( 'authenticated_userid returned %r; returning %r' % ( userid, effective_principals), 'effective_principals', request ) return effective_principals groups = [] # Get the groups here ... effective_principals.append(Authenticated) effective_principals.append(userid) effective_principals.extend(groups) debug and self._log( 'returning effective principals: %r' % ( effective_principals,), 'effective_principals', request ) return effective_principals def remember(self, request, principal, tokens=None, max_age=None): """ Accepts the following kw args: ``max_age=<int-seconds>`` Return a list of headers which will set appropriate cookies on the response. """ debug = self.debug value = {} value['principal'] = principal value['auth_ticket'] = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for x in range(128)) value['tokens'] = tokens if tokens is not None else [] user = DBSession.query(User).filter(User.email == principal).first() if user is None: raise ValueError('Invalid principal provided') debug and self._log('Remember user: %s, ticket: %s' % (user.email, value['auth_ticket']), 'remember', request) ticket = value['auth_ticket'] remote_addr = request.environ['REMOTE_ADDR'] if 'REMOTE_ADDR' in request.environ else None user.tickets.append(UserTickets(ticket=ticket, remote_addr=remote_addr)) if self.domains is None: self.domains = [] self.domains.append(request.domain) return self.cookie.get_headers(value, domains=self.domains) def forget(self, request): """ A list of headers which will delete appropriate cookies.""" debug = self.debug user = request.user if user.ticket: debug and self._log('forgetting user: %s, removing ticket: %s' % (user.id, user.ticket.ticket), 'forget', request) DBSession.delete(user.ticket) return self.cookie.get_headers('', max_age=0)
class PaildocketAuthenticationPolicy(object): def __init__(self, secret, salt='paildocket-auth', cookie_name='auth', secure=False, http_only=False, path='/', domains=None, hashalg='sha512', timeout=None, reissue_time=None, max_age=None, debug=False): """ :param secret: Secret for the secure cookie generator. :param salt: Salt for collision protection (but don't use the same secret anyway). :param cookie_name: Name of the auth ticket cookie. :param secure: Only send the cookie over a secure connection. :param http_only: Set HttpOnly flag on the cookie to prevent access by JavaScript (on conforming browsers). :param path: The path for the cookie. :param domains: The domains for the cookie. :param hashalg: The hashing algorithm to use for the cookie signature. :param timeout: The maximum age of the ticket, in seconds. When this amount of time passes after the ticket is created, the ticket will no longer be valid. :param reissue_time: The number of seconds before an authentication token cookie is reissued. If provided, must be less than `timeout`. :param max_age: The maximum age of the cookie in the browser, in seconds. If provided, must be greater than `timeout` and `reissue_time`. :param debug: If true, log verbosely. """ if secure: raise NotImplementedError if reissue_time: if not timeout or reissue_time >= timeout: raise ValueError('reissue_time must be less than timeout') if max_age: if not timeout or timeout >= max_age: raise ValueError('max_age must be greater than timeout') if not reissue_time or reissue_time >= max_age: raise ValueError('max_age must be greater than reissue_time') self.cookie = SignedCookieProfile( secret, salt, cookie_name, secure=secure, httponly=http_only, path=path, domains=domains, hashalg=hashalg, max_age=max_age, ) self.reissue_time = reissue_time self.timeout = timeout self.debug = debug def _new_ticket(self): randbytes = 32 hashalg = 'sha256' return hashlib.new(hashalg, urandom(randbytes)).hexdigest() def remember(self, request, principal, **kwargs): if self.debug: logger.debug( '`remember` called with principal {0!r}'.format(principal)) value = {} value['principal'] = principal value['ticket'] = ticket = self._new_ticket() value['issued'] = datetime.datetime.utcnow().strftime(_iso_format) q = request.db_session.query(User).filter(User.email == principal) user = q.first() if user is None: raise ValueError('Unknown principal {0!r}'.format(principal)) remote_address = request.environ.get('REMOTE_ADDR') user.tickets.append( UserTicket(ticket=ticket, remote_address=remote_address)) return self.cookie.get_headers(value) def forget(self, request): if self.debug: logger.debug('`forget` called') ticket_instance = request.auth.get('ticket_instance') if ticket_instance: request.db_session.delete(ticket_instance) request.auth['revoked'] = True return self.cookie.get_headers('', max_age=0) def unauthenticated_userid(self, request): """No support for unauthenticated userid""" if self.debug: logger.debug('`unauthenticated_userid` called') return None def authenticated_userid(self, request): # TODO: break this up, it's way too complex if self.debug: logger.debug('`authenticated_userid` called') userid = request.auth.get('userid') if userid is not None: if self.debug: fmt = 'Found userid {0!r} already in request.auth' logger.debug(fmt.format(userid)) return userid result = self.cookie.bind(request).get_value() if not result: if self.debug: logger.debug('Failed to find auth ticket in cookie') return None principal = result['principal'] ticket = result['ticket'] issued_unparsed = result['issued'] issued = datetime.datetime.strptime(issued_unparsed, _iso_format) if self.debug: fmt = ( 'Cookie contains ticket {0!r} for principal {1!r} issued {2!r}' ) logger.debug(fmt.format(ticket, principal, issued_unparsed)) ticket_instance = UserTicket.find_ticket_with_principal( request.db_session, ticket, principal) if ticket_instance is None: fmt = ( 'Failed to locate ticket {0!r} for principal {1!r} in database' ) logger.debug(fmt.format(ticket, principal)) return None userid = ticket_instance.user_id # TODO fix this, authenticated_userid must return None if timed out self._timeout_or_reissue(request, ticket_instance, issued, principal) request.auth['userid'] = userid request.auth['ticket_instance'] = ticket_instance return userid def effective_principals(self, request): principals = [Everyone] userid = self.authenticated_userid(request) if userid is None: return principals if request.user.admin: principals.append(Administrator) principals.append(Authenticated) principals.append(request.user.principal) return principals def _timeout_or_reissue(self, request, ticket_instance, issued, principal): now = datetime.datetime.utcnow() headers = self._get_timeout_headers(request, now, ticket_instance) if not headers: headers = self._get_reissue_headers( request, now, ticket_instance, issued, principal) if headers: def add_reissue_or_revoke_headers(request, response): auth = request.auth if 'reissued' not in auth and 'revoked' not in auth: for k, v in headers: response.headerlist.append((k, v)) request.add_response_callback(add_reissue_or_revoke_headers) def _get_timeout_headers(self, request, now, ticket_instance): if self.timeout: elapsed = (now - ticket_instance.created) if elapsed.total_seconds() > self.timeout: return self.forget(request) def _get_reissue_headers(self, request, now, ticket_instance, issued, principal): if self.reissue_time and 'reissued' not in request.auth: elapsed = (now - issued) if elapsed.total_seconds() > self.reissue_time: request.db_session.delete(ticket_instance) request.auth['reissued'] = True return self.remember(request, principal)
class PluggableSession(dict): """ Dictionary-like session object """ # configuration parameters _cookie_on_exception = set_on_exception _timeout = timeout _reissue_time = reissue_time # dirty flag _dirty = False def __init__(self, request): self._cookie = CookieHelper( secret, salt, cookie_name, secure=secure, max_age=max_age, httponly=httponly, path=path, domains=domain, hashalg=hashalg, ) self._session_id = None self.request = request reg = request.registry plug = reg.queryUtility(IPlugSession) if plug is None: raise RuntimeError( 'Unable to find any registered IPlugSession') now = time.time() created = renewed = now new = True value = None state = {} # Get the session_id self._session_id = self._cookie.bind(request).get_value() if self._session_id is not None: try: sess_val = plug.loads(self, request) value = serializer.loads(bytes_(sess_val)) except ValueError: value = None # Cleanup the session, since it failed to deserialize plug.clear(self, request) self._session_id = None if value is not None: try: rval, cval, sval = value renewed = float(rval) created = float(cval) state = sval new = False except (TypeError, ValueError): # value failed to unpack properly or renewed was not # a numeric type so we'll fail deserialization here state = {} # Clean up the session since it failed to unpack plug.clear(self, request) self._session_id = None if self._timeout is not None: if now - renewed > self._timeout: # expire the session because it was not renewed # before the timeout threshold state = {} # Session expired, cleanup this session plug.clear(self, request) self._session_id = None # Generate a new session id if self._session_id is None: self._generate_new_id() self.created = created self.accessed = renewed self.renewed = renewed self.new = new self._plug = plug dict.__init__(self, state) # ISession methods def changed(self): if not self._dirty: self._dirty = True def save_session_callback(request, response): self._save_session(response) self.request = None # explicitly break cycle for gc self.request.add_response_callback(save_session_callback) def invalidate(self): self._plug.clear(self, self.request) self._generate_new_id() now = time.time() self.created = self.renewed = now self.new = True self.clear() # non-modifying dictionary methods get = manage_accessed(dict.get) __getitem__ = manage_accessed(dict.__getitem__) items = manage_accessed(dict.items) values = manage_accessed(dict.values) keys = manage_accessed(dict.keys) __contains__ = manage_accessed(dict.__contains__) __len__ = manage_accessed(dict.__len__) __iter__ = manage_accessed(dict.__iter__) if not PY3: iteritems = manage_accessed(dict.iteritems) itervalues = manage_accessed(dict.itervalues) iterkeys = manage_accessed(dict.iterkeys) has_key = manage_accessed(dict.has_key) # modifying dictionary methods clear = manage_changed(dict.clear) update = manage_changed(dict.update) setdefault = manage_changed(dict.setdefault) pop = manage_changed(dict.pop) popitem = manage_changed(dict.popitem) __setitem__ = manage_changed(dict.__setitem__) __delitem__ = manage_changed(dict.__delitem__) # flash API methods @manage_changed def flash(self, msg, queue='', allow_duplicate=True): storage = self.setdefault('_f_' + queue, []) if allow_duplicate or (msg not in storage): storage.append(msg) @manage_changed def pop_flash(self, queue=''): storage = self.pop('_f_' + queue, []) return storage @manage_accessed def peek_flash(self, queue=''): storage = self.get('_f_' + queue, []) return storage # CSRF API methods @manage_changed def new_csrf_token(self): token = text_(binascii.hexlify(os.urandom(20))) self['_csrft_'] = token return token @manage_accessed def get_csrf_token(self): token = self.get('_csrft_', None) if token is None: token = self.new_csrf_token() return token # non-API methods def _save_session(self, response): if not self._cookie_on_exception: exception = getattr(self.request, 'exception', None) if exception is not None: # dont set a cookie during exceptions return False sess_val = native_( serializer.dumps((self.accessed, self.created, dict(self)))) self._plug.dumps(self, self.request, sess_val) self._cookie.set_cookies(response, self._session_id) return True def _generate_new_id(self): self._session_id = text_(binascii.hexlify(os.urandom(20)))