Skip to content

Commit fc4477d

Browse files
committed
hash and test claims
1 parent 7c463a3 commit fc4477d

File tree

2 files changed

+415
-75
lines changed

2 files changed

+415
-75
lines changed

ansible_base/rbac/claims.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import hashlib
2+
import json
13
from typing import Union
24

35
from django.apps import apps
@@ -107,3 +109,76 @@ def get_user_claims(user: Model) -> dict[str, Union[list[str], dict[str, Union[s
107109
claims['global_roles'].append(rd.name)
108110

109111
return claims
112+
113+
114+
def get_user_claims_hashable_form(claims: dict) -> dict[str, Union[list[str], dict[str, list[str]]]]:
115+
"""Convert user claims to hashable form suitable for generating deterministic hashes.
116+
117+
Args:
118+
claims: Claims dictionary from get_user_claims()
119+
120+
The hashable form:
121+
- Removes the 'objects' section entirely
122+
- Converts object role references from array indexes to ansible_id values
123+
- Sorts all collections for deterministic ordering
124+
- Uses ansible_id for object references (or id if no ansible_id, for future use)
125+
126+
Returns:
127+
{
128+
'global_roles': ['Platform Auditor', 'System Admin'], # sorted
129+
'object_roles': {
130+
'Organization Admin': ['uuid1', 'uuid2'], # sorted ansible_ids
131+
'Team Member': ['uuid3', 'uuid4'] # sorted ansible_ids
132+
}
133+
}
134+
"""
135+
136+
hashable_claims = {'global_roles': sorted(claims['global_roles']), 'object_roles': {}}
137+
138+
# Convert object_roles from indexes to ansible_ids
139+
for role_name, role_data in claims['object_roles'].items():
140+
content_type = role_data['content_type']
141+
object_indexes = role_data['objects']
142+
143+
# Get the objects array for this content type
144+
objects_array = claims['objects'].get(content_type, [])
145+
146+
# Convert indexes to ansible_ids
147+
ansible_ids = []
148+
for index in object_indexes:
149+
if index < len(objects_array):
150+
obj_data = objects_array[index]
151+
# Use ansible_id if available, otherwise fall back to id (for future use)
152+
ansible_id = obj_data.get('ansible_id') or str(obj_data.get('id', ''))
153+
if ansible_id:
154+
ansible_ids.append(ansible_id)
155+
156+
# Sort ansible_ids for deterministic ordering
157+
hashable_claims['object_roles'][role_name] = sorted(ansible_ids)
158+
159+
return hashable_claims
160+
161+
162+
def get_claims_hash(hashable_claims: dict) -> str:
163+
"""Generate a deterministic SHA-256 hash from hashable claims data.
164+
165+
Args:
166+
hashable_claims: Output from get_user_claims_hashable_form()
167+
168+
Returns:
169+
64-character hex string representing the SHA-256 hash of the claims
170+
171+
The hash is generated by:
172+
1. Serializing the hashable claims to JSON with sorted keys
173+
2. Encoding to UTF-8 bytes
174+
3. Computing SHA-256 hash
175+
4. Returning as hexadecimal string
176+
"""
177+
# Serialize to JSON with sorted keys for deterministic output
178+
json_str = json.dumps(hashable_claims, sort_keys=True, separators=(',', ':'))
179+
180+
# Encode to bytes and compute SHA-256 hash
181+
json_bytes = json_str.encode('utf-8')
182+
hash_digest = hashlib.sha256(json_bytes).hexdigest()
183+
184+
return hash_digest

0 commit comments

Comments
 (0)