text: import datetime
  
  import sqlalchemy as sa
  from pyramid_authsanity import interfaces
  from zope import interface
  
  from h import models
  from h.auth.util import principals_for_user
  
  TICKET_TTL = datetime.timedelta(days=7)
  
  # We only want to update the `expires` column when the tickets `expires` is at
  # least one minute smaller than the potential new value. This prevents that we
  # update the `expires` column on every single request.
  TICKET_REFRESH_INTERVAL = datetime.timedelta(minutes=1)
  
  
  class AuthTicketNotLoadedError(Exception):
      pass
  
  
  @interface.implementer(interfaces.IAuthService)
  class AuthTicketService:
      def __init__(self, session, user_service):
          self.session = session
          self.usersvc = user_service
  
          self._userid = None
  
      def userid(self):
          """
          Return current userid, or None.
  
          Raises ``AuthTicketNotLoadedError`` when auth ticket has not been
          loaded yet, which signals the auth policy to call ``verify_ticket``.
          """
  
          if self._userid is None:
              raise AuthTicketNotLoadedError("auth ticket is not loaded yet")
  
          return self._userid
  
      def groups(self):
          """Returns security principals of the logged-in user."""
  
          if self._userid is None:
              raise AuthTicketNotLoadedError("auth ticket is not loaded yet")
  
          user = self.usersvc.fetch(self._userid)
          return principals_for_user(user)
  
      def verify_ticket(self, principal, ticket_id):
          """
          Verifies an authentication claim (usually extracted from a cookie)
          against the stored tickets.
  
          This will only successfully verify a ticket when it is found in the
          database, the principal is the same, and it hasn't expired yet.
          """
  
          if ticket_id is None:
              return False
  
          ticket = (
              self.session.query(models.AuthTicket)
              .filter(
                  models.AuthTicket.id == ticket_id,
                  models.AuthTicket.user_userid == principal,
                  models.AuthTicket.expires > sa.func.now(),
              )
              .one_or_none()
          )
  
          if ticket is None:
              return False
  
          self._userid = ticket.user_userid
  
          # We don't want to update the `expires` column of an auth ticket on
          # every single request, but only when the ticket hasn't been touched
          # within a the defined `TICKET_REFRESH_INTERVAL`.
          if (utcnow() - ticket.updated) > TICKET_REFRESH_INTERVAL:
              ticket.expires = utcnow() + TICKET_TTL
  
          return True
  
      def add_ticket(self, principal, ticket_id):
          """Store a new ticket with the given id and principal in the database."""
  
          user = self.usersvc.fetch(principal)
          if user is None:
              raise ValueError("Cannot find user with userid %s" % principal)
  
          ticket = models.AuthTicket(
              id=ticket_id,
              user=user,
              user_userid=user.userid,
              expires=(utcnow() + TICKET_TTL),
          )
          self.session.add(ticket)
          # We cache the new userid, this will allow us to migrate the old
          # session policy to this new ticket policy.
          self._userid = user.userid
  
      def remove_ticket(self, ticket_id):
          """Delete a ticket by id from the database."""
  
          if ticket_id:
              self.session.query(models.AuthTicket).filter_by(id=ticket_id).delete()
          self._userid = None
  
  
  def auth_ticket_service_factory(_context, request):
      """Return a AuthTicketService instance for the passed context and request."""
      user_service = request.find_service(name="user")
      return AuthTicketService(request.db, user_service)
  
  
  def utcnow():
      return datetime.datetime.utcnow()
  