or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-utilities.mdcryptography.mdfunctional-programming.mdgenerators.mdhttp-clients.mdindex.mdjwt-utilities.mdserialization.mdspecialized-utilities.mdspring-integration.mdtext-processing.md

text-processing.mddocs/

0

# Text Processing

1

2

Message sanitization and text processing utilities for secure handling of user input, system messages, and principal name transformations in CAS applications.

3

4

## Message Sanitization

5

6

### MessageSanitizer Interface

7

8

Core interface for message sanitization providing secure handling of potentially malicious or sensitive content.

9

10

```java { .api }

11

public interface MessageSanitizer {

12

13

// Primary sanitization method

14

String sanitize(String message);

15

16

// Batch sanitization

17

Collection<String> sanitize(Collection<String> messages);

18

Map<String, String> sanitize(Map<String, String> messageMap);

19

20

// Configuration methods

21

boolean isEnabled();

22

Set<String> getSupportedPatterns();

23

}

24

```

25

26

### DefaultMessageSanitizer

27

28

Default implementation providing comprehensive message sanitization with configurable rules and patterns.

29

30

```java { .api }

31

public class DefaultMessageSanitizer implements MessageSanitizer {

32

33

// Configuration

34

private final boolean enabled;

35

private final Set<Pattern> sanitizationPatterns;

36

private final Map<String, String> replacementMap;

37

38

// Constructors

39

public DefaultMessageSanitizer();

40

public DefaultMessageSanitizer(boolean enabled);

41

public DefaultMessageSanitizer(Set<Pattern> patterns, Map<String, String> replacements);

42

43

// MessageSanitizer implementation

44

@Override

45

public String sanitize(String message);

46

47

@Override

48

public Collection<String> sanitize(Collection<String> messages);

49

50

@Override

51

public Map<String, String> sanitize(Map<String, String> messageMap);

52

53

@Override

54

public boolean isEnabled();

55

56

@Override

57

public Set<String> getSupportedPatterns();

58

59

// Configuration methods

60

public void addSanitizationPattern(Pattern pattern, String replacement);

61

public void addSanitizationRule(String regex, String replacement);

62

public void removeSanitizationPattern(Pattern pattern);

63

}

64

```

65

66

### Usage Examples

67

68

**Basic message sanitization:**

69

```java

70

@Service

71

public class UserInputService {

72

73

private final MessageSanitizer messageSanitizer;

74

75

public UserInputService(MessageSanitizer messageSanitizer) {

76

this.messageSanitizer = messageSanitizer;

77

}

78

79

public String processUserComment(String rawComment) {

80

if (StringUtils.isBlank(rawComment)) {

81

return rawComment;

82

}

83

84

// Sanitize potentially malicious content

85

String sanitized = messageSanitizer.sanitize(rawComment);

86

87

// Additional validation

88

if (sanitized.length() > 1000) {

89

throw new IllegalArgumentException("Comment too long after sanitization");

90

}

91

92

return sanitized;

93

}

94

95

public Map<String, String> processFormData(Map<String, String> formData) {

96

// Sanitize all form field values

97

Map<String, String> sanitized = messageSanitizer.sanitize(formData);

98

99

// Log sanitization if content was changed

100

formData.forEach((key, original) -> {

101

String cleaned = sanitized.get(key);

102

if (!Objects.equals(original, cleaned)) {

103

log.info("Sanitized field '{}': '{}' -> '{}'", key, original, cleaned);

104

}

105

});

106

107

return sanitized;

108

}

109

110

public List<String> processErrorMessages(List<String> errorMessages) {

111

// Sanitize error messages before display

112

Collection<String> sanitized = messageSanitizer.sanitize(errorMessages);

113

return new ArrayList<>(sanitized);

114

}

115

}

116

```

117

118

**Custom sanitizer configuration:**

119

```java

120

@Configuration

121

public class SanitizationConfiguration {

122

123

@Bean

124

public MessageSanitizer customMessageSanitizer() {

125

DefaultMessageSanitizer sanitizer = new DefaultMessageSanitizer(true);

126

127

// Remove script tags

128

sanitizer.addSanitizationRule("<script[^>]*>.*?</script>", "");

129

130

// Remove potentially dangerous HTML attributes

131

sanitizer.addSanitizationRule("\\s*javascript\\s*:", "");

132

sanitizer.addSanitizationRule("\\s*vbscript\\s*:", "");

133

sanitizer.addSanitizationRule("\\s*onload\\s*=", "");

134

sanitizer.addSanitizationRule("\\s*onerror\\s*=", "");

135

136

// Sanitize SQL injection attempts

137

sanitizer.addSanitizationRule("(?i)(union|select|insert|update|delete|drop)\\s", "");

138

139

// Remove excessive whitespace

140

sanitizer.addSanitizationRule("\\s+", " ");

141

142

// Replace sensitive patterns

143

sanitizer.addSanitizationRule("password\\s*[=:]\\s*\\S+", "password=[REDACTED]");

144

sanitizer.addSanitizationRule("token\\s*[=:]\\s*\\S+", "token=[REDACTED]");

145

146

return sanitizer;

147

}

148

149

@Bean

150

@ConditionalOnProperty(name = "cas.message.sanitization.strict", havingValue = "true")

151

public MessageSanitizer strictMessageSanitizer() {

152

DefaultMessageSanitizer sanitizer = new DefaultMessageSanitizer(true);

153

154

// Strict HTML removal

155

sanitizer.addSanitizationRule("<[^>]+>", "");

156

157

// Allow only alphanumeric and basic punctuation

158

sanitizer.addSanitizationRule("[^a-zA-Z0-9\\s.,!?-]", "");

159

160

return sanitizer;

161

}

162

}

163

```

164

165

## MessageSanitationContributor Interface

166

167

Interface for contributing custom sanitization rules and patterns to the message sanitization process.

168

169

```java { .api }

170

public interface MessageSanitationContributor {

171

172

// Contribution methods

173

Collection<Pattern> getPatterns();

174

Map<String, String> getReplacements();

175

176

// Priority and ordering

177

int getOrder();

178

179

// Conditional application

180

boolean supports(String messageType);

181

boolean isEnabled();

182

}

183

```

184

185

### TicketCatalogMessageSanitationContributor

186

187

Specialized contributor for ticket-related message sanitization.

188

189

```java { .api }

190

public class TicketCatalogMessageSanitationContributor implements MessageSanitationContributor {

191

192

// Constructor

193

public TicketCatalogMessageSanitationContributor();

194

195

@Override

196

public Collection<Pattern> getPatterns();

197

198

@Override

199

public Map<String, String> getReplacements();

200

201

@Override

202

public int getOrder();

203

204

@Override

205

public boolean supports(String messageType);

206

207

@Override

208

public boolean isEnabled();

209

}

210

```

211

212

### Usage Examples

213

214

**Custom sanitization contributor:**

215

```java

216

@Component

217

@Order(100)

218

public class AuthenticationMessageSanitationContributor implements MessageSanitationContributor {

219

220

private final Collection<Pattern> patterns;

221

private final Map<String, String> replacements;

222

223

public AuthenticationMessageSanitationContributor() {

224

this.patterns = Arrays.asList(

225

Pattern.compile("(?i)credential\\s*[=:]\\s*\\S+"),

226

Pattern.compile("(?i)password\\s*[=:]\\s*\\S+"),

227

Pattern.compile("(?i)secret\\s*[=:]\\s*\\S+"),

228

Pattern.compile("TGT-\\d+-\\S+"), // Ticket Granting Tickets

229

Pattern.compile("ST-\\d+-\\S+") // Service Tickets

230

);

231

232

this.replacements = Map.of(

233

"(?i)credential\\s*[=:]\\s*\\S+", "credential=[PROTECTED]",

234

"(?i)password\\s*[=:]\\s*\\S+", "password=[PROTECTED]",

235

"(?i)secret\\s*[=:]\\s*\\S+", "secret=[PROTECTED]",

236

"TGT-\\d+-\\S+", "TGT-***",

237

"ST-\\d+-\\S+", "ST-***"

238

);

239

}

240

241

@Override

242

public Collection<Pattern> getPatterns() {

243

return patterns;

244

}

245

246

@Override

247

public Map<String, String> getReplacements() {

248

return replacements;

249

}

250

251

@Override

252

public int getOrder() {

253

return 100;

254

}

255

256

@Override

257

public boolean supports(String messageType) {

258

return messageType != null && (

259

messageType.contains("authentication") ||

260

messageType.contains("login") ||

261

messageType.contains("ticket")

262

);

263

}

264

265

@Override

266

public boolean isEnabled() {

267

return true;

268

}

269

}

270

271

@Component

272

@Order(200)

273

public class SessionMessageSanitationContributor implements MessageSanitationContributor {

274

275

@Override

276

public Collection<Pattern> getPatterns() {

277

return Arrays.asList(

278

Pattern.compile("JSESSIONID=[^;\\s]+"),

279

Pattern.compile("sessionId=[^;\\s]+"),

280

Pattern.compile("sid=[^;\\s]+")

281

);

282

}

283

284

@Override

285

public Map<String, String> getReplacements() {

286

return Map.of(

287

"JSESSIONID=[^;\\s]+", "JSESSIONID=[HIDDEN]",

288

"sessionId=[^;\\s]+", "sessionId=[HIDDEN]",

289

"sid=[^;\\s]+", "sid=[HIDDEN]"

290

);

291

}

292

293

@Override

294

public int getOrder() {

295

return 200;

296

}

297

298

@Override

299

public boolean supports(String messageType) {

300

return true; // Apply to all message types

301

}

302

303

@Override

304

public boolean isEnabled() {

305

return true;

306

}

307

}

308

```

309

310

**Composite sanitizer with contributors:**

311

```java

312

@Service

313

public class CompositeMessageSanitizationService {

314

315

private final List<MessageSanitationContributor> contributors;

316

private final MessageSanitizer baseSanitizer;

317

318

public CompositeMessageSanitizationService(

319

List<MessageSanitationContributor> contributors,

320

MessageSanitizer baseSanitizer) {

321

322

// Sort contributors by order

323

this.contributors = contributors.stream()

324

.filter(MessageSanitationContributor::isEnabled)

325

.sorted(Comparator.comparingInt(MessageSanitationContributor::getOrder))

326

.collect(Collectors.toList());

327

328

this.baseSanitizer = baseSanitizer;

329

}

330

331

public String sanitizeMessage(String message, String messageType) {

332

if (StringUtils.isBlank(message)) {

333

return message;

334

}

335

336

String sanitized = message;

337

338

// Apply base sanitization first

339

if (baseSanitizer.isEnabled()) {

340

sanitized = baseSanitizer.sanitize(sanitized);

341

}

342

343

// Apply contributor-specific sanitization

344

for (MessageSanitationContributor contributor : contributors) {

345

if (contributor.supports(messageType)) {

346

sanitized = applyContributorSanitization(sanitized, contributor);

347

}

348

}

349

350

return sanitized;

351

}

352

353

private String applyContributorSanitization(String message, MessageSanitationContributor contributor) {

354

String sanitized = message;

355

356

// Apply replacement patterns

357

Map<String, String> replacements = contributor.getReplacements();

358

for (Map.Entry<String, String> entry : replacements.entrySet()) {

359

sanitized = sanitized.replaceAll(entry.getKey(), entry.getValue());

360

}

361

362

// Apply additional patterns

363

Collection<Pattern> patterns = contributor.getPatterns();

364

for (Pattern pattern : patterns) {

365

sanitized = pattern.matcher(sanitized).replaceAll("[SANITIZED]");

366

}

367

368

return sanitized;

369

}

370

}

371

```

372

373

## Principal Name Transformers

374

375

### PrincipalNameTransformer Interface

376

377

Interface for transforming principal names during authentication processing.

378

379

```java { .api }

380

public interface PrincipalNameTransformer {

381

382

// Primary transformation method

383

String transform(String formUserId);

384

385

// Configuration methods

386

String getName();

387

boolean isEnabled();

388

}

389

```

390

391

### Core Transformer Implementations

392

393

#### NoOpPrincipalNameTransformer

394

395

No-operation transformer that returns the input unchanged.

396

397

```java { .api }

398

public class NoOpPrincipalNameTransformer implements PrincipalNameTransformer {

399

400

@Override

401

public String transform(String formUserId);

402

403

@Override

404

public String getName();

405

406

@Override

407

public boolean isEnabled();

408

}

409

```

410

411

#### ConvertCasePrincipalNameTransformer

412

413

Transformer for case conversion (uppercase/lowercase).

414

415

```java { .api }

416

public class ConvertCasePrincipalNameTransformer implements PrincipalNameTransformer {

417

418

// Case conversion modes

419

public enum CaseConversion {

420

UPPERCASE, LOWERCASE, NONE

421

}

422

423

// Constructor

424

public ConvertCasePrincipalNameTransformer(CaseConversion conversion);

425

426

@Override

427

public String transform(String formUserId);

428

}

429

```

430

431

#### PrefixSuffixPrincipalNameTransformer

432

433

Transformer for adding prefixes and/or suffixes to principal names.

434

435

```java { .api }

436

public class PrefixSuffixPrincipalNameTransformer implements PrincipalNameTransformer {

437

438

// Constructor

439

public PrefixSuffixPrincipalNameTransformer(String prefix, String suffix);

440

441

@Override

442

public String transform(String formUserId);

443

}

444

```

445

446

#### RegexPrincipalNameTransformer

447

448

Transformer using regular expressions for complex name transformations.

449

450

```java { .api }

451

public class RegexPrincipalNameTransformer implements PrincipalNameTransformer {

452

453

// Constructor

454

public RegexPrincipalNameTransformer(String pattern, String replacement);

455

public RegexPrincipalNameTransformer(Pattern compiledPattern, String replacement);

456

457

@Override

458

public String transform(String formUserId);

459

}

460

```

461

462

#### BlockingPrincipalNameTransformer

463

464

Transformer that blocks/rejects certain principal names.

465

466

```java { .api }

467

public class BlockingPrincipalNameTransformer implements PrincipalNameTransformer {

468

469

// Constructor

470

public BlockingPrincipalNameTransformer(Set<String> blockedNames);

471

public BlockingPrincipalNameTransformer(Pattern blockingPattern);

472

473

@Override

474

public String transform(String formUserId);

475

}

476

```

477

478

#### ChainingPrincipalNameTransformer

479

480

Transformer that chains multiple transformers in sequence.

481

482

```java { .api }

483

public class ChainingPrincipalNameTransformer implements PrincipalNameTransformer {

484

485

// Constructor

486

public ChainingPrincipalNameTransformer(List<PrincipalNameTransformer> transformers);

487

488

@Override

489

public String transform(String formUserId);

490

491

// Chain management

492

public void addTransformer(PrincipalNameTransformer transformer);

493

public List<PrincipalNameTransformer> getTransformers();

494

}

495

```

496

497

#### GroovyPrincipalNameTransformer

498

499

Transformer using Groovy scripts for dynamic transformations.

500

501

```java { .api }

502

public class GroovyPrincipalNameTransformer implements PrincipalNameTransformer {

503

504

// Constructor

505

public GroovyPrincipalNameTransformer(String groovyScript);

506

public GroovyPrincipalNameTransformer(Resource groovyScriptResource);

507

508

@Override

509

public String transform(String formUserId);

510

}

511

```

512

513

### Usage Examples

514

515

**Basic transformers:**

516

```java

517

@Configuration

518

public class PrincipalTransformerConfiguration {

519

520

@Bean

521

@ConditionalOnProperty(name = "cas.principal.transform.case", havingValue = "lowercase")

522

public PrincipalNameTransformer lowercaseTransformer() {

523

return new ConvertCasePrincipalNameTransformer(CaseConversion.LOWERCASE);

524

}

525

526

@Bean

527

@ConditionalOnProperty(name = "cas.principal.transform.domain.enabled", havingValue = "true")

528

public PrincipalNameTransformer domainTransformer(

529

@Value("${cas.principal.transform.domain.suffix:@example.com}") String suffix) {

530

531

return new PrefixSuffixPrincipalNameTransformer("", suffix);

532

}

533

534

@Bean

535

public PrincipalNameTransformer emailToUsernameTransformer() {

536

// Transform email addresses to usernames

537

return new RegexPrincipalNameTransformer("(.+)@.+", "$1");

538

}

539

540

@Bean

541

public PrincipalNameTransformer blockingTransformer() {

542

// Block administrative accounts

543

Set<String> blockedNames = Set.of("admin", "root", "administrator", "guest");

544

return new BlockingPrincipalNameTransformer(blockedNames);

545

}

546

}

547

```

548

549

**Complex chained transformations:**

550

```java

551

@Configuration

552

public class ComplexPrincipalTransformation {

553

554

@Bean

555

public PrincipalNameTransformer chainedPrincipalTransformer() {

556

List<PrincipalNameTransformer> transformers = Arrays.asList(

557

// 1. Block dangerous usernames first

558

new BlockingPrincipalNameTransformer(Set.of("admin", "root")),

559

560

// 2. Extract username from email if present

561

new RegexPrincipalNameTransformer("(.+)@.+", "$1"),

562

563

// 3. Convert to lowercase

564

new ConvertCasePrincipalNameTransformer(CaseConversion.LOWERCASE),

565

566

// 4. Remove special characters

567

new RegexPrincipalNameTransformer("[^a-z0-9._-]", ""),

568

569

// 5. Add domain if not present

570

new RegexPrincipalNameTransformer("^([^@]+)$", "$1@company.com")

571

);

572

573

return new ChainingPrincipalNameTransformer(transformers);

574

}

575

}

576

```

577

578

**Groovy-based dynamic transformation:**

579

```java

580

@Bean

581

@ConditionalOnProperty(name = "cas.principal.transform.groovy.enabled", havingValue = "true")

582

public PrincipalNameTransformer groovyPrincipalTransformer() {

583

String groovyScript = """

584

// Dynamic principal transformation script

585

586

// Input: formUserId (String)

587

// Output: transformed principal name (String)

588

589

if (formUserId == null || formUserId.isEmpty()) {

590

return formUserId

591

}

592

593

def transformed = formUserId.toLowerCase()

594

595

// Handle email addresses

596

if (transformed.contains('@')) {

597

def parts = transformed.split('@')

598

def username = parts[0]

599

def domain = parts[1]

600

601

// Map domains to internal format

602

def domainMappings = [

603

'gmail.com': 'external',

604

'company.com': 'internal',

605

'contractor.com': 'contractor'

606

]

607

608

def mappedDomain = domainMappings[domain] ?: 'unknown'

609

return username + '.' + mappedDomain

610

}

611

612

// Handle employee ID format (EMP123456)

613

if (transformed.startsWith('emp')) {

614

return transformed.substring(3)

615

}

616

617

// Default transformation

618

return transformed.replaceAll('[^a-z0-9._-]', '')

619

""";

620

621

return new GroovyPrincipalNameTransformer(groovyScript);

622

}

623

```

624

625

**Usage in authentication handler:**

626

```java

627

@Component

628

public class CustomAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {

629

630

private final PrincipalNameTransformer principalTransformer;

631

private final UserRepository userRepository;

632

633

public CustomAuthenticationHandler(

634

PrincipalNameTransformer principalTransformer,

635

UserRepository userRepository) {

636

this.principalTransformer = principalTransformer;

637

this.userRepository = userRepository;

638

}

639

640

@Override

641

protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(

642

UsernamePasswordCredential credential,

643

String originalPassword) throws GeneralSecurityException {

644

645

try {

646

// Transform the principal name

647

String originalUsername = credential.getUsername();

648

String transformedUsername = principalTransformer.transform(originalUsername);

649

650

log.debug("Transformed principal: '{}' -> '{}'", originalUsername, transformedUsername);

651

652

// Authenticate with transformed username

653

User user = userRepository.findByUsername(transformedUsername);

654

if (user == null) {

655

throw new AccountNotFoundException("User not found: " + transformedUsername);

656

}

657

658

if (!passwordEncoder.matches(originalPassword, user.getPasswordHash())) {

659

throw new FailedLoginException("Invalid credentials");

660

}

661

662

// Create principal with transformed name

663

Principal principal = principalFactory.createPrincipal(

664

transformedUsername,

665

user.getAttributes()

666

);

667

668

return createHandlerResult(credential, principal);

669

670

} catch (Exception e) {

671

log.error("Authentication failed for user: {}", credential.getUsername(), e);

672

throw new FailedLoginException("Authentication failed", e);

673

}

674

}

675

}

676

```

677

678

This text processing library provides comprehensive capabilities for secure message handling and flexible principal name transformations, essential for production CAS deployments with security and compliance requirements.