17
17
import base64
18
18
import binascii
19
19
import os
20
+ from typing import cast
20
21
21
22
import 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
23
25
from werkzeug .utils import secure_filename
24
26
25
27
JWT_TOKEN_SECRET = os .getenv ("RENKU_JWT_TOKEN_SECRET" , "bW9menZ3cnh6cWpkcHVuZ3F5aWJycmJn" )
@@ -79,7 +81,7 @@ class RenkuHeaders:
79
81
80
82
@staticmethod
81
83
def decode_token (token ):
82
- """Extract authorization token."""
84
+ """Extract the Gitlab access token form a bearer authorization header value ."""
83
85
components = token .split (" " )
84
86
85
87
rfc_compliant = token .lower ().startswith ("bearer" )
@@ -92,45 +94,22 @@ def decode_token(token):
92
94
93
95
@staticmethod
94
96
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" )
97
107
return UserIdentityToken ().load (decoded )
98
108
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
-
117
109
118
110
class IdentityHeaders (Schema ):
119
111
"""User identity schema."""
120
112
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
-
134
113
@post_load
135
114
def set_user (self , data , ** kwargs ):
136
115
"""Extract user object from a JWT."""
@@ -151,12 +130,12 @@ def set_user(self, data, **kwargs):
151
130
class RequiredIdentityHeaders (IdentityHeaders ):
152
131
"""Identity schema for required headers."""
153
132
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
156
135
157
136
158
137
class OptionalIdentityHeaders (IdentityHeaders ):
159
138
"""Identity schema for optional headers."""
160
139
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