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",

`