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)
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 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)