or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

constants-configuration.mdcore-functionality.mdcrypto-utilities.mdenums-types.mdindex.mdprofile-management.mdreflection-utilities.mdutility-functions.md

profile-management.mddocs/

0

# Profile Management

1

2

This document covers the profile management functionality in the `org.keycloak.common.profile` package that provides advanced feature flag management, configuration resolvers, and profile customization capabilities.

3

4

## Profile Configuration Resolver Interface

5

6

The `ProfileConfigResolver` interface defines the contract for resolving profile configurations from various sources.

7

8

```java { .api }

9

public interface ProfileConfigResolver {

10

/**

11

* Gets the profile name to use

12

*/

13

Profile.ProfileName getProfileName();

14

15

/**

16

* Gets the configuration for a specific feature

17

*/

18

FeatureConfig getFeatureConfig(String feature);

19

}

20

```

21

22

### FeatureConfig Enum

23

24

```java { .api }

25

public enum FeatureConfig {

26

/**

27

* Feature is explicitly enabled

28

*/

29

ENABLED,

30

31

/**

32

* Feature is explicitly disabled

33

*/

34

DISABLED,

35

36

/**

37

* Feature configuration not specified, use default

38

*/

39

UNCONFIGURED

40

}

41

```

42

43

## Properties-Based Configuration Resolver

44

45

The `PropertiesProfileConfigResolver` class resolves profile configuration from Java properties.

46

47

```java { .api }

48

public class PropertiesProfileConfigResolver implements ProfileConfigResolver {

49

/**

50

* Constructor taking properties object

51

*/

52

public PropertiesProfileConfigResolver(Properties properties);

53

54

/**

55

* Constructor taking property getter function

56

*/

57

public PropertiesProfileConfigResolver(UnaryOperator<String> getter);

58

59

/**

60

* Gets profile name from properties

61

*/

62

public Profile.ProfileName getProfileName();

63

64

/**

65

* Gets feature configuration from properties

66

*/

67

public FeatureConfig getFeatureConfig(String feature);

68

69

/**

70

* Gets property key for a feature

71

*/

72

public static String getPropertyKey(Feature feature);

73

74

/**

75

* Gets property key for a feature string

76

*/

77

public static String getPropertyKey(String feature);

78

}

79

```

80

81

### Usage Examples

82

83

```java

84

// Configure with Properties object

85

Properties props = new Properties();

86

props.setProperty("kc.profile", "preview");

87

props.setProperty("kc.features.authorization", "enabled");

88

props.setProperty("kc.features.scripts", "disabled");

89

90

ProfileConfigResolver resolver = new PropertiesProfileConfigResolver(props);

91

92

// Configure with system property getter

93

ProfileConfigResolver systemResolver = new PropertiesProfileConfigResolver(

94

System::getProperty

95

);

96

97

// Configure with environment variable getter

98

ProfileConfigResolver envResolver = new PropertiesProfileConfigResolver(

99

key -> System.getenv(key.replace('.', '_').toUpperCase())

100

);

101

102

// Get property keys for features

103

String authzKey = PropertiesProfileConfigResolver.getPropertyKey(Feature.AUTHORIZATION);

104

// Result: "kc.features.authorization"

105

106

String scriptsKey = PropertiesProfileConfigResolver.getPropertyKey("scripts");

107

// Result: "kc.features.scripts"

108

```

109

110

## Comma-Separated List Configuration Resolver

111

112

The `CommaSeparatedListProfileConfigResolver` class parses comma-separated feature lists.

113

114

```java { .api }

115

public class CommaSeparatedListProfileConfigResolver implements ProfileConfigResolver {

116

/**

117

* Constructor taking enabled and disabled feature lists

118

*/

119

public CommaSeparatedListProfileConfigResolver(String enabledFeatures, String disabledFeatures);

120

121

/**

122

* Gets profile name (always returns default)

123

*/

124

public Profile.ProfileName getProfileName();

125

126

/**

127

* Gets feature configuration from the lists

128

*/

129

public FeatureConfig getFeatureConfig(String feature);

130

}

131

```

132

133

### Usage Examples

134

135

```java

136

// Configure with feature lists

137

String enabled = "authorization,scripts,docker";

138

String disabled = "web-authn,recovery-codes";

139

140

ProfileConfigResolver resolver = new CommaSeparatedListProfileConfigResolver(enabled, disabled);

141

142

// Check feature configuration

143

FeatureConfig authzConfig = resolver.getFeatureConfig("authorization");

144

// Result: FeatureConfig.ENABLED

145

146

FeatureConfig webauthnConfig = resolver.getFeatureConfig("web-authn");

147

// Result: FeatureConfig.DISABLED

148

149

FeatureConfig unconfiguredConfig = resolver.getFeatureConfig("token-exchange");

150

// Result: FeatureConfig.UNCONFIGURED

151

152

// Empty lists

153

ProfileConfigResolver emptyResolver = new CommaSeparatedListProfileConfigResolver(null, null);

154

FeatureConfig config = emptyResolver.getFeatureConfig("any-feature");

155

// Result: FeatureConfig.UNCONFIGURED

156

```

157

158

## Profile Exception

159

160

The `ProfileException` class represents runtime exceptions related to profile operations.

161

162

```java { .api }

163

public class ProfileException extends RuntimeException {

164

/**

165

* Constructor with message

166

*/

167

public ProfileException(String message);

168

169

/**

170

* Constructor with message and cause

171

*/

172

public ProfileException(String message, Throwable cause);

173

}

174

```

175

176

### Usage Examples

177

178

```java

179

public void validateProfileConfiguration(ProfileConfigResolver resolver) {

180

try {

181

Profile.ProfileName profileName = resolver.getProfileName();

182

if (profileName == null) {

183

throw new ProfileException("Profile name cannot be null");

184

}

185

} catch (Exception e) {

186

throw new ProfileException("Failed to validate profile configuration", e);

187

}

188

}

189

```

190

191

## Advanced Profile Configuration Patterns

192

193

### Multi-Source Configuration Resolver

194

195

```java

196

public class MultiSourceProfileConfigResolver implements ProfileConfigResolver {

197

private final List<ProfileConfigResolver> resolvers;

198

199

public MultiSourceProfileConfigResolver(ProfileConfigResolver... resolvers) {

200

this.resolvers = Arrays.asList(resolvers);

201

}

202

203

@Override

204

public Profile.ProfileName getProfileName() {

205

// Use first non-null profile name

206

for (ProfileConfigResolver resolver : resolvers) {

207

Profile.ProfileName name = resolver.getProfileName();

208

if (name != null) {

209

return name;

210

}

211

}

212

return null;

213

}

214

215

@Override

216

public FeatureConfig getFeatureConfig(String feature) {

217

// Use first explicit configuration (not UNCONFIGURED)

218

for (ProfileConfigResolver resolver : resolvers) {

219

FeatureConfig config = resolver.getFeatureConfig(feature);

220

if (config != FeatureConfig.UNCONFIGURED) {

221

return config;

222

}

223

}

224

return FeatureConfig.UNCONFIGURED;

225

}

226

}

227

```

228

229

### Configuration Validation Utilities

230

231

```java

232

public class ProfileConfigValidator {

233

234

public static void validateFeatureConfig(String feature, FeatureConfig config, Set<String> validFeatures) {

235

if (config != FeatureConfig.UNCONFIGURED && !validFeatures.contains(feature)) {

236

throw new ProfileException("Unknown feature: " + feature);

237

}

238

}

239

240

public static void validateProfileName(Profile.ProfileName profileName) {

241

if (profileName == null) {

242

throw new ProfileException("Profile name cannot be null");

243

}

244

}

245

246

public static void validateDependencies(Map<String, FeatureConfig> featureConfigs) {

247

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

248

if (entry.getValue() == FeatureConfig.ENABLED) {

249

validateFeatureDependencies(entry.getKey(), featureConfigs);

250

}

251

}

252

}

253

254

private static void validateFeatureDependencies(String feature, Map<String, FeatureConfig> configs) {

255

// Get feature dependencies from Profile.Feature enum

256

try {

257

Profile.Feature f = Profile.Feature.valueOf(feature.toUpperCase().replace('-', '_'));

258

Set<Profile.Feature> dependencies = f.getDependencies();

259

260

for (Profile.Feature dep : dependencies) {

261

String depName = dep.getKey();

262

FeatureConfig depConfig = configs.get(depName);

263

264

if (depConfig == FeatureConfig.DISABLED) {

265

throw new ProfileException(

266

String.format("Feature %s requires %s to be enabled", feature, depName)

267

);

268

}

269

}

270

} catch (IllegalArgumentException e) {

271

// Feature not found in enum, skip validation

272

}

273

}

274

}

275

```

276

277

### Environment-Based Configuration

278

279

```java

280

public class EnvironmentProfileConfigResolver implements ProfileConfigResolver {

281

private final String profilePrefix;

282

private final String featurePrefix;

283

284

public EnvironmentProfileConfigResolver(String profilePrefix, String featurePrefix) {

285

this.profilePrefix = profilePrefix;

286

this.featurePrefix = featurePrefix;

287

}

288

289

@Override

290

public Profile.ProfileName getProfileName() {

291

String profileValue = System.getenv(profilePrefix + "PROFILE");

292

if (profileValue == null) {

293

return null;

294

}

295

296

try {

297

return Profile.ProfileName.valueOf(profileValue.toUpperCase());

298

} catch (IllegalArgumentException e) {

299

throw new ProfileException("Invalid profile name: " + profileValue);

300

}

301

}

302

303

@Override

304

public FeatureConfig getFeatureConfig(String feature) {

305

String envKey = featurePrefix + feature.toUpperCase().replace('-', '_');

306

String value = System.getenv(envKey);

307

308

if (value == null) {

309

return FeatureConfig.UNCONFIGURED;

310

}

311

312

switch (value.toLowerCase()) {

313

case "true":

314

case "enabled":

315

case "on":

316

return FeatureConfig.ENABLED;

317

case "false":

318

case "disabled":

319

case "off":

320

return FeatureConfig.DISABLED;

321

default:

322

throw new ProfileException("Invalid feature config value: " + value + " for " + envKey);

323

}

324

}

325

}

326

```

327

328

### JSON Configuration Resolver

329

330

```java

331

public class JsonProfileConfigResolver implements ProfileConfigResolver {

332

private final JsonObject config;

333

334

public JsonProfileConfigResolver(String jsonConfig) {

335

try {

336

this.config = JsonParser.parseString(jsonConfig).getAsJsonObject();

337

} catch (Exception e) {

338

throw new ProfileException("Invalid JSON configuration", e);

339

}

340

}

341

342

@Override

343

public Profile.ProfileName getProfileName() {

344

if (config.has("profile")) {

345

String profileName = config.get("profile").getAsString();

346

try {

347

return Profile.ProfileName.valueOf(profileName.toUpperCase());

348

} catch (IllegalArgumentException e) {

349

throw new ProfileException("Invalid profile name: " + profileName);

350

}

351

}

352

return null;

353

}

354

355

@Override

356

public FeatureConfig getFeatureConfig(String feature) {

357

if (config.has("features")) {

358

JsonObject features = config.getAsJsonObject("features");

359

if (features.has(feature)) {

360

boolean enabled = features.get(feature).getAsBoolean();

361

return enabled ? FeatureConfig.ENABLED : FeatureConfig.DISABLED;

362

}

363

}

364

return FeatureConfig.UNCONFIGURED;

365

}

366

}

367

```

368

369

## Complete Profile Configuration Example

370

371

```java

372

public class ProfileManager {

373

374

public static Profile configureProfile() {

375

// Create multiple configuration sources

376

ProfileConfigResolver[] resolvers = {

377

// 1. System properties (highest priority)

378

new PropertiesProfileConfigResolver(System::getProperty),

379

380

// 2. Environment variables

381

new EnvironmentProfileConfigResolver("KC_", "KC_FEATURE_"),

382

383

// 3. Configuration file

384

createFileConfigResolver("keycloak.properties"),

385

386

// 4. Default configuration

387

new CommaSeparatedListProfileConfigResolver("authorization", null)

388

};

389

390

// Combine resolvers with priority order

391

ProfileConfigResolver resolver = new MultiSourceProfileConfigResolver(resolvers);

392

393

// Validate configuration

394

validateConfiguration(resolver);

395

396

// Configure profile

397

return Profile.configure(resolver);

398

}

399

400

private static ProfileConfigResolver createFileConfigResolver(String filename) {

401

try {

402

Properties props = new Properties();

403

try (InputStream is = ProfileManager.class.getClassLoader().getResourceAsStream(filename)) {

404

if (is != null) {

405

props.load(is);

406

}

407

}

408

return new PropertiesProfileConfigResolver(props);

409

} catch (Exception e) {

410

throw new ProfileException("Failed to load configuration file: " + filename, e);

411

}

412

}

413

414

private static void validateConfiguration(ProfileConfigResolver resolver) {

415

try {

416

ProfileConfigValidator.validateProfileName(resolver.getProfileName());

417

418

// Validate key features

419

Set<String> keyFeatures = Set.of("authorization", "scripts", "docker", "web-authn");

420

for (String feature : keyFeatures) {

421

FeatureConfig config = resolver.getFeatureConfig(feature);

422

ProfileConfigValidator.validateFeatureConfig(feature, config,

423

Profile.getAllUnversionedFeatureNames());

424

}

425

} catch (Exception e) {

426

throw new ProfileException("Configuration validation failed", e);

427

}

428

}

429

}

430

```