Skip to content

Reapplying changes from tokens branch #4949

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

Closed
wants to merge 3 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
2 changes: 2 additions & 0 deletions requirements/main.in
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ passlib>=1.6.4
premailer
psycopg2
pycurl
pymacaroons
pynacl
pyramid>=1.9,<1.11.0
pyramid_jinja2>=2.5
pyramid_mailer>=0.14.1
Expand Down
27 changes: 27 additions & 0 deletions requirements/main.txt
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,33 @@ pycurl==7.43.0.2 \
pygments==2.2.0 \
--hash=sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d \
--hash=sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc
pymacaroons==0.13.0 \
--hash=sha256:3e14dff6a262fdbf1a15e769ce635a8aea72e6f8f91e408f9a97166c53b91907 \
--hash=sha256:1e6bba42a5f66c245adf38a5a4006a99dcc06a0703786ea636098667d42903b8
PyNaCl==1.2.1 \
--hash=sha256:0bfa0d94d2be6874e40f896e0a67e290749151e7de767c5aefbad1121cad7512 \
--hash=sha256:1d33e775fab3f383167afb20b9927aaf4961b953d76eeb271a5703a6d756b65b \
--hash=sha256:eb2acabbd487a46b38540a819ef67e477a674481f84a82a7ba2234b9ba46f752 \
--hash=sha256:14339dc233e7a9dda80a3800e64e7ff89d0878ba23360eea24f1af1b13772cac \
--hash=sha256:cf6877124ae6a0698404e169b3ba534542cfbc43f939d46b927d956daf0a373a \
--hash=sha256:eeee629828d0eb4f6d98ac41e9a3a6461d114d1d0aa111a8931c049359298da0 \
--hash=sha256:d0eb5b2795b7ee2cbcfcadacbe95a13afbda048a262bd369da9904fecb568975 \
--hash=sha256:11aa4e141b2456ce5cecc19c130e970793fa3a2c2e6fbb8ad65b28f35aa9e6b6 \
--hash=sha256:8ac1167195b32a8755de06efd5b2d2fe76fc864517dab66aaf65662cc59e1988 \
--hash=sha256:d795f506bcc9463efb5ebb0f65ed77921dcc9e0a50499dedd89f208445de9ecb \
--hash=sha256:be71cd5fce04061e1f3d39597f93619c80cdd3558a6c9ba99a546f144a8d8101 \
--hash=sha256:2a42b2399d0428619e58dac7734838102d35f6dcdee149e0088823629bf99fbb \
--hash=sha256:73a5a96fb5fbf2215beee2353a128d382dbca83f5341f0d3c750877a236569ef \
--hash=sha256:d8aaf7e5d6b0e0ef7d6dbf7abeb75085713d0100b4eb1a4e4e857de76d77ac45 \
--hash=sha256:2dce05ac8b3c37b9e2f65eab56c544885607394753e9613fd159d5e2045c2d98 \
--hash=sha256:f5ce9e26d25eb0b2d96f3ef0ad70e1d3ae89b5d60255c462252a3e456a48c053 \
--hash=sha256:6453b0dae593163ffc6db6f9c9c1597d35c650598e2c39c0590d1757207a1ac2 \
--hash=sha256:fabf73d5d0286f9e078774f3435601d2735c94ce9e514ac4fb945701edead7e4 \
--hash=sha256:13bdc1fe084ff9ac7653ae5a924cae03bf4bb07c6667c9eb5b6eb3c570220776 \
--hash=sha256:8f505f42f659012794414fa57c498404e64db78f1d98dfd40e318c569f3c783b \
--hash=sha256:04e30e5bdeeb2d5b34107f28cd2f5bbfdc6c616f3be88fc6f53582ff1669eeca \
--hash=sha256:8abb4ef79161a5f58848b30ab6fb98d8c466da21fdd65558ce1d7afc02c70b5f \
--hash=sha256:e0d38fa0a75f65f556fb912f2c6790d1fa29b7dd27a1d9cc5591b281321eaaa9
pyparsing==2.3.0 \
--hash=sha256:40856e74d4987de5d01761a22d1621ae1c7f8774585acae358aa5c5936c6c90b \
--hash=sha256:f353aab21fd474459d97b709e527b5571314ee5f067441dc9f88e33eecd96592
Expand Down
18 changes: 17 additions & 1 deletion tests/common/db/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
# limitations under the License.

import datetime
import uuid

import factory
import factory.fuzzy

from warehouse.accounts.models import User, Email
from warehouse.accounts.models import User, Email, AccountToken

from .base import WarehouseFactory, FuzzyEmail

Expand Down Expand Up @@ -45,3 +46,18 @@ class Meta:
primary = True
unverify_reason = None
transient_bounces = 0


class AccountTokenFactory(WarehouseFactory):
class Meta:
model = AccountToken

id = factory.Sequence(lambda n: uuid.uuid4())
secret = factory.fuzzy.FuzzyText(length=100)
username = factory.fuzzy.FuzzyText()
description = factory.fuzzy.FuzzyText(length=100)
is_active = True
created = factory.fuzzy.FuzzyNaiveDateTime(
datetime.datetime(2005, 1, 1), datetime.datetime(2010, 1, 1)
)
last_used = factory.fuzzy.FuzzyNaiveDateTime(datetime.datetime(2011, 1, 1))
233 changes: 230 additions & 3 deletions tests/unit/accounts/test_auth_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@
import pretend
import uuid

from pyramid import authentication
from pyramid.interfaces import IAuthenticationPolicy
from pymacaroons import Macaroon
from pyramid import authentication, security
from pyramid.interfaces import IAuthenticationPolicy, IAuthorizationPolicy
from zope.interface.verify import verifyClass

from warehouse.accounts import auth_policy
from warehouse.accounts.interfaces import IUserService
from warehouse.accounts.interfaces import IUserService, IAccountTokenService

from ...common.db.accounts import AccountTokenFactory, UserFactory


class TestBasicAuthAuthenticationPolicy:
Expand Down Expand Up @@ -105,3 +108,227 @@ def test_unauthenticated_userid(self, monkeypatch):
assert policy.unauthenticated_userid(request) is userid
assert add_vary_cb.calls == [pretend.call("Cookie")]
assert request.add_response_callback.calls == [pretend.call(vary_cb)]


class TestAccountTokenAuthenticationPolicy:
def test_verify(self):
assert verifyClass(
IAuthenticationPolicy, auth_policy.AccountTokenAuthenticationPolicy
)

def test_routes_not_allowed(self):
request = pretend.stub(matched_route=pretend.stub(name="not_allowed_route"))

authn_policy = auth_policy.AccountTokenAuthenticationPolicy(
pretend.stub(), ["allowed_route"]
)

assert authn_policy.unauthenticated_userid(request) is None

def test_require_known(self):
# Ensure we don't accept just any macaroon
request = pretend.stub(
find_service=lambda iface, **kw: {
IAccountTokenService: pretend.stub(
get_unverified_macaroon=(lambda: (None, None))
)
}[iface],
matched_route=pretend.stub(name="allowed_route"),
add_response_callback=lambda x: None,
)

authn_policy = auth_policy.AccountTokenAuthenticationPolicy(
pretend.stub(), ["allowed_route"]
)

assert authn_policy.unauthenticated_userid(request) is None

def test_macaroon_verifier(self, db_request):
user = UserFactory.create(username="test_user")

account_token = AccountTokenFactory.create(
secret="some_secret", username=user.username
)

macaroon = Macaroon(
location="pypi.org", identifier="some_id", key="wrong_secret"
)

request = pretend.stub(
find_service=lambda iface, **kw: {
IAccountTokenService: pretend.stub(
get_unverified_macaroon=(lambda: (macaroon, account_token))
)
}[iface],
matched_route=pretend.stub(name="allowed_route"),
add_response_callback=lambda x: None,
)

authn_policy = auth_policy.AccountTokenAuthenticationPolicy(
pretend.stub(), ["allowed_route"]
)

assert authn_policy.unauthenticated_userid(request) is None

def test_account_token_auth(self, db_request):
# Test basic happy path
user = UserFactory.create(username="test_user")
account_token = AccountTokenFactory.create(
secret="some_secret", username=user.username
)

macaroon = Macaroon(
location="pypi.org", identifier=str(account_token.id), key="some_secret"
)

request = pretend.stub(
find_service=lambda iface, **kw: {
IUserService: pretend.stub(
find_userid=(lambda x: user.id if x == "test_user" else None)
),
IAccountTokenService: pretend.stub(
get_unverified_macaroon=(lambda: (macaroon, account_token)),
update_last_used=(lambda x: None),
),
}[iface],
matched_route=pretend.stub(name="allowed_route"),
add_response_callback=lambda x: None,
session={},
)

authn_policy = auth_policy.AccountTokenAuthenticationPolicy(
pretend.stub(), ["allowed_route"]
)

assert authn_policy.unauthenticated_userid(request) == user.id

# Make sure we allow first-party and third-party caveats
macaroon.add_first_party_caveat("first party caveat")

macaroon.add_third_party_caveat(
location="mysite.com", key="anykey", key_id="anykeyid"
)

assert authn_policy.unauthenticated_userid(request) == user.id

def test_account_token_interface(self):
def _authenticate(a, b):
return a, b

policy = auth_policy.AccountTokenAuthenticationPolicy(_authenticate, ["route"])

assert policy.remember("", "") == []
assert policy.forget("") == []
assert policy._auth_callback(1, 2) == (1, 2)
assert policy._routes_allowed == ["route"]


class TestAccountTokenAuthorizationPolicy:
def test_verify(self):
assert verifyClass(
IAuthorizationPolicy, auth_policy.AccountTokenAuthorizationPolicy
)

def test_have_request(self, monkeypatch):
monkeypatch.setattr(auth_policy, "get_current_request", lambda: None)
authz_policy = auth_policy.AccountTokenAuthorizationPolicy(pretend.stub())

assert isinstance(
authz_policy.permits(pretend.stub(), pretend.stub(), pretend.stub()),
security.Denied,
)

def test_macaroon_verifier(self, db_request, monkeypatch):
user = UserFactory.create(username="test_user")

account_token = AccountTokenFactory.create(
secret="some_secret", username=user.username
)

macaroon = Macaroon(
location="pypi.org", identifier="some_id", key="wrong_secret"
)

monkeypatch.setattr(auth_policy, "get_current_request", lambda: request)
request = pretend.stub(
find_service=lambda iface, **kw: {
IAccountTokenService: pretend.stub(
get_unverified_macaroon=(lambda: (macaroon, account_token))
)
}[iface]
)

authz_policy = auth_policy.AccountTokenAuthorizationPolicy(pretend.stub())

assert isinstance(
authz_policy.permits(pretend.stub(), pretend.stub(), pretend.stub()),
security.Denied,
)

def test_account_token_authz(self, db_request, monkeypatch):
user = UserFactory.create(username="test_user")

account_token = AccountTokenFactory.create(
secret="some_secret", username=user.username
)

macaroon = Macaroon(
location="pypi.org", identifier="some_id", key="some_secret"
)

monkeypatch.setattr(auth_policy, "get_current_request", lambda: request)
request = pretend.stub(
find_service=lambda iface, **kw: {
IAccountTokenService: pretend.stub(
get_unverified_macaroon=(lambda: (macaroon, account_token))
)
}[iface]
)

authz_policy = auth_policy.AccountTokenAuthorizationPolicy(
pretend.stub(permits=(lambda *args, **kwargs: "allow"))
)

assert (
authz_policy.permits(pretend.stub(), pretend.stub(), pretend.stub())
== "allow"
)

# Make sure we allow first-party and third-party caveats
macaroon.add_first_party_caveat("first party caveat")

macaroon.add_third_party_caveat(
location="mysite.com", key="anykey", key_id="anykeyid"
)

assert (
authz_policy.permits(pretend.stub(), pretend.stub(), pretend.stub())
== "allow"
)

def test_missing_macaroon(self, monkeypatch):
monkeypatch.setattr(auth_policy, "get_current_request", lambda: request)

request = pretend.stub(
find_service=lambda iface, **kw: {
IAccountTokenService: pretend.stub(
get_unverified_macaroon=(lambda: (None, None))
)
}[iface]
)

authz_policy = auth_policy.AccountTokenAuthorizationPolicy(
pretend.stub(permits=(lambda *args, **kwargs: "allow"))
)

assert (
authz_policy.permits(pretend.stub(), pretend.stub(), pretend.stub())
== "allow"
)

def test_principals_allowed_by_permission(self):
authz_policy = auth_policy.AccountTokenAuthorizationPolicy(
pretend.stub(principals_allowed_by_permission=(lambda a, b: (a, b)))
)

assert authz_policy.principals_allowed_by_permission(1, 2) == (1, 2)
27 changes: 23 additions & 4 deletions tests/unit/accounts/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
from warehouse import accounts
from warehouse.accounts.interfaces import (
IUserService,
IAccountTokenService,
ITokenService,
IPasswordBreachedService,
)
from warehouse.accounts.services import (
TokenServiceFactory,
HaveIBeenPwnedPasswordBreachedService,
database_login_factory,
database_account_token_factory,
)
from warehouse.accounts.models import DisableReason
from warehouse.errors import BasicAuthBreachedPassword
Expand Down Expand Up @@ -253,18 +255,26 @@ def test_without_userid(self):


def test_includeme(monkeypatch):
account_token_obj = pretend.stub()
account_token_cls = pretend.call_recorder(
lambda authenticate, routes_allowed: account_token_obj
)
basic_authn_obj = pretend.stub()
basic_authn_cls = pretend.call_recorder(lambda check: basic_authn_obj)
session_authn_obj = pretend.stub()
session_authn_cls = pretend.call_recorder(lambda callback: session_authn_obj)
authn_obj = pretend.stub()
authn_cls = pretend.call_recorder(lambda *a: authn_obj)
authz_obj = pretend.stub()
authz_cls = pretend.call_recorder(lambda: authz_obj)
authz_cls = pretend.call_recorder(lambda policy: authz_obj)
acl_authz_obj = pretend.stub()
acl_authz_cls = pretend.call_recorder(lambda *args: acl_authz_obj)
monkeypatch.setattr(accounts, "AccountTokenAuthenticationPolicy", account_token_cls)
monkeypatch.setattr(accounts, "BasicAuthAuthenticationPolicy", basic_authn_cls)
monkeypatch.setattr(accounts, "SessionAuthenticationPolicy", session_authn_cls)
monkeypatch.setattr(accounts, "MultiAuthenticationPolicy", authn_cls)
monkeypatch.setattr(accounts, "ACLAuthorizationPolicy", authz_cls)
monkeypatch.setattr(accounts, "AccountTokenAuthorizationPolicy", authz_cls)
monkeypatch.setattr(accounts, "ACLAuthorizationPolicy", acl_authz_cls)

config = pretend.stub(
registry=pretend.stub(settings={}),
Expand All @@ -281,6 +291,7 @@ def test_includeme(monkeypatch):

assert config.register_service_factory.calls == [
pretend.call(database_login_factory, IUserService),
pretend.call(database_account_token_factory, IAccountTokenService),
pretend.call(
TokenServiceFactory(name="password"), ITokenService, name="password"
),
Expand All @@ -299,7 +310,15 @@ def test_includeme(monkeypatch):
]
assert config.set_authentication_policy.calls == [pretend.call(authn_obj)]
assert config.set_authorization_policy.calls == [pretend.call(authz_obj)]
assert account_token_cls.calls == [
pretend.call(
authenticate=accounts._authenticate,
routes_allowed=["forklift.legacy.file_upload"],
)
]
assert basic_authn_cls.calls == [pretend.call(check=accounts._basic_auth_login)]
assert session_authn_cls.calls == [pretend.call(callback=accounts._authenticate)]
assert authn_cls.calls == [pretend.call([session_authn_obj, basic_authn_obj])]
assert authz_cls.calls == [pretend.call()]
assert authn_cls.calls == [
pretend.call([account_token_obj, session_authn_obj, basic_authn_obj])
]
assert authz_cls.calls == [pretend.call(policy=acl_authz_obj)]
Loading