or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

adaptive-authentication.mdauthentication-handlers.mdauthentication-policies.mdcredential-handling.mdindex.mdpassword-policies.mdprincipal-resolution.md

principal-resolution.mddocs/

0

# Principal Resolution

1

2

Principal resolution is the process of identifying and constructing user principals with their associated attributes after successful authentication. The CAS authentication API provides a flexible framework for resolving principals from various sources and managing their attributes through configurable merging strategies.

3

4

## Core Principal Interface

5

6

```java { .api }

7

package org.apereo.cas.authentication.principal;

8

9

import java.io.Serializable;

10

import java.util.List;

11

import java.util.Map;

12

13

public interface Principal extends Serializable {

14

String getId();

15

Map<String, List<Object>> getAttributes();

16

}

17

```

18

19

## Principal Implementations

20

21

### Simple Principal

22

23

The default implementation that provides immutable attribute storage:

24

25

```java { .api }

26

package org.apereo.cas.authentication.principal;

27

28

import com.fasterxml.jackson.annotation.JsonCreator;

29

import com.fasterxml.jackson.annotation.JsonProperty;

30

import java.util.List;

31

import java.util.Map;

32

import java.util.TreeMap;

33

34

public class SimplePrincipal implements Principal {

35

private final String id;

36

private final Map<String, List<Object>> attributes;

37

38

@JsonCreator

39

public SimplePrincipal(@JsonProperty("id") String id,

40

@JsonProperty("attributes") Map<String, List<Object>> attributes) {

41

this.id = id;

42

this.attributes = attributes != null ?

43

new TreeMap<>(String.CASE_INSENSITIVE_ORDER) {{

44

putAll(attributes);

45

}} : new TreeMap<>(String.CASE_INSENSITIVE_ORDER);

46

}

47

48

public SimplePrincipal(String id) {

49

this(id, null);

50

}

51

52

public String getId() { return id; }

53

54

public Map<String, List<Object>> getAttributes() { return attributes; }

55

56

@Override

57

public boolean equals(Object obj) {

58

if (obj == null || !this.getClass().equals(obj.getClass())) {

59

return false;

60

}

61

return getId().equals(((SimplePrincipal) obj).getId());

62

}

63

64

@Override

65

public int hashCode() {

66

return getId().hashCode();

67

}

68

}

69

```

70

71

### Null Principal

72

73

Null object pattern implementation for cases where no principal is available:

74

75

```java { .api }

76

package org.apereo.cas.authentication.principal;

77

78

import java.util.Collections;

79

import java.util.List;

80

import java.util.Map;

81

82

public final class NullPrincipal implements Principal {

83

private static final NullPrincipal INSTANCE = new NullPrincipal();

84

85

private NullPrincipal() {}

86

87

public static NullPrincipal getInstance() {

88

return INSTANCE;

89

}

90

91

public String getId() {

92

return null;

93

}

94

95

public Map<String, List<Object>> getAttributes() {

96

return Collections.emptyMap();

97

}

98

99

@Override

100

public String toString() {

101

return "[Principal id=null, attributes={}]";

102

}

103

}

104

```

105

106

## Principal Factory

107

108

Principal factories are responsible for creating principal instances:

109

110

```java { .api }

111

package org.apereo.cas.authentication.principal;

112

113

import java.util.List;

114

import java.util.Map;

115

116

public interface PrincipalFactory {

117

Principal createPrincipal(String id);

118

Principal createPrincipal(String id, Map<String, List<Object>> attributes);

119

Principal createPrincipal(String id, Map<String, List<Object>> attributes,

120

Map<String, List<Object>> originalAttributes);

121

}

122

```

123

124

### Default Principal Factory

125

126

```java { .api }

127

package org.apereo.cas.authentication.principal;

128

129

import java.util.List;

130

import java.util.Map;

131

132

public class DefaultPrincipalFactory implements PrincipalFactory {

133

134

public Principal createPrincipal(String id) {

135

return new SimplePrincipal(id);

136

}

137

138

public Principal createPrincipal(String id, Map<String, List<Object>> attributes) {

139

return new SimplePrincipal(id, attributes);

140

}

141

142

public Principal createPrincipal(String id,

143

Map<String, List<Object>> attributes,

144

Map<String, List<Object>> originalAttributes) {

145

return new SimplePrincipal(id, attributes);

146

}

147

}

148

```

149

150

### Groovy Principal Factory

151

152

Factory that uses Groovy scripts for dynamic principal creation:

153

154

```java { .api }

155

package org.apereo.cas.authentication.principal;

156

157

import org.apereo.cas.util.scripting.ExecutableCompiledGroovyScript;

158

import java.util.List;

159

import java.util.Map;

160

161

public class GroovyPrincipalFactory implements PrincipalFactory {

162

private final ExecutableCompiledGroovyScript watchableScript;

163

164

public GroovyPrincipalFactory(ExecutableCompiledGroovyScript watchableScript) {

165

this.watchableScript = watchableScript;

166

}

167

168

public Principal createPrincipal(String id, Map<String, List<Object>> attributes) {

169

Object result = watchableScript.execute(id, attributes, Principal.class);

170

if (result instanceof Principal) {

171

return (Principal) result;

172

}

173

return new SimplePrincipal(id, attributes);

174

}

175

176

public Principal createPrincipal(String id) {

177

return createPrincipal(id, null);

178

}

179

180

public Principal createPrincipal(String id,

181

Map<String, List<Object>> attributes,

182

Map<String, List<Object>> originalAttributes) {

183

Object result = watchableScript.execute(id, attributes, originalAttributes, Principal.class);

184

if (result instanceof Principal) {

185

return (Principal) result;

186

}

187

return new SimplePrincipal(id, attributes);

188

}

189

}

190

```

191

192

### RESTful Principal Factory

193

194

Factory that creates principals by calling REST endpoints:

195

196

```java { .api }

197

package org.apereo.cas.authentication.principal;

198

199

import org.springframework.http.HttpEntity;

200

import org.springframework.http.HttpMethod;

201

import org.springframework.web.client.RestTemplate;

202

import java.util.List;

203

import java.util.Map;

204

205

public class RestfulPrincipalFactory implements PrincipalFactory {

206

private final RestTemplate restTemplate;

207

private final String endpoint;

208

209

public RestfulPrincipalFactory(RestTemplate restTemplate, String endpoint) {

210

this.restTemplate = restTemplate;

211

this.endpoint = endpoint;

212

}

213

214

public Principal createPrincipal(String id, Map<String, List<Object>> attributes) {

215

try {

216

Map<String, Object> request = Map.of("id", id, "attributes", attributes);

217

HttpEntity<Map<String, Object>> entity = new HttpEntity<>(request);

218

219

Map<String, Object> response = restTemplate.exchange(

220

endpoint, HttpMethod.POST, entity, Map.class).getBody();

221

222

if (response != null && response.containsKey("id")) {

223

String principalId = (String) response.get("id");

224

Map<String, List<Object>> principalAttributes =

225

(Map<String, List<Object>>) response.get("attributes");

226

return new SimplePrincipal(principalId, principalAttributes);

227

}

228

} catch (Exception e) {

229

// Log error and fall back to default behavior

230

}

231

232

return new SimplePrincipal(id, attributes);

233

}

234

235

public Principal createPrincipal(String id) {

236

return createPrincipal(id, null);

237

}

238

239

public Principal createPrincipal(String id,

240

Map<String, List<Object>> attributes,

241

Map<String, List<Object>> originalAttributes) {

242

return createPrincipal(id, attributes);

243

}

244

}

245

```

246

247

## Principal Factory Utilities

248

249

```java { .api }

250

package org.apereo.cas.authentication.principal;

251

252

public final class PrincipalFactoryUtils {

253

private PrincipalFactoryUtils() {}

254

255

public static PrincipalFactory newPrincipalFactory() {

256

return new DefaultPrincipalFactory();

257

}

258

}

259

```

260

261

## Principal Election Strategy

262

263

When multiple authentication handlers produce different principals, an election strategy determines which principal to use:

264

265

```java { .api }

266

package org.apereo.cas.authentication.principal;

267

268

import org.apereo.cas.authentication.Authentication;

269

import java.util.Collection;

270

271

public interface PrincipalElectionStrategy {

272

Principal electPrincipal(Collection<Authentication> authentications);

273

}

274

```

275

276

### Default Election Strategy

277

278

```java { .api }

279

package org.apereo.cas.authentication.principal;

280

281

import org.apereo.cas.authentication.Authentication;

282

import java.util.Collection;

283

284

public class DefaultPrincipalElectionStrategy implements PrincipalElectionStrategy {

285

286

public Principal electPrincipal(Collection<Authentication> authentications) {

287

return authentications.stream()

288

.findFirst()

289

.map(Authentication::getPrincipal)

290

.orElse(NullPrincipal.getInstance());

291

}

292

}

293

```

294

295

### Chaining Election Strategy

296

297

```java { .api }

298

package org.apereo.cas.authentication.principal;

299

300

import org.apereo.cas.authentication.Authentication;

301

import java.util.ArrayList;

302

import java.util.Collection;

303

import java.util.List;

304

305

public class ChainingPrincipalElectionStrategy implements PrincipalElectionStrategy {

306

private final List<PrincipalElectionStrategy> chain = new ArrayList<>();

307

308

public void addStrategy(PrincipalElectionStrategy strategy) {

309

chain.add(strategy);

310

}

311

312

public Principal electPrincipal(Collection<Authentication> authentications) {

313

return chain.stream()

314

.map(strategy -> strategy.electPrincipal(authentications))

315

.filter(principal -> principal != null && !NullPrincipal.getInstance().equals(principal))

316

.findFirst()

317

.orElse(NullPrincipal.getInstance());

318

}

319

}

320

```

321

322

## Principal Resolvers

323

324

Principal resolvers are responsible for enriching principals with attributes from external sources:

325

326

```java { .api }

327

package org.apereo.cas.authentication.principal;

328

329

import org.apereo.cas.authentication.AuthenticationHandler;

330

import org.apereo.cas.authentication.Credential;

331

import java.util.Optional;

332

333

public interface PrincipalResolver {

334

Optional<Principal> resolve(Credential credential,

335

Optional<Principal> currentPrincipal,

336

Optional<AuthenticationHandler> handler);

337

boolean supports(Credential credential);

338

IPersonAttributeDao getAttributeRepository();

339

}

340

```

341

342

### Echoing Principal Resolver

343

344

Simple resolver that returns the principal as-is:

345

346

```java { .api }

347

package org.apereo.cas.authentication.principal.resolvers;

348

349

import org.apereo.cas.authentication.AuthenticationHandler;

350

import org.apereo.cas.authentication.Credential;

351

import org.apereo.cas.authentication.principal.Principal;

352

import org.apereo.cas.authentication.principal.PrincipalResolver;

353

import java.util.Optional;

354

355

public class EchoingPrincipalResolver implements PrincipalResolver {

356

357

public Optional<Principal> resolve(Credential credential,

358

Optional<Principal> currentPrincipal,

359

Optional<AuthenticationHandler> handler) {

360

return currentPrincipal;

361

}

362

363

public boolean supports(Credential credential) {

364

return true;

365

}

366

367

public IPersonAttributeDao getAttributeRepository() {

368

return null;

369

}

370

}

371

```

372

373

### Proxying Principal Resolver

374

375

Resolver that creates proxy principals for service authentication:

376

377

```java { .api }

378

package org.apereo.cas.authentication.principal.resolvers;

379

380

import org.apereo.cas.authentication.AuthenticationHandler;

381

import org.apereo.cas.authentication.Credential;

382

import org.apereo.cas.authentication.principal.Principal;

383

import org.apereo.cas.authentication.principal.PrincipalFactory;

384

import org.apereo.cas.authentication.principal.PrincipalResolver;

385

import java.util.Optional;

386

387

public class ProxyingPrincipalResolver implements PrincipalResolver {

388

private final PrincipalFactory principalFactory;

389

390

public ProxyingPrincipalResolver(PrincipalFactory principalFactory) {

391

this.principalFactory = principalFactory;

392

}

393

394

public Optional<Principal> resolve(Credential credential,

395

Optional<Principal> currentPrincipal,

396

Optional<AuthenticationHandler> handler) {

397

String principalId = credential.getId();

398

if (principalId != null) {

399

Principal principal = principalFactory.createPrincipal(principalId);

400

return Optional.of(principal);

401

}

402

return currentPrincipal;

403

}

404

405

public boolean supports(Credential credential) {

406

return credential != null && credential.getId() != null;

407

}

408

409

public IPersonAttributeDao getAttributeRepository() {

410

return null;

411

}

412

}

413

```

414

415

### Chaining Principal Resolver

416

417

Resolver that chains multiple resolvers together:

418

419

```java { .api }

420

package org.apereo.cas.authentication.principal.resolvers;

421

422

import org.apereo.cas.authentication.AuthenticationHandler;

423

import org.apereo.cas.authentication.Credential;

424

import org.apereo.cas.authentication.principal.Principal;

425

import org.apereo.cas.authentication.principal.PrincipalResolver;

426

import java.util.ArrayList;

427

import java.util.List;

428

import java.util.Optional;

429

430

public class ChainingPrincipalResolver implements PrincipalResolver {

431

private final List<PrincipalResolver> chain = new ArrayList<>();

432

433

public void addResolver(PrincipalResolver resolver) {

434

chain.add(resolver);

435

}

436

437

public Optional<Principal> resolve(Credential credential,

438

Optional<Principal> currentPrincipal,

439

Optional<AuthenticationHandler> handler) {

440

Optional<Principal> result = currentPrincipal;

441

442

for (PrincipalResolver resolver : chain) {

443

if (resolver.supports(credential)) {

444

result = resolver.resolve(credential, result, handler);

445

if (result.isPresent()) {

446

break;

447

}

448

}

449

}

450

451

return result;

452

}

453

454

public boolean supports(Credential credential) {

455

return chain.stream().anyMatch(resolver -> resolver.supports(credential));

456

}

457

458

public IPersonAttributeDao getAttributeRepository() {

459

return chain.stream()

460

.map(PrincipalResolver::getAttributeRepository)

461

.filter(Objects::nonNull)

462

.findFirst()

463

.orElse(null);

464

}

465

}

466

```

467

468

## Attribute Merging Strategies

469

470

Attribute mergers determine how to combine attributes from multiple sources:

471

472

```java { .api }

473

package org.apereo.cas.authentication.principal.merger;

474

475

import java.util.List;

476

import java.util.Map;

477

478

public interface AttributeMerger {

479

Map<String, List<Object>> mergeAttributes(Map<String, List<Object>> toModify,

480

Map<String, List<Object>> toMerge);

481

}

482

```

483

484

### Base Additive Attribute Merger

485

486

```java { .api }

487

package org.apereo.cas.authentication.principal.merger;

488

489

import java.util.ArrayList;

490

import java.util.LinkedHashMap;

491

import java.util.List;

492

import java.util.Map;

493

494

public abstract class BaseAdditiveAttributeMerger implements AttributeMerger {

495

496

public final Map<String, List<Object>> mergeAttributes(Map<String, List<Object>> toModify,

497

Map<String, List<Object>> toMerge) {

498

Map<String, List<Object>> results = new LinkedHashMap<>(toModify);

499

500

for (Map.Entry<String, List<Object>> entry : toMerge.entrySet()) {

501

String key = entry.getKey();

502

List<Object> values = entry.getValue();

503

504

if (results.containsKey(key)) {

505

List<Object> existingValues = new ArrayList<>(results.get(key));

506

List<Object> mergedValues = mergeValues(existingValues, values);

507

results.put(key, mergedValues);

508

} else {

509

results.put(key, new ArrayList<>(values));

510

}

511

}

512

513

return results;

514

}

515

516

protected abstract List<Object> mergeValues(List<Object> existing, List<Object> additional);

517

}

518

```

519

520

### Multivalued Attribute Merger

521

522

Adds all values from both sources:

523

524

```java { .api }

525

package org.apereo.cas.authentication.principal.merger;

526

527

import java.util.ArrayList;

528

import java.util.List;

529

530

public class MultivaluedAttributeMerger extends BaseAdditiveAttributeMerger {

531

532

protected List<Object> mergeValues(List<Object> existing, List<Object> additional) {

533

List<Object> merged = new ArrayList<>(existing);

534

merged.addAll(additional);

535

return merged;

536

}

537

}

538

```

539

540

### Non-colliding Attribute Adder

541

542

Only adds attributes that don't already exist:

543

544

```java { .api }

545

package org.apereo.cas.authentication.principal.merger;

546

547

import java.util.List;

548

import java.util.Map;

549

import java.util.LinkedHashMap;

550

551

public class NoncollidingAttributeAdder implements AttributeMerger {

552

553

public Map<String, List<Object>> mergeAttributes(Map<String, List<Object>> toModify,

554

Map<String, List<Object>> toMerge) {

555

Map<String, List<Object>> results = new LinkedHashMap<>(toModify);

556

557

for (Map.Entry<String, List<Object>> entry : toMerge.entrySet()) {

558

String key = entry.getKey();

559

if (!results.containsKey(key)) {

560

results.put(key, entry.getValue());

561

}

562

}

563

564

return results;

565

}

566

}

567

```

568

569

### Replacing Attribute Adder

570

571

Replaces existing attributes with new values:

572

573

```java { .api }

574

package org.apereo.cas.authentication.principal.merger;

575

576

import java.util.LinkedHashMap;

577

import java.util.List;

578

import java.util.Map;

579

580

public class ReplacingAttributeAdder implements AttributeMerger {

581

582

public Map<String, List<Object>> mergeAttributes(Map<String, List<Object>> toModify,

583

Map<String, List<Object>> toMerge) {

584

Map<String, List<Object>> results = new LinkedHashMap<>(toModify);

585

results.putAll(toMerge);

586

return results;

587

}

588

}

589

```

590

591

### Return Changes Attribute Merger

592

593

Only returns the new/changed attributes:

594

595

```java { .api }

596

package org.apereo.cas.authentication.principal.merger;

597

598

import java.util.LinkedHashMap;

599

import java.util.List;

600

import java.util.Map;

601

602

public class ReturnChangesAttributeMerger implements AttributeMerger {

603

604

public Map<String, List<Object>> mergeAttributes(Map<String, List<Object>> toModify,

605

Map<String, List<Object>> toMerge) {

606

return new LinkedHashMap<>(toMerge);

607

}

608

}

609

```

610

611

### Return Original Attribute Merger

612

613

Always returns the original attributes unchanged:

614

615

```java { .api }

616

package org.apereo.cas.authentication.principal.merger;

617

618

import java.util.LinkedHashMap;

619

import java.util.List;

620

import java.util.Map;

621

622

public class ReturnOriginalAttributeMerger implements AttributeMerger {

623

624

public Map<String, List<Object>> mergeAttributes(Map<String, List<Object>> toModify,

625

Map<String, List<Object>> toMerge) {

626

return new LinkedHashMap<>(toModify);

627

}

628

}

629

```

630

631

## Principal Name Transformation

632

633

```java { .api }

634

package org.apereo.cas.authentication.principal;

635

636

import org.apereo.cas.util.transforms.PrefixSuffixPrincipalNameTransformer;

637

import org.apereo.cas.util.transforms.RegexPrincipalNameTransformer;

638

import org.apereo.cas.util.transforms.UpperCasePrincipalNameTransformer;

639

import org.apereo.cas.util.transforms.LowerCasePrincipalNameTransformer;

640

641

public final class PrincipalNameTransformerUtils {

642

643

public static PrincipalNameTransformer newPrefixSuffixTransformer(String prefix, String suffix) {

644

return new PrefixSuffixPrincipalNameTransformer(prefix, suffix);

645

}

646

647

public static PrincipalNameTransformer newRegexTransformer(String pattern, String replacement) {

648

return new RegexPrincipalNameTransformer(pattern, replacement);

649

}

650

651

public static PrincipalNameTransformer newUpperCaseTransformer() {

652

return new UpperCasePrincipalNameTransformer();

653

}

654

655

public static PrincipalNameTransformer newLowerCaseTransformer() {

656

return new LowerCasePrincipalNameTransformer();

657

}

658

}

659

```

660

661

## Resolution Execution Plan

662

663

```java { .api }

664

package org.apereo.cas.authentication.principal;

665

666

import org.apereo.cas.authentication.AuthenticationHandler;

667

import java.util.Collection;

668

669

public interface PrincipalResolutionExecutionPlan {

670

void registerPrincipalResolver(PrincipalResolver resolver);

671

void registerPrincipalResolver(PrincipalResolver resolver, AuthenticationHandler handler);

672

Collection<PrincipalResolver> getRegisteredPrincipalResolvers();

673

}

674

675

public class DefaultPrincipalResolutionExecutionPlan implements PrincipalResolutionExecutionPlan {

676

private final Map<AuthenticationHandler, PrincipalResolver> resolverMap = new LinkedHashMap<>();

677

private PrincipalResolver globalResolver;

678

679

public void registerPrincipalResolver(PrincipalResolver resolver) {

680

this.globalResolver = resolver;

681

}

682

683

public void registerPrincipalResolver(PrincipalResolver resolver, AuthenticationHandler handler) {

684

resolverMap.put(handler, resolver);

685

}

686

687

public Collection<PrincipalResolver> getRegisteredPrincipalResolvers() {

688

Collection<PrincipalResolver> resolvers = new ArrayList<>(resolverMap.values());

689

if (globalResolver != null) {

690

resolvers.add(globalResolver);

691

}

692

return resolvers;

693

}

694

}

695

```

696

697

## Configuration Examples

698

699

### Spring Configuration

700

701

```java { .api }

702

@Configuration

703

public class PrincipalResolutionConfiguration {

704

705

@Bean

706

public PrincipalFactory principalFactory() {

707

return new DefaultPrincipalFactory();

708

}

709

710

@Bean

711

public PrincipalElectionStrategy principalElectionStrategy() {

712

return new DefaultPrincipalElectionStrategy();

713

}

714

715

@Bean

716

public AttributeMerger attributeMerger() {

717

return new MultivaluedAttributeMerger();

718

}

719

720

@Bean

721

public PrincipalResolver echoingPrincipalResolver() {

722

return new EchoingPrincipalResolver();

723

}

724

}

725

```

726

727

### Programmatic Usage

728

729

```java { .api }

730

// Create principal with attributes

731

Map<String, List<Object>> attributes = Map.of(

732

"email", List.of("user@example.com"),

733

"roles", List.of("admin", "user"),

734

"department", List.of("IT")

735

);

736

737

PrincipalFactory factory = new DefaultPrincipalFactory();

738

Principal principal = factory.createPrincipal("username", attributes);

739

740

// Merge attributes from multiple sources

741

AttributeMerger merger = new MultivaluedAttributeMerger();

742

Map<String, List<Object>> additionalAttrs = Map.of(

743

"groups", List.of("developers", "admins"),

744

"roles", List.of("manager") // This will be merged with existing roles

745

);

746

747

Map<String, List<Object>> mergedAttrs = merger.mergeAttributes(

748

principal.getAttributes(), additionalAttrs);

749

750

// Create election strategy chain

751

ChainingPrincipalElectionStrategy electionStrategy = new ChainingPrincipalElectionStrategy();

752

electionStrategy.addStrategy(new DefaultPrincipalElectionStrategy());

753

```

754

755

Principal resolution provides the foundation for user identity management in CAS, allowing flexible attribute resolution, merging strategies, and principal election to support complex enterprise requirements.