Beispiel #1
0
def test_csrf_middleware(cookie_value, header_value, should_return_error):
    req, resp, resource, params = Mock(), Mock(), Mock(), Mock()
    req.cookies = {
        settings.API_CSRF_COOKIE_NAME: cookie_value,
    }
    req.headers = {
        settings.API_CSRF_HEADER_NAME: header_value,
    }
    req.method = "POST"  # testing CSRF -- need to be unsafe method
    resp.cookies = {}
    resp.headers = {}
    resp.complete = False
    resp.status = GOOD_STATUS
    resp.text = GOOD_BODY
    resource.csrf_exempt = False

    middleware = CsrfMiddleware()
    middleware.default_secret = DEFAULT_SESSION_SECRET
    middleware.process_resource(req, resp, resource, params)
    if should_return_error:
        assert resp.status == BAD_STATUS, "response status should be %s, is %s" % (
            BAD_STATUS, resp.status)
        assert resp.text == BAD_BODY, "response body should be a proper error message"
        assert resp.complete, "response should be complete in case of error"
    else:
        assert resp.status == GOOD_STATUS
        assert resp.text == GOOD_BODY
        assert not resp.complete

    middleware.process_response(req, resp, None, None)

    resp.append_header.assert_called()
    cookie_domains = set()
    for call_args, call_kwargs in resp.append_header.call_args_list:
        assert call_args[0] == "Set-Cookie"

        cookies = http_cookies.SimpleCookie(call_args[1])
        assert cookies.get('mcod_csrf_token', None) is not None

        cookie = cookies['mcod_csrf_token']
        new_cookie_value = cookie.value
        assert new_cookie_value != cookie_value, "server should assign a new CSRF token for every response"
        assert cookie["path"] == '/'
        assert ((not settings.SESSION_COOKIE_SECURE and not cookie["secure"])
                or (settings.SESSION_COOKIE_SECURE and cookie["secure"]))
        cookie_domain = cookie["domain"]
        assert cookie_domain in settings.API_CSRF_COOKIE_DOMAINS
        cookie_domains.add(cookie_domain)
        assert not cookie["httponly"]

    assert cookie_domains == set(settings.API_CSRF_COOKIE_DOMAINS)
Beispiel #2
0
    def __init__(self, iterable, status, headers):
        self._text = None

        self._content = b''.join(iterable)

        self._status = status
        self._status_code = int(status[:3])
        self._headers = CaseInsensitiveDict(headers)

        cookies = http_cookies.SimpleCookie()
        for name, value in headers:
            if name.lower() == 'set-cookie':
                cookies.load(value)

        self._cookies = dict(
            (morsel.key, Cookie(morsel)) for morsel in cookies.values())

        self._encoding = helpers.get_encoding_from_headers(self._headers)
Beispiel #3
0
    def unset_cookie(self, name):
        """Unset a cookie in the response

        Clears the contents of the cookie, and instructs the user
        agent to immediately expire its own copy of the cookie.

        Warning:
            In order to successfully remove a cookie, both the
            path and the domain must match the values that were
            used when the cookie was created.
        """
        if self._cookies is None:
            self._cookies = http_cookies.SimpleCookie()

        self._cookies[name] = ''

        # NOTE(Freezerburn): SimpleCookie apparently special cases the
        # expires attribute to automatically use strftime and set the
        # time as a delta from the current time. We use -1 here to
        # basically tell the browser to immediately expire the cookie,
        # thus removing it from future request objects.
        self._cookies[name]['expires'] = -1
Beispiel #4
0
    def unset_cookie(self, name, domain=None, path=None):
        """Unset a cookie in the response.

        Clears the contents of the cookie, and instructs the user
        agent to immediately expire its own copy of the cookie.

        Note:
            Modern browsers place restriction on cookies without the
            "same-site" cookie attribute set. To that end this attribute
            is set to ``'Lax'`` by this method.

            (See also: `Same-Site warnings`_)

        Warning:
            In order to successfully remove a cookie, both the
            path and the domain must match the values that were
            used when the cookie was created.

        Args:
            name (str): Cookie name

        Keyword Args:
            domain (str): Restricts the cookie to a specific domain and
                    any subdomains of that domain. By default, the user
                    agent will return the cookie only to the origin server.
                    When overriding this default behavior, the specified
                    domain must include the origin server. Otherwise, the
                    user agent will reject the cookie.

                    Note:
                        Cookies do not provide isolation by port, so the domain
                        should not provide one. (See also: RFC 6265, Section 8.5)

                    (See also: RFC 6265, Section 4.1.2.3)

            path (str): Scopes the cookie to the given path plus any
                subdirectories under that path (the "/" character is
                interpreted as a directory separator). If the cookie
                does not specify a path, the user agent defaults to the
                path component of the requested URI.

                Warning:
                    User agent interfaces do not always isolate
                    cookies by path, and so this should not be
                    considered an effective security measure.

                (See also: RFC 6265, Section 4.1.2.4)

        .. _Same-Site warnings:
            https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#Fixing_common_warnings
        """  # noqa: E501
        if self._cookies is None:
            self._cookies = http_cookies.SimpleCookie()

        self._cookies[name] = ''

        # NOTE(Freezerburn): SimpleCookie apparently special cases the
        # expires attribute to automatically use strftime and set the
        # time as a delta from the current time. We use -1 here to
        # basically tell the browser to immediately expire the cookie,
        # thus removing it from future request objects.
        self._cookies[name]['expires'] = -1

        # NOTE(CaselIT): Set SameSite to Lax to avoid setting invalid cookies.
        # See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#Fixing_common_warnings  # noqa: E501
        self._cookies[name]['samesite'] = 'Lax'

        if domain:
            self._cookies[name]['domain'] = domain

        if path:
            self._cookies[name]['path'] = path
Beispiel #5
0
    def set_cookie(self,
                   name,
                   value,
                   expires=None,
                   max_age=None,
                   domain=None,
                   path=None,
                   secure=None,
                   http_only=True,
                   same_site=None):
        """Set a response cookie.

        Note:
            This method can be called multiple times to add one or
            more cookies to the response.

        See Also:
            To learn more about setting cookies, see
            :ref:`Setting Cookies <setting-cookies>`. The parameters
            listed below correspond to those defined in `RFC 6265`_.

        Args:
            name (str): Cookie name
            value (str): Cookie value

        Keyword Args:
            expires (datetime): Specifies when the cookie should expire.
                By default, cookies expire when the user agent exits.

                (See also: RFC 6265, Section 4.1.2.1)
            max_age (int): Defines the lifetime of the cookie in
                seconds. By default, cookies expire when the user agent
                exits. If both `max_age` and `expires` are set, the
                latter is ignored by the user agent.

                Note:
                    Coercion to ``int`` is attempted if provided with
                    ``float`` or ``str``.

                (See also: RFC 6265, Section 4.1.2.2)

            domain (str): Restricts the cookie to a specific domain and
                any subdomains of that domain. By default, the user
                agent will return the cookie only to the origin server.
                When overriding this default behavior, the specified
                domain must include the origin server. Otherwise, the
                user agent will reject the cookie.

                Note:
                    Cookies do not provide isolation by port, so the domain
                    should not provide one. (See also: RFC 6265, Section 8.5)

                (See also: RFC 6265, Section 4.1.2.3)

            path (str): Scopes the cookie to the given path plus any
                subdirectories under that path (the "/" character is
                interpreted as a directory separator). If the cookie
                does not specify a path, the user agent defaults to the
                path component of the requested URI.

                Warning:
                    User agent interfaces do not always isolate
                    cookies by path, and so this should not be
                    considered an effective security measure.

                (See also: RFC 6265, Section 4.1.2.4)

            secure (bool): Direct the client to only return the cookie
                in subsequent requests if they are made over HTTPS
                (default: ``True``). This prevents attackers from
                reading sensitive cookie data.

                Note:
                    The default value for this argument is normally
                    ``True``, but can be modified by setting
                    :py:attr:`~.ResponseOptions.secure_cookies_by_default`
                    via :any:`App.resp_options`.

                Warning:
                    For the `secure` cookie attribute to be effective,
                    your application will need to enforce HTTPS.

                (See also: RFC 6265, Section 4.1.2.5)

            http_only (bool): The HttpOnly attribute limits the scope of the
                cookie to HTTP requests.  In particular, the attribute
                instructs the user agent to omit the cookie when providing
                access to cookies via "non-HTTP" APIs. This is intended to
                mitigate some forms of cross-site scripting. (default: ``True``)

                Note:
                    HttpOnly cookies are not visible to javascript scripts
                    in the browser. They are automatically sent to the server
                    on javascript ``XMLHttpRequest`` or ``Fetch`` requests.

                (See also: RFC 6265, Section 4.1.2.6)

            same_site (str): Helps protect against CSRF attacks by restricting
                when a cookie will be attached to the request by the user agent.
                When set to ``'Strict'``, the cookie will only be sent along
                with "same-site" requests.  If the value is ``'Lax'``, the
                cookie will be sent with same-site requests, and with
                "cross-site" top-level navigations.  If the value is ``'None'``,
                the cookie will be sent with same-site and cross-site requests.
                Finally, when this attribute is not set on the cookie, the
                attribute will be treated as if it had been set to ``'None'``.

                (See also: `Same-Site RFC Draft`_)

        Raises:
            KeyError: `name` is not a valid cookie name.
            ValueError: `value` is not a valid cookie value.

        .. _RFC 6265:
            http://tools.ietf.org/html/rfc6265

        .. _Same-Site RFC Draft:
            https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7

        """

        if not is_ascii_encodable(name):
            raise KeyError('name is not ascii encodable')
        if not is_ascii_encodable(value):
            raise ValueError('value is not ascii encodable')

        value = str(value)

        if self._cookies is None:
            self._cookies = http_cookies.SimpleCookie()

        try:
            self._cookies[name] = value
        except http_cookies.CookieError as e:  # pragma: no cover
            # NOTE(tbug): we raise a KeyError here, to avoid leaking
            # the CookieError to the user. SimpleCookie (well, BaseCookie)
            # only throws CookieError on issues with the cookie key
            raise KeyError(str(e))

        if expires:
            # set Expires on cookie. Format is Wdy, DD Mon YYYY HH:MM:SS GMT

            # NOTE(tbug): we never actually need to
            # know that GMT is named GMT when formatting cookies.
            # It is a function call less to just write "GMT" in the fmt string:
            fmt = '%a, %d %b %Y %H:%M:%S GMT'
            if expires.tzinfo is None:
                # naive
                self._cookies[name]['expires'] = expires.strftime(fmt)
            else:
                # aware
                gmt_expires = expires.astimezone(GMT_TIMEZONE)
                self._cookies[name]['expires'] = gmt_expires.strftime(fmt)

        if max_age:
            # RFC 6265 section 5.2.2 says about the max-age value:
            #   "If the remainder of attribute-value contains a non-DIGIT
            #    character, ignore the cookie-av."
            # That is, RFC-compliant response parsers will ignore the max-age
            # attribute if the value contains a dot, as in floating point
            # numbers. Therefore, attempt to convert the value to an integer.
            self._cookies[name]['max-age'] = int(max_age)

        if domain:
            self._cookies[name]['domain'] = domain

        if path:
            self._cookies[name]['path'] = path

        is_secure = self.options.secure_cookies_by_default if secure is None else secure

        if is_secure:
            self._cookies[name]['secure'] = True

        if http_only:
            self._cookies[name]['httponly'] = http_only

        # PERF(kgriffs): Morsel.__setitem__() will lowercase this anyway,
        #   so we can just pass this in and when __setitem__() calls
        #   lower() it will be very slightly faster.
        if same_site:
            same_site = same_site.lower()

            if same_site not in _RESERVED_SAMESITE_VALUES:
                raise ValueError(
                    "same_site must be set to either 'lax', 'strict', or 'none'"
                )

            self._cookies[name]['samesite'] = same_site.capitalize()