fix(jans-cedarling): Fix retrieving resource entity from default enti… · JanssenProject/jans@b9379e0 (original) (raw)
`@@ -81,12 +81,12 @@ fn parse_default_entities(
`
81
81
`default_entities_data: &HashMap<String, Value>,
`
82
82
`namespace: Option<&str>,
`
83
83
`logger: Logger,
`
84
``
`-
) -> Result<HashMap<String, Entity>, BuildEntityError> {
`
``
84
`+
) -> Result<HashMap<EntityUid, Entity>, BuildEntityError> {
`
85
85
`let mut default_entities = HashMap::new();
`
86
86
``
87
``
`-
for (entity_id, entity_data) in default_entities_data {
`
``
87
`+
for (entry_id, entity_data) in default_entities_data {
`
88
88
`// Validate entity ID to prevent injection attacks
`
89
``
`-
if entity_id.trim().is_empty() {
`
``
89
`+
if entry_id.trim().is_empty() {
`
90
90
`return Err(BuildEntityError::new(
`
91
91
`"DefaultEntity".to_string(),
`
92
92
`BuildEntityErrorKind::InvalidEntityData(
`
`@@ -105,14 +105,14 @@ fn parse_default_entities(
`
105
105
`"onload=",
`
106
106
`"onerror=",
`
107
107
`];
`
108
``
`-
let entity_id_lower = entity_id.to_lowercase();
`
``
108
`+
let entry_id_lower = entry_id.to_lowercase();
`
109
109
`for pattern in &dangerous_patterns {
`
110
``
`-
if entity_id_lower.contains(pattern) {
`
``
110
`+
if entry_id_lower.contains(pattern) {
`
111
111
`return Err(BuildEntityError::new(
`
112
112
`"DefaultEntity".to_string(),
`
113
113
`BuildEntityErrorKind::InvalidEntityData(format!(
`
114
114
`"Entity ID '{}' contains potentially dangerous content",
`
115
``
`-
entity_id
`
``
115
`+
entry_id
`
116
116
`)),
`
117
117
`));
`
118
118
`}
`
`@@ -126,13 +126,15 @@ fn parse_default_entities(
`
126
126
`UNKNOWN_ENTITY_TYPE.to_string(),
`
127
127
`BuildEntityErrorKind::InvalidEntityData(format!(
`
128
128
`"Default entity data for '{}' must be a JSON object",
`
129
``
`-
entity_id
`
``
129
`+
entry_id
`
130
130
`)),
`
131
131
`));
`
132
132
`};
`
133
133
``
134
134
`// Check if this is the new Cedar entity format (with uid, attrs, parents fields)
`
135
``
`-
let (entity_type, cedar_attrs, parents) = if entity_obj.contains_key("uid") {
`
``
135
`+
let (entity_type, entity_id_from_uid, cedar_attrs, parents) = if entity_obj
`
``
136
`+
.contains_key("uid")
`
``
137
`+
{
`
136
138
`// New Cedar entity format: {"uid": {"type": "...", "id": "..."}, "attrs": {}, "parents": [...]}
`
137
139
`let uid_obj = entity_obj
`
138
140
`.get("uid")
`
`@@ -142,7 +144,7 @@ fn parse_default_entities(
`
142
144
`UNKNOWN_ENTITY_TYPE.to_string(),
`
143
145
`BuildEntityErrorKind::InvalidEntityData(format!(
`
144
146
`"Default entity '{}' has invalid uid field",
`
145
``
`-
entity_id
`
``
147
`+
entry_id
`
146
148
`)),
`
147
149
`)
`
148
150
`})?;
`
`@@ -156,7 +158,7 @@ fn parse_default_entities(
`
156
158
`UNKNOWN_ENTITY_TYPE.to_string(),
`
157
159
`BuildEntityErrorKind::InvalidEntityData(format!(
`
158
160
`"Default entity '{}' has invalid uid.type field",
`
159
``
`-
entity_id
`
``
161
`+
entry_id
`
160
162
`)),
`
161
163
`)
`
162
164
`})?;
`
`@@ -168,7 +170,7 @@ fn parse_default_entities(
`
168
170
`let entity_id_from_uid = uid_obj.get("id")
`
169
171
`.and_then(|v| v.as_str())
`
170
172
`// Fall back to the HashMap key if uid.id is not specified
`
171
``
`-
.unwrap_or(entity_id);
`
``
173
`+
.unwrap_or(entry_id);
`
172
174
``
173
175
`// Parse attributes from attrs field
`
174
176
`let empty_map = serde_json::Map::new();
`
`@@ -208,7 +210,7 @@ fn parse_default_entities(
`
208
210
`.set_level(LogLevel::WARN)
`
209
211
`.set_message(format!(
`
210
212
`"Could not parse parent UID '{}' for default entity '{}': {}",
`
211
``
`-
parent_uid_str, entity_id, e
`
``
213
`+
parent_uid_str, entry_id, e
`
212
214
`));
`
213
215
``
214
216
` logger.log_any(log_entry);
`
`@@ -227,7 +229,12 @@ fn parse_default_entities(
`
227
229
`}
`
228
230
`}
`
229
231
``
230
``
`-
(full_entity_type, cedar_attrs, parents_set)
`
``
232
`+
(
`
``
233
`+
full_entity_type,
`
``
234
`+
entity_id_from_uid,
`
``
235
`+
cedar_attrs,
`
``
236
`+
parents_set,
`
``
237
`+
)
`
231
238
`} else if entity_obj.contains_key("entity_type") {
`
232
239
`// Old format with entity_type field
`
233
240
`let entity_type = entity_obj
`
`@@ -238,35 +245,44 @@ fn parse_default_entities(
`
238
245
`UNKNOWN_ENTITY_TYPE.to_string(),
`
239
246
`BuildEntityErrorKind::InvalidEntityData(format!(
`
240
247
`"Default entity '{}' has invalid entity_type field",
`
241
``
`-
entity_id
`
``
248
`+
entry_id
`
242
249
`)),
`
243
250
`)
`
244
251
`})?;
`
245
252
``
``
253
`+
let entity_id_from_uid = entity_obj
`
``
254
`+
.get("entity_id")
`
``
255
`+
.and_then(|v| v.as_str())
`
``
256
`+
.unwrap_or(entry_id);
`
``
257
+
246
258
`// Convert JSON attributes to Cedar expressions
`
247
259
`let cedar_attrs = parse_entity_attrs(
`
248
260
` entity_obj
`
249
261
`.iter()
`
250
262
`.filter(|(key, _)| key != &"entity_type" && key != &"entity_id"),
`
251
263
` entity_type,
`
252
``
`-
entity_id,
`
``
264
`+
entry_id,
`
253
265
`)?;
`
254
266
``
255
``
`-
(entity_type.to_string(), cedar_attrs, HashSet::new())
`
``
267
`+
(
`
``
268
`+
entity_type.to_string(),
`
``
269
`+
entity_id_from_uid,
`
``
270
`+
cedar_attrs,
`
``
271
`+
HashSet::new(),
`
``
272
`+
)
`
256
273
`} else {
`
257
274
`return Err(BuildEntityError::new(
`
258
275
`UNKNOWN_ENTITY_TYPE.to_string(),
`
259
276
`BuildEntityErrorKind::InvalidEntityData(format!(
`
260
277
`"Default entity '{}' must have either uid field (Cedar format) or entity_type field (legacy format)",
`
261
``
`-
entity_id
`
``
278
`+
entry_id
`
262
279
`)),
`
263
280
`));
`
264
281
`};
`
265
282
``
266
283
`// Build the Cedar entity
`
267
``
`-
let entity = build_cedar_entity(&entity_type, entity_id, cedar_attrs, parents)?;
`
268
``
-
269
``
`-
default_entities.insert(entity_id.clone(), entity);
`
``
284
`+
let entity = build_cedar_entity(&entity_type, entity_id_from_uid, cedar_attrs, parents)?;
`
``
285
`+
default_entities.insert(entity.uid().clone(), entity);
`
270
286
`}
`
271
287
``
272
288
`Ok(default_entities)
`
`@@ -288,7 +304,7 @@ pub struct EntityBuilder {
`
288
304
`config: EntityBuilderConfig,
`
289
305
`iss_entities: HashMap<Origin, Entity>,
`
290
306
`schema: Option,
`
291
``
`-
default_entities: HashMap<String, Entity>,
`
``
307
`+
default_entities: HashMap<EntityUid, Entity>,
`
292
308
`}
`
293
309
``
294
310
`impl EntityBuilder {
`
`@@ -335,7 +351,7 @@ impl EntityBuilder {
`
335
351
`pub fn build_entities(
`
336
352
`&self,
`
337
353
`tokens: &HashMap<String, Arc>,
`
338
``
`-
resource: &EntityData,
`
``
354
`+
resource_data: &EntityData,
`
339
355
`) -> Result<AuthorizeEntitiesData, BuildEntityError> {
`
340
356
`let mut tkn_principal_mappings = TokenPrincipalMappings::default();
`
341
357
`let mut built_entities = BuiltEntities::from(&self.iss_entities);
`
`@@ -386,7 +402,12 @@ impl EntityBuilder {
`
386
402
`(None, Vec::new())
`
387
403
`};
`
388
404
``
389
``
`-
let resource = self.build_resource_entity(resource)?;
`
``
405
`+
let mut resource = self.build_resource_entity(resource_data)?;
`
``
406
`+
if let Some(resource_default_entity) = self.default_entities.get(&resource.uid())
`
``
407
`+
&& resource_data.attributes.is_empty()
`
``
408
`+
{
`
``
409
`+
resource = resource_default_entity.clone()
`
``
410
`+
}
`
390
411
``
391
412
`let issuers = self.iss_entities.values().cloned().collect();
`
392
413
`Ok(AuthorizeEntitiesData {
`
`@@ -467,8 +488,11 @@ pub fn build_cedar_entity(
`
467
488
`attrs: HashMap<String, RestrictedExpression>,
`
468
489
`parents: HashSet,
`
469
490
`) -> Result<Entity, BuildEntityError> {
`
470
``
`-
let uid = EntityUid::from_str(&format!("{}::"{}"", type_name, id))
`
471
``
`-
.map_err(|e| BuildEntityErrorKind::from(Box::new(e)).while_building(type_name))?;
`
``
491
`+
let uid = EntityUid::from_str(&format!("{}::"{}"", type_name, id)).map_err(
`
``
492
`+
|e: cedar_policy::ParseErrors| {
`
``
493
`+
BuildEntityErrorKind::from(Box::new(e)).while_building(type_name)
`
``
494
`+
},
`
``
495
`+
)?;
`
472
496
`let entity = Entity::new(uid, attrs, parents)
`
473
497
`.map_err(|e| BuildEntityErrorKind::from(Box::new(e)).while_building(type_name))?;
`
474
498
``
`@@ -542,7 +566,7 @@ mod test {
`
542
566
`use serde_json::{Value, json};
`
543
567
`use std::collections::HashMap;
`
544
568
`use std::sync::LazyLock;
`
545
``
`-
use test_utils::assert_eq;
`
``
569
`+
use test_utils::{SortedJson, assert_eq};
`
546
570
``
547
571
`pub static CEDARLING_VALIDATOR_SCHEMA: LazyLock = LazyLock::new(|| {
`
548
572
`ValidatorSchema::from_str(include_str!("../../../schema/cedarling_core.cedarschema"))
`
`@@ -738,7 +762,7 @@ mod test {
`
738
762
``
739
763
`// Verify the entity
`
740
764
`let entity = parsed_entities
`
741
``
`-
.get("1694c954f8d9")
`
``
765
`+
.get(&EntityUid::from_str("Jans::DefaultEntity::"1694c954f8d9"").unwrap())
`
742
766
`.expect("should have entity");
`
743
767
`assert_eq!(entity.uid().type_name().to_string(), "Jans::DefaultEntity");
`
744
768
`assert_eq!(entity.uid().id().as_ref() as &str, "1694c954f8d9");
`
`@@ -863,7 +887,7 @@ mod test {
`
863
887
`// Verify specific default entity
`
864
888
`let default_entity = entities_data
`
865
889
`.default_entities
`
866
``
`-
.get("1694c954f8d9")
`
``
890
`+
.get(&EntityUid::from_str("Jans::DefaultEntity::"1694c954f8d9"").unwrap())
`
867
891
`.expect("should have default entity 1694c954f8d9");
`
868
892
``
869
893
`assert_eq!(
`
`@@ -916,10 +940,11 @@ mod test {
`
916
940
`"should have 2 default entities"
`
917
941
`);
`
918
942
``
``
943
`+
let uid = EntityUid::from_str("Jans::DefaultEntity::"74d109b20248"").unwrap();
`
919
944
`// Verify the second default entity is also present
`
920
945
`let second_default_entity = entities_data
`
921
946
`.default_entities
`
922
``
`-
.get("74d109b20248")
`
``
947
`+
.get(&uid)
`
923
948
`.expect("should have default entity 74d109b20248");
`
924
949
`assert_eq!(
`
925
950
` second_default_entity.uid().type_name().to_string(),
`
`@@ -1509,9 +1534,9 @@ mod test {
`
1509
1534
``
1510
1535
`assert_eq!(parsed_entities.len(), 1, "should have 1 entity");
`
1511
1536
``
1512
``
`-
let entity = parsed_entities
`
1513
``
`-
.get("2694c954f8d8")
`
1514
``
`-
.expect("should have entity");
`
``
1537
`+
let uid =
`
``
1538
`+
&EntityUid::from_str("Gluu::Flex::AdminUI::Resources::Features::"License"").unwrap();
`
``
1539
`+
let entity = parsed_entities.get(&uid).expect("should have entity");
`
1515
1540
`let uid_str = entity.uid().to_string();
`
1516
1541
``
1517
1542
`// Verify the namespace was added correctly to both the entity type and parent type
`
`@@ -1544,10 +1569,16 @@ mod test {
`
1544
1569
`},
`
1545
1570
`"attrs": {
`
1546
1571
`"attribute": "value"
`
1547
``
`-
}
`
``
1572
`+
},
`
``
1573
`+
"parents": [
`
``
1574
`+
{
`
``
1575
`+
"type": "NewNamespace::ParentResource",
`
``
1576
`+
"id": "SomeTestID"
`
``
1577
`+
}
`
``
1578
`+
]
`
1548
1579
`});
`
1549
1580
``
1550
``
`-
let default_entities_data = HashMap::from([("test123".to_string(), entity_data)]);
`
``
1581
`+
let default_entities_data = HashMap::from([("test123".to_string(), entity_data.clone())]);
`
1551
1582
``
1552
1583
`let parsed_entities = parse_default_entities(
`
1553
1584
`&default_entities_data,
`
`@@ -1556,12 +1587,29 @@ mod test {
`
1556
1587
`)
`
1557
1588
`.expect("should parse default entities");
`
1558
1589
``
1559
``
`-
let entity = parsed_entities.get("test123").expect("should have entity");
`
``
1590
`+
let uid: &EntityUid =
`
``
1591
`+
&EntityUid::from_str("Existing::Namespace::Features::"TestFeature"").unwrap();
`
``
1592
`+
let entity = parsed_entities.get(&uid).expect("should have entity");
`
``
1593
+
``
1594
`+
let result_entity_json = entity
`
``
1595
`+
.to_json_value()
`
``
1596
`+
.expect("entity should be converted to json");
`
``
1597
+
``
1598
`+
assert_eq!(
`
``
1599
`+
result_entity_json.sorted(),
`
``
1600
`+
entity_data.sorted(),
`
``
1601
`+
"entity json data should be equal"
`
``
1602
`+
);
`
1560
1603
`assert_eq!(
`
1561
1604
` entity.uid().type_name().to_string(),
`
1562
1605
`"Existing::Namespace::Features",
`
1563
1606
`"Existing namespace should not be double-prefixed"
`
1564
1607
`);
`
``
1608
`+
assert_eq!(
`
``
1609
`+
entity.uid().id().unescaped(),
`
``
1610
`+
"TestFeature",
`
``
1611
`` +
"ID of entity should be TestFeature"
``
``
1612
`+
);
`
1565
1613
`}
`
1566
1614
``
1567
1615
`#[test]
`
`@@ -1636,7 +1684,8 @@ mod test {
`
1636
1684
`parse_default_entities(&default_entities_data, Some("Test"), TEST_LOGGER.clone())
`
1637
1685
`.expect("should parse with empty attrs and parents");
`
1638
1686
``
1639
``
`-
let entity = parsed_entities.get("test789").expect("should have entity");
`
``
1687
`+
let uid: &EntityUid = &EntityUid::from_str("Test::EmptyTest::"test789"").unwrap();
`
``
1688
`+
let entity = parsed_entities.get(&uid).expect("should have entity");
`
1640
1689
`assert_eq!(
`
1641
1690
` entity.uid().type_name().to_string(),
`
1642
1691
`"Test::EmptyTest",
`