Esempio n. 1
0
class CookieCSRFStoragePolicy(object):
    """ An alternative CSRF implementation that stores its information in
    unauthenticated cookies, known as the 'Double Submit Cookie' method in the
    `OWASP CSRF guidelines <https://www.owasp.org/index.php/
    Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#
    Double_Submit_Cookie>`_. This gives some additional flexibility with
    regards to scaling as the tokens can be generated and verified by a
    front-end server.

    .. versionadded:: 1.9

    .. versionchanged: 1.10

       Added the ``samesite`` option and made the default ``'Lax'``.

    """
    _token_factory = staticmethod(lambda: text_(uuid.uuid4().hex))

    def __init__(self, cookie_name='csrf_token', secure=False, httponly=False,
                 domain=None, max_age=None, path='/', samesite='Lax'):
        serializer = SimpleSerializer()
        self.cookie_profile = CookieProfile(
            cookie_name=cookie_name,
            secure=secure,
            max_age=max_age,
            httponly=httponly,
            path=path,
            domains=[domain],
            serializer=serializer,
            samesite=samesite,
        )
        self.cookie_name = cookie_name

    def new_csrf_token(self, request):
        """ Sets a new CSRF token into the request and returns it. """
        token = self._token_factory()
        request.cookies[self.cookie_name] = token
        def set_cookie(request, response):
            self.cookie_profile.set_cookies(
                response,
                token,
            )
        request.add_response_callback(set_cookie)
        return token

    def get_csrf_token(self, request):
        """ Returns the currently active CSRF token by checking the cookies
        sent with the current request."""
        bound_cookies = self.cookie_profile.bind(request)
        token = bound_cookies.get_value()
        if not token:
            token = self.new_csrf_token(request)
        return token

    def check_csrf_token(self, request, supplied_token):
        """ Returns ``True`` if the ``supplied_token`` is valid."""
        expected_token = self.get_csrf_token(request)
        return not strings_differ(
            bytes_(expected_token), bytes_(supplied_token))
Esempio n. 2
0
class CookieCSRFStoragePolicy(object):
    """ An alternative CSRF implementation that stores its information in
    unauthenticated cookies, known as the 'Double Submit Cookie' method in the
    `OWASP CSRF guidelines <https://www.owasp.org/index.php/
    Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#
    Double_Submit_Cookie>`_. This gives some additional flexibility with
    regards to scaling as the tokens can be generated and verified by a
    front-end server.

    .. versionadded:: 1.9

    """
    _token_factory = staticmethod(lambda: text_(uuid.uuid4().hex))

    def __init__(self,
                 cookie_name='csrf_token',
                 secure=False,
                 httponly=False,
                 domain=None,
                 max_age=None,
                 path='/'):
        serializer = _SimpleSerializer()
        self.cookie_profile = CookieProfile(cookie_name=cookie_name,
                                            secure=secure,
                                            max_age=max_age,
                                            httponly=httponly,
                                            path=path,
                                            domains=[domain],
                                            serializer=serializer)
        self.cookie_name = cookie_name

    def new_csrf_token(self, request):
        """ Sets a new CSRF token into the request and returns it. """
        token = self._token_factory()
        request.cookies[self.cookie_name] = token

        def set_cookie(request, response):
            self.cookie_profile.set_cookies(
                response,
                token,
            )

        request.add_response_callback(set_cookie)
        return token

    def get_csrf_token(self, request):
        """ Returns the currently active CSRF token by checking the cookies
        sent with the current request."""
        bound_cookies = self.cookie_profile.bind(request)
        token = bound_cookies.get_value()
        if not token:
            token = self.new_csrf_token(request)
        return token

    def check_csrf_token(self, request, supplied_token):
        """ Returns ``True`` if the ``supplied_token`` is valid."""
        expected_token = self.get_csrf_token(request)
        return not strings_differ(bytes_(expected_token),
                                  bytes_(supplied_token))
Esempio n. 3
0
class JWTCookieAuthenticationPolicy(JWTAuthenticationPolicy):
    def __init__(
        self,
        private_key,
        public_key=None,
        algorithm="HS512",
        leeway=0,
        expiration=None,
        default_claims=None,
        http_header="Authorization",
        auth_type="JWT",
        callback=None,
        json_encoder=None,
        audience=None,
        cookie_name=None,
        https_only=True,
        samesite=None,
        reissue_time=None,
        cookie_path=None,
        accept_header=False,
        header_first=False,
        reissue_callback=None,
    ):
        super(JWTCookieAuthenticationPolicy, self).__init__(
            private_key,
            public_key,
            algorithm,
            leeway,
            expiration,
            default_claims,
            http_header,
            auth_type,
            callback,
            json_encoder,
            audience,
        )

        self.https_only = asbool(https_only)
        self.samesite = samesite
        self.cookie_name = cookie_name or "Authorization"
        self.max_age = self.expiration and self.expiration.total_seconds()

        if reissue_time and isinstance(reissue_time, datetime.timedelta):
            reissue_time = reissue_time.total_seconds()
        self.reissue_time = int(
            reissue_time) if reissue_time is not None else None
        self.accept_header = asbool(accept_header)
        self.header_first = asbool(header_first)

        def _default_reissue_callback(request, principal, **claims):
            return self.create_token(principal, self.expiration, self.audience,
                                     **claims)

        self.reissue_callback = reissue_callback or _default_reissue_callback

        self.cookie_profile = CookieProfile(
            cookie_name=self.cookie_name,
            secure=self.https_only,
            samesite=self.samesite,
            max_age=self.max_age,
            httponly=True,
            path=cookie_path,
        )

    @staticmethod
    def make_from(policy, **kwargs):
        if not isinstance(policy, JWTAuthenticationPolicy):
            pol_type = policy.__class__.__name__
            raise ValueError("Invalid policy type %s" % pol_type)

        return JWTCookieAuthenticationPolicy(
            private_key=policy.private_key,
            public_key=policy.public_key,
            algorithm=policy.algorithm,
            leeway=policy.leeway,
            expiration=policy.expiration,
            default_claims=policy.default_claims,
            http_header=policy.http_header,
            auth_type=policy.auth_type,
            callback=policy.callback,
            json_encoder=policy.json_encoder,
            audience=policy.audience,
            **kwargs)

    def _get_cookies(self, request, value, max_age=None, domains=None):
        profile = self.cookie_profile(request)
        if domains is None:
            domains = [request.domain]

        kw = {"domains": domains}
        if max_age is not None:
            kw["max_age"] = max_age

        headers = profile.get_headers(value, **kw)
        return headers

    def remember(self, request, token, **kw):
        if hasattr(request,
                   "_jwt_cookie_reissued") and request._jwt_cookie_reissued:
            request._jwt_cookie_reissue_revoked = True

        return self._get_cookies(request,
                                 token,
                                 self.max_age,
                                 domains=kw.get("domains"))

    def forget(self, request):
        request._jwt_cookie_reissue_revoked = True
        return self._get_cookies(request, None)

    def get_token(self, request):
        if self.accept_header:
            token = super().get_token(request)
            if token and self.header_first:
                return token

        profile = self.cookie_profile.bind(request)
        cookie = profile.get_value()

        if not cookie and self.accept_header:
            return token

        # if we handle reissue at this early stage we avoid reissuing cookies
        # on requests that used header authentication
        if (cookie and self.reissue_time is not None
                and not hasattr(request, "_jwt_cookie_reissued")):
            claims = self._internal_jwt_claims(request, cookie)
            if claims:
                self._handle_reissue(request, claims)

        return cookie

    # store claims in request to avoid decoding twice in reissue_callback
    def _internal_jwt_claims(self, request, token):
        if not hasattr(request, "_internal_jwt_claims"):
            request._internal_jwt_claims = self.jwt_decode(request, token)
        return request._internal_jwt_claims

    # redefined get_claims to use internally stored claims
    def get_claims(self, request):
        token = self.get_token(request)
        if not token:
            return {}
        return self._internal_jwt_claims(request, token)

    def _handle_reissue(self, request, claims):
        if not request or not claims:
            raise ValueError(
                "Cannot handle JWT reissue: insufficient arguments")

        if "iat" not in claims:
            raise ReissueError("Token claim's is missing IAT")
        if "sub" not in claims:
            raise ReissueError("Token claim's is missing SUB")

        # avoid getting called again from reissue_callback via get_token() or get_claims()
        request._jwt_cookie_reissued = False

        token_dt = claims["iat"]
        principal = claims["sub"]
        now = time.time()

        if now < token_dt + self.reissue_time:
            # Token not yet eligible for reissuing
            return

        try:
            token = self.reissue_callback(request, principal, **claims)
        except Exception as e:
            raise ReissueError("Callback raised exception") from e

        def reissue_jwt_cookie(request, response):
            if not hasattr(request, "_jwt_cookie_reissue_revoked"):
                for k, v in headers:
                    response.headerlist.append((k, v))

        if token:
            headers = self.remember(request, token)
            request.add_response_callback(reissue_jwt_cookie)
            request._jwt_cookie_reissued = True
Esempio n. 4
0
class JWTCookieAuthenticationPolicy(JWTAuthenticationPolicy):
    def __init__(
        self,
        private_key,
        public_key=None,
        algorithm="HS512",
        leeway=0,
        expiration=None,
        default_claims=None,
        http_header="Authorization",
        auth_type="JWT",
        callback=None,
        json_encoder=None,
        audience=None,
        cookie_name=None,
        https_only=True,
        reissue_time=None,
        cookie_path=None,
    ):
        super(JWTCookieAuthenticationPolicy, self).__init__(
            private_key,
            public_key,
            algorithm,
            leeway,
            expiration,
            default_claims,
            http_header,
            auth_type,
            callback,
            json_encoder,
            audience,
        )

        self.https_only = https_only
        self.cookie_name = cookie_name or "Authorization"
        self.max_age = self.expiration and self.expiration.total_seconds()

        if reissue_time and isinstance(reissue_time, datetime.timedelta):
            reissue_time = reissue_time.total_seconds()
        self.reissue_time = reissue_time

        serializer = _SimpleSerializer()

        self.cookie_profile = CookieProfile(
            cookie_name=self.cookie_name,
            secure=self.https_only,
            max_age=self.max_age,
            httponly=True,
            path=cookie_path,
            serializer=serializer
        )

    @staticmethod
    def make_from(policy, **kwargs):
        if not isinstance(policy, JWTAuthenticationPolicy):
            pol_type = policy.__class__.__name__
            raise ValueError("Invalid policy type %s" % pol_type)

        return JWTCookieAuthenticationPolicy(
            private_key=policy.private_key,
            public_key=policy.public_key,
            algorithm=policy.algorithm,
            leeway=policy.leeway,
            expiration=policy.expiration,
            default_claims=policy.default_claims,
            http_header=policy.http_header,
            auth_type=policy.auth_type,
            callback=policy.callback,
            json_encoder=policy.json_encoder,
            audience=policy.audience,
            **kwargs
        )

    def _get_cookies(self, request, value, max_age=None, domains=None):
        profile = self.cookie_profile(request)
        if domains is None:
            domains = [request.domain]

        kw = {"domains": domains}
        if max_age is not None:
            kw["max_age"] = max_age

        headers = profile.get_headers(value, **kw)
        return headers

    def remember(self, request, principal, **kw):
        token = self.create_token(principal, self.expiration, self.audience, **kw)

        if hasattr(request, "_jwt_cookie_reissued"):
            request._jwt_cookie_reissue_revoked = True

        domains = kw.get("domains")

        return self._get_cookies(request, token, self.max_age, domains=domains)

    def forget(self, request):
        request._jwt_cookie_reissue_revoked = True
        return self._get_cookies(request, None)

    def get_claims(self, request):
        profile = self.cookie_profile.bind(request)
        cookie = profile.get_value()

        reissue = self.reissue_time is not None

        if cookie is None:
            return {}

        claims = self.jwt_decode(request, cookie)

        if reissue and not hasattr(request, "_jwt_cookie_reissued"):
            self._handle_reissue(request, claims)
        return claims

    def _handle_reissue(self, request, claims):
        if not request or not claims:
            raise ValueError("Cannot handle JWT reissue: insufficient arguments")

        if "iat" not in claims:
            raise ReissueError("Token claim's is missing IAT")
        if "sub" not in claims:
            raise ReissueError("Token claim's is missing SUB")

        token_dt = claims["iat"]
        principal = claims["sub"]
        now = time.time()

        if now < token_dt + self.reissue_time:
            # Token not yet eligible for reissuing
            return

        extra_claims = dict(
            filter(lambda item: item[0] not in self.jwt_std_claims, claims.items())
        )
        headers = self.remember(request, principal, **extra_claims)

        def reissue_jwt_cookie(request, response):
            if not hasattr(request, "_jwt_cookie_reissue_revoked"):
                for k, v in headers:
                    response.headerlist.append((k, v))

        request.add_response_callback(reissue_jwt_cookie)
        request._jwt_cookie_reissued = True