Skip to content

Commit a115391

Browse files
committed
Feedback from code review
1 parent 7fe3604 commit a115391

File tree

15 files changed

+549
-263
lines changed

15 files changed

+549
-263
lines changed

dev/environment

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,3 @@ TOKEN_PASSWORD_SECRET="an insecure password reset secret key"
3737
TOKEN_EMAIL_SECRET="an insecure email verification secret key"
3838

3939
WAREHOUSE_LEGACY_DOMAIN=pypi.python.org
40-
41-
ACCOUNT_TOKEN_SECRET="an insecure token api secret"
42-
ACCOUNT_TOKEN_PUBLIC_ID="a public token api id"

tests/common/db/accounts.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class Meta:
5353
model = AccountToken
5454

5555
id = factory.Sequence(lambda n: uuid.uuid4())
56+
secret = factory.fuzzy.FuzzyText(length=100)
5657
username = factory.fuzzy.FuzzyText()
5758
description = factory.fuzzy.FuzzyText(length=100)
5859
is_active = True

tests/unit/accounts/test_auth_policy.py

Lines changed: 166 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414
import uuid
1515

1616
from pymacaroons import Macaroon
17-
from pyramid import authentication
18-
from pyramid.interfaces import IAuthenticationPolicy
17+
from pyramid import authentication, security
18+
from pyramid.interfaces import IAuthenticationPolicy, IAuthorizationPolicy
1919
from zope.interface.verify import verifyClass
2020

2121
from warehouse.accounts import auth_policy
22-
from warehouse.accounts.interfaces import IUserService
22+
from warehouse.accounts.interfaces import IUserService, IAccountTokenService
2323

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

@@ -112,174 +112,223 @@ def test_unauthenticated_userid(self, monkeypatch):
112112

113113
class TestAccountTokenAuthenticationPolicy:
114114
def test_verify(self):
115-
assert isinstance(
116-
auth_policy.AccountTokenAuthenticationPolicy(pretend.stub()),
117-
authentication.CallbackAuthenticationPolicy,
115+
assert verifyClass(
116+
IAuthenticationPolicy, auth_policy.AccountTokenAuthenticationPolicy
118117
)
119118

120-
def test_account_token_routes_allowed(self):
121-
request = pretend.stub(matched_route=pretend.stub(name="not_a_real_route"))
119+
def test_routes_not_allowed(self):
120+
request = pretend.stub(matched_route=pretend.stub(name="not_allowed_route"))
122121

123-
policy = auth_policy.AccountTokenAuthenticationPolicy(pretend.stub())
124-
assert policy.unauthenticated_userid(request) is None
122+
authn_policy = auth_policy.AccountTokenAuthenticationPolicy(
123+
pretend.stub(), ["allowed_route"]
124+
)
125+
126+
assert authn_policy.unauthenticated_userid(request) is None
125127

126-
def test_account_token_required_parameter(self):
128+
def test_require_known(self):
129+
# Ensure we don't accept just any macaroon
127130
request = pretend.stub(
128-
matched_route=pretend.stub(name="forklift.legacy.file_upload"), params={}
131+
find_service=lambda iface, **kw: {
132+
IAccountTokenService: pretend.stub(
133+
get_unverified_macaroon=(lambda: (None, None))
134+
)
135+
}[iface],
136+
matched_route=pretend.stub(name="allowed_route"),
137+
add_response_callback=lambda x: None,
129138
)
130139

131-
policy = auth_policy.AccountTokenAuthenticationPolicy(pretend.stub())
132-
assert policy.unauthenticated_userid(request) is None
140+
authn_policy = auth_policy.AccountTokenAuthenticationPolicy(
141+
pretend.stub(), ["allowed_route"]
142+
)
143+
144+
assert authn_policy.unauthenticated_userid(request) is None
145+
146+
def test_macaroon_verifier(self, db_request):
147+
user = UserFactory.create(username="test_user")
148+
149+
account_token = AccountTokenFactory.create(
150+
secret="some_secret", username=user.username
151+
)
152+
153+
macaroon = Macaroon(
154+
location="pypi.org", identifier="some_id", key="wrong_secret"
155+
)
133156

134-
def test_account_token_malformed(self):
135157
request = pretend.stub(
136-
matched_route=pretend.stub(name="forklift.legacy.file_upload"),
137-
params={"account_token": "DEADBEEF"},
158+
find_service=lambda iface, **kw: {
159+
IAccountTokenService: pretend.stub(
160+
get_unverified_macaroon=(lambda: (macaroon, account_token))
161+
)
162+
}[iface],
163+
matched_route=pretend.stub(name="allowed_route"),
164+
add_response_callback=lambda x: None,
138165
)
139166

140-
policy = auth_policy.AccountTokenAuthenticationPolicy(pretend.stub())
141-
assert policy.unauthenticated_userid(request) is None
167+
authn_policy = auth_policy.AccountTokenAuthenticationPolicy(
168+
pretend.stub(), ["allowed_route"]
169+
)
170+
171+
assert authn_policy.unauthenticated_userid(request) is None
172+
173+
def test_account_token_auth(self, db_request):
174+
# Test basic happy path
175+
user = UserFactory.create(username="test_user")
176+
account_token = AccountTokenFactory.create(
177+
secret="some_secret", username=user.username
178+
)
142179

143-
def test_account_token_bad_settings(self):
144-
# Test bad location
145180
macaroon = Macaroon(
146-
location="notpypi.org", identifier="example_id", key="example_secret"
181+
location="pypi.org", identifier=str(account_token.id), key="some_secret"
147182
)
148183

149184
request = pretend.stub(
150-
matched_route=pretend.stub(name="forklift.legacy.file_upload"),
151-
params={"account_token": macaroon.serialize()},
152-
registry=pretend.stub(
153-
settings={
154-
"account_token.id": "example_id",
155-
"account_token.secret": "example_secret",
156-
}
157-
),
185+
find_service=lambda iface, **kw: {
186+
IUserService: pretend.stub(
187+
find_userid=(lambda x: user.id if x == "test_user" else None)
188+
),
189+
IAccountTokenService: pretend.stub(
190+
get_unverified_macaroon=(lambda: (macaroon, account_token)),
191+
update_last_used=(lambda x: None),
192+
),
193+
}[iface],
194+
matched_route=pretend.stub(name="allowed_route"),
195+
add_response_callback=lambda x: None,
196+
session={},
158197
)
159198

160-
policy = auth_policy.AccountTokenAuthenticationPolicy(pretend.stub())
161-
assert policy.unauthenticated_userid(request) is None
199+
authn_policy = auth_policy.AccountTokenAuthenticationPolicy(
200+
pretend.stub(), ["allowed_route"]
201+
)
162202

163-
# Test bad identifier
164-
macaroon = Macaroon(
165-
location="pypi.org", identifier="bad_id", key="example_secret"
203+
assert authn_policy.unauthenticated_userid(request) == user.id
204+
205+
# Make sure we allow first-party and third-party caveats
206+
macaroon.add_first_party_caveat("first party caveat")
207+
208+
macaroon.add_third_party_caveat(
209+
location="mysite.com", key="anykey", key_id="anykeyid"
166210
)
167211

168-
request.params["account_token"] = macaroon.serialize()
169-
policy = auth_policy.AccountTokenAuthenticationPolicy(pretend.stub())
170-
assert policy.unauthenticated_userid(request) is None
212+
assert authn_policy.unauthenticated_userid(request) == user.id
171213

172-
# Tamper with macaroon
173-
macaroon = Macaroon(
174-
location="pypi.org", identifier="example_id", key="example_secret"
214+
def test_account_token_interface(self):
215+
def _authenticate(a, b):
216+
return a, b
217+
218+
policy = auth_policy.AccountTokenAuthenticationPolicy(_authenticate, ["route"])
219+
220+
assert policy.remember("", "") == []
221+
assert policy.forget("") == []
222+
assert policy._auth_callback(1, 2) == (1, 2)
223+
assert policy._routes_allowed == ["route"]
224+
225+
226+
class TestAccountTokenAuthorizationPolicy:
227+
def test_verify(self):
228+
assert verifyClass(
229+
IAuthorizationPolicy, auth_policy.AccountTokenAuthorizationPolicy
175230
)
176231

177-
serialized = macaroon.serialize()
232+
def test_have_request(self, monkeypatch):
233+
monkeypatch.setattr(auth_policy, "get_current_request", lambda: None)
234+
authz_policy = auth_policy.AccountTokenAuthorizationPolicy(pretend.stub())
178235

179-
request.params["account_token"] = "".join(
180-
(serialized[:-8], "AAAAAAA", serialized[-1:])
236+
assert isinstance(
237+
authz_policy.permits(pretend.stub(), pretend.stub(), pretend.stub()),
238+
security.Denied,
239+
)
240+
241+
def test_macaroon_verifier(self, db_request, monkeypatch):
242+
user = UserFactory.create(username="test_user")
243+
244+
account_token = AccountTokenFactory.create(
245+
secret="some_secret", username=user.username
181246
)
182-
assert policy.unauthenticated_userid(request) is None
183247

184-
def test_account_token_with_no_user(self, db_request):
185248
macaroon = Macaroon(
186-
location="pypi.org", identifier="example_id", key="example_secret"
249+
location="pypi.org", identifier="some_id", key="wrong_secret"
187250
)
188251

252+
monkeypatch.setattr(auth_policy, "get_current_request", lambda: request)
189253
request = pretend.stub(
190254
find_service=lambda iface, **kw: {
191-
IUserService: pretend.stub(find_userid_by_account_token=pretend.stub())
192-
}[iface],
193-
params={"account_token": macaroon.serialize()},
194-
registry=pretend.stub(
195-
settings={
196-
"account_token.id": "example_id",
197-
"account_token.secret": "example_secret",
198-
}
199-
),
200-
matched_route=pretend.stub(name="forklift.legacy.file_upload"),
201-
db=db_request,
202-
session={},
255+
IAccountTokenService: pretend.stub(
256+
get_unverified_macaroon=(lambda: (macaroon, account_token))
257+
)
258+
}[iface]
203259
)
204260

205-
policy = auth_policy.AccountTokenAuthenticationPolicy(pretend.stub())
206-
assert policy.unauthenticated_userid(request) is None
261+
authz_policy = auth_policy.AccountTokenAuthorizationPolicy(pretend.stub())
207262

208-
def test_account_token_auth(self, db_request):
209-
# Test basic happy path
263+
assert isinstance(
264+
authz_policy.permits(pretend.stub(), pretend.stub(), pretend.stub()),
265+
security.Denied,
266+
)
267+
268+
def test_account_token_authz(self, db_request, monkeypatch):
210269
user = UserFactory.create(username="test_user")
211-
account_token = AccountTokenFactory.create(username=user.username)
212-
account_token_id = str(account_token.id)
213270

214-
macaroon = Macaroon(
215-
location="pypi.org", identifier="example_id", key="example_secret"
271+
account_token = AccountTokenFactory.create(
272+
secret="some_secret", username=user.username
216273
)
217274

218-
macaroon.add_first_party_caveat(f"id: {account_token_id}")
275+
macaroon = Macaroon(
276+
location="pypi.org", identifier="some_id", key="some_secret"
277+
)
219278

279+
monkeypatch.setattr(auth_policy, "get_current_request", lambda: request)
220280
request = pretend.stub(
221281
find_service=lambda iface, **kw: {
222-
IUserService: pretend.stub(
223-
find_userid_by_account_token=(
224-
lambda x: user.id if x == account_token_id else None
225-
)
282+
IAccountTokenService: pretend.stub(
283+
get_unverified_macaroon=(lambda: (macaroon, account_token))
226284
)
227-
}[iface],
228-
params={"account_token": macaroon.serialize()},
229-
registry=pretend.stub(
230-
settings={
231-
"account_token.id": "example_id",
232-
"account_token.secret": "example_secret",
233-
}
234-
),
235-
matched_route=pretend.stub(name="forklift.legacy.file_upload"),
236-
db=db_request,
237-
session={},
285+
}[iface]
238286
)
239287

240-
policy = auth_policy.AccountTokenAuthenticationPolicy(pretend.stub())
241-
assert policy.unauthenticated_userid(request) == user.id
288+
authz_policy = auth_policy.AccountTokenAuthorizationPolicy(
289+
pretend.stub(permits=(lambda *args, **kwargs: "allow"))
290+
)
242291

243-
# Test package_list caveats
244-
macaroon.add_first_party_caveat("package_list: pyexample1 pyexample2")
245-
macaroon.add_third_party_caveat(
246-
location="mysite.com", key="anykey", key_id="anykeyid"
292+
assert (
293+
authz_policy.permits(pretend.stub(), pretend.stub(), pretend.stub())
294+
== "allow"
247295
)
248-
request.params["account_token"] = macaroon.serialize()
249-
request.session["account_token_package_list"] = None
250296

251-
assert policy.unauthenticated_userid(request) == user.id
252-
assert request.session["account_token_package_list"] == "pyexample1 pyexample2"
297+
# Make sure we allow first-party and third-party caveats
298+
macaroon.add_first_party_caveat("first party caveat")
253299

254-
# Ensure you can't overwrite previous caveats
255-
takeover_user = UserFactory.create(username="takeover_user")
256-
takeover_account_token = AccountTokenFactory.create(
257-
username=takeover_user.username
300+
macaroon.add_third_party_caveat(
301+
location="mysite.com", key="anykey", key_id="anykeyid"
258302
)
259-
takeover_account_token_id = str(takeover_account_token.id)
260-
261-
macaroon.add_first_party_caveat(f"id: {takeover_account_token_id}")
262-
macaroon.add_first_party_caveat("package_list: additionalpackage")
263303

264-
request.params["account_token"] = macaroon.serialize()
265-
request.session["account_token_package_list"] = None
304+
assert (
305+
authz_policy.permits(pretend.stub(), pretend.stub(), pretend.stub())
306+
== "allow"
307+
)
266308

267-
assert policy.unauthenticated_userid(request) == user.id
268-
assert request.session["account_token_package_list"] == "pyexample1 pyexample2"
309+
def test_missing_macaroon(self, monkeypatch):
310+
monkeypatch.setattr(auth_policy, "get_current_request", lambda: request)
269311

270-
def test_first_party_caveat_validation(self):
271-
policy = auth_policy.AccountTokenAuthenticationPolicy(pretend.stub())
312+
request = pretend.stub(
313+
find_service=lambda iface, **kw: {
314+
IAccountTokenService: pretend.stub(
315+
get_unverified_macaroon=(lambda: (None, None))
316+
)
317+
}[iface]
318+
)
272319

273-
assert policy._validate_first_party_caveat("id")
274-
assert policy._validate_first_party_caveat("package_list")
275-
assert not policy._validate_first_party_caveat("not_valid")
320+
authz_policy = auth_policy.AccountTokenAuthorizationPolicy(
321+
pretend.stub(permits=(lambda *args, **kwargs: "allow"))
322+
)
276323

277-
def test_account_token_interface(self):
278-
def _authenticate(a, b):
279-
return a, b
324+
assert (
325+
authz_policy.permits(pretend.stub(), pretend.stub(), pretend.stub())
326+
== "allow"
327+
)
280328

281-
policy = auth_policy.AccountTokenAuthenticationPolicy(_authenticate)
329+
def test_principals_allowed_by_permission(self):
330+
authz_policy = auth_policy.AccountTokenAuthorizationPolicy(
331+
pretend.stub(principals_allowed_by_permission=(lambda a, b: (a, b)))
332+
)
282333

283-
assert policy.remember("", "") == []
284-
assert policy.forget("") == []
285-
assert policy._auth_callback(1, 2) == (1, 2)
334+
assert authz_policy.principals_allowed_by_permission(1, 2) == (1, 2)

0 commit comments

Comments
 (0)