Configure Extension with UiPageProvider #1
2 changed files with 75 additions and 22 deletions
validate configuration before saving it
commit
88ffb9b9f4
|
|
@ -1,15 +1,23 @@
|
||||||
package de.ccc.hamburg.keycloak.attribute_endpoints;
|
package de.ccc.hamburg.keycloak.attribute_endpoints;
|
||||||
|
|
||||||
import com.google.auto.service.AutoService;
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.component.ComponentValidationException;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||||
|
import org.keycloak.representations.userprofile.config.UPConfig;
|
||||||
import org.keycloak.services.ui.extend.UiPageProvider;
|
import org.keycloak.services.ui.extend.UiPageProvider;
|
||||||
import org.keycloak.services.ui.extend.UiPageProviderFactory;
|
import org.keycloak.services.ui.extend.UiPageProviderFactory;
|
||||||
|
import org.keycloak.userprofile.UserProfileProvider;
|
||||||
|
|
||||||
|
kritzl marked this conversation as resolved
Outdated
|
|||||||
import java.util.List;
|
import com.google.auto.service.AutoService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements UiPageProvider to show a config page in the admin
|
* Implements UiPageProvider to show a config page in the admin
|
||||||
|
|
@ -29,55 +37,108 @@ public class AdminUiPage implements UiPageProvider, UiPageProviderFactory<Compon
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return PROVIDER_ID;
|
return PROVIDER_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHelpText() {
|
public String getHelpText() {
|
||||||
return "Configure endpoints of the Attribute Endpoint Provider.";
|
return "Configure endpoints of the Attribute Endpoint Provider.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) {
|
||||||
|
String errorString = "\n";
|
||||||
|
Boolean hasError = false;
|
||||||
|
|
||||||
|
Pattern slugPattern = Pattern.compile("^[a-zA-Z0-9_-]*$");
|
||||||
|
String configAttributeSlug = model.getConfig().getFirst("slug");
|
||||||
|
|
||||||
|
if (!slugPattern.matcher(configAttributeSlug).matches()) {
|
||||||
|
june
commented
Currently this gives us an ugly exception in the Keycloak logs for an empty slug, as Currently this gives us an ugly exception in the Keycloak logs for an empty slug, as `configAttributeSlug` is then `null`. Not critical as it still just errors, but we should probably check, if `configAttributeSlug` is `null`. I guess then adding a null check to all the other `config$thing`s is probably a good idea as well.
|
|||||||
|
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) {
|
||||||
|
hasError = true;
|
||||||
|
errorString += " • [Auth Role] does not exist\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
String configMatchRole = model.getConfig().getFirst("match-role");
|
||||||
|
RoleModel matchRole = realm.getRole(configMatchRole);
|
||||||
|
if (matchRole == null) {
|
||||||
|
hasError = true;
|
||||||
|
errorString += " • [Match Role] does not exist\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
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))) {
|
||||||
|
hasError = true;
|
||||||
|
errorString += " • [Attribute Group] does not exist\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
String configAttributeRegex = model.getConfig().getFirst("attribute-regex");
|
||||||
|
Boolean regexIsBlank = configAttributeRegex == null;
|
||||||
|
|
||||||
|
if (!regexIsBlank) {
|
||||||
|
try {
|
||||||
|
Pattern.compile(configAttributeRegex);
|
||||||
|
} catch (Exception e) {
|
||||||
|
hasError = true;
|
||||||
|
errorString += " • [Attribute RegEx] is not a valid regex pattern\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasError) {
|
||||||
|
throw new ComponentValidationException(errorString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ProviderConfigProperty> getConfigProperties() {
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
return ProviderConfigurationBuilder.create()
|
return ProviderConfigurationBuilder.create()
|
||||||
.property()
|
.property()
|
||||||
.name("slug")
|
.name("slug")
|
||||||
.label("Slug")
|
.label("Slug")
|
||||||
.helpText("The slug in the path of the API endpoint (e.g. /realms/:realm/attribute-endpoint-provider/export/:slug)")
|
.helpText(
|
||||||
|
"The slug in the path of the API endpoint (e.g. /realms/:realm/attribute-endpoint-provider/export/:slug)")
|
||||||
.type(ProviderConfigProperty.STRING_TYPE)
|
.type(ProviderConfigProperty.STRING_TYPE)
|
||||||
.add()
|
.add()
|
||||||
|
|
||||||
.property()
|
.property()
|
||||||
.name("attribute-group")
|
.name("attribute-group")
|
||||||
.label("Attribute Group")
|
.label("Attribute Group")
|
||||||
.helpText("The attribute group to export.")
|
.helpText("The attribute group to export.")
|
||||||
.type(ProviderConfigProperty.STRING_TYPE)
|
.type(ProviderConfigProperty.STRING_TYPE)
|
||||||
.add()
|
.add()
|
||||||
|
|
||||||
.property()
|
.property()
|
||||||
.name("match-role")
|
.name("match-role")
|
||||||
.label("Match Role")
|
.label("Match Role")
|
||||||
.helpText("Export only attributes of users with this role.")
|
.helpText("Export only attributes of users with this role.")
|
||||||
.type(ProviderConfigProperty.STRING_TYPE)
|
.type(ProviderConfigProperty.STRING_TYPE)
|
||||||
.add()
|
.add()
|
||||||
|
|
||||||
.property()
|
.property()
|
||||||
.name("auth-role")
|
.name("auth-role")
|
||||||
.label("Auth Role")
|
.label("Auth Role")
|
||||||
.helpText("Role needeed by the authenticated account to be able to use this endpoint.")
|
.helpText("Role needeed by the authenticated account to be able to use this endpoint.")
|
||||||
.type(ProviderConfigProperty.STRING_TYPE)
|
.type(ProviderConfigProperty.STRING_TYPE)
|
||||||
.add()
|
.add()
|
||||||
|
|
||||||
.property()
|
.property()
|
||||||
.name("attribute-regex")
|
.name("attribute-regex")
|
||||||
.label("Attribute RegEx")
|
.label("Attribute RegEx")
|
||||||
.helpText("A RegEx Rule used to verify each attribute value. Only matching values are returned.")
|
.helpText("A RegEx Rule used to verify each attribute value. Only matching values are returned.")
|
||||||
.type(ProviderConfigProperty.STRING_TYPE)
|
.type(ProviderConfigProperty.STRING_TYPE)
|
||||||
.add()
|
.add()
|
||||||
|
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package de.ccc.hamburg.keycloak.attribute_endpoints;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
@ -23,7 +22,7 @@ import org.keycloak.services.managers.AuthenticationManager.AuthResult;
|
||||||
import org.keycloak.services.resource.RealmResourceProvider;
|
import org.keycloak.services.resource.RealmResourceProvider;
|
||||||
import org.keycloak.userprofile.UserProfileProvider;
|
import org.keycloak.userprofile.UserProfileProvider;
|
||||||
|
|
||||||
import io.quarkus.security.UnauthorizedException;
|
import jakarta.ws.rs.ForbiddenException;
|
||||||
import jakarta.ws.rs.GET;
|
import jakarta.ws.rs.GET;
|
||||||
import jakarta.ws.rs.NotAuthorizedException;
|
import jakarta.ws.rs.NotAuthorizedException;
|
||||||
import jakarta.ws.rs.NotFoundException;
|
import jakarta.ws.rs.NotFoundException;
|
||||||
|
|
@ -102,16 +101,9 @@ public class AttributeEndpointsResourceProvider implements RealmResourceProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
UserModel authUser = auth.getUser();
|
||||||
|
if (!authUser.hasRole(authRole)) {
|
||||||
{
|
throw new ForbiddenException("User does not have required auth role.");
|
||||||
UserModel user = auth.getUser();
|
|
||||||
if (!user.hasRole(authRole)) {
|
|
||||||
throw new UnauthorizedException("User does not have required auth role.");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println(e);
|
|
||||||
return Response.status(403, e.getMessage()).build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (componentList.size() > 1) {
|
if (componentList.size() > 1) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue
I think getting rid of the emojis, while they are fun, might be a good idea.