or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

authentication.mdauthorization.mdexceptions.mdindex.md

exceptions.mddocs/

0

# Exception Handling

1

2

The CDAP Security SPI provides a structured exception hierarchy with HTTP status codes for proper error handling in web contexts and authorization scenarios.

3

4

## Exception Hierarchy

5

6

All security-related exceptions implement `HttpErrorStatusProvider` to provide appropriate HTTP status codes for web applications.

7

8

### UnauthorizedException

9

10

Runtime exception thrown when a Principal is not authorized to perform an Action on an EntityId.

11

12

```java { .api }

13

class UnauthorizedException extends RuntimeException implements HttpErrorStatusProvider {

14

/**

15

* Create exception for single action authorization failure.

16

*/

17

UnauthorizedException(Principal principal, Action action, EntityId entityId);

18

19

/**

20

* Create exception for multiple actions authorization failure.

21

*/

22

UnauthorizedException(Principal principal, Set<Action> actions, EntityId entityId);

23

24

/**

25

* Create exception with cause for multiple actions authorization failure.

26

*/

27

UnauthorizedException(Principal principal, Set<Action> actions, EntityId entityId, Throwable ex);

28

29

/**

30

* Create exception for entity access denial.

31

*/

32

UnauthorizedException(Principal principal, EntityId entityId);

33

34

/**

35

* Create exception for conditional authorization failure.

36

*/

37

UnauthorizedException(Principal principal, Set<Action> actions, EntityId entityId, boolean needHaveAll);

38

39

/**

40

* Create exception with custom message.

41

*/

42

UnauthorizedException(String message);

43

44

/**

45

* Get HTTP status code.

46

*

47

* @return HTTP_FORBIDDEN (403)

48

*/

49

int getStatusCode();

50

}

51

```

52

53

### AlreadyExistsException

54

55

Checked exception thrown when attempting to create a Role or entity that already exists.

56

57

```java { .api }

58

class AlreadyExistsException extends Exception implements HttpErrorStatusProvider {

59

/**

60

* Create exception for role that already exists.

61

*/

62

AlreadyExistsException(Role role);

63

64

/**

65

* Create exception with custom message.

66

*/

67

AlreadyExistsException(String message);

68

69

/**

70

* Get HTTP status code.

71

*

72

* @return HTTP_CONFLICT (409)

73

*/

74

int getStatusCode();

75

}

76

```

77

78

### BadRequestException

79

80

Checked exception thrown for invalid input scenarios.

81

82

```java { .api }

83

class BadRequestException extends Exception implements HttpErrorStatusProvider {

84

/**

85

* Create exception for role-related bad request.

86

*/

87

BadRequestException(Role role);

88

89

/**

90

* Create exception with custom message.

91

*/

92

BadRequestException(String message);

93

94

/**

95

* Get HTTP status code.

96

*

97

* @return HTTP_BAD_REQUEST (400)

98

*/

99

int getStatusCode();

100

}

101

```

102

103

### NotFoundException

104

105

Checked exception thrown when attempting to access unknown entities or roles.

106

107

```java { .api }

108

class NotFoundException extends Exception implements HttpErrorStatusProvider {

109

/**

110

* Create exception for role not found.

111

*/

112

NotFoundException(Role role);

113

114

/**

115

* Create exception with custom message.

116

*/

117

NotFoundException(String message);

118

119

/**

120

* Get HTTP status code.

121

*

122

* @return HTTP_NOT_FOUND (404)

123

*/

124

int getStatusCode();

125

}

126

```

127

128

## Usage Examples

129

130

### Authorization Enforcement

131

132

```java

133

public class MyAuthorizer extends AbstractAuthorizer {

134

135

@Override

136

public void enforce(EntityId entity, Principal principal, Set<Action> actions)

137

throws Exception {

138

139

// Check if user exists

140

if (!userExists(principal)) {

141

throw new UnauthorizedException("Principal '" + principal.getName() + "' does not exist");

142

}

143

144

// Check each required action

145

Set<Action> missingActions = new HashSet<>();

146

for (Action action : actions) {

147

if (!hasPermission(entity, principal, action)) {

148

missingActions.add(action);

149

}

150

}

151

152

// Throw exception if any actions are not permitted

153

if (!missingActions.isEmpty()) {

154

throw new UnauthorizedException(principal, missingActions, entity);

155

}

156

}

157

158

@Override

159

public void createRole(Role role) throws Exception {

160

if (roleExists(role)) {

161

throw new AlreadyExistsException(role);

162

}

163

164

// Create the role

165

createRoleInBackend(role);

166

}

167

168

@Override

169

public void dropRole(Role role) throws Exception {

170

if (!roleExists(role)) {

171

throw new NotFoundException(role);

172

}

173

174

// Delete the role

175

deleteRoleFromBackend(role);

176

}

177

}

178

```

179

180

### Web Controller Error Handling

181

182

```java

183

@RestController

184

public class AuthorizationController {

185

186

@PostMapping("/roles")

187

public ResponseEntity<String> createRole(@RequestBody Role role) {

188

try {

189

authorizer.createRole(role);

190

return ResponseEntity.ok("Role created successfully");

191

192

} catch (AlreadyExistsException e) {

193

return ResponseEntity.status(e.getStatusCode())

194

.body("Role already exists: " + e.getMessage());

195

196

} catch (BadRequestException e) {

197

return ResponseEntity.status(e.getStatusCode())

198

.body("Invalid role data: " + e.getMessage());

199

}

200

}

201

202

@DeleteMapping("/roles/{roleName}")

203

public ResponseEntity<String> deleteRole(@PathVariable String roleName) {

204

try {

205

Role role = new Role(roleName);

206

authorizer.dropRole(role);

207

return ResponseEntity.ok("Role deleted successfully");

208

209

} catch (NotFoundException e) {

210

return ResponseEntity.status(e.getStatusCode())

211

.body("Role not found: " + e.getMessage());

212

213

} catch (Exception e) {

214

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)

215

.body("Error deleting role: " + e.getMessage());

216

}

217

}

218

219

@PostMapping("/authorize")

220

public ResponseEntity<String> checkAuthorization(

221

@RequestBody AuthorizationRequest request) {

222

223

try {

224

authorizer.enforce(request.getEntity(), request.getPrincipal(),

225

request.getActions());

226

return ResponseEntity.ok("Authorized");

227

228

} catch (UnauthorizedException e) {

229

return ResponseEntity.status(e.getStatusCode())

230

.body("Access denied: " + e.getMessage());

231

232

} catch (Exception e) {

233

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)

234

.body("Authorization check failed: " + e.getMessage());

235

}

236

}

237

}

238

```

239

240

### Exception Handling with Detailed Messages

241

242

```java

243

public class DetailedAuthorizationEnforcer implements AuthorizationEnforcer {

244

245

@Override

246

public void enforce(EntityId entity, Principal principal, Set<Action> actions)

247

throws Exception {

248

249

List<String> errors = new ArrayList<>();

250

251

// Validate principal

252

if (principal == null || principal.getName() == null) {

253

throw new UnauthorizedException("Principal cannot be null or have null name");

254

}

255

256

// Check if principal exists

257

if (!principalExists(principal)) {

258

throw new UnauthorizedException("Principal '" + principal.getName() +

259

"' of type " + principal.getType() + " does not exist");

260

}

261

262

// Check each action individually for detailed error reporting

263

for (Action action : actions) {

264

if (!hasPermission(entity, principal, action)) {

265

errors.add("Missing permission for action: " + action.name());

266

}

267

}

268

269

if (!errors.isEmpty()) {

270

String detailedMessage = String.format(

271

"Principal '%s' is not authorized to perform actions %s on entity '%s'. Reasons: %s",

272

principal.getName(), actions, entity, String.join("; ", errors));

273

274

throw new UnauthorizedException(detailedMessage);

275

}

276

}

277

278

@Override

279

public Set<? extends EntityId> isVisible(Set<? extends EntityId> entityIds,

280

Principal principal) throws Exception {

281

282

if (principal == null) {

283

throw new UnauthorizedException("Cannot check visibility with null principal");

284

}

285

286

return entityIds.stream()

287

.filter(entity -> {

288

try {

289

// Check if principal has any permission on this entity

290

return hasAnyPermission(entity, principal);

291

} catch (Exception e) {

292

// Log error but don't fail the entire operation

293

logger.warn("Error checking visibility for entity {} and principal {}: {}",

294

entity, principal, e.getMessage());

295

return false;

296

}

297

})

298

.collect(Collectors.toSet());

299

}

300

}

301

```

302

303

### Global Exception Handler

304

305

```java

306

@ControllerAdvice

307

public class SecurityExceptionHandler {

308

309

@ExceptionHandler(UnauthorizedException.class)

310

public ResponseEntity<ErrorResponse> handleUnauthorized(UnauthorizedException e) {

311

ErrorResponse error = new ErrorResponse(

312

"UNAUTHORIZED",

313

e.getMessage(),

314

System.currentTimeMillis()

315

);

316

317

return ResponseEntity.status(e.getStatusCode()).body(error);

318

}

319

320

@ExceptionHandler(AlreadyExistsException.class)

321

public ResponseEntity<ErrorResponse> handleAlreadyExists(AlreadyExistsException e) {

322

ErrorResponse error = new ErrorResponse(

323

"ALREADY_EXISTS",

324

e.getMessage(),

325

System.currentTimeMillis()

326

);

327

328

return ResponseEntity.status(e.getStatusCode()).body(error);

329

}

330

331

@ExceptionHandler(NotFoundException.class)

332

public ResponseEntity<ErrorResponse> handleNotFound(NotFoundException e) {

333

ErrorResponse error = new ErrorResponse(

334

"NOT_FOUND",

335

e.getMessage(),

336

System.currentTimeMillis()

337

);

338

339

return ResponseEntity.status(e.getStatusCode()).body(error);

340

}

341

342

@ExceptionHandler(BadRequestException.class)

343

public ResponseEntity<ErrorResponse> handleBadRequest(BadRequestException e) {

344

ErrorResponse error = new ErrorResponse(

345

"BAD_REQUEST",

346

e.getMessage(),

347

System.currentTimeMillis()

348

);

349

350

return ResponseEntity.status(e.getStatusCode()).body(error);

351

}

352

353

public static class ErrorResponse {

354

private final String error;

355

private final String message;

356

private final long timestamp;

357

358

public ErrorResponse(String error, String message, long timestamp) {

359

this.error = error;

360

this.message = message;

361

this.timestamp = timestamp;

362

}

363

364

// Getters...

365

}

366

}

367

```

368

369

### Audit Logging with Exceptions

370

371

```java

372

public class AuditingAuthorizer extends AbstractAuthorizer {

373

private final AuditLogger auditLogger;

374

375

@Override

376

public void enforce(EntityId entity, Principal principal, Set<Action> actions)

377

throws Exception {

378

379

long startTime = System.currentTimeMillis();

380

381

try {

382

// Perform authorization check

383

performAuthorizationCheck(entity, principal, actions);

384

385

// Log successful authorization

386

auditLogger.logAuthorizationSuccess(principal, entity, actions,

387

System.currentTimeMillis() - startTime);

388

389

} catch (UnauthorizedException e) {

390

// Log authorization failure with details

391

auditLogger.logAuthorizationFailure(principal, entity, actions,

392

e.getMessage(), System.currentTimeMillis() - startTime);

393

394

// Re-throw the exception

395

throw e;

396

397

} catch (Exception e) {

398

// Log unexpected errors

399

auditLogger.logAuthorizationError(principal, entity, actions,

400

e.getClass().getSimpleName() + ": " + e.getMessage(),

401

System.currentTimeMillis() - startTime);

402

403

// Re-throw the exception

404

throw e;

405

}

406

}

407

}