KEYCLOAK-5082 : Add new redirect-rewrite-rule parameters for the adapters (#4255)

* add rewrite rule config property

* add subsystem support for redirect rewrite

* update deployment unit test

* add license headers

* Optimize rewrite method
This commit is contained in:
Sebastien Blanc 2017-06-29 12:50:42 +02:00 committed by Stian Thorgersen
parent 620bea3553
commit 500a21685f
17 changed files with 395 additions and 8 deletions

View file

@ -92,6 +92,8 @@ public class KeycloakDeployment {
protected boolean pkce = false; protected boolean pkce = false;
protected boolean ignoreOAuthQueryParameter; protected boolean ignoreOAuthQueryParameter;
protected Map<String, String> redirectRewriteRules;
public KeycloakDeployment() { public KeycloakDeployment() {
} }
@ -446,4 +448,14 @@ public class KeycloakDeployment {
public boolean isOAuthQueryParameterEnabled() { public boolean isOAuthQueryParameterEnabled() {
return !this.ignoreOAuthQueryParameter; return !this.ignoreOAuthQueryParameter;
} }
public Map<String, String> getRedirectRewriteRules() {
return redirectRewriteRules;
}
public void setRewriteRedirectRules(Map<String, String> redirectRewriteRules) {
this.redirectRewriteRules = redirectRewriteRules;
}
} }

View file

@ -116,6 +116,7 @@ public class KeycloakDeploymentBuilder {
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests()); deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl()); deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl());
deployment.setIgnoreOAuthQueryParameter(adapterConfig.isIgnoreOAuthQueryParameter()); deployment.setIgnoreOAuthQueryParameter(adapterConfig.isIgnoreOAuthQueryParameter());
deployment.setRewriteRedirectRules(adapterConfig.getRedirectRewriteRules());
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) { if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url"); throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");

View file

@ -25,7 +25,6 @@ import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome; import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.common.util.Encode;
import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.UriUtils; import org.keycloak.common.util.UriUtils;
import org.keycloak.constants.AdapterConstants; import org.keycloak.constants.AdapterConstants;
@ -38,7 +37,10 @@ import org.keycloak.representations.IDToken;
import org.keycloak.util.TokenUtil; import org.keycloak.util.TokenUtil;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.atomic.AtomicLong; import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.logging.Level;
/** /**
@ -141,6 +143,7 @@ public class OAuthRequestAuthenticator {
protected String getRedirectUri(String state) { protected String getRedirectUri(String state) {
String url = getRequestUrl(); String url = getRequestUrl();
log.debugf("callback uri: %s", url); log.debugf("callback uri: %s", url);
if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) { if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
int port = sslRedirectPort(); int port = sslRedirectPort();
if (port < 0) { if (port < 0) {
@ -170,7 +173,7 @@ public class OAuthRequestAuthenticator {
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone() KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE) .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName()) .queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
.queryParam(OAuth2Constants.REDIRECT_URI, url) .queryParam(OAuth2Constants.REDIRECT_URI, rewrittenRedirectUri(url))
.queryParam(OAuth2Constants.STATE, state) .queryParam(OAuth2Constants.STATE, state)
.queryParam("login", "true"); .queryParam("login", "true");
if(loginHint != null && loginHint.length() > 0){ if(loginHint != null && loginHint.length() > 0){
@ -320,10 +323,11 @@ public class OAuthRequestAuthenticator {
AccessTokenResponse tokenResponse = null; AccessTokenResponse tokenResponse = null;
strippedOauthParametersRequestUri = stripOauthParametersFromRedirect(); strippedOauthParametersRequestUri = stripOauthParametersFromRedirect();
try { try {
// For COOKIE store we don't have httpSessionId and single sign-out won't be available // For COOKIE store we don't have httpSessionId and single sign-out won't be available
String httpSessionId = deployment.getTokenStore() == TokenStore.SESSION ? reqAuthenticator.changeHttpSessionId(true) : null; String httpSessionId = deployment.getTokenStore() == TokenStore.SESSION ? reqAuthenticator.changeHttpSessionId(true) : null;
tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, strippedOauthParametersRequestUri, httpSessionId); tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, rewrittenRedirectUri(strippedOauthParametersRequestUri), httpSessionId);
} catch (ServerRequest.HttpFailure failure) { } catch (ServerRequest.HttpFailure failure) {
log.error("failed to turn code into token"); log.error("failed to turn code into token");
log.error("status from server: " + failure.getStatus()); log.error("status from server: " + failure.getStatus());
@ -376,5 +380,22 @@ public class OAuthRequestAuthenticator {
return builder.build().toString(); return builder.build().toString();
} }
private String rewrittenRedirectUri(String originalUri) {
Map<String, String> rewriteRules = deployment.getRedirectRewriteRules();
if(rewriteRules != null && !rewriteRules.isEmpty()) {
try {
URL url = new URL(originalUri);
Map.Entry<String, String> rule = rewriteRules.entrySet().iterator().next();
StringBuilder redirectUriBuilder = new StringBuilder(url.getProtocol());
redirectUriBuilder.append("://"+ url.getAuthority());
redirectUriBuilder.append(url.getPath().replaceFirst(rule.getKey(), rule.getValue()));
return redirectUriBuilder.toString();
} catch (MalformedURLException ex) {
log.error("Not a valid request url");
throw new RuntimeException(ex);
}
}
return originalUri;
}
} }

View file

@ -75,6 +75,7 @@ public class KeycloakDeploymentBuilderTest {
assertEquals(10, deployment.getTokenMinimumTimeToLive()); assertEquals(10, deployment.getTokenMinimumTimeToLive());
assertEquals(20, deployment.getMinTimeBetweenJwksRequests()); assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
assertEquals(120, deployment.getPublicKeyCacheTtl()); assertEquals(120, deployment.getPublicKeyCacheTtl());
assertEquals("/api/$1", deployment.getRedirectRewriteRules().get("^/wsmaster/api/(.*)$"));
} }
@Test @Test

View file

@ -33,5 +33,8 @@
"token-minimum-time-to-live": 10, "token-minimum-time-to-live": 10,
"min-time-between-jwks-requests": 20, "min-time-between-jwks-requests": 20,
"public-key-cache-ttl": 120, "public-key-cache-ttl": 120,
"ignore-oauth-query-parameter": true "ignore-oauth-query-parameter": true,
"redirect-rewrite-rules" : {
"^/wsmaster/api/(.*)$" : "/api/$1"
}
} }

View file

@ -38,6 +38,8 @@ public final class KeycloakAdapterConfigService {
private static final String CREDENTIALS_JSON_NAME = "credentials"; private static final String CREDENTIALS_JSON_NAME = "credentials";
private static final String REDIRECT_REWRITE_RULE_JSON_NAME = "redirect-rewrite-rule";
private static final KeycloakAdapterConfigService INSTANCE = new KeycloakAdapterConfigService(); private static final KeycloakAdapterConfigService INSTANCE = new KeycloakAdapterConfigService();
public static KeycloakAdapterConfigService getInstance() { public static KeycloakAdapterConfigService getInstance() {
@ -130,6 +132,56 @@ public final class KeycloakAdapterConfigService {
return deployment.get(CREDENTIALS_JSON_NAME); return deployment.get(CREDENTIALS_JSON_NAME);
} }
public void addRedirectRewriteRule(ModelNode operation, ModelNode model) {
ModelNode redirectRewritesRules = redirectRewriteRuleFromOp(operation);
if (!redirectRewritesRules.isDefined()) {
redirectRewritesRules = new ModelNode();
}
String redirectRewriteRuleName = redirectRewriteRule(operation);
if (!redirectRewriteRuleName.contains(".")) {
redirectRewritesRules.get(redirectRewriteRuleName).set(model.get("value").asString());
} else {
String[] parts = redirectRewriteRuleName.split("\\.");
String provider = parts[0];
String property = parts[1];
ModelNode redirectRewriteRule = redirectRewritesRules.get(provider);
if (!redirectRewriteRule.isDefined()) {
redirectRewriteRule = new ModelNode();
}
redirectRewriteRule.get(property).set(model.get("value").asString());
redirectRewritesRules.set(provider, redirectRewriteRule);
}
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
deployment.get(REDIRECT_REWRITE_RULE_JSON_NAME).set(redirectRewritesRules);
}
public void removeRedirectRewriteRule(ModelNode operation) {
ModelNode redirectRewritesRules = redirectRewriteRuleFromOp(operation);
if (!redirectRewritesRules.isDefined()) {
throw new RuntimeException("Can not remove redirect rewrite rule. No rules defined for deployment in op " + operation.toString());
}
String ruleName = credentialNameFromOp(operation);
redirectRewritesRules.remove(ruleName);
}
public void updateRedirectRewriteRule(ModelNode operation, String attrName, ModelNode resolvedValue) {
ModelNode redirectRewritesRules = redirectRewriteRuleFromOp(operation);
if (!redirectRewritesRules.isDefined()) {
throw new RuntimeException("Can not update redirect rewrite rule. No rules defined for deployment in op " + operation.toString());
}
String ruleName = credentialNameFromOp(operation);
redirectRewritesRules.get(ruleName).set(resolvedValue);
}
private ModelNode redirectRewriteRuleFromOp(ModelNode operation) {
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
return deployment.get(REDIRECT_REWRITE_RULE_JSON_NAME);
}
private String realmNameFromOp(ModelNode operation) { private String realmNameFromOp(ModelNode operation) {
return valueFromOpAddress(RealmDefinition.TAG_NAME, operation); return valueFromOpAddress(RealmDefinition.TAG_NAME, operation);
} }
@ -142,6 +194,10 @@ public final class KeycloakAdapterConfigService {
return valueFromOpAddress(CredentialDefinition.TAG_NAME, operation); return valueFromOpAddress(CredentialDefinition.TAG_NAME, operation);
} }
private String redirectRewriteRule(ModelNode operation) {
return valueFromOpAddress(RedirecRewritetRuleDefinition.TAG_NAME, operation);
}
private String valueFromOpAddress(String addrElement, ModelNode operation) { private String valueFromOpAddress(String addrElement, ModelNode operation) {
String deploymentName = getValueOfAddrElement(operation.get(ADDRESS), addrElement); String deploymentName = getValueOfAddrElement(operation.get(ADDRESS), addrElement);
if (deploymentName == null) throw new RuntimeException("Can't find '" + addrElement + "' in address " + operation.toString()); if (deploymentName == null) throw new RuntimeException("Can't find '" + addrElement + "' in address " + operation.toString());

View file

@ -48,6 +48,7 @@ public class KeycloakExtension implements Extension {
static final RealmDefinition REALM_DEFINITION = new RealmDefinition(); static final RealmDefinition REALM_DEFINITION = new RealmDefinition();
static final SecureDeploymentDefinition SECURE_DEPLOYMENT_DEFINITION = new SecureDeploymentDefinition(); static final SecureDeploymentDefinition SECURE_DEPLOYMENT_DEFINITION = new SecureDeploymentDefinition();
static final CredentialDefinition CREDENTIAL_DEFINITION = new CredentialDefinition(); static final CredentialDefinition CREDENTIAL_DEFINITION = new CredentialDefinition();
static final RedirecRewritetRuleDefinition REDIRECT_RULE_DEFINITON = new RedirecRewritetRuleDefinition();
public static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) { public static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) {
StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME); StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME);
@ -77,6 +78,7 @@ public class KeycloakExtension implements Extension {
registration.registerSubModel(REALM_DEFINITION); registration.registerSubModel(REALM_DEFINITION);
ManagementResourceRegistration secureDeploymentRegistration = registration.registerSubModel(SECURE_DEPLOYMENT_DEFINITION); ManagementResourceRegistration secureDeploymentRegistration = registration.registerSubModel(SECURE_DEPLOYMENT_DEFINITION);
secureDeploymentRegistration.registerSubModel(CREDENTIAL_DEFINITION); secureDeploymentRegistration.registerSubModel(CREDENTIAL_DEFINITION);
secureDeploymentRegistration.registerSubModel(REDIRECT_RULE_DEFINITON);
subsystem.registerXMLElementWriter(PARSER); subsystem.registerXMLElementWriter(PARSER);
} }

View file

@ -96,12 +96,17 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
PathElement.pathElement(SecureDeploymentDefinition.TAG_NAME, name)); PathElement.pathElement(SecureDeploymentDefinition.TAG_NAME, name));
addSecureDeployment.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode()); addSecureDeployment.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
List<ModelNode> credentialsToAdd = new ArrayList<ModelNode>(); List<ModelNode> credentialsToAdd = new ArrayList<ModelNode>();
List<ModelNode> redirectRulesToAdd = new ArrayList<ModelNode>();
while (reader.hasNext() && nextTag(reader) != END_ELEMENT) { while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
String tagName = reader.getLocalName(); String tagName = reader.getLocalName();
if (tagName.equals(CredentialDefinition.TAG_NAME)) { if (tagName.equals(CredentialDefinition.TAG_NAME)) {
readCredential(reader, addr, credentialsToAdd); readCredential(reader, addr, credentialsToAdd);
continue; continue;
} }
if (tagName.equals(RedirecRewritetRuleDefinition.TAG_NAME)) {
readRewriteRule(reader, addr, redirectRulesToAdd);
continue;
}
SimpleAttributeDefinition def = SecureDeploymentDefinition.lookup(tagName); SimpleAttributeDefinition def = SecureDeploymentDefinition.lookup(tagName);
if (def == null) throw new XMLStreamException("Unknown secure-deployment tag " + tagName); if (def == null) throw new XMLStreamException("Unknown secure-deployment tag " + tagName);
@ -111,6 +116,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
// Must add credentials after the deployment is added. // Must add credentials after the deployment is added.
resourcesToAdd.add(addSecureDeployment); resourcesToAdd.add(addSecureDeployment);
resourcesToAdd.addAll(credentialsToAdd); resourcesToAdd.addAll(credentialsToAdd);
resourcesToAdd.addAll(redirectRulesToAdd);
} }
public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException { public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException {
@ -150,6 +156,43 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
} }
} }
public void readRewriteRule(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> rewriteRuleToToAdd) throws XMLStreamException {
String name = readNameAttribute(reader);
Map<String, String> values = new HashMap<>();
String textValue = null;
while (reader.hasNext()) {
int next = reader.next();
if (next == CHARACTERS) {
// text value of redirect rule element
String text = reader.getText();
if (text == null || text.trim().isEmpty()) {
continue;
}
textValue = text;
} else if (next == START_ELEMENT) {
String key = reader.getLocalName();
reader.next();
String value = reader.getText();
reader.next();
values.put(key, value);
} else if (next == END_ELEMENT) {
break;
}
}
if (textValue != null) {
ModelNode addRedirectRule = getRedirectRuleToAdd(parent, name, textValue);
rewriteRuleToToAdd.add(addRedirectRule);
} else {
for (Map.Entry<String, String> entry : values.entrySet()) {
ModelNode addRedirectRule = getRedirectRuleToAdd(parent, name + "." + entry.getKey(), entry.getValue());
rewriteRuleToToAdd.add(addRedirectRule);
}
}
}
private ModelNode getCredentialToAdd(PathAddress parent, String name, String value) { private ModelNode getCredentialToAdd(PathAddress parent, String name, String value) {
ModelNode addCredential = new ModelNode(); ModelNode addCredential = new ModelNode();
addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD); addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
@ -159,6 +202,15 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
return addCredential; return addCredential;
} }
private ModelNode getRedirectRuleToAdd(PathAddress parent, String name, String value) {
ModelNode addRedirectRule = new ModelNode();
addRedirectRule.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(RedirecRewritetRuleDefinition.TAG_NAME, name));
addRedirectRule.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
addRedirectRule.get(RedirecRewritetRuleDefinition.VALUE.getName()).set(value);
return addRedirectRule;
}
// expects that the current tag will have one single attribute called "name" // expects that the current tag will have one single attribute called "name"
private String readNameAttribute(XMLExtendedStreamReader reader) throws XMLStreamException { private String readNameAttribute(XMLExtendedStreamReader reader) throws XMLStreamException {
String name = null; String name = null;
@ -220,6 +272,11 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
writeCredentials(writer, credentials); writeCredentials(writer, credentials);
} }
ModelNode redirectRewriteRule = deploymentElements.get(RedirecRewritetRuleDefinition.TAG_NAME);
if (redirectRewriteRule.isDefined()) {
writeRedirectRules(writer, redirectRewriteRule);
}
writer.writeEndElement(); writer.writeEndElement();
} }
} }
@ -266,6 +323,34 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
} }
} }
private void writeRedirectRules(XMLExtendedStreamWriter writer, ModelNode redirectRules) throws XMLStreamException {
Map<String, Object> parsed = new LinkedHashMap<>();
for (Property redirectRule : redirectRules.asPropertyList()) {
String ruleName = redirectRule.getName();
String ruleValue = redirectRule.getValue().get(RedirecRewritetRuleDefinition.VALUE.getName()).asString();
parsed.put(ruleName, ruleValue);
}
for (Map.Entry<String, Object> entry : parsed.entrySet()) {
writer.writeStartElement(RedirecRewritetRuleDefinition.TAG_NAME);
writer.writeAttribute("name", entry.getKey());
Object value = entry.getValue();
if (value instanceof String) {
writeCharacters(writer, (String) value);
} else {
Map<String, String> redirectRulesProps = (Map<String, String>) value;
for (Map.Entry<String, String> prop : redirectRulesProps.entrySet()) {
writer.writeStartElement(prop.getKey());
writeCharacters(writer, prop.getValue());
writer.writeEndElement();
}
}
writer.writeEndElement();
}
}
// code taken from org.jboss.as.controller.AttributeMarshaller // code taken from org.jboss.as.controller.AttributeMarshaller
private void writeCharacters(XMLExtendedStreamWriter writer, String content) throws XMLStreamException { private void writeCharacters(XMLExtendedStreamWriter writer, String content) throws XMLStreamException {
if (content.indexOf('\n') > -1) { if (content.indexOf('\n') > -1) {

View file

@ -0,0 +1,61 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.subsystem.adapter.extension;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.SimpleResourceDefinition;
import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
import org.jboss.as.controller.operations.validation.StringLengthValidator;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.dmr.ModelType;
/**
*
* @author sblanc
*/
public class RedirecRewritetRuleDefinition extends SimpleResourceDefinition {
public static final String TAG_NAME = "redirect-rewrite-rule";
protected static final AttributeDefinition VALUE =
new SimpleAttributeDefinitionBuilder("value", ModelType.STRING, false)
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, false, true))
.build();
public RedirecRewritetRuleDefinition() {
super(PathElement.pathElement(TAG_NAME),
KeycloakExtension.getResourceDescriptionResolver(TAG_NAME),
new RedirectRewriteRuleAddHandler(VALUE),
RedirectRewriteRuleRemoveHandler.INSTANCE);
}
@Override
public void registerOperations(ManagementResourceRegistration resourceRegistration) {
super.registerOperations(resourceRegistration);
resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
}
@Override
public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
super.registerAttributes(resourceRegistration);
resourceRegistration.registerReadWriteAttribute(VALUE, null, new RedirectRewriteRuleReadWriteAttributeHandler());
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.subsystem.adapter.extension;
import org.jboss.as.controller.AbstractAddStepHandler;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.dmr.ModelNode;
public class RedirectRewriteRuleAddHandler extends AbstractAddStepHandler {
public RedirectRewriteRuleAddHandler(AttributeDefinition... attributes) {
super(attributes);
}
@Override
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
ckService.addRedirectRewriteRule(operation, context.resolveExpressions(model));
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.subsystem.adapter.extension;
import org.jboss.as.controller.AbstractWriteAttributeHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.dmr.ModelNode;
public class RedirectRewriteRuleReadWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
@Override
protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
ModelNode resolvedValue, ModelNode currentValue, AbstractWriteAttributeHandler.HandbackHolder<KeycloakAdapterConfigService> hh) throws OperationFailedException {
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
ckService.updateRedirectRewriteRule(operation, attributeName, resolvedValue);
hh.setHandback(ckService);
return false;
}
@Override
protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException {
ckService.updateRedirectRewriteRule(operation, attributeName, valueToRestore);
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.subsystem.adapter.extension;
import org.jboss.as.controller.AbstractRemoveStepHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.dmr.ModelNode;
public class RedirectRewriteRuleRemoveHandler extends AbstractRemoveStepHandler {
public static RedirectRewriteRuleRemoveHandler INSTANCE = new RedirectRewriteRuleRemoveHandler();
private RedirectRewriteRuleRemoveHandler() {}
@Override
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
ckService.removeRedirectRewriteRule(operation);
}
}

View file

@ -65,6 +65,7 @@ keycloak.secure-deployment.connection-pool-size=Connection pool size for the cli
keycloak.secure-deployment.resource=Application name keycloak.secure-deployment.resource=Application name
keycloak.secure-deployment.use-resource-role-mappings=Use resource level permissions from token keycloak.secure-deployment.use-resource-role-mappings=Use resource level permissions from token
keycloak.secure-deployment.credentials=Adapter credentials keycloak.secure-deployment.credentials=Adapter credentials
keycloak.secure-deployment.redirect-rewrite-rule=Apply a rewrite rule for the redirect URI
keycloak.secure-deployment.bearer-only=Bearer Token Auth only keycloak.secure-deployment.bearer-only=Bearer Token Auth only
keycloak.secure-deployment.enable-basic-auth=Enable Basic Authentication keycloak.secure-deployment.enable-basic-auth=Enable Basic Authentication
keycloak.secure-deployment.public-client=Public client keycloak.secure-deployment.public-client=Public client
@ -95,3 +96,8 @@ keycloak.credential=Credential
keycloak.credential.value=Credential value keycloak.credential.value=Credential value
keycloak.credential.add=Credential add keycloak.credential.add=Credential add
keycloak.credential.remove=Credential remove keycloak.credential.remove=Credential remove
keycloak.redirect-rewrite-rule=redirect-rewrite-rule
keycloak.redirect-rewrite-rule.value=redirect-rewrite-rule value
keycloak.redirect-rewrite-rule.add=redirect-rewrite-rule add
keycloak.redirect-rewrite-rule.remove=redirect-rewrite-rule remove

View file

@ -101,6 +101,7 @@
<xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" /> <xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/> <xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="credential" type="credential-type" minOccurs="1" maxOccurs="1"/> <xs:element name="credential" type="credential-type" minOccurs="1" maxOccurs="1"/>
<xs:element name="redirect-rewrite-rule" type="redirect-rewrite-rule-type" minOccurs="1" maxOccurs="1"/>
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/> <xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/> <xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
@ -127,4 +128,10 @@
</xs:sequence> </xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" /> <xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType> </xs:complexType>
<xs:complexType name="redirect-rewrite-rule-type" mixed="true">
<xs:sequence maxOccurs="unbounded" minOccurs="0">
<xs:any processContents="lax"></xs:any>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>
</xs:schema> </xs:schema>

View file

@ -53,6 +53,7 @@
<auth-server-url>http://localhost:8080/auth</auth-server-url> <auth-server-url>http://localhost:8080/auth</auth-server-url>
<ssl-required>EXTERNAL</ssl-required> <ssl-required>EXTERNAL</ssl-required>
<credential name="secret">0aa31d98-e0aa-404c-b6e0-e771dba1e798</credential> <credential name="secret">0aa31d98-e0aa-404c-b6e0-e771dba1e798</credential>
<redirect-rewrite-rule name="^/wsmaster/api/(.*)$">api/$1/</redirect-rewrite-rule>
</secure-deployment> </secure-deployment>
<secure-deployment name="http-endpoint"> <secure-deployment name="http-endpoint">
<realm>master</realm> <realm>master</realm>
@ -66,5 +67,6 @@
<credential name="jwt"> <credential name="jwt">
<client-keystore-file>/tmp/keystore.jks</client-keystore-file> <client-keystore-file>/tmp/keystore.jks</client-keystore-file>
</credential> </credential>
<redirect-rewrite-rule name="^/wsmaster/api/(.*)$">/api/$1/</redirect-rewrite-rule>
</secure-deployment> </secure-deployment>
</subsystem> </subsystem>

View file

@ -62,7 +62,8 @@ public class BaseAdapterConfig extends BaseRealmConfig {
protected boolean publicClient; protected boolean publicClient;
@JsonProperty("credentials") @JsonProperty("credentials")
protected Map<String, Object> credentials = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); protected Map<String, Object> credentials = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
@JsonProperty("redirect-rewrite-rules")
protected Map<String, String> redirectRewriteRules;
public boolean isUseResourceRoleMappings() { public boolean isUseResourceRoleMappings() {
return useResourceRoleMappings; return useResourceRoleMappings;
@ -167,4 +168,14 @@ public class BaseAdapterConfig extends BaseRealmConfig {
public void setPublicClient(boolean publicClient) { public void setPublicClient(boolean publicClient) {
this.publicClient = publicClient; this.publicClient = publicClient;
} }
public Map<String, String> getRedirectRewriteRules() {
return redirectRewriteRules;
}
public void setRedirectRewriteRules(Map<String, String> redirectRewriteRules) {
this.redirectRewriteRules = redirectRewriteRules;
}
} }

View file

@ -258,6 +258,7 @@ public class TokenEndpoint {
} }
event.user(userSession.getUser()); event.user(userSession.getUser());
event.session(userSession.getId()); event.session(userSession.getId());
String redirectUri = clientSession.getNote(OIDCLoginProtocol.REDIRECT_URI_PARAM); String redirectUri = clientSession.getNote(OIDCLoginProtocol.REDIRECT_URI_PARAM);