xref: /aosp_15_r20/external/tink/python/tink/jwt/_jwt_validator_test.py (revision e7b1675dde1b92d52ec075b0a92829627f2c52a5)
1# Copyright 2021 Google LLC
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13"""Tests for tink.python.tink.jwt._jwt_validator."""
14
15import datetime
16
17from absl.testing import absltest
18from tink import jwt
19from tink.jwt import _jwt_validator
20
21
22class JwtValidatorTest(absltest.TestCase):
23
24  def test_validator_getters(self):
25    fixed_now = datetime.datetime.fromtimestamp(12345, datetime.timezone.utc)
26    clock_skew = datetime.timedelta(minutes=1)
27    validator = jwt.new_validator(
28        expected_type_header='type_header',
29        expected_issuer='issuer',
30        expected_audience='audience',
31        fixed_now=fixed_now,
32        clock_skew=clock_skew)
33    self.assertTrue(validator.has_expected_type_header())
34    self.assertEqual(validator.expected_type_header(), 'type_header')
35    self.assertTrue(validator.has_expected_issuer())
36    self.assertEqual(validator.expected_issuer(), 'issuer')
37    self.assertTrue(validator.has_expected_audience())
38    self.assertEqual(validator.expected_audience(), 'audience')
39    self.assertFalse(validator.allow_missing_expiration())
40    self.assertFalse(validator.ignore_issuer())
41    self.assertFalse(validator.ignore_audiences())
42    self.assertTrue(validator.has_fixed_now())
43    self.assertEqual(validator.fixed_now(), fixed_now)
44    self.assertEqual(validator.clock_skew(), clock_skew)
45
46  def test_validator_ignore_getters(self):
47    validator = jwt.new_validator(
48        allow_missing_expiration=True,
49        ignore_type_header=True,
50        ignore_issuer=True,
51        ignore_audiences=True)
52    self.assertTrue(validator.allow_missing_expiration())
53    self.assertTrue(validator.ignore_type_header())
54    self.assertTrue(validator.ignore_issuer())
55    self.assertTrue(validator.ignore_audiences())
56
57  def test_empty_validator_getters(self):
58    validator = jwt.new_validator()
59    self.assertFalse(validator.has_expected_type_header())
60    self.assertFalse(validator.has_expected_issuer())
61    self.assertFalse(validator.has_expected_audience())
62    self.assertFalse(validator.has_fixed_now())
63    self.assertFalse(validator.clock_skew(), datetime.timedelta())
64
65  def test_too_much_clock_skew(self):
66    with self.assertRaises(ValueError):
67      jwt.new_validator(clock_skew=datetime.timedelta(minutes=20))
68
69  def test_validate_expired_fails(self):
70    expired = (datetime.datetime.now(tz=datetime.timezone.utc)
71               - datetime.timedelta(minutes=1))
72    token = jwt.new_raw_jwt(expiration=expired)
73    validator = jwt.new_validator()
74    with self.assertRaises(jwt.JwtInvalidError):
75      _jwt_validator.validate(validator, token)
76
77  def test_validate_not_expired_success(self):
78    still_valid = (datetime.datetime.now(tz=datetime.timezone.utc)
79                   + datetime.timedelta(minutes=1))
80    token = jwt.new_raw_jwt(expiration=still_valid)
81    validator = jwt.new_validator()
82    _jwt_validator.validate(validator, token)
83
84  def test_validate_token_that_expires_now_fails(self):
85    now = datetime.datetime.fromtimestamp(1234.0, tz=datetime.timezone.utc)
86    token = jwt.new_raw_jwt(expiration=now)
87    validator = jwt.new_validator()
88    with self.assertRaises(jwt.JwtInvalidError):
89      _jwt_validator.validate(validator, token)
90
91  def test_validate_recently_expired_with_clock_skew_success(self):
92    recently_expired = (datetime.datetime.now(tz=datetime.timezone.utc)
93                        - datetime.timedelta(minutes=1))
94    token = jwt.new_raw_jwt(expiration=recently_expired)
95    validator = jwt.new_validator(clock_skew=datetime.timedelta(minutes=2))
96    # because of clock_skew, the recently expired token is valid
97    _jwt_validator.validate(validator, token)
98
99  def test_validate_not_before_in_the_future_fails(self):
100    in_the_future = (datetime.datetime.now(tz=datetime.timezone.utc)
101                     + datetime.timedelta(minutes=1))
102    token = jwt.new_raw_jwt(not_before=in_the_future, without_expiration=True)
103    validator = jwt.new_validator(allow_missing_expiration=True)
104    with self.assertRaises(jwt.JwtInvalidError):
105      _jwt_validator.validate(validator, token)
106
107  def test_validate_not_before_in_the_past_success(self):
108    in_the_past = (datetime.datetime.now(tz=datetime.timezone.utc)
109                   - datetime.timedelta(minutes=1))
110    token = jwt.new_raw_jwt(not_before=in_the_past, without_expiration=True)
111    validator = jwt.new_validator(allow_missing_expiration=True)
112    _jwt_validator.validate(validator, token)
113
114  def test_validate_not_before_is_now_success(self):
115    now = datetime.datetime.fromtimestamp(12345, datetime.timezone.utc)
116    token = jwt.new_raw_jwt(not_before=now, without_expiration=True)
117    validator = jwt.new_validator(allow_missing_expiration=True)
118    _jwt_validator.validate(validator, token)
119
120  def test_validate_not_before_almost_reached_with_clock_skew_success(self):
121    in_one_minute = (datetime.datetime.now(tz=datetime.timezone.utc)
122                     + datetime.timedelta(minutes=1))
123    token = jwt.new_raw_jwt(not_before=in_one_minute, without_expiration=True)
124    validator = jwt.new_validator(
125        allow_missing_expiration=True, clock_skew=datetime.timedelta(minutes=2))
126    _jwt_validator.validate(validator, token)
127
128  def test_validate_issued_at(self):
129    in_one_minute = (datetime.datetime.now(tz=datetime.timezone.utc)
130                     + datetime.timedelta(minutes=1))
131    one_minute_ago = (datetime.datetime.now(tz=datetime.timezone.utc)
132                      - datetime.timedelta(minutes=1))
133    token_with_issued_at_in_the_future = jwt.new_raw_jwt(
134        issued_at=in_one_minute, without_expiration=True)
135    token_with_issued_at_in_the_past = jwt.new_raw_jwt(
136        issued_at=one_minute_ago, without_expiration=True)
137    token_without_issued_at = jwt.new_raw_jwt(without_expiration=True)
138
139    validator = jwt.new_validator(allow_missing_expiration=True)
140    _jwt_validator.validate(validator, token_with_issued_at_in_the_future)
141    _jwt_validator.validate(validator, token_with_issued_at_in_the_past)
142    _jwt_validator.validate(validator, token_without_issued_at)
143
144    issued_at_validator = jwt.new_validator(
145        expect_issued_in_the_past=True, allow_missing_expiration=True)
146    with self.assertRaises(jwt.JwtInvalidError):
147      _jwt_validator.validate(issued_at_validator,
148                              token_with_issued_at_in_the_future)
149    _jwt_validator.validate(issued_at_validator,
150                            token_with_issued_at_in_the_past)
151    with self.assertRaises(jwt.JwtInvalidError):
152      _jwt_validator.validate(issued_at_validator, token_without_issued_at)
153
154  def test_validate_issued_at_with_clock_skew(self):
155    in_one_minute = (datetime.datetime.now(tz=datetime.timezone.utc)
156                     + datetime.timedelta(minutes=1))
157    token_one_minute_in_the_future = jwt.new_raw_jwt(
158        issued_at=in_one_minute, without_expiration=True)
159
160    validator_without_clock_skew = jwt.new_validator(
161        expect_issued_in_the_past=True, allow_missing_expiration=True)
162    with self.assertRaises(jwt.JwtInvalidError):
163      _jwt_validator.validate(validator_without_clock_skew,
164                              token_one_minute_in_the_future)
165
166    validator_with_clock_skew = jwt.new_validator(
167        expect_issued_in_the_past=True,
168        allow_missing_expiration=True,
169        clock_skew=datetime.timedelta(minutes=2))
170    _jwt_validator.validate(validator_with_clock_skew,
171                            token_one_minute_in_the_future)
172
173  def test_requires_type_header_but_no_type_header_set_fails(self):
174    token = jwt.new_raw_jwt(without_expiration=True)
175    validator = jwt.new_validator(
176        expected_type_header='type_header', allow_missing_expiration=True)
177    with self.assertRaises(jwt.JwtInvalidError):
178      _jwt_validator.validate(validator, token)
179
180  def test_invalid_type_header_fails(self):
181    token = jwt.new_raw_jwt(type_header='unknown', without_expiration=True)
182    validator = jwt.new_validator(
183        expected_type_header='type_header', allow_missing_expiration=True)
184    with self.assertRaises(jwt.JwtInvalidError):
185      _jwt_validator.validate(validator, token)
186
187  def test_correct_type_header_success(self):
188    token = jwt.new_raw_jwt(type_header='type_header', without_expiration=True)
189    validator = jwt.new_validator(
190        expected_type_header='type_header', allow_missing_expiration=True)
191    _jwt_validator.validate(validator, token)
192
193  def test_type_header_in_token_but_not_in_validator_fails(self):
194    validator = jwt.new_validator(allow_missing_expiration=True)
195    token_with_type_header = jwt.new_raw_jwt(
196        type_header='type_header', without_expiration=True)
197    with self.assertRaises(jwt.JwtInvalidError):
198      _jwt_validator.validate(validator, token_with_type_header)
199
200  def test_ignore_type_header_success(self):
201    validator = jwt.new_validator(
202        ignore_type_header=True, allow_missing_expiration=True)
203    token_without_type_header = jwt.new_raw_jwt(without_expiration=True)
204    _jwt_validator.validate(validator, token_without_type_header)
205    token_with_type_header = jwt.new_raw_jwt(
206        type_header='type_header', without_expiration=True)
207    _jwt_validator.validate(validator, token_with_type_header)
208
209  def test_requires_issuer_but_no_issuer_set_fails(self):
210    token = jwt.new_raw_jwt(without_expiration=True)
211    validator = jwt.new_validator(
212        expected_issuer='issuer', allow_missing_expiration=True)
213    with self.assertRaises(jwt.JwtInvalidError):
214      _jwt_validator.validate(validator, token)
215
216  def test_invalid_issuer_fails(self):
217    token = jwt.new_raw_jwt(issuer='unknown', without_expiration=True)
218    validator = jwt.new_validator(
219        expected_issuer='issuer', allow_missing_expiration=True)
220    with self.assertRaises(jwt.JwtInvalidError):
221      _jwt_validator.validate(validator, token)
222
223  def test_correct_issuer_success(self):
224    token = jwt.new_raw_jwt(issuer='issuer', without_expiration=True)
225    validator = jwt.new_validator(
226        expected_issuer='issuer', allow_missing_expiration=True)
227    _jwt_validator.validate(validator, token)
228
229  def test_issuer_in_token_but_not_in_validator_fails(self):
230    validator = jwt.new_validator(allow_missing_expiration=True)
231    token_with_issuer = jwt.new_raw_jwt(
232        issuer='issuer', without_expiration=True)
233    with self.assertRaises(jwt.JwtInvalidError):
234      _jwt_validator.validate(validator, token_with_issuer)
235
236  def test_ignore_issuer_success(self):
237    validator = jwt.new_validator(
238        ignore_issuer=True, allow_missing_expiration=True)
239    token_without_issuer = jwt.new_raw_jwt(without_expiration=True)
240    _jwt_validator.validate(validator, token_without_issuer)
241    token_with_issuer = jwt.new_raw_jwt(
242        issuer='issuer', without_expiration=True)
243    _jwt_validator.validate(validator, token_with_issuer)
244
245  def test_requires_audience_but_no_audience_set_fails(self):
246    token = jwt.new_raw_jwt(without_expiration=True)
247    validator = jwt.new_validator(
248        expected_audience='audience', allow_missing_expiration=True)
249    with self.assertRaises(jwt.JwtInvalidError):
250      _jwt_validator.validate(validator, token)
251
252  def test_wrong_audience_fails(self):
253    token = jwt.new_raw_jwt(audiences=['unknown'], without_expiration=True)
254    validator = jwt.new_validator(
255        expected_audience='audience', allow_missing_expiration=True)
256    with self.assertRaises(jwt.JwtInvalidError):
257      _jwt_validator.validate(validator, token)
258
259  def test_correct_audience_success(self):
260    token = jwt.new_raw_jwt(audiences=['you', 'me'], without_expiration=True)
261    validator = jwt.new_validator(
262        expected_audience='me', allow_missing_expiration=True)
263    _jwt_validator.validate(validator, token)
264
265  def test_audience_in_token_but_not_in_validator_fails(self):
266    validator = jwt.new_validator(allow_missing_expiration=True)
267    token_with_audience = jwt.new_raw_jwt(
268        audiences=['audience'], without_expiration=True)
269    with self.assertRaises(jwt.JwtInvalidError):
270      _jwt_validator.validate(validator, token_with_audience)
271
272  def test_no_audience_success(self):
273    validator = jwt.new_validator(allow_missing_expiration=True)
274    token = jwt.new_raw_jwt(without_expiration=True)
275    _jwt_validator.validate(validator, token)
276
277  def test_ignore_audiences_success(self):
278    validator = jwt.new_validator(
279        ignore_audiences=True, allow_missing_expiration=True)
280    token_without_audience = jwt.new_raw_jwt(without_expiration=True)
281    _jwt_validator.validate(validator, token_without_audience)
282    token_with_audience = jwt.new_raw_jwt(
283        audiences=['audience'], without_expiration=True)
284    _jwt_validator.validate(validator, token_with_audience)
285
286  def test_validate_with_fixed_now_expired_fails(self):
287    in_two_minutes = (
288        datetime.datetime.now(tz=datetime.timezone.utc) +
289        datetime.timedelta(minutes=2))
290    in_one_minute = in_two_minutes - datetime.timedelta(minutes=1)
291    token = jwt.new_raw_jwt(expiration=in_one_minute)
292    validator = jwt.new_validator(fixed_now=in_two_minutes)
293    with self.assertRaises(jwt.JwtInvalidError):
294      _jwt_validator.validate(validator, token)
295
296  def test_validate_with_fixed_now_not_yet_valid_fails(self):
297    two_minutes_ago = (
298        datetime.datetime.now(tz=datetime.timezone.utc) -
299        datetime.timedelta(minutes=2))
300    one_minute_ago = two_minutes_ago + datetime.timedelta(minutes=1)
301    token = jwt.new_raw_jwt(not_before=one_minute_ago, without_expiration=True)
302    validator = jwt.new_validator(fixed_now=two_minutes_ago)
303    with self.assertRaises(jwt.JwtInvalidError):
304      _jwt_validator.validate(validator, token)
305
306  def test_validate_with_fixed_now_valid_success(self):
307    fixed_now = datetime.datetime.fromtimestamp(12345, datetime.timezone.utc)
308    validator = jwt.new_validator(fixed_now=fixed_now)
309    expiration = fixed_now + datetime.timedelta(minutes=1)
310    not_before = fixed_now - datetime.timedelta(minutes=1)
311    token = jwt.new_raw_jwt(expiration=expiration, not_before=not_before)
312    _jwt_validator.validate(validator, token)
313
314  def test_invalid_clock_skew_fail(self):
315    with self.assertRaises(ValueError):
316      jwt.new_validator(clock_skew=datetime.timedelta(minutes=1000))
317
318  def test_fixed_now_without_timezone_fail(self):
319    with self.assertRaises(ValueError):
320      jwt.new_validator(fixed_now=datetime.datetime.fromtimestamp(12345))
321
322
323if __name__ == '__main__':
324  absltest.main()
325