1
1
import logging
2
+ import uuid
2
3
from datetime import datetime
3
4
from typing import Optional , Tuple
4
5
18
19
from ansible_base .lib .logging .runtime import log_excess_runtime
19
20
from ansible_base .lib .utils .auth import get_user_by_ansible_id
20
21
from ansible_base .lib .utils .translations import translatableConditionally as _
22
+ from ansible_base .rbac .claims import get_claims_hash , get_user_claims , get_user_claims_hashable_form
21
23
from ansible_base .resource_registry .models import Resource , ResourceType
24
+ from ansible_base .resource_registry .rest_client import get_resource_server_client
22
25
from ansible_base .resource_registry .signals .handlers import no_reverse_sync
23
26
24
27
logger = logging .getLogger ("ansible_base.jwt_consumer.common.auth" )
@@ -226,7 +229,7 @@ def validate_token(self, unencrypted_token, decryption_key, request_id=None):
226
229
return validated_body
227
230
228
231
def decode_jwt_token (self , unencrypted_token , decryption_key , additional_options = {}):
229
- local_required_field = ["sub" , "user_data" , "exp" , "objects" , "object_roles" , "global_roles " , "version" ]
232
+ local_required_field = ["sub" , "user_data" , "exp" , "claims_hash " , "version" ]
230
233
options = {"require" : local_required_field }
231
234
options .update (additional_options )
232
235
return jwt .decode (
@@ -258,17 +261,98 @@ def get_role_definition(self, name: str) -> Optional[Model]:
258
261
259
262
def process_rbac_permissions (self ):
260
263
"""
261
- This is a default process_permissions which should be usable if you are using RBAC from DAB
264
+ Process RBAC permissions using claims hash logic
262
265
"""
263
266
if self .token is None or self .user is None :
264
- logger .error ("Unable to process rbac permissions because user or token is not defined, please call authenticate first " )
267
+ logger .error ("Unable to process rbac permissions because user or token is not defined" )
265
268
return
266
269
270
+ jwt_claims_hash = self .token .get ("claims_hash" )
271
+ if not jwt_claims_hash :
272
+ logger .error ("No claims_hash found in JWT token" )
273
+ return
274
+
275
+ user_ansible_id = self .token .get ("sub" )
276
+ if not user_ansible_id :
277
+ logger .error ("No subject (sub) found in JWT token" )
278
+ return
279
+
280
+ # Validate UUID format (consistent with rest of codebase)
281
+ try :
282
+ uuid .UUID (user_ansible_id )
283
+ except (ValueError , TypeError ):
284
+ logger .error (f"Invalid UUID format for user_ansible_id: { user_ansible_id } " )
285
+ return
286
+
287
+ # Check cached claims hash
288
+ cached_claims_hash = self .cache .get_cached_claims_hash (user_ansible_id )
289
+
290
+ if cached_claims_hash == jwt_claims_hash :
291
+ logger .debug (f"Claims hash matches cached value for user { user_ansible_id } " )
292
+ return
293
+
294
+ # Calculate local claims hash
295
+ local_claims = get_user_claims (self .user )
296
+ local_hashable_claims = get_user_claims_hashable_form (local_claims )
297
+ local_claims_hash = get_claims_hash (local_hashable_claims )
298
+
299
+ if local_claims_hash == jwt_claims_hash :
300
+ logger .debug (f"Claims hash matches local calculation for user { user_ansible_id } " )
301
+ # Update cache with the correct hash
302
+ self .cache .cache_claims_hash (user_ansible_id , jwt_claims_hash )
303
+ return
304
+
305
+ # Claims hash mismatch - fetch from gateway
306
+ logger .info (f"Claims hash mismatch for user { user_ansible_id } . JWT: { jwt_claims_hash } , Local: { local_claims_hash } . Fetching from gateway." )
307
+ gateway_claims = self ._fetch_jwt_claims_from_gateway (user_ansible_id )
308
+
309
+ if gateway_claims :
310
+ # Extract claims structure from gateway response
311
+ objects = gateway_claims .get ('objects' , {})
312
+ object_roles = gateway_claims .get ('object_roles' , {})
313
+ global_roles = gateway_claims .get ('global_roles' , [])
314
+
315
+ # Process the RBAC permissions with the gateway claims
316
+ self ._apply_rbac_permissions (objects , object_roles , global_roles )
317
+
318
+ # Update cache with the new hash
319
+ self .cache .cache_claims_hash (user_ansible_id , jwt_claims_hash )
320
+ else :
321
+ self .log_and_raise (
322
+ _ ("Unable to validate user permissions - gateway claims fetch failed for user %(user_ansible_id)s" ), {"user_ansible_id" : user_ansible_id }
323
+ )
324
+
325
+ def _fetch_jwt_claims_from_gateway (self , user_ansible_id : str ) -> Optional [dict ]:
326
+ """
327
+ Fetch JWT claims from the gateway endpoint using resource server client
328
+ """
329
+ try :
330
+ # Use the resource server client to make the request
331
+ client = get_resource_server_client (service_path = "api/gateway/v1" )
332
+
333
+ logger .debug (f"Fetching claims from gateway for user { user_ansible_id } " )
334
+ response = client ._make_request ("GET" , f"jwt_claims/{ user_ansible_id } /" )
335
+
336
+ if response .status_code == 200 :
337
+ claims_data = response .json ()
338
+ return claims_data
339
+ else :
340
+ logger .error (f"Gateway request failed with status { response .status_code } " )
341
+ return None
342
+
343
+ except Exception as e :
344
+ logger .error (f"Error fetching claims from gateway: { e } " )
345
+ return None
346
+
347
+ def _apply_rbac_permissions (self , objects , object_roles , global_roles ):
348
+ """
349
+ Apply RBAC permissions from claims data
350
+ """
267
351
from ansible_base .rbac .models import RoleUserAssignment
268
352
269
353
role_diff = RoleUserAssignment .objects .filter (user = self .user , role_definition__name__in = settings .ANSIBLE_BASE_JWT_MANAGED_ROLES )
270
354
271
- for system_role_name in self . token . get ( " global_roles" , []) :
355
+ for system_role_name in global_roles :
272
356
logger .debug (f"Processing system role { system_role_name } for { self .user .username } " )
273
357
rd = self .get_role_definition (system_role_name )
274
358
if rd :
@@ -282,7 +366,7 @@ def process_rbac_permissions(self):
282
366
logger .error (f"Unable to grant { self .user .username } system level role { system_role_name } because it does not exist" )
283
367
continue
284
368
285
- for object_role_name in self . token . get ( ' object_roles' , {}) .keys ():
369
+ for object_role_name in object_roles .keys ():
286
370
rd = self .get_role_definition (object_role_name )
287
371
if rd is None :
288
372
logger .error (f"Unable to grant { self .user .username } object role { object_role_name } because it does not exist" )
@@ -291,11 +375,11 @@ def process_rbac_permissions(self):
291
375
logger .error (f"Unable to grant { self .user .username } object role { object_role_name } because it is not a JWT managed role" )
292
376
continue
293
377
294
- object_type = self . token [ ' object_roles' ] [object_role_name ]['content_type' ]
295
- object_indexes = self . token [ ' object_roles' ] [object_role_name ]['objects' ]
378
+ object_type = object_roles [object_role_name ]['content_type' ]
379
+ object_indexes = object_roles [object_role_name ]['objects' ]
296
380
297
381
for index in object_indexes :
298
- object_data = self . token [ ' objects' ] [object_type ][index ]
382
+ object_data = objects [object_type ][index ]
299
383
try :
300
384
resource , obj = self .get_or_create_resource (object_type , object_data )
301
385
except IntegrityError as e :
0 commit comments