def test_signing_and_acceptance_of_multiple_algorithms(monkeypatch, user, call_auth_endpoint, rsa_keys): monkeypatch.setattr(api_settings, "JWT_PRIVATE_KEY", rsa_keys["secret"]["rsa1"]) monkeypatch.setattr(api_settings, "JWT_PUBLIC_KEY", rsa_keys["public"]["rsa1"]) for algo in [["HS256", "RS256"], ["RS256", "HS256"]]: monkeypatch.setattr(api_settings, "JWT_ALGORITHM", algo) response = call_auth_endpoint("username", "password") token = response.json()["token"] # check needs to succeed no matter which algo is first algo = [algo[1], algo[0]] monkeypatch.setattr(api_settings, "JWT_ALGORITHM", algo) payload = JSONWebTokenAuthentication.jwt_decode_token(token) assert response.status_code == status.HTTP_201_CREATED assert payload["user_id"] == user.id assert payload["username"] == user.get_username() # changing the signature raises exception token += "a" with raises(InvalidSignatureError): assert JSONWebTokenAuthentication.jwt_decode_token(token) == None
def test_InvalidAlgorithmError(): hdr = '{"alg": "bad", "typ": "JWT"}' jwt = b64encode(hdr.encode("ascii")) + "..".encode("ascii") with raises(InvalidAlgorithmError): assert JSONWebTokenAuthentication.jwt_decode_token(jwt) == None
def test_keys_with_key_id(monkeypatch, user, call_auth_endpoint, rsa_keys): monkeypatch.setattr(api_settings, "JWT_PRIVATE_KEY", {"rsa1": rsa_keys["secret"]["rsa1"]}) monkeypatch.setattr(api_settings, "JWT_PUBLIC_KEY", rsa_keys["public"]) secret_key = OrderedDict([("hash1", "one"), ("hash2", "two")]) monkeypatch.setattr(api_settings, "JWT_SECRET_KEY", secret_key) for kid, algo in { "hash1": ["HS256", "RS256"], "rsa1": ["RS256", "HS256"] }.items(): monkeypatch.setattr(api_settings, "JWT_ALGORITHM", algo) response = call_auth_endpoint("username", "password") token = response.json()["token"] # check needs to succeed no matter which algo is first algo = [algo[1], algo[0]] monkeypatch.setattr(api_settings, "JWT_ALGORITHM", algo) payload = JSONWebTokenAuthentication.jwt_decode_token(token) hdr = get_unverified_header(token) assert hdr["kid"] == kid assert response.status_code == status.HTTP_201_CREATED assert payload["user_id"] == user.id assert payload["username"] == user.get_username()
def save(self, **kwargs): token = self.validated_data.get('token') payload = JSONWebTokenAuthentication.jwt_decode_token(token) iat = payload.get('iat', unix_epoch()) expires_at_unix_time = iat + api_settings.JWT_EXPIRATION_DELTA.total_seconds( ) # For refreshed tokens, record the token id of the original token. # This allows us to invalidate the whole family of tokens from # the same original authentication event. token_id = payload.get('orig_jti') or payload.get('jti') self.validated_data.update({ 'token_id': token_id, 'user': check_user(payload), 'expires_at': make_aware(datetime.utcfromtimestamp(expires_at_unix_time)), }) # Don't store the token if we can rely on token IDs. # The token values are still sensitive until they expire. if api_settings.JWT_TOKEN_ID == 'require': del self.validated_data['token'] return super(BlacklistTokenSerializer, self).save(**kwargs)
def test_valid_credentials_return_jwt_uuid4_token_id(user, call_auth_endpoint): response = call_auth_endpoint("username", "password") token = response.json()["token"] payload = JSONWebTokenAuthentication.jwt_decode_token(token) pattern = r'[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}\Z' assert re.match(pattern, payload['jti'])
def test_auth__valid_credentials__returns_jwt_token(self): response = call_auth_endpoint(self.client, "foobar", "foo") token = response.json()['token'] payload = JSONWebTokenAuthentication.jwt_decode_token(token) self.assertEqual(response.status_code, HTTP_200_OK) self.assertEqual(payload['user_id'], self.active_user.id) self.assertEqual(payload['username'], self.active_user.get_username())
def test_valid_credentials_return_jwt_without_token_id_when_configured_off( monkeypatch, user, call_auth_endpoint): monkeypatch.setattr(api_settings, "JWT_TOKEN_ID", "off") response = call_auth_endpoint("username", "password") token = response.json()["token"] payload = JSONWebTokenAuthentication.jwt_decode_token(token) assert "jti" not in payload
def test_valid_credentials_return_jwt(user, call_auth_endpoint): response = call_auth_endpoint("username", "password") token = response.json()["token"] payload = JSONWebTokenAuthentication.jwt_decode_token(token) assert response.status_code == status.HTTP_200_OK assert payload["user_id"] == user.id assert payload["username"] == user.get_username()
def test_obtain_token_command_should_produce_valid_token(monkeypatch, user): output = StringIO() monkeypatch.setattr(settings, "DEBUG", True) call_command('obtain_token', str(user.pk), stdout=output) output.seek(0) printed_token = output.read() payload = JSONWebTokenAuthentication.jwt_decode_token( printed_token.strip().encode()) assert payload['user_id'] == user.pk
def test_valid_token__returns_new_token_with_new_token_id( call_auth_refresh_endpoint, user): payload = JSONWebTokenAuthentication.jwt_create_payload(user) auth_token = JSONWebTokenAuthentication.jwt_encode_payload(payload) refresh_response = call_auth_refresh_endpoint(auth_token) refresh_token = refresh_response.json()["token"] refresh_token_payload = JSONWebTokenAuthentication.jwt_decode_token( refresh_token) assert refresh_token_payload["jti"] != str(payload["jti"])
def _check_payload(token): try: payload = JSONWebTokenAuthentication.jwt_decode_token(token) except jwt.ExpiredSignature: msg = _('Token has expired.') raise serializers.ValidationError(msg) except jwt.DecodeError: msg = _('Error decoding token.') raise serializers.ValidationError(msg) return payload
def test_valid_token__returns_new_token_preserving_original_token_id_for_subsequent_refreshes( call_auth_refresh_endpoint, user): payload = JSONWebTokenAuthentication.jwt_create_payload(user) payload["orig_jti"] = uuid.uuid4() auth_token = JSONWebTokenAuthentication.jwt_encode_payload(payload) refresh_response = call_auth_refresh_endpoint(auth_token) refresh_token = refresh_response.json()["token"] refresh_token_payload = JSONWebTokenAuthentication.jwt_decode_token( refresh_token) assert refresh_token_payload["orig_jti"] == str(payload["orig_jti"])
def test_valid_token__returns_new_token_preserving_token_id_for_first_refresh( call_auth_refresh_endpoint, user): payload = JSONWebTokenAuthentication.jwt_create_payload(user) auth_token = JSONWebTokenAuthentication.jwt_encode_payload(payload) assert "orig_jti" not in payload refresh_response = call_auth_refresh_endpoint(auth_token) refresh_token = refresh_response.json()["token"] refresh_token_payload = JSONWebTokenAuthentication.jwt_decode_token( refresh_token) assert refresh_token_payload["orig_jti"] == str(payload["jti"])
def test_valid_credentials_with_no_user_id_setting_returns_jwt( monkeypatch, user, call_auth_endpoint): monkeypatch.setattr(api_settings, "JWT_PAYLOAD_INCLUDE_USER_ID", False) response = call_auth_endpoint("username", "password") token = response.json()["token"] payload = JSONWebTokenAuthentication.jwt_decode_token(token) assert response.status_code == status.HTTP_200_OK assert "user_id" not in payload assert payload["username"] == user.get_username()
def test_insist_on_key_id(monkeypatch, user, call_auth_endpoint): secret = "averybadone" monkeypatch.setattr(api_settings, "JWT_SECRET_KEY", secret) response = call_auth_endpoint("username", "password") assert response.status_code == status.HTTP_201_CREATED token = response.json()["token"] # check if key is still accepted when giving it a name monkeypatch.setattr(api_settings, "JWT_SECRET_KEY", {"kid": secret}) payload = JSONWebTokenAuthentication.jwt_decode_token(token) assert payload["user_id"] == user.id assert payload["username"] == user.get_username() # check if we insist on the key beging named monkeypatch.setattr(api_settings, "JWT_INSIST_ON_KID", True) with raises(InvalidTokenError): assert JSONWebTokenAuthentication.jwt_decode_token(token) == None
def test_auth__valid_credentials_with_no_user_id_setting__returns_jwt_token( self, mock_settings): mock_settings = setup_default_mocked_api_settings(mock_settings) mock_settings.JWT_PAYLOAD_INCLUDE_USER_ID = False response = call_auth_endpoint(self.client, "foobar", "foo") token = response.json()['token'] payload = JSONWebTokenAuthentication.jwt_decode_token(token) self.assertEqual(response.status_code, HTTP_200_OK) self.assertNotIn('user_id', payload) self.assertEqual(payload['username'], self.active_user.get_username())
def test_keys_key_id_not_found(monkeypatch, user, call_auth_endpoint): secret_key = OrderedDict([("hash1", "one"), ("hash2", "two")]) monkeypatch.setattr(api_settings, "JWT_SECRET_KEY", secret_key) response = call_auth_endpoint("username", "password") token = response.json()["token"] secret_key = OrderedDict(hash3="three") monkeypatch.setattr(api_settings, "JWT_SECRET_KEY", secret_key) with raises(InvalidTokenError): assert JSONWebTokenAuthentication.jwt_decode_token(token) == None
def test_multi_keys_hash_hash(monkeypatch, user, call_auth_endpoint): monkeypatch.setattr(api_settings, "JWT_SECRET_KEY", ["key1", "key2"]) response = call_auth_endpoint("username", "password") token = response.json()["token"] monkeypatch.setattr(api_settings, "JWT_SECRET_KEY", ["key2", "key1"]) payload = JSONWebTokenAuthentication.jwt_decode_token(token) assert response.status_code == status.HTTP_201_CREATED assert payload["user_id"] == user.id assert payload["username"] == user.get_username()
def test_auth__valid_credentials_with_JWT_GET_USER_SECRET_KEY_handler_set__returns_jwt_token( self, mock_settings): # Use default settings and override JWT_GET_USER_SECRET_KEY setting mock_settings = setup_default_mocked_api_settings(mock_settings) mock_settings.JWT_GET_USER_SECRET_KEY = jwt_get_user_secret_key response = call_auth_endpoint(self.client, "foobar", "foo") token = response.json()['token'] payload = JSONWebTokenAuthentication.jwt_decode_token(token) self.assertEqual(response.status_code, HTTP_200_OK) self.assertEqual(payload['user_id'], self.active_user.id) self.assertEqual(payload['username'], self.active_user.get_username())
def save(self, **kwargs): token = self.validated_data.get('token') payload = JSONWebTokenAuthentication.jwt_decode_token(token) iat = payload.get('iat', unix_epoch()) expires_at_unix_time = iat + api_settings.JWT_EXPIRATION_DELTA.total_seconds() self.validated_data.update({ 'user': check_user(payload), 'expires_at': make_aware(datetime.utcfromtimestamp(expires_at_unix_time)), }) return super(BlacklistTokenSerializer, self).save(**kwargs)
def test_valid_credentials_with_aud_and_iss_settings_return_jwt( monkeypatch, user, call_auth_endpoint): monkeypatch.setattr(api_settings, "JWT_AUDIENCE", "test-aud") monkeypatch.setattr(api_settings, "JWT_ISSUER", "test-iss") response = call_auth_endpoint("username", "password") token = response.json()["token"] payload = JSONWebTokenAuthentication.jwt_decode_token(token) assert response.status_code == status.HTTP_200_OK assert payload["aud"] == "test-aud" assert payload["iss"] == "test-iss" assert payload["user_id"] == user.id assert payload["username"] == user.get_username()
def test_impersonation_sets_cookie(monkeypatch, user, super_user, create_authenticated_client): imp_cookie = "jwt-imp" monkeypatch.setattr(api_settings, "JWT_IMPERSONATION_COOKIE", imp_cookie) api_client = create_authenticated_client(super_user) data = {"user": user.id} url = reverse("impersonate") response = api_client.post(url, data, format="json") token = response.json()["token"] payload = JSONWebTokenAuthentication.jwt_decode_token(token) cookie_token = response.client.cookies.get(imp_cookie) cookie_payload = JSONWebTokenAuthentication.jwt_decode_token( cookie_token.value) assert response.status_code == status.HTTP_201_CREATED assert "token" in response.json() assert imp_cookie in response.client.cookies assert payload["user_id"] == user.id assert cookie_payload["user_id"] == user.id
def test_superuser_can_impersonate(user, super_user, create_authenticated_client): api_client = create_authenticated_client(super_user) data = {"user": user.id} url = reverse("impersonate") response = api_client.post(url, data, format="json") token = response.json()["token"] payload = JSONWebTokenAuthentication.jwt_decode_token(token) assert response.status_code == status.HTTP_201_CREATED assert "token" in response.json() assert payload["user_id"] == user.id
def test_valid_credentials_with_JWT_GET_USER_SECRET_KEY_set_return_jwt( monkeypatch, user, call_auth_endpoint): def jwt_get_user_secret_key(user): return "{0}-{1}-{2}".format(user.pk, user.get_username(), "key") monkeypatch.setattr(api_settings, "JWT_GET_USER_SECRET_KEY", jwt_get_user_secret_key) response = call_auth_endpoint("username", "password") token = response.json()["token"] payload = JSONWebTokenAuthentication.jwt_decode_token(token) assert response.status_code == status.HTTP_200_OK assert payload["user_id"] == user.id assert payload["username"] == user.get_username()
def test_valid_credentials_return_jwt_with_expected_claims( user, call_auth_endpoint): response = call_auth_endpoint("username", "password") token = response.json()["token"] payload = JSONWebTokenAuthentication.jwt_decode_token(token) expected_claims = { 'jti', 'username', 'iat', 'exp', 'user_id', 'orig_iat', } assert set(payload.keys()) == expected_claims
def test_multi_keys_rsa_rsa(monkeypatch, user, call_auth_endpoint, rsa_keys): monkeypatch.setattr(api_settings, "JWT_ALGORITHM", "RS256") monkeypatch.setattr(api_settings, "JWT_PUBLIC_KEY", list(rsa_keys["public"].values())) for skey in rsa_keys["secret"].values(): monkeypatch.setattr(api_settings, "JWT_PRIVATE_KEY", skey) response = call_auth_endpoint("username", "password") token = response.json()["token"] payload = JSONWebTokenAuthentication.jwt_decode_token(token) assert response.status_code == status.HTTP_201_CREATED assert payload["user_id"] == user.id assert payload["username"] == user.get_username()
def test_auth__valid_credentials_with_aud_and_iss_settings__returns_jwt_token( self, mock_settings): # Use default settings and override JWT_AUDIENCE and JWT_ISSUER settings mock_settings = setup_default_mocked_api_settings(mock_settings) mock_settings.JWT_AUDIENCE = 'test-aud' mock_settings.JWT_ISSUER = 'test-iss' response = call_auth_endpoint(self.client, "foobar", "foo") token = response.json()['token'] payload = JSONWebTokenAuthentication.jwt_decode_token(token) self.assertEqual(response.status_code, HTTP_200_OK) self.assertEqual(payload['aud'], mock_settings.JWT_AUDIENCE) self.assertEqual(payload['iss'], mock_settings.JWT_ISSUER) self.assertEqual(payload['user_id'], self.active_user.id) self.assertEqual(payload['username'], self.active_user.get_username())
def check_payload(token): from rest_framework_jwt.authentication import JSONWebTokenAuthentication if apps.is_installed('rest_framework_jwt.blacklist'): if BlacklistedToken.objects.filter(token=force_str(token)).exists(): msg = _('Token is blacklisted.') raise serializers.ValidationError(msg) try: payload = JSONWebTokenAuthentication.jwt_decode_token(token) except jwt.ExpiredSignature: msg = _('Token has expired.') raise serializers.ValidationError(msg) except jwt.DecodeError: msg = _('Error decoding token.') raise serializers.ValidationError(msg) return payload
def test_refreshed_token_is_blocked_by_original_id(user, call_auth_refresh_endpoint, monkeypatch, id_setting): monkeypatch.setattr(api_settings, "JWT_TOKEN_ID", id_setting) original_payload = JSONWebTokenAuthentication.jwt_create_payload(user) original_token = JSONWebTokenAuthentication.jwt_encode_payload( original_payload) refresh_response = call_auth_refresh_endpoint(original_token) refreshed_token = refresh_response.json()['token'] payload = JSONWebTokenAuthentication.jwt_decode_token(refreshed_token) expiration = timezone.now() + timedelta(days=1) BlacklistedToken( token_id=original_payload['jti'], expires_at=expiration, user=user, ).save() assert BlacklistedToken.is_blocked(refreshed_token, payload) is True
def check_payload(token): from rest_framework_jwt.authentication import JSONWebTokenAuthentication try: payload = JSONWebTokenAuthentication.jwt_decode_token(token) except jwt.ExpiredSignature: msg = _('Token has expired.') raise serializers.ValidationError(msg) except jwt.DecodeError: msg = _('Error decoding token.') raise serializers.ValidationError(msg) except jwt.InvalidTokenError: msg = _('Invalid token.') raise serializers.ValidationError(msg) if apps.is_installed('rest_framework_jwt.blacklist'): from rest_framework_jwt.blacklist.models import BlacklistedToken if BlacklistedToken.is_blocked(token, payload): msg = _('Token is blacklisted.') raise serializers.ValidationError(msg) return payload