1717import base64
1818import binascii
1919import os
20+ from typing import cast
2021
2122import jwt
22- from marshmallow import Schema , ValidationError , fields , post_load , pre_load
23+ from flask import app
24+ from marshmallow import Schema , ValidationError , fields , post_load
2325from werkzeug .utils import secure_filename
2426
2527JWT_TOKEN_SECRET = os .getenv ("RENKU_JWT_TOKEN_SECRET" , "bW9menZ3cnh6cWpkcHVuZ3F5aWJycmJn" )
@@ -79,7 +81,7 @@ class RenkuHeaders:
7981
8082 @staticmethod
8183 def decode_token (token ):
82- """Extract authorization token."""
84+ """Extract the Gitlab access token form a bearer authorization header value ."""
8385 components = token .split (" " )
8486
8587 rfc_compliant = token .lower ().startswith ("bearer" )
@@ -92,45 +94,22 @@ def decode_token(token):
9294
9395 @staticmethod
9496 def decode_user (data ):
95- """Extract renku user from a JWT."""
96- decoded = jwt .decode (data , JWT_TOKEN_SECRET , algorithms = ["HS256" ], audience = "renku" )
97+ """Extract renku user from the Keycloak ID token which is a JWT."""
98+ try :
99+ jwk = cast (jwt .PyJWKClient , app .config ["KEYCLOAK_JWK_CLIENT" ])
100+ key = jwk .get_signing_key_from_jwt (data )
101+ decoded = jwt .decode (data , key = key , algorithms = ["RS256" ], audience = "renku" )
102+ except jwt .PyJWTError :
103+ # NOTE: older tokens used to be signed with HS256 so use this as a backup if the validation with RS256
104+ # above fails. We used to need HS256 because a step that is now removed was generating an ID token and
105+ # signing it from data passed in individual header fields.
106+ decoded = jwt .decode (data , JWT_TOKEN_SECRET , algorithms = ["HS256" ], audience = "renku" )
97107 return UserIdentityToken ().load (decoded )
98108
99- @staticmethod
100- def reset_old_headers (data ):
101- """Process old version of old headers."""
102- # TODO: This should be removed once support for them is phased out.
103- if "renku-user-id" in data :
104- data .pop ("renku-user-id" )
105-
106- if "renku-user-fullname" in data and "renku-user-email" in data :
107- renku_user = {
108- "aud" : ["renku" ],
109- "name" : decode_b64 (data .pop ("renku-user-fullname" )),
110- "email" : decode_b64 (data .pop ("renku-user-email" )),
111- }
112- renku_user ["sub" ] = renku_user ["email" ]
113- data ["renku-user" ] = jwt .encode (renku_user , JWT_TOKEN_SECRET , algorithm = "HS256" )
114-
115- return data
116-
117109
118110class IdentityHeaders (Schema ):
119111 """User identity schema."""
120112
121- @pre_load
122- def set_fields (self , data , ** kwargs ):
123- """Set fields for serialization."""
124- # NOTE: We don't process headers which are not meant for determining identity.
125- # TODO: Remove old headers support once support for them is phased out.
126- old_keys = ["renku-user-id" , "renku-user-fullname" , "renku-user-email" ]
127- expected_keys = old_keys + [field .data_key for field in self .fields .values ()]
128-
129- data = {key .lower (): value for key , value in data .items () if key .lower () in expected_keys }
130- data = RenkuHeaders .reset_old_headers (data )
131-
132- return data
133-
134113 @post_load
135114 def set_user (self , data , ** kwargs ):
136115 """Extract user object from a JWT."""
@@ -151,12 +130,12 @@ def set_user(self, data, **kwargs):
151130class RequiredIdentityHeaders (IdentityHeaders ):
152131 """Identity schema for required headers."""
153132
154- user_token = fields .String (required = True , data_key = "renku-user" )
155- auth_token = fields .String (required = True , data_key = "authorization" )
133+ user_token = fields .String (required = True , data_key = "renku-user" ) # Keycloak ID token
134+ auth_token = fields .String (required = True , data_key = "authorization" ) # Gitlab access token
156135
157136
158137class OptionalIdentityHeaders (IdentityHeaders ):
159138 """Identity schema for optional headers."""
160139
161- user_token = fields .String (data_key = "renku-user" )
162- auth_token = fields .String (data_key = "authorization" )
140+ user_token = fields .String (data_key = "renku-user" ) # Keycloak ID token
141+ auth_token = fields .String (data_key = "authorization" ) # Gitlab access token
0 commit comments