Skip to content

Commit e0d9948

Browse files
committed
Added bearer doctest
1 parent 350595f commit e0d9948

1 file changed

Lines changed: 127 additions & 0 deletions

File tree

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
BEARER TOKEN AUTHENTICATION
2+
---------------------------
3+
4+
Running this test from the buildout directory:
5+
6+
bin/test test_doctests -t bearer
7+
8+
9+
Test Setup
10+
~~~~~~~~~~
11+
12+
Needed Imports:
13+
14+
>>> import json
15+
>>> import transaction
16+
>>> from plone.app.testing import setRoles
17+
>>> from plone.app.testing import TEST_USER_ID
18+
>>> from plone.testing.zope import Browser
19+
>>> from senaite.jsonapi.pas.plugin import create_token
20+
>>> from senaite.jsonapi.pas.plugin import rotate_secret
21+
>>> from senaite.jsonapi.pas.plugin import timestamp
22+
23+
24+
Functional Helpers:
25+
26+
>>> def fresh_browser():
27+
... b = Browser(self.portal)
28+
... b.addHeader("Accept-Language", "en-US")
29+
... b.handleErrors = False
30+
... return b
31+
32+
>>> def with_bearer(b, token):
33+
... b.addHeader("Authorization", "Bearer {}".format(token))
34+
... return b
35+
36+
>>> def is_authenticated(b):
37+
... b.open("{}/users/current".format(api_url))
38+
... return json.loads(b.contents)["items"][0]["authenticated"]
39+
40+
41+
Variables:
42+
43+
>>> portal = self.portal
44+
>>> portal_url = portal.absolute_url()
45+
>>> api_url = "{}/@@API/senaite/v1".format(portal_url)
46+
>>> setRoles(portal, TEST_USER_ID, ["LabManager", "Manager"])
47+
>>> transaction.commit()
48+
49+
50+
No token, no authentication
51+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
52+
53+
A fresh browser without any credentials is anonymous:
54+
55+
>>> is_authenticated(fresh_browser())
56+
False
57+
58+
59+
A valid Bearer token authenticates the request
60+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
61+
62+
Issue a token for the test user (this is what /login does internally
63+
once the user is authenticated):
64+
65+
>>> token = create_token(TEST_USER_ID)
66+
>>> transaction.commit()
67+
>>> isinstance(token, basestring) and len(token) > 0
68+
True
69+
70+
A fresh browser carrying that token in the ``Authorization: Bearer``
71+
header is authenticated by the JWT PAS plugin:
72+
73+
>>> is_authenticated(with_bearer(fresh_browser(), token))
74+
True
75+
76+
77+
An invalid Bearer token does not authenticate
78+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
79+
80+
A garbage value cannot be decoded, so the request stays anonymous:
81+
82+
>>> is_authenticated(with_bearer(fresh_browser(), "not-a-real-token"))
83+
False
84+
85+
86+
A token signed with the wrong secret does not authenticate
87+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
88+
89+
A well-formed JWT signed with a different secret fails signature
90+
verification:
91+
92+
>>> import jwt as pyjwt
93+
>>> forged = pyjwt.encode(
94+
... {"userid": TEST_USER_ID, "exp": timestamp(seconds=3600)},
95+
... "wrong-secret", algorithm="HS256")
96+
>>> is_authenticated(with_bearer(fresh_browser(), forged))
97+
False
98+
99+
100+
An expired token does not authenticate
101+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
102+
103+
A token whose ``exp`` claim lies in the past is rejected by PyJWT:
104+
105+
>>> expired_token = create_token(TEST_USER_ID, exp=timestamp(seconds=-60))
106+
>>> transaction.commit()
107+
>>> is_authenticated(with_bearer(fresh_browser(), expired_token))
108+
False
109+
110+
111+
Rotating the user's secret revokes existing tokens
112+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
113+
114+
After ``rotate_secret``, the previously-issued token no longer
115+
verifies:
116+
117+
>>> rotate_secret(TEST_USER_ID)
118+
>>> transaction.commit()
119+
>>> is_authenticated(with_bearer(fresh_browser(), token))
120+
False
121+
122+
A token issued after the rotation is accepted again:
123+
124+
>>> new_token = create_token(TEST_USER_ID)
125+
>>> transaction.commit()
126+
>>> is_authenticated(with_bearer(fresh_browser(), new_token))
127+
True

0 commit comments

Comments
 (0)