Compare commits

..

7 commits

Author SHA1 Message Date
324fe35f5a
add note on how to get an example keycloak setup for the example setup 2026-03-31 16:03:59 +02:00
bd9e5a6f3e
add Makefile and docs on how to build the provider 2026-03-31 16:03:59 +02:00
964a593dbd
remove emojis from example in README as well 2026-03-31 15:58:02 +02:00
85bd0c6572
only perform further validation, if config string isn't null
For the configAttributeSlug the further validation fails ugly otherwise
and there's generally no need to do further validation, if a config
string is null.
2026-03-31 15:54:39 +02:00
5e791af057
add null checks 2026-03-31 14:31:07 +02:00
393284ebdc
remove realm exports 2026-03-31 14:07:25 +02:00
ffa6c92c65
renaming and sort some code 2026-03-31 14:05:24 +02:00
5 changed files with 29 additions and 5212 deletions

View file

@ -5,10 +5,10 @@ For this it will provide API endpoints for every configured attribute-group.
The configuration of the provider is possible via an admin page.
Every endpoint responds with a list of all attribute values, that:
- is in the attribute group matching `attribute-group`
- matches an optional RegEx Pattern `attribute-regex`
- belongs to a user with a role matching `match-role`
- is non-empty
- are in the attribute group matching `attribute-group`
- match an optional RegEx Pattern `attribute-regex`
- belong to a user with a role matching `match-role`
- are non-empty
Multivalue attributes are flattened in the response.
@ -61,7 +61,7 @@ We assume an unconfigured, fresh Keycloak installation running under `http://loc
- After creating:
- give it the role `myattribute-export`
- set a password in the users settings `Creadentials > Set password`. For Example `"password"`
8. Under `🪪 Attribute Endpoints 🚀 > Create item`, add a new endpoint to the provider
8. Under `Attribute Endpoints > Create item`, add a new endpoint to the provider
Example:
- `Slug` = `"ssh_keys"`
- `Attribute Group` = `"my-attributes-group"`

View file

@ -24,7 +24,7 @@ import com.google.auto.service.AutoService;
*/
@AutoService(UiPageProviderFactory.class)
public class AdminUiPage implements UiPageProvider, UiPageProviderFactory<ComponentModel> {
public static final String PROVIDER_ID = "🪪 Attribute Endpoints 🚀";
public static final String PROVIDER_ID = "Attribute Endpoints";
@Override
public void init(Config.Scope config) {
@ -54,22 +54,28 @@ public class AdminUiPage implements UiPageProvider, UiPageProviderFactory<Compon
Pattern slugPattern = Pattern.compile("^[a-zA-Z0-9_-]*$");
String configAttributeSlug = model.getConfig().getFirst("slug");
if (!slugPattern.matcher(configAttributeSlug).matches()) {
if (configAttributeSlug == null) {
hasError = true;
errorString += " • [Slug] can not be empty\n";
} else if (!slugPattern.matcher(configAttributeSlug).matches()) {
hasError = true;
errorString += " • [Slug] can only contain anlphanumeric characters, dash and underscore (a-z A-Z 0-9 _ - )\n";
}
String configAuthRole = model.getConfig().getFirst("auth-role");
RoleModel authRole = realm.getRole(configAuthRole);
if (authRole == null) {
if (configAuthRole == null) {
hasError = true;
errorString += " • [Auth Role] can not be empty\n";
} else if (realm.getRole(configAuthRole) == null) {
hasError = true;
errorString += " • [Auth Role] does not exist\n";
}
String configMatchRole = model.getConfig().getFirst("match-role");
RoleModel matchRole = realm.getRole(configMatchRole);
if (matchRole == null) {
if (configMatchRole == null) {
hasError = true;
errorString += " • [Match Role] can not be empty\n";
} else if (realm.getRole(configMatchRole) == null) {
hasError = true;
errorString += " • [Match Role] does not exist\n";
}
@ -77,7 +83,10 @@ public class AdminUiPage implements UiPageProvider, UiPageProviderFactory<Compon
UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class);
UPConfig upconfig = profileProvider.getConfiguration();
String configAttributeGroup = model.getConfig().getFirst("attribute-group");
if (!upconfig.getGroups().stream().anyMatch(g -> g.getName().equals(configAttributeGroup))) {
if (configAttributeGroup == null) {
hasError = true;
errorString += " • [Attribute Group] can not be empty\n";
} else if (!upconfig.getGroups().stream().anyMatch(g -> g.getName().equals(configAttributeGroup))) {
hasError = true;
errorString += " • [Attribute Group] does not exist\n";
}

View file

@ -53,7 +53,7 @@ public class AttributeEndpointsResourceProvider implements RealmResourceProvider
@GET
@Path("export/{slug}")
@Produces(MediaType.APPLICATION_JSON)
public Response exportKeys(@PathParam("slug") String slug) {
public Response exportAttributeValues(@PathParam("slug") String slug) {
KeycloakContext context = session.getContext();
RealmModel realm = context.getRealm();
@ -62,11 +62,16 @@ public class AttributeEndpointsResourceProvider implements RealmResourceProvider
.filter(c -> c.getConfig().getFirst("slug").equals(slug))
.toList();
Auth auth = AttributeEndpointsResourceProvider.getAuth(session);
if (componentList.isEmpty()) {
throw new NotFoundException("Endpoint not found.");
}
Auth auth = AttributeEndpointsResourceProvider.getAuth(session);
if (componentList.size() > 1) {
throw new NotFoundException(
"Endpoint Configuration Error - Multiple configurations exist for this endpoint.");
}
ComponentModel component = componentList.get(0);
@ -106,11 +111,6 @@ public class AttributeEndpointsResourceProvider implements RealmResourceProvider
throw new ForbiddenException("User does not have required auth role.");
}
if (componentList.size() > 1) {
throw new NotFoundException(
"Endpoint Configuration Error - Multiple configurations exist for this endpoint.");
}
List<String> attributeNames = upconfig.getAttributes()
.stream()
.filter(a -> a.getGroup() != null && a.getGroup().equals(configAttributeGroup))

File diff suppressed because it is too large Load diff

2525
test.json

File diff suppressed because it is too large Load diff