KEYCLOAK-4813 Destination validation counts on port being not specified

This commit is contained in:
Hynek Mlnarik committed 2017-05-23 12:52:48 +02:00
1 parent 8d40ee17f1
commit f47283f61a
7 files changed
+143 -19

No files matched your search

@@ -39,7 +39,11 @@ import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConsta
import javax.xml.crypto.dsig.CanonicalizationMethod;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -47,9 +51,26 @@ import java.util.List;
*/
public class SamlProtocolFactory extends AbstractLoginProtocolFactory {
private static final Pattern PROTOCOL_MAP_PATTERN = Pattern.compile("\\s*([a-zA-Z][a-zA-Z\\d+-.]*)\\s*=\\s*(\\d+)\\s*");
private static final String[] DEFAULT_PROTOCOL_TO_PORT_MAP = new String[] { "http=80", "https=443" };
private final Map<Integer, String> knownPorts = new HashMap<>();
private final Map<String, Integer> knownProtocols = new HashMap<>();
private void addToProtocolPortMaps(String protocolMapping) {
Matcher m = PROTOCOL_MAP_PATTERN.matcher(protocolMapping);
if (m.matches()) {
Integer port = Integer.valueOf(m.group(2));
String proto = m.group(1);
knownPorts.put(port, proto);
knownProtocols.put(proto, port);
}
}
@Override
public Object createProtocolEndpoint(RealmModel realm, EventBuilder event) {
return new SamlService(realm, event);
return new SamlService(realm, event, knownProtocols, knownPorts);
}
@Override
@@ -61,6 +82,15 @@ public class SamlProtocolFactory extends AbstractLoginProtocolFactory {
public void init(Config.Scope config) {
//PicketLinkCoreSTS sts = PicketLinkCoreSTS.instance();
//sts.installDefaultConfiguration();
String[] protocolMappings = config.getArray("knownProtocols");
if (protocolMappings == null) {
protocolMappings = DEFAULT_PROTOCOL_TO_PORT_MAP;
}
for (String protocolMapping : protocolMappings) {
addToProtocolPortMaps(protocolMapping);
}
}
@Override
@@ -67,7 +67,6 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
@@ -87,6 +86,7 @@ import org.keycloak.rotation.KeyLocator;
import org.keycloak.saml.SPMetadataDescriptor;
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
import org.keycloak.sessions.AuthenticationSessionModel;
import java.util.Map;
/**
* Resource class for the saml connect token service
@@ -98,8 +98,13 @@ public class SamlService extends AuthorizationEndpointBase {
protected static final Logger logger = Logger.getLogger(SamlService.class);
public SamlService(RealmModel realm, EventBuilder event) {
private final Map<String, Integer> knownPorts;
private final Map<Integer, String> knownProtocols;
public SamlService(RealmModel realm, EventBuilder event, Map<String, Integer> knownPorts, Map<Integer, String> knownProtocols) {
super(realm, event);
this.knownPorts = knownPorts;
this.knownProtocols = knownProtocols;
}
public abstract class BindingProtocol {
@@ -239,7 +244,7 @@ public class SamlService extends AuthorizationEndpointBase {
protected Response loginRequest(String relayState, AuthnRequestType requestAbstractType, ClientModel client) {
SamlClient samlClient = new SamlClient(client);
// validate destination
if (requestAbstractType.getDestination() != null && !uriInfo.getAbsolutePath().equals(requestAbstractType.getDestination())) {
if (! isValidDestination(requestAbstractType.getDestination())) {
event.detail(Details.REASON, "invalid_destination");
event.error(Errors.INVALID_SAML_AUTHN_REQUEST);
return ErrorPage.error(session, Messages.INVALID_REQUEST);
@@ -341,7 +346,7 @@ public class SamlService extends AuthorizationEndpointBase {
protected Response logoutRequest(LogoutRequestType logoutRequest, ClientModel client, String relayState) {
SamlClient samlClient = new SamlClient(client);
// validate destination
if (logoutRequest.getDestination() != null && !uriInfo.getAbsolutePath().equals(logoutRequest.getDestination())) {
if (! isValidDestination(logoutRequest.getDestination())) {
event.detail(Details.REASON, "invalid_destination");
event.error(Errors.INVALID_SAML_LOGOUT_REQUEST);
return ErrorPage.error(session, Messages.INVALID_REQUEST);
@@ -683,11 +688,35 @@ public class SamlService extends AuthorizationEndpointBase {
@NoCache
@Consumes({"application/soap+xml",MediaType.TEXT_XML})
public Response soapBinding(InputStream inputStream) {
SamlEcpProfileService bindingService = new SamlEcpProfileService(realm, event);
SamlEcpProfileService bindingService = new SamlEcpProfileService(realm, event, knownPorts, knownProtocols);
ResteasyProviderFactory.getInstance().injectProperties(bindingService);
return bindingService.authenticate(inputStream);
}
private boolean isValidDestination(URI destination) {
if (destination == null) {
return false;
}
URI expected = uriInfo.getAbsolutePath();
if (Objects.equals(expected, destination)) {
return true;
}
Integer portByScheme = knownPorts.get(expected.getScheme());
if (expected.getPort() < 0 && portByScheme != null) {
return Objects.equals(uriInfo.getRequestUriBuilder().port(portByScheme).build(), destination);
}
String protocolByPort = knownProtocols.get(expected.getPort());
if (expected.getPort() >= 0 && Objects.equals(protocolByPort, expected.getScheme())) {
return Objects.equals(uriInfo.getRequestUriBuilder().port(-1).build(), destination);
}
return false;
}
}
@@ -43,6 +43,7 @@ import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeaderElement;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -53,8 +54,8 @@ public class SamlEcpProfileService extends SamlService {
private static final String NS_PREFIX_SAML_PROTOCOL = "samlp";
private static final String NS_PREFIX_SAML_ASSERTION = "saml";
public SamlEcpProfileService(RealmModel realm, EventBuilder event) {
super(realm, event);
public SamlEcpProfileService(RealmModel realm, EventBuilder event, Map<String, Integer> knownPorts, Map<Integer, String> knownProtocols) {
super(realm, event, knownPorts, knownProtocols);
}
public Response authenticate(InputStream inputStream) {
@@ -56,6 +56,8 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.LoginProtocolFactory;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.protocol.oidc.utils.RedirectUtils;
@@ -1017,7 +1019,8 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
return ParsedCodeContext.response(redirectToErrorPage(Messages.CLIENT_NOT_FOUND));
}
SamlService samlService = new SamlService(realmModel, event);
LoginProtocolFactory factory = (LoginProtocolFactory) session.getKeycloakSessionFactory().getProviderFactory(LoginProtocol.class, SamlProtocol.LOGIN_PROTOCOL);
SamlService samlService = (SamlService) factory.createProtocolEndpoint(realmModel, event);
ResteasyProviderFactory.getInstance().injectProperties(samlService);
AuthenticationSessionModel authSession = samlService.getOrCreateLoginSessionForIdpInitiatedSso(session, realmModel, oClient.get(), null);