Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove no-longer-needed transitional cookie code #8865

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions h/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,19 @@ def configure(environ=None, settings=None): # pylint: disable=too-many-statemen
settings_manager.set("mail.port", "MANDRILL_PORT", default=587)
settings_manager.set("mail.tls", "MANDRILL_TLS", default=True)

settings_manager.set(
"h_api_auth_cookie_secret_key",
"H_API_AUTH_COOKIE_SECRET_KEY",
type_=_to_utf8,
required=True,
)
settings_manager.set(
"h_api_auth_cookie_salt",
"H_API_AUTH_COOKIE_SALT",
type_=_to_utf8,
required=True,
)

# Get resolved settings.
settings = settings_manager.settings

Expand Down
5 changes: 5 additions & 0 deletions h/security/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ def includeme(config): # pragma: no cover
settings["h_auth_cookie_secret"] = derive_key(
settings["secret_key"], settings["secret_salt"], b"h.auth.cookie_secret"
)
settings["h_api_auth_cookie_secret"] = derive_key(
settings["h_api_auth_cookie_secret_key"],
settings["h_api_auth_cookie_salt"],
b"h_api_auth_cookie_secret",
)

# Set the default authentication policy. This can be overridden by modules
# that include this one.
Expand Down
22 changes: 17 additions & 5 deletions h/security/policy/_cookie.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@ class CookiePolicy:
straps the login for the client (when the popup shows).
"""

def __init__(self, cookie: SignedCookieProfile, helper: AuthTicketCookieHelper):
self.cookie = cookie
def __init__(
self,
html_authcookie: SignedCookieProfile,
api_authcookie: SignedCookieProfile,
helper: AuthTicketCookieHelper,
):
self.html_authcookie = html_authcookie
self.api_authcookie = api_authcookie
self.helper = helper

def identity(self, request):
self.helper.add_vary_by_cookie(request)
return self.helper.identity(self.cookie, request)
return self.helper.identity(self.html_authcookie, request)

def authenticated_userid(self, request):
return Identity.authenticated_userid(self.identity(request))
Expand All @@ -41,11 +47,17 @@ def remember(self, request, userid, **kw): # pylint:disable=unused-argument
request.session.update(data)
request.session.new_csrf_token()

return self.helper.remember(self.cookie, request, userid)
return [
*self.helper.remember(self.html_authcookie, request, userid),
*self.helper.remember(self.api_authcookie, request, userid),
]

def forget(self, request):
self.helper.add_vary_by_cookie(request)
return self.helper.forget(self.cookie, request)
return [
*self.helper.forget(self.html_authcookie, request),
*self.helper.forget(self.api_authcookie, request),
]

def permits(self, request, context, permission) -> Allowed | Denied:
return identity_permits(self.identity(request), context, permission)
28 changes: 21 additions & 7 deletions h/security/policy/top_level.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,37 @@ def _load_identity(self, request):
@RequestLocalCache()
def get_subpolicy(request):
"""Return the subpolicy for TopLevelSecurityPolicy to delegate to for `request`."""
cookie = webob.cookies.SignedCookieProfile(
secret=request.registry.settings["h_auth_cookie_secret"],
salt="authsanity",
cookie_name="auth",

# The cookie that's used to authenticate API requests.
api_authcookie = webob.cookies.SignedCookieProfile(
secret=request.registry.settings["h_api_auth_cookie_secret"],
salt=request.registry.settings["h_api_auth_cookie_salt"],
cookie_name="h_api_authcookie",
max_age=30 * 24 * 3600, # 30 days
httponly=True,
secure=request.scheme == "https",
samesite="strict",
path="/api/",
)
cookie = cookie.bind(request)
api_authcookie = api_authcookie.bind(request)

if is_api_request(request):
return APIPolicy(
[
BearerTokenPolicy(),
AuthClientPolicy(),
APICookiePolicy(cookie, AuthTicketCookieHelper()),
APICookiePolicy(api_authcookie, AuthTicketCookieHelper()),
]
)

return CookiePolicy(cookie, AuthTicketCookieHelper())
# The cookie that's used to authenticate HTML page requests.
html_authcookie = webob.cookies.SignedCookieProfile(
secret=request.registry.settings["h_auth_cookie_secret"],
salt="authsanity",
cookie_name="auth",
max_age=30 * 24 * 3600, # 30 days
httponly=True,
secure=request.scheme == "https",
)
html_authcookie = html_authcookie.bind(request)
return CookiePolicy(html_authcookie, api_authcookie, AuthTicketCookieHelper())
4 changes: 4 additions & 0 deletions tests/functional/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
"h.sentry_dsn_frontend": "TEST_SENTRY_DSN_FRONTEND",
"pyramid.debug_all": False,
"secret_key": "notasecret",
"h_api_auth_cookie_secret_key": b"test_h_api_auth_cookie_secret_key",
"h_api_auth_cookie_salt": b"test_h_api_auth_cookie_salt",
"sqlalchemy.url": os.environ["DATABASE_URL"],
}

Expand All @@ -35,6 +37,8 @@
"AUTH_DOMAIN": TEST_SETTINGS["h.authority"],
"SENTRY_DSN_FRONTEND": TEST_SETTINGS["h.sentry_dsn_frontend"],
"SECRET_KEY": TEST_SETTINGS["secret_key"],
"H_API_AUTH_COOKIE_SECRET_KEY": TEST_SETTINGS["h_api_auth_cookie_secret_key"],
"H_API_AUTH_COOKIE_SALT": TEST_SETTINGS["h_api_auth_cookie_salt"],
"DATABASE_URL": TEST_SETTINGS["sqlalchemy.url"],
}

Expand Down
2 changes: 2 additions & 0 deletions tests/unit/h/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ def test_configure_updates_settings_from_env_vars(
# Required settings
"es.url": "https://es6-search-cluster",
"secret_key": "notasecret",
"h_api_auth_cookie_secret_key": b"test_h_api_auth_cookie_secret_key",
"h_api_auth_cookie_salt": b"test_h_api_auth_cookie_salt",
"sqlalchemy.url": "postgres://user@dbhost/dbname",
}

Expand Down
103 changes: 85 additions & 18 deletions tests/unit/h/security/policy/_cookie_test.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,66 @@
from unittest.mock import create_autospec, sentinel
from unittest.mock import call, create_autospec, sentinel

import pytest
from webob.cookies import SignedCookieProfile

from h.security.policy._cookie import CookiePolicy
from h.security.policy.helpers import AuthTicketCookieHelper


class TestCookiePolicy:
def test_identity(self, cookie_policy, helper, pyramid_request):
def test_identity(self, cookie_policy, helper, pyramid_request, html_authcookie):
identity = cookie_policy.identity(pyramid_request)

helper.add_vary_by_cookie.assert_called_once_with(pyramid_request)
helper.identity.assert_called_once_with(sentinel.cookie, pyramid_request)
helper.identity.assert_called_once_with(html_authcookie, pyramid_request)
assert identity == helper.identity.return_value

def test_authenticated_userid(
self, cookie_policy, helper, pyramid_request, Identity
self, cookie_policy, helper, pyramid_request, Identity, html_authcookie
):
authenticated_userid = cookie_policy.authenticated_userid(pyramid_request)

helper.add_vary_by_cookie.assert_called_once_with(pyramid_request)
helper.identity.assert_called_once_with(sentinel.cookie, pyramid_request)
helper.identity.assert_called_once_with(html_authcookie, pyramid_request)
Identity.authenticated_userid.assert_called_once_with(
helper.identity.return_value
)
assert authenticated_userid == Identity.authenticated_userid.return_value

def test_remember(self, cookie_policy, helper, pyramid_request):
def test_remember(
self, cookie_policy, html_authcookie, api_authcookie, helper, pyramid_request
):
pyramid_request.session["data"] = "old"

def remember(cookie, *_args, **_kwargs):
if cookie == html_authcookie:
return [
sentinel.html_authcookie_header_1,
sentinel.html_authcookie_header_2,
]
if cookie == api_authcookie:
return [
sentinel.api_authcookie_header_1,
sentinel.api_authcookie_header_2,
]

assert False, "Should never reach here" # pragma: no cover

helper.remember.side_effect = remember

headers = cookie_policy.remember(pyramid_request, sentinel.userid, foo="bar")

assert not pyramid_request.session
helper.remember.assert_called_once_with(
sentinel.cookie, pyramid_request, sentinel.userid
)
assert headers == helper.remember.return_value
assert helper.remember.call_args_list == [
call(html_authcookie, pyramid_request, sentinel.userid),
call(api_authcookie, pyramid_request, sentinel.userid),
]
assert headers == [
sentinel.html_authcookie_header_1,
sentinel.html_authcookie_header_2,
sentinel.api_authcookie_header_1,
sentinel.api_authcookie_header_2,
]

def test_remember_with_existing_user(
self, cookie_policy, pyramid_request, factories, Identity
Expand All @@ -51,32 +76,74 @@ def test_remember_with_existing_user(
assert pyramid_request.session["data"] == "old"
assert pyramid_request.session["_csrft_"] != "old_csrf_token"

def test_forget(self, cookie_policy, helper, pyramid_request):
def test_forget(
self, cookie_policy, helper, pyramid_request, html_authcookie, api_authcookie
):
def forget(cookie, *_args, **_kwargs):
if cookie == html_authcookie:
return [
sentinel.html_authcookie_header_1,
sentinel.html_authcookie_header_2,
]
if cookie == api_authcookie:
return [
sentinel.api_authcookie_header_1,
sentinel.api_authcookie_header_2,
]

assert False, "Should never reach here" # pragma: no cover

helper.forget.side_effect = forget

headers = cookie_policy.forget(pyramid_request)

helper.add_vary_by_cookie.assert_called_once_with(pyramid_request)
helper.forget.assert_called_once_with(sentinel.cookie, pyramid_request)
assert headers == helper.forget.return_value

def test_permits(self, cookie_policy, helper, pyramid_request, identity_permits):
assert helper.forget.call_args_list == [
call(html_authcookie, pyramid_request),
call(api_authcookie, pyramid_request),
]
assert headers == [
sentinel.html_authcookie_header_1,
sentinel.html_authcookie_header_2,
sentinel.api_authcookie_header_1,
sentinel.api_authcookie_header_2,
]

def test_permits(
self, cookie_policy, helper, pyramid_request, identity_permits, html_authcookie
):
permits = cookie_policy.permits(
pyramid_request, sentinel.context, sentinel.permission
)

helper.add_vary_by_cookie.assert_called_once_with(pyramid_request)
helper.identity.assert_called_once_with(sentinel.cookie, pyramid_request)
helper.identity.assert_called_once_with(html_authcookie, pyramid_request)
identity_permits.assert_called_once_with(
helper.identity.return_value, sentinel.context, sentinel.permission
)
assert permits == identity_permits.return_value

@pytest.fixture
def html_authcookie(self, pyramid_request):
html_authcookie = SignedCookieProfile("secret", "salt", cookie_name="auth")
pyramid_request.cookies[html_authcookie.cookie_name] = html_authcookie
return html_authcookie.bind(pyramid_request)

@pytest.fixture
def api_authcookie(self, pyramid_request):
api_authcookie = SignedCookieProfile(
"secret", "salt", cookie_name="h_api_authcookie"
)

return api_authcookie.bind(pyramid_request)

@pytest.fixture
def helper(self):
return create_autospec(AuthTicketCookieHelper, instance=True, spec_set=True)

@pytest.fixture
def cookie_policy(self, helper):
return CookiePolicy(sentinel.cookie, helper)
def cookie_policy(self, html_authcookie, api_authcookie, helper):
return CookiePolicy(html_authcookie, api_authcookie, helper)


@pytest.fixture(autouse=True)
Expand Down
Loading
Loading