| # Copyright 2021 Google LLC |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| """Tests for tink.python.tink.jwt._jwt_validator.""" |
| |
| import datetime |
| |
| from absl.testing import absltest |
| from tink import jwt |
| from tink.jwt import _jwt_validator |
| |
| |
| class JwtValidatorTest(absltest.TestCase): |
| |
| def test_validator_getters(self): |
| fixed_now = datetime.datetime.fromtimestamp(12345, datetime.timezone.utc) |
| clock_skew = datetime.timedelta(minutes=1) |
| validator = jwt.new_validator( |
| expected_type_header='type_header', |
| expected_issuer='issuer', |
| expected_audience='audience', |
| fixed_now=fixed_now, |
| clock_skew=clock_skew) |
| self.assertTrue(validator.has_expected_type_header()) |
| self.assertEqual(validator.expected_type_header(), 'type_header') |
| self.assertTrue(validator.has_expected_issuer()) |
| self.assertEqual(validator.expected_issuer(), 'issuer') |
| self.assertTrue(validator.has_expected_audience()) |
| self.assertEqual(validator.expected_audience(), 'audience') |
| self.assertFalse(validator.allow_missing_expiration()) |
| self.assertFalse(validator.ignore_issuer()) |
| self.assertFalse(validator.ignore_audiences()) |
| self.assertTrue(validator.has_fixed_now()) |
| self.assertEqual(validator.fixed_now(), fixed_now) |
| self.assertEqual(validator.clock_skew(), clock_skew) |
| |
| def test_validator_ignore_getters(self): |
| validator = jwt.new_validator( |
| allow_missing_expiration=True, |
| ignore_type_header=True, |
| ignore_issuer=True, |
| ignore_audiences=True) |
| self.assertTrue(validator.allow_missing_expiration()) |
| self.assertTrue(validator.ignore_type_header()) |
| self.assertTrue(validator.ignore_issuer()) |
| self.assertTrue(validator.ignore_audiences()) |
| |
| def test_empty_validator_getters(self): |
| validator = jwt.new_validator() |
| self.assertFalse(validator.has_expected_type_header()) |
| self.assertFalse(validator.has_expected_issuer()) |
| self.assertFalse(validator.has_expected_audience()) |
| self.assertFalse(validator.has_fixed_now()) |
| self.assertFalse(validator.clock_skew(), datetime.timedelta()) |
| |
| def test_too_much_clock_skew(self): |
| with self.assertRaises(ValueError): |
| jwt.new_validator(clock_skew=datetime.timedelta(minutes=20)) |
| |
| def test_validate_expired_fails(self): |
| expired = (datetime.datetime.now(tz=datetime.timezone.utc) |
| - datetime.timedelta(minutes=1)) |
| token = jwt.new_raw_jwt(expiration=expired) |
| validator = jwt.new_validator() |
| with self.assertRaises(jwt.JwtInvalidError): |
| _jwt_validator.validate(validator, token) |
| |
| def test_validate_not_expired_success(self): |
| still_valid = (datetime.datetime.now(tz=datetime.timezone.utc) |
| + datetime.timedelta(minutes=1)) |
| token = jwt.new_raw_jwt(expiration=still_valid) |
| validator = jwt.new_validator() |
| _jwt_validator.validate(validator, token) |
| |
| def test_validate_token_that_expires_now_fails(self): |
| now = datetime.datetime.fromtimestamp(1234.0, tz=datetime.timezone.utc) |
| token = jwt.new_raw_jwt(expiration=now) |
| validator = jwt.new_validator() |
| with self.assertRaises(jwt.JwtInvalidError): |
| _jwt_validator.validate(validator, token) |
| |
| def test_validate_recently_expired_with_clock_skew_success(self): |
| recently_expired = (datetime.datetime.now(tz=datetime.timezone.utc) |
| - datetime.timedelta(minutes=1)) |
| token = jwt.new_raw_jwt(expiration=recently_expired) |
| validator = jwt.new_validator(clock_skew=datetime.timedelta(minutes=2)) |
| # because of clock_skew, the recently expired token is valid |
| _jwt_validator.validate(validator, token) |
| |
| def test_validate_not_before_in_the_future_fails(self): |
| in_the_future = (datetime.datetime.now(tz=datetime.timezone.utc) |
| + datetime.timedelta(minutes=1)) |
| token = jwt.new_raw_jwt(not_before=in_the_future, without_expiration=True) |
| validator = jwt.new_validator(allow_missing_expiration=True) |
| with self.assertRaises(jwt.JwtInvalidError): |
| _jwt_validator.validate(validator, token) |
| |
| def test_validate_not_before_in_the_past_success(self): |
| in_the_past = (datetime.datetime.now(tz=datetime.timezone.utc) |
| - datetime.timedelta(minutes=1)) |
| token = jwt.new_raw_jwt(not_before=in_the_past, without_expiration=True) |
| validator = jwt.new_validator(allow_missing_expiration=True) |
| _jwt_validator.validate(validator, token) |
| |
| def test_validate_not_before_is_now_success(self): |
| now = datetime.datetime.fromtimestamp(12345, datetime.timezone.utc) |
| token = jwt.new_raw_jwt(not_before=now, without_expiration=True) |
| validator = jwt.new_validator(allow_missing_expiration=True) |
| _jwt_validator.validate(validator, token) |
| |
| def test_validate_not_before_almost_reached_with_clock_skew_success(self): |
| in_one_minute = (datetime.datetime.now(tz=datetime.timezone.utc) |
| + datetime.timedelta(minutes=1)) |
| token = jwt.new_raw_jwt(not_before=in_one_minute, without_expiration=True) |
| validator = jwt.new_validator( |
| allow_missing_expiration=True, clock_skew=datetime.timedelta(minutes=2)) |
| _jwt_validator.validate(validator, token) |
| |
| def test_validate_issued_at(self): |
| in_one_minute = (datetime.datetime.now(tz=datetime.timezone.utc) |
| + datetime.timedelta(minutes=1)) |
| one_minute_ago = (datetime.datetime.now(tz=datetime.timezone.utc) |
| - datetime.timedelta(minutes=1)) |
| token_with_issued_at_in_the_future = jwt.new_raw_jwt( |
| issued_at=in_one_minute, without_expiration=True) |
| token_with_issued_at_in_the_past = jwt.new_raw_jwt( |
| issued_at=one_minute_ago, without_expiration=True) |
| token_without_issued_at = jwt.new_raw_jwt(without_expiration=True) |
| |
| validator = jwt.new_validator(allow_missing_expiration=True) |
| _jwt_validator.validate(validator, token_with_issued_at_in_the_future) |
| _jwt_validator.validate(validator, token_with_issued_at_in_the_past) |
| _jwt_validator.validate(validator, token_without_issued_at) |
| |
| issued_at_validator = jwt.new_validator( |
| expect_issued_in_the_past=True, allow_missing_expiration=True) |
| with self.assertRaises(jwt.JwtInvalidError): |
| _jwt_validator.validate(issued_at_validator, |
| token_with_issued_at_in_the_future) |
| _jwt_validator.validate(issued_at_validator, |
| token_with_issued_at_in_the_past) |
| with self.assertRaises(jwt.JwtInvalidError): |
| _jwt_validator.validate(issued_at_validator, token_without_issued_at) |
| |
| def test_validate_issued_at_with_clock_skew(self): |
| in_one_minute = (datetime.datetime.now(tz=datetime.timezone.utc) |
| + datetime.timedelta(minutes=1)) |
| token_one_minute_in_the_future = jwt.new_raw_jwt( |
| issued_at=in_one_minute, without_expiration=True) |
| |
| validator_without_clock_skew = jwt.new_validator( |
| expect_issued_in_the_past=True, allow_missing_expiration=True) |
| with self.assertRaises(jwt.JwtInvalidError): |
| _jwt_validator.validate(validator_without_clock_skew, |
| token_one_minute_in_the_future) |
| |
| validator_with_clock_skew = jwt.new_validator( |
| expect_issued_in_the_past=True, |
| allow_missing_expiration=True, |
| clock_skew=datetime.timedelta(minutes=2)) |
| _jwt_validator.validate(validator_with_clock_skew, |
| token_one_minute_in_the_future) |
| |
| def test_requires_type_header_but_no_type_header_set_fails(self): |
| token = jwt.new_raw_jwt(without_expiration=True) |
| validator = jwt.new_validator( |
| expected_type_header='type_header', allow_missing_expiration=True) |
| with self.assertRaises(jwt.JwtInvalidError): |
| _jwt_validator.validate(validator, token) |
| |
| def test_invalid_type_header_fails(self): |
| token = jwt.new_raw_jwt(type_header='unknown', without_expiration=True) |
| validator = jwt.new_validator( |
| expected_type_header='type_header', allow_missing_expiration=True) |
| with self.assertRaises(jwt.JwtInvalidError): |
| _jwt_validator.validate(validator, token) |
| |
| def test_correct_type_header_success(self): |
| token = jwt.new_raw_jwt(type_header='type_header', without_expiration=True) |
| validator = jwt.new_validator( |
| expected_type_header='type_header', allow_missing_expiration=True) |
| _jwt_validator.validate(validator, token) |
| |
| def test_type_header_in_token_but_not_in_validator_fails(self): |
| validator = jwt.new_validator(allow_missing_expiration=True) |
| token_with_type_header = jwt.new_raw_jwt( |
| type_header='type_header', without_expiration=True) |
| with self.assertRaises(jwt.JwtInvalidError): |
| _jwt_validator.validate(validator, token_with_type_header) |
| |
| def test_ignore_type_header_success(self): |
| validator = jwt.new_validator( |
| ignore_type_header=True, allow_missing_expiration=True) |
| token_without_type_header = jwt.new_raw_jwt(without_expiration=True) |
| _jwt_validator.validate(validator, token_without_type_header) |
| token_with_type_header = jwt.new_raw_jwt( |
| type_header='type_header', without_expiration=True) |
| _jwt_validator.validate(validator, token_with_type_header) |
| |
| def test_requires_issuer_but_no_issuer_set_fails(self): |
| token = jwt.new_raw_jwt(without_expiration=True) |
| validator = jwt.new_validator( |
| expected_issuer='issuer', allow_missing_expiration=True) |
| with self.assertRaises(jwt.JwtInvalidError): |
| _jwt_validator.validate(validator, token) |
| |
| def test_invalid_issuer_fails(self): |
| token = jwt.new_raw_jwt(issuer='unknown', without_expiration=True) |
| validator = jwt.new_validator( |
| expected_issuer='issuer', allow_missing_expiration=True) |
| with self.assertRaises(jwt.JwtInvalidError): |
| _jwt_validator.validate(validator, token) |
| |
| def test_correct_issuer_success(self): |
| token = jwt.new_raw_jwt(issuer='issuer', without_expiration=True) |
| validator = jwt.new_validator( |
| expected_issuer='issuer', allow_missing_expiration=True) |
| _jwt_validator.validate(validator, token) |
| |
| def test_issuer_in_token_but_not_in_validator_fails(self): |
| validator = jwt.new_validator(allow_missing_expiration=True) |
| token_with_issuer = jwt.new_raw_jwt( |
| issuer='issuer', without_expiration=True) |
| with self.assertRaises(jwt.JwtInvalidError): |
| _jwt_validator.validate(validator, token_with_issuer) |
| |
| def test_ignore_issuer_success(self): |
| validator = jwt.new_validator( |
| ignore_issuer=True, allow_missing_expiration=True) |
| token_without_issuer = jwt.new_raw_jwt(without_expiration=True) |
| _jwt_validator.validate(validator, token_without_issuer) |
| token_with_issuer = jwt.new_raw_jwt( |
| issuer='issuer', without_expiration=True) |
| _jwt_validator.validate(validator, token_with_issuer) |
| |
| def test_requires_audience_but_no_audience_set_fails(self): |
| token = jwt.new_raw_jwt(without_expiration=True) |
| validator = jwt.new_validator( |
| expected_audience='audience', allow_missing_expiration=True) |
| with self.assertRaises(jwt.JwtInvalidError): |
| _jwt_validator.validate(validator, token) |
| |
| def test_wrong_audience_fails(self): |
| token = jwt.new_raw_jwt(audiences=['unknown'], without_expiration=True) |
| validator = jwt.new_validator( |
| expected_audience='audience', allow_missing_expiration=True) |
| with self.assertRaises(jwt.JwtInvalidError): |
| _jwt_validator.validate(validator, token) |
| |
| def test_correct_audience_success(self): |
| token = jwt.new_raw_jwt(audiences=['you', 'me'], without_expiration=True) |
| validator = jwt.new_validator( |
| expected_audience='me', allow_missing_expiration=True) |
| _jwt_validator.validate(validator, token) |
| |
| def test_audience_in_token_but_not_in_validator_fails(self): |
| validator = jwt.new_validator(allow_missing_expiration=True) |
| token_with_audience = jwt.new_raw_jwt( |
| audiences=['audience'], without_expiration=True) |
| with self.assertRaises(jwt.JwtInvalidError): |
| _jwt_validator.validate(validator, token_with_audience) |
| |
| def test_no_audience_success(self): |
| validator = jwt.new_validator(allow_missing_expiration=True) |
| token = jwt.new_raw_jwt(without_expiration=True) |
| _jwt_validator.validate(validator, token) |
| |
| def test_ignore_audiences_success(self): |
| validator = jwt.new_validator( |
| ignore_audiences=True, allow_missing_expiration=True) |
| token_without_audience = jwt.new_raw_jwt(without_expiration=True) |
| _jwt_validator.validate(validator, token_without_audience) |
| token_with_audience = jwt.new_raw_jwt( |
| audiences=['audience'], without_expiration=True) |
| _jwt_validator.validate(validator, token_with_audience) |
| |
| def test_validate_with_fixed_now_expired_fails(self): |
| in_two_minutes = ( |
| datetime.datetime.now(tz=datetime.timezone.utc) + |
| datetime.timedelta(minutes=2)) |
| in_one_minute = in_two_minutes - datetime.timedelta(minutes=1) |
| token = jwt.new_raw_jwt(expiration=in_one_minute) |
| validator = jwt.new_validator(fixed_now=in_two_minutes) |
| with self.assertRaises(jwt.JwtInvalidError): |
| _jwt_validator.validate(validator, token) |
| |
| def test_validate_with_fixed_now_not_yet_valid_fails(self): |
| two_minutes_ago = ( |
| datetime.datetime.now(tz=datetime.timezone.utc) - |
| datetime.timedelta(minutes=2)) |
| one_minute_ago = two_minutes_ago + datetime.timedelta(minutes=1) |
| token = jwt.new_raw_jwt(not_before=one_minute_ago, without_expiration=True) |
| validator = jwt.new_validator(fixed_now=two_minutes_ago) |
| with self.assertRaises(jwt.JwtInvalidError): |
| _jwt_validator.validate(validator, token) |
| |
| def test_validate_with_fixed_now_valid_success(self): |
| fixed_now = datetime.datetime.fromtimestamp(12345, datetime.timezone.utc) |
| validator = jwt.new_validator(fixed_now=fixed_now) |
| expiration = fixed_now + datetime.timedelta(minutes=1) |
| not_before = fixed_now - datetime.timedelta(minutes=1) |
| token = jwt.new_raw_jwt(expiration=expiration, not_before=not_before) |
| _jwt_validator.validate(validator, token) |
| |
| def test_invalid_clock_skew_fail(self): |
| with self.assertRaises(ValueError): |
| jwt.new_validator(clock_skew=datetime.timedelta(minutes=1000)) |
| |
| def test_fixed_now_without_timezone_fail(self): |
| with self.assertRaises(ValueError): |
| jwt.new_validator(fixed_now=datetime.datetime.fromtimestamp(12345)) |
| |
| |
| if __name__ == '__main__': |
| absltest.main() |