示例#1
0
def cookies(reqs: dict,
            expectation='cookies-secure-with-httponly-sessions') -> dict:
    """
    :param reqs: dictionary containing all the request and response objects
    :param expectation: test expectation
        cookies-secure-with-httponly-sessions-and-samesite: All cookies are secure,
          use HttpOnly if needed, and SameSite
        cookies-secure-with-httponly-sessions: All cookies have secure flag set, all session cookies are HttpOnly
        cookies-without-secure-flag-but-protected-by-hsts: Cookies don't have secure, but site uses HSTS
        cookies-session-without-secure-flag-but-protected-by-hsts: Same, but session cookie
        cookies-without-secure-flag: Cookies set without secure flag
        cookies-samesite-flag-invalid: Cookies set with invalid SameSite value (must be either unset, Strict, or Lax)
        cookies-session-without-secure-flag: Session cookies lack the Secure flag
        cookies-session-without-httponly-flag: Session cookies lack the HttpOnly flag
        cookies-not-found: No cookies found in HTTP requests
    :return: dictionary with:
        data: the cookie jar
        expectation: test expectation
        pass: whether the site's configuration met its expectation
        result: short string describing the result of the test
    """

    output = {
        'data': None,
        'expectation': expectation,
        'pass': False,
        'result': None,
        'sameSite': None,
    }
    session = reqs['session']  # all requests and their associated cookies

    # The order of how bad the various results are
    goodness = [
        'cookies-without-secure-flag-but-protected-by-hsts',
        'cookies-without-secure-flag',
        'cookies-session-without-secure-flag-but-protected-by-hsts',
        'cookies-samesite-flag-invalid',
        'cookies-anticsrf-without-samesite-flag',
        'cookies-session-without-httponly-flag',
        'cookies-session-without-secure-flag'
    ]

    # TODO: Support cookies set over http-equiv (ugh)
    # https://github.com/mozilla/http-observatory/issues/265

    # Get their HTTP Strict Transport Security status, which can help when cookies are set without Secure
    hsts = strict_transport_security(reqs)['pass']

    # If there are no cookies
    if not session.cookies:
        output['result'] = 'cookies-not-found'

    else:
        jar = {}

        # There are certain cookies we ignore, because they are set by service providers and sites have
        # no control over them.
        for cookie in COOKIES_TO_DELETE:
            del (session.cookies[cookie])

        for cookie in session.cookies:
            # The HttpOnly and SameSite functionality is a bit broken
            cookie.httponly = cookie.samesite = False
            for key in cookie._rest:
                if key.lower() == 'httponly' and getattr(cookie,
                                                         'httponly') is False:
                    cookie.httponly = True
                elif key.lower() == 'samesite' and getattr(
                        cookie, 'samesite') is False:
                    if cookie._rest[key] is True or cookie._rest[key].strip(
                    ).lower() == 'strict':
                        cookie.samesite = 'Strict'
                        output['sameSite'] = True
                    elif cookie._rest[key].strip().lower() == 'lax':
                        cookie.samesite = 'Lax'
                        output['sameSite'] = True
                    else:
                        output['result'] = only_if_worse(
                            'cookies-samesite-flag-invalid', output['result'],
                            goodness)

            # Add it to the jar
            jar[cookie.name] = {
                i: getattr(cookie, i, None)
                for i in [
                    'domain', 'expires', 'httponly', 'max-age', 'path', 'port',
                    'samesite', 'secure'
                ]
            }

            # Is it a session identifier or an anti-csrf token?
            sessionid = any(i in cookie.name.lower()
                            for i in ('login', 'sess'))
            anticsrf = True if 'csrf' in cookie.name.lower() else False

            if not cookie.secure and hsts:
                output['result'] = only_if_worse(
                    'cookies-without-secure-flag-but-protected-by-hsts',
                    output['result'], goodness)

            elif not cookie.secure:
                output['result'] = only_if_worse('cookies-without-secure-flag',
                                                 output['result'], goodness)

            # Anti-CSRF tokens should be set using the SameSite option
            if anticsrf and not cookie.samesite:
                output['result'] = only_if_worse(
                    'cookies-anticsrf-without-samesite-flag', output['result'],
                    goodness)

            # Login and session cookies should be set with Secure
            if sessionid and not cookie.secure and hsts:
                output['result'] = only_if_worse(
                    'cookies-session-without-secure-flag-but-protected-by-hsts',
                    output['result'], goodness)
            elif sessionid and not cookie.secure:
                output['result'] = only_if_worse(
                    'cookies-session-without-secure-flag', output['result'],
                    goodness)

            # Login and session cookies should be set with HttpOnly
            if sessionid and not cookie.httponly:
                output['result'] = only_if_worse(
                    'cookies-session-without-httponly-flag', output['result'],
                    goodness)

        # Store whether or not we saw SameSite cookies, if cookies were set
        if output['result'] is None:
            if output['sameSite']:
                output[
                    'result'] = 'cookies-secure-with-httponly-sessions-and-samesite'
            else:
                output['result'] = 'cookies-secure-with-httponly-sessions'
                output['sameSite'] = False

        # Save the cookie jar
        output['data'] = jar if len(str(jar)) < 32768 else {}

    # Check to see if the test passed or failed
    if output['result'] in (
            'cookies-not-found',
            'cookies-secure-with-httponly-sessions-and-samesite', expectation):
        output['pass'] = True

    return output
示例#2
0
def cookies(reqs: dict, expectation='cookies-secure-with-httponly-sessions') -> dict:
    """
    :param reqs: dictionary containing all the request and response objects
    :param expectation: test expectation
        cookies-secure-with-httponly-sessions: All cookies have secure flag set, all session cookies are HttpOnly
        cookies-without-secure-flag-but-protected-by-hsts: Cookies don't have secure, but site uses HSTS
        cookies-session-without-secure-flag-but-protected-by-hsts: Same, but session cookie
        cookies-without-secure-flag: Cookies set without secure flag
        cookies-session-without-secure-flag: Session cookies lack the Secure flag
        cookies-session-without-httponly-flag: Session cookies lack the HttpOnly flag
        cookies-not-found: No cookies found in HTTP requests
    :return: dictionary with:
        data: the cookie jar
        expectation: test expectation
        pass: whether the site's configuration met its expectation
        result: short string describing the result of the test
    """

    output = {
        'data': None,
        'expectation': expectation,
        'pass': False,
        'result': None,
    }
    session = reqs['session']  # all requests and their associated cookies

    # The order of how bad the various results are
    goodness = ['cookies-without-secure-flag-but-protected-by-hsts',
                'cookies-without-secure-flag',
                'cookies-session-without-secure-flag-but-protected-by-hsts',
                'cookies-session-without-secure-flag',
                'cookies-session-without-httponly-flag']

    # Get their HTTP Strict Transport Security status, which can help when cookies are set without Secure
    hsts = strict_transport_security(reqs)['pass']

    # If there are no cookies
    if not session.cookies:
        output['result'] = 'cookies-not-found'

    else:
        jar = {}

        for cookie in session.cookies:
            # The httponly functionality is a bit broken
            if not hasattr(cookie, 'httponly'):
                if 'httponly' in [key.lower() for key in cookie._rest]:
                    cookie.httponly = True
                else:
                    cookie.httponly = False

            # Add it to the jar
            jar[cookie.name] = {i: getattr(cookie, i, None) for i in ['domain', 'expires', 'httponly',
                                                                      'max-age', 'path', 'port', 'secure']}

            # Is it a session identifier?
            sessionid = any(i in cookie.name.lower() for i in ('login', 'sess'))

            if not cookie.secure and hsts:
                output['result'] = only_if_worse('cookies-without-secure-flag-but-protected-by-hsts',
                                                 output['result'],
                                                 goodness)

            elif not cookie.secure:
                output['result'] = only_if_worse('cookies-without-secure-flag',
                                                 output['result'],
                                                 goodness)

            # Login and session cookies should be set with Secure
            if sessionid and not cookie.secure and hsts:
                output['result'] = only_if_worse('cookies-session-without-secure-flag-but-protected-by-hsts',
                                                 output['result'],
                                                 goodness)
            elif sessionid and not cookie.secure:
                output['result'] = only_if_worse('cookies-session-without-secure-flag',
                                                 output['result'],
                                                 goodness)

            # Login and session cookies should be set with HttpOnly
            if sessionid and not cookie.httponly:
                output['result'] = only_if_worse('cookies-session-without-httponly-flag',
                                                 output['result'],
                                                 goodness)

        # Save the cookie jar
        output['data'] = jar if len(str(jar)) < 32768 else {}

        # Got through the cookie check properly
        if not output['result']:
            output['result'] = 'cookies-secure-with-httponly-sessions'

    # Check to see if the test passed or failed
    if output['result'] in ('cookies-not-found', expectation):
        output['pass'] = True

    return output
示例#3
0
def cookies(reqs: dict,
            expectation='cookies-secure-with-httponly-sessions') -> dict:
    """
    :param reqs: dictionary containing all the request and response objects
    :param expectation: test expectation
        cookies-secure-with-httponly-sessions: All cookies have secure flag set, all session cookies are HttpOnly
        cookies-without-secure-flag-but-protected-by-hsts: Cookies don't have secure, but site uses HSTS
        cookies-session-without-secure-flag-but-protected-by-hsts: Same, but session cookie
        cookies-without-secure-flag: Cookies set without secure flag
        cookies-session-without-secure-flag: Session cookies lack the Secure flag
        cookies-session-without-httponly-flag: Session cookies lack the HttpOnly flag
        cookies-not-found: No cookies found in HTTP requests
    :return: dictionary with:
        data: the cookie jar
        expectation: test expectation
        pass: whether the site's configuration met its expectation
        result: short string describing the result of the test
    """

    output = {
        'data': None,
        'expectation': expectation,
        'pass': False,
        'result': None,
    }
    session = reqs['session']  # all requests and their associated cookies

    # The order of how bad the various results are
    goodness = [
        'cookies-without-secure-flag-but-protected-by-hsts',
        'cookies-without-secure-flag',
        'cookies-session-without-secure-flag-but-protected-by-hsts',
        'cookies-session-without-httponly-flag',
        'cookies-session-without-secure-flag'
    ]

    # TODO: Support cookies set over http-equiv (ugh)
    # https://github.com/mozilla/http-observatory/issues/265

    # Get their HTTP Strict Transport Security status, which can help when cookies are set without Secure
    hsts = strict_transport_security(reqs)['pass']

    # If there are no cookies
    if not session.cookies:
        output['result'] = 'cookies-not-found'

    else:
        jar = {}

        # Ignore the CloudFlare __cfduid tracking cookies. They *are* actually bad, but it is out of a site's
        # control.  See https://github.com/mozilla/http-observatory/issues/121 for additional details. Hopefully
        # this will eventually be fixed on CloudFlare's end.
        del (session.cookies['__cfduid'])

        for cookie in session.cookies:
            # The httponly functionality is a bit broken
            if not hasattr(cookie, 'httponly'):
                if 'httponly' in [key.lower() for key in cookie._rest]:
                    cookie.httponly = True
                else:
                    cookie.httponly = False

            # Add it to the jar
            jar[cookie.name] = {
                i: getattr(cookie, i, None)
                for i in [
                    'domain', 'expires', 'httponly', 'max-age', 'path', 'port',
                    'secure'
                ]
            }

            # Is it a session identifier?
            sessionid = any(i in cookie.name.lower()
                            for i in ('login', 'sess'))

            if not cookie.secure and hsts:
                output['result'] = only_if_worse(
                    'cookies-without-secure-flag-but-protected-by-hsts',
                    output['result'], goodness)

            elif not cookie.secure:
                output['result'] = only_if_worse('cookies-without-secure-flag',
                                                 output['result'], goodness)

            # Login and session cookies should be set with Secure
            if sessionid and not cookie.secure and hsts:
                output['result'] = only_if_worse(
                    'cookies-session-without-secure-flag-but-protected-by-hsts',
                    output['result'], goodness)
            elif sessionid and not cookie.secure:
                output['result'] = only_if_worse(
                    'cookies-session-without-secure-flag', output['result'],
                    goodness)

            # Login and session cookies should be set with HttpOnly
            if sessionid and not cookie.httponly:
                output['result'] = only_if_worse(
                    'cookies-session-without-httponly-flag', output['result'],
                    goodness)

        # Save the cookie jar
        output['data'] = jar if len(str(jar)) < 32768 else {}

        # Got through the cookie check properly
        if not output['result']:
            output['result'] = 'cookies-secure-with-httponly-sessions'

    # Check to see if the test passed or failed
    if output['result'] in ('cookies-not-found', expectation):
        output['pass'] = True

    return output
示例#4
0
def subresource_integrity(reqs: dict, expectation='sri-implemented-and-external-scripts-loaded-securely') -> dict:
    """
    :param reqs: dictionary containing all the request and response objects
    :param expectation: test expectation
        sri-implemented-and-all-scripts-loaded-securely: all same origin, and uses SRI
        sri-implemented-and-external-scripts-loaded-securely: integrity attribute exists on all external scripts,
          and scripts loaded [default for HTML]
        sri-implemented-but-external-scripts-not-loaded-securely: SRI implemented, but with scripts loaded over HTTP
        sri-not-implemented-but-external-scripts-loaded-securely: SRI isn't implemented,
          but all scripts are loaded over HTTPS
        sri-not-implemented-and-external-scripts-not-loaded-securely: SRI isn't implemented,
          and scripts are downloaded over HTTP
        sri-not-implemented-but-all-scripts-loaded-from-secure-origin: SRI isn't implemented,
          but all scripts come from secure origins (self)
        sri-not-implemented-but-no-scripts-loaded: SRI isn't implemented, because the page doesn't load any scripts
        sri-not-implemented-response-not-html: SRI isn't needed, because the page isn't HTML [default for non-HTML]
        request-did-not-return-status-code-200: Only look for SRI on pages that returned 200, not things like 404s
        html-not-parsable: Can't parse the page's content
    :return: dictionary with:
        data: all external scripts and their integrity / crossorigin attributes
        expectation: test expectation
        pass: whether the site's external scripts met expectations
        result: short string describing the result of the test
    """
    output = {
        'data': {},
        'expectation': expectation,
        'pass': False,
        'result': None,
    }
    response = reqs['responses']['auto']

    # The order of how "good" the results are
    goodness = ['sri-implemented-and-all-scripts-loaded-securely',
                'sri-implemented-and-external-scripts-loaded-securely',
                'sri-implemented-but-external-scripts-not-loaded-securely',
                'sri-not-implemented-but-external-scripts-loaded-securely',
                'sri-not-implemented-and-external-scripts-not-loaded-securely',
                'sri-not-implemented-response-not-html']

    # If the response to get / fails
    if response.status_code != 200:
        output['result'] = 'request-did-not-return-status-code-200'

    # If the content isn't HTML, there's no scripts to load; this is okay
    elif response.headers.get('Content-Type', '').split(';')[0] not in ('text/html', 'application/xhtml+xml'):
        output['result'] = 'sri-not-implemented-response-not-html'

    else:
        # Try to parse the HTML
        try:
            soup = bs(reqs['resources']['/'], 'html.parser')
        except:
            output['result'] = 'html-not-parsable'
            return output

        # Track to see if any scripts were on foreign TLDs
        scripts_on_foreign_origin = False

        # Get all the scripts
        scripts = soup.find_all('script')
        for script in scripts:
            if script.has_attr('src'):
                # Script tag parameters
                src = urlparse(script['src'])
                integrity = script.get('integrity')
                crossorigin = script.get('crossorigin')

                # Check to see if they're on the same second-level domain
                # TODO: update the PSL list on startup
                psl = PublicSuffixList()
                samesld = True if (psl.privatesuffix(urlparse(response.url).netloc) ==
                                   psl.privatesuffix(src.netloc)) else False

                # Check to see if it's the same origin or second-level domain
                if src.netloc == '' or samesld:
                    secureorigin = True
                elif src.netloc != '' and '.' not in src.netloc:  # like localhost
                    secureorigin = False
                    scripts_on_foreign_origin = True
                else:
                    secureorigin = False
                    scripts_on_foreign_origin = True

                # See if it's a secure scheme
                if src.scheme == 'https' or (src.scheme == '' and urlparse(response.url).scheme == 'https'):
                    securescheme = True
                else:
                    securescheme = False

                # Add it to the scripts data result, if it's not a relative URI
                if not secureorigin:
                    output['data'][script['src']] = {
                                                        'crossorigin': crossorigin,
                                                        'integrity': integrity
                                                    }

                    if integrity and not securescheme:
                        output['result'] = only_if_worse('sri-implemented-but-external-scripts-not-loaded-securely',
                                                         output['result'],
                                                         goodness)
                    elif not integrity and securescheme:
                        output['result'] = only_if_worse('sri-not-implemented-but-external-scripts-loaded-securely',
                                                         output['result'],
                                                         goodness)
                    elif not integrity and not securescheme:
                        output['result'] = only_if_worse('sri-not-implemented-and-external-scripts'
                                                         '-not-loaded-securely',
                                                         output['result'],
                                                         goodness)

                # Grant bonus even if they use SRI on the same origin
                else:
                    if integrity and securescheme and not output['result']:
                        output['result'] = 'sri-implemented-and-all-scripts-loaded-securely'

        # If the page doesn't load any scripts
        if not scripts:
            output['result'] = 'sri-not-implemented-but-no-scripts-loaded'

        # If all the scripts are loaded from a secure origin, not triggering a need for SRI
        elif scripts and not scripts_on_foreign_origin and not output['result']:
            output['result'] = 'sri-not-implemented-but-all-scripts-loaded-from-secure-origin'

        # If the page loaded from a foreign origin, but everything included SRI
        elif scripts and scripts_on_foreign_origin and not output['result']:
            output['result'] = only_if_worse('sri-implemented-and-external-scripts-loaded-securely',
                                             output['result'],
                                             goodness)

    # Code defensively on the size of the data
    output['data'] = output['data'] if len(str(output['data'])) < 32768 else {}

    # Check to see if the test passed or failed
    if output['result'] in ('sri-implemented-and-all-scripts-loaded-securely',
                            'sri-implemented-and-external-scripts-loaded-securely',
                            'sri-not-implemented-response-not-html',
                            'sri-not-implemented-but-all-scripts-loaded-from-secure-origin',
                            'sri-not-implemented-but-no-scripts-loaded',
                            expectation):
        output['pass'] = True

    return output
示例#5
0
def subresource_integrity(reqs: dict, expectation='sri-implemented-and-external-scripts-loaded-securely') -> dict:
    """
    :param reqs: dictionary containing all the request and response objects
    :param expectation: test expectation
        sri-implemented-and-all-scripts-loaded-securely: all same origin, and uses SRI
        sri-implemented-and-external-scripts-loaded-securely: integrity attribute exists on all external scripts,
          and scripts loaded [default for HTML]
        sri-implemented-but-external-scripts-not-loaded-securely: SRI implemented, but with scripts loaded over HTTP
        sri-not-implemented-but-external-scripts-loaded-securely: SRI isn't implemented,
          but all scripts are loaded over HTTPS
        sri-not-implemented-and-external-scripts-not-loaded-securely: SRI isn't implemented,
          and scripts are downloaded over HTTP
        sri-not-implemented-but-all-scripts-loaded-from-secure-origin: SRI isn't implemented,
          but all scripts come from secure origins (self)
        sri-not-implemented-but-no-scripts-loaded: SRI isn't implemented, because the page doesn't load any scripts
        sri-not-implemented-response-not-html: SRI isn't needed, because the page isn't HTML [default for non-HTML]
        request-did-not-return-status-code-200: Only look for SRI on pages that returned 200, not things like 404s
        html-not-parsable: Can't parse the page's content
    :return: dictionary with:
        data: all external scripts and their integrity / crossorigin attributes
        expectation: test expectation
        pass: whether the site's external scripts met expectations
        result: short string describing the result of the test
    """
    output = {
        'data': {},
        'expectation': expectation,
        'pass': False,
        'result': None,
    }
    response = reqs['responses']['auto']

    # The order of how "good" the results are
    goodness = ['sri-implemented-and-all-scripts-loaded-securely',
                'sri-implemented-and-external-scripts-loaded-securely',
                'sri-implemented-but-external-scripts-not-loaded-securely',
                'sri-not-implemented-but-external-scripts-loaded-securely',
                'sri-not-implemented-and-external-scripts-not-loaded-securely',
                'sri-not-implemented-response-not-html']

    # If the content isn't HTML, there's no scripts to load; this is okay
    if response.headers.get('Content-Type', '').split(';')[0] not in HTML_TYPES:
        output['result'] = 'sri-not-implemented-response-not-html'

    else:
        # Try to parse the HTML
        try:
            soup = bs(reqs['resources']['__path__'], 'html.parser')
        except:
            output['result'] = 'html-not-parsable'
            return output

        # Track to see if any scripts were on foreign TLDs
        scripts_on_foreign_origin = False

        # Get all the scripts
        scripts = soup.find_all('script')
        for script in scripts:
            if script.has_attr('src'):
                # Script tag parameters
                src = urlparse(script['src'])
                integrity = script.get('integrity')
                crossorigin = script.get('crossorigin')

                # Check to see if they're on the same second-level domain
                # TODO: update the PSL list on startup
                psl = PublicSuffixList()
                samesld = True if (psl.privatesuffix(urlparse(response.url).netloc) ==
                                   psl.privatesuffix(src.netloc)) else False

                if src.scheme == '':
                    if src.netloc == '':
                        # Relative URL (src="/path")
                        relativeorigin = True
                        relativeprotocol = False
                    else:
                        # Relative protocol (src="//host/path")
                        relativeorigin = False
                        relativeprotocol = True
                else:
                    relativeorigin = False
                    relativeprotocol = False

                # Check to see if it's the same origin or second-level domain
                if relativeorigin or (samesld and not relativeprotocol):
                    secureorigin = True
                else:
                    secureorigin = False
                    scripts_on_foreign_origin = True

                # See if it's a secure scheme
                if src.scheme == 'https' or (relativeorigin and urlparse(response.url).scheme == 'https'):
                    securescheme = True
                else:
                    securescheme = False

                # Add it to the scripts data result, if it's not a relative URI
                if not secureorigin:
                    output['data'][script['src']] = {
                        'crossorigin': crossorigin,
                        'integrity': integrity
                    }

                    if integrity and not securescheme:
                        output['result'] = only_if_worse('sri-implemented-but-external-scripts-not-loaded-securely',
                                                         output['result'],
                                                         goodness)
                    elif not integrity and securescheme:
                        output['result'] = only_if_worse('sri-not-implemented-but-external-scripts-loaded-securely',
                                                         output['result'],
                                                         goodness)
                    elif not integrity and not securescheme and samesld:
                        output['result'] = only_if_worse('sri-not-implemented-and-external-scripts'
                                                         '-not-loaded-securely',
                                                         output['result'],
                                                         goodness)
                    elif not integrity and not securescheme:
                        output['result'] = only_if_worse('sri-not-implemented-and-external-scripts'
                                                         '-not-loaded-securely',
                                                         output['result'],
                                                         goodness)

                # Grant bonus even if they use SRI on the same origin
                else:
                    if integrity and securescheme and not output['result']:
                        output['result'] = 'sri-implemented-and-all-scripts-loaded-securely'

        # If the page doesn't load any scripts
        if not scripts:
            output['result'] = 'sri-not-implemented-but-no-scripts-loaded'

        # If all the scripts are loaded from a secure origin, not triggering a need for SRI
        elif scripts and not scripts_on_foreign_origin and not output['result']:
            output['result'] = 'sri-not-implemented-but-all-scripts-loaded-from-secure-origin'

        # If the page loaded from a foreign origin, but everything included SRI
        elif scripts and scripts_on_foreign_origin and not output['result']:
            output['result'] = only_if_worse('sri-implemented-and-external-scripts-loaded-securely',
                                             output['result'],
                                             goodness)

    # Code defensively on the size of the data
    output['data'] = output['data'] if len(str(output['data'])) < 32768 else {}

    # Check to see if the test passed or failed
    if output['result'] in ('sri-implemented-and-all-scripts-loaded-securely',
                            'sri-implemented-and-external-scripts-loaded-securely',
                            'sri-not-implemented-response-not-html',
                            'sri-not-implemented-but-all-scripts-loaded-from-secure-origin',
                            'sri-not-implemented-but-no-scripts-loaded',
                            expectation):
        output['pass'] = True

    return output
示例#6
0
def cookies(reqs: dict, expectation='cookies-secure-with-httponly-sessions') -> dict:
    """
    :param reqs: dictionary containing all the request and response objects
    :param expectation: test expectation
        cookies-secure-with-httponly-sessions-and-samesite: All cookies are secure,
          use HttpOnly if needed, and SameSite
        cookies-secure-with-httponly-sessions: All cookies have secure flag set, all session cookies are HttpOnly
        cookies-without-secure-flag-but-protected-by-hsts: Cookies don't have secure, but site uses HSTS
        cookies-session-without-secure-flag-but-protected-by-hsts: Same, but session cookie
        cookies-without-secure-flag: Cookies set without secure flag
        cookies-samesite-flag-invalid: Cookies set with invalid SameSite value (must be either unset, Strict, or Lax)
        cookies-session-without-secure-flag: Session cookies lack the Secure flag
        cookies-session-without-httponly-flag: Session cookies lack the HttpOnly flag
        cookies-not-found: No cookies found in HTTP requests
    :return: dictionary with:
        data: the cookie jar
        expectation: test expectation
        pass: whether the site's configuration met its expectation
        result: short string describing the result of the test
    """

    output = {
        'data': None,
        'expectation': expectation,
        'pass': False,
        'result': None,
        'sameSite': None,
    }
    session = reqs['session']  # all requests and their associated cookies

    # The order of how bad the various results are
    goodness = ['cookies-without-secure-flag-but-protected-by-hsts',
                'cookies-without-secure-flag',
                'cookies-session-without-secure-flag-but-protected-by-hsts',
                'cookies-samesite-flag-invalid',
                'cookies-anticsrf-without-samesite-flag',
                'cookies-session-without-httponly-flag',
                'cookies-session-without-secure-flag']

    # TODO: Support cookies set over http-equiv (ugh)
    # https://github.com/mozilla/http-observatory/issues/265

    # Get their HTTP Strict Transport Security status, which can help when cookies are set without Secure
    hsts = strict_transport_security(reqs)['pass']

    # If there are no cookies
    if not session.cookies:
        output['result'] = 'cookies-not-found'

    else:
        jar = {}

        # There are certain cookies we ignore, because they are set by service providers and sites have
        # no control over them.
        for cookie in COOKIES_TO_DELETE:
            del(session.cookies[cookie])

        for cookie in session.cookies:
            # The HttpOnly and SameSite functionality is a bit broken
            cookie.httponly = cookie.samesite = False
            for key in cookie._rest:
                if key.lower() == 'httponly' and getattr(cookie, 'httponly') is False:
                    cookie.httponly = True
                elif key.lower() == 'samesite' and getattr(cookie, 'samesite') is False:
                    if cookie._rest[key] is True or cookie._rest[key].strip().lower() == 'strict':
                        cookie.samesite = 'Strict'
                        output['sameSite'] = True
                    elif cookie._rest[key].strip().lower() == 'lax':
                        cookie.samesite = 'Lax'
                        output['sameSite'] = True
                    else:
                        output['result'] = only_if_worse('cookies-samesite-flag-invalid',
                                                         output['result'],
                                                         goodness)

            # Add it to the jar
            jar[cookie.name] = {i: getattr(cookie, i, None) for i in ['domain', 'expires', 'httponly',
                                                                      'max-age', 'path', 'port', 'samesite', 'secure']}

            # Is it a session identifier or an anti-csrf token?
            sessionid = any(i in cookie.name.lower() for i in ('login', 'sess'))
            anticsrf = True if 'csrf' in cookie.name.lower() else False

            if not cookie.secure and hsts:
                output['result'] = only_if_worse('cookies-without-secure-flag-but-protected-by-hsts',
                                                 output['result'],
                                                 goodness)

            elif not cookie.secure:
                output['result'] = only_if_worse('cookies-without-secure-flag',
                                                 output['result'],
                                                 goodness)

            # Anti-CSRF tokens should be set using the SameSite option
            if anticsrf and not cookie.samesite:
                output['result'] = only_if_worse('cookies-anticsrf-without-samesite-flag',
                                                 output['result'],
                                                 goodness)

            # Login and session cookies should be set with Secure
            if sessionid and not cookie.secure and hsts:
                output['result'] = only_if_worse('cookies-session-without-secure-flag-but-protected-by-hsts',
                                                 output['result'],
                                                 goodness)
            elif sessionid and not cookie.secure:
                output['result'] = only_if_worse('cookies-session-without-secure-flag',
                                                 output['result'],
                                                 goodness)

            # Login and session cookies should be set with HttpOnly
            if sessionid and not cookie.httponly:
                output['result'] = only_if_worse('cookies-session-without-httponly-flag',
                                                 output['result'],
                                                 goodness)

        # Store whether or not we saw SameSite cookies, if cookies were set
        if output['result'] is None:
            if output['sameSite']:
                output['result'] = 'cookies-secure-with-httponly-sessions-and-samesite'
            else:
                output['result'] = 'cookies-secure-with-httponly-sessions'
                output['sameSite'] = False

        # Save the cookie jar
        output['data'] = jar if len(str(jar)) < 32768 else {}

    # Check to see if the test passed or failed
    if output['result'] in ('cookies-not-found',
                            'cookies-secure-with-httponly-sessions-and-samesite',
                            expectation):
        output['pass'] = True

    return output