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
```