[KEYCLOAK-12192] - Missing Input Validation in IDP Authorization URLs

This commit is contained in:
Pedro Igor 2020-03-04 09:21:25 -03:00 committed by Stian Thorgersen
parent 0cf0955318
commit 2f489a41eb
41 changed files with 401 additions and 34 deletions

View file

@ -18,10 +18,14 @@
package org.keycloak.common.util;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLDecoder;
import java.util.regex.Pattern;
import org.keycloak.common.enums.SslRequired;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@ -84,4 +88,28 @@ public class UriUtils {
public static String stripQueryParam(String url, String name){
return url.replaceFirst("[\\?&]"+name+"=[^&]*$|"+name+"=[^&]*&", "");
}
public static void checkUrl(SslRequired sslRequired, String url, String name) throws IllegalArgumentException{
if (url == null) {
return;
}
URL parsed;
try {
parsed = new URL(url);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("The url [" + name + "] is malformed", e);
}
String protocol = parsed.getProtocol().toLowerCase();
if (!("http".equalsIgnoreCase(protocol) || "https".equalsIgnoreCase(protocol))) {
throw new IllegalArgumentException("Invalid protocol/scheme for url [" + name + "]");
}
if (!"https".equals(protocol) && sslRequired.isRequired(url)) {
throw new IllegalArgumentException("The url [" + name + "] requires secure connections");
}
}
}

View file

@ -54,4 +54,14 @@ public interface IdentityProviderFactory<T extends IdentityProvider> extends Pro
* @return
*/
Map<String, String> parseConfig(KeycloakSession session, InputStream inputStream);
/**
* <p>Creates a provider specific {@link IdentityProviderModel} instance.
*
* <p>Providers may want to implement their own {@link IdentityProviderModel} type so that validations
* can be performed when managing the provider configuration
*
* @return the provider specific instance
*/
<C extends IdentityProviderModel> C createConfig();
}

View file

@ -49,6 +49,9 @@ import org.keycloak.authorization.store.ResourceServerStore;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.UriUtils;
@ -285,7 +288,7 @@ public class RepresentationToModel {
DefaultRequiredActions.addActions(newRealm);
}
importIdentityProviders(rep, newRealm);
importIdentityProviders(rep, newRealm, session);
importIdentityProviderMappers(rep, newRealm);
Map<String, ClientScopeModel> clientScopes = new HashMap<>();
@ -1856,10 +1859,10 @@ public class RepresentationToModel {
}
}
private static void importIdentityProviders(RealmRepresentation rep, RealmModel newRealm) {
private static void importIdentityProviders(RealmRepresentation rep, RealmModel newRealm, KeycloakSession session) {
if (rep.getIdentityProviders() != null) {
for (IdentityProviderRepresentation representation : rep.getIdentityProviders()) {
newRealm.addIdentityProvider(toModel(newRealm, representation));
newRealm.addIdentityProvider(toModel(newRealm, representation, session));
}
}
}
@ -1872,8 +1875,20 @@ public class RepresentationToModel {
}
}
public static IdentityProviderModel toModel(RealmModel realm, IdentityProviderRepresentation representation) {
IdentityProviderModel identityProviderModel = new IdentityProviderModel();
public static IdentityProviderModel toModel(RealmModel realm, IdentityProviderRepresentation representation, KeycloakSession session) {
IdentityProviderFactory providerFactory = (IdentityProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(
IdentityProvider.class, representation.getProviderId());
if (providerFactory == null) {
providerFactory = (IdentityProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(
SocialIdentityProvider.class, representation.getProviderId());
}
if (providerFactory == null) {
throw new IllegalArgumentException("Invalid identity provider id [" + representation.getProviderId() + "]");
}
IdentityProviderModel identityProviderModel = providerFactory.createConfig();
identityProviderModel.setInternalId(representation.getInternalId());
identityProviderModel.setAlias(representation.getAlias());
@ -1909,6 +1924,8 @@ public class RepresentationToModel {
identityProviderModel.setPostBrokerLoginFlowId(flowModel.getId());
}
identityProviderModel.validate(realm);
return identityProviderModel;
}

View file

@ -197,4 +197,14 @@ public class IdentityProviderModel implements Serializable {
this.displayName = displayName;
}
/**
* <p>Validates this configuration.
*
* <p>Sub-classes can override this method in order to enforce provider specific validations.
*
* @param realm the realm
*/
public void validate(RealmModel realm) {
}
}

View file

@ -48,7 +48,10 @@ public class KeycloakOIDCIdentityProviderFactory extends AbstractIdentityProvide
@Override
public Map<String, String> parseConfig(KeycloakSession session, InputStream inputStream) {
return OIDCIdentityProviderFactory.parseOIDCConfig(session, inputStream);
}
@Override
public OIDCIdentityProviderConfig createConfig() {
return new OIDCIdentityProviderConfig();
}
}

View file

@ -16,7 +16,12 @@
*/
package org.keycloak.broker.oidc;
import static org.keycloak.common.util.UriUtils.checkUrl;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
/**
@ -28,6 +33,10 @@ public class OAuth2IdentityProviderConfig extends IdentityProviderModel {
super(model);
}
public OAuth2IdentityProviderConfig() {
super();
}
public String getAuthorizationUrl() {
return getConfig().get("authorizationUrl");
}
@ -123,4 +132,13 @@ public class OAuth2IdentityProviderConfig extends IdentityProviderModel {
public void setForwardParameters(String forwardParameters) {
getConfig().put("forwardParameters", forwardParameters);
}
@Override
public void validate(RealmModel realm) {
SslRequired sslRequired = realm.getSslRequired();
checkUrl(sslRequired, getAuthorizationUrl(), "authorization_url");
checkUrl(sslRequired, getTokenUrl(), "token_url");
checkUrl(sslRequired, getUserInfoUrl(), "userinfo_url");
}
}

View file

@ -16,7 +16,12 @@
*/
package org.keycloak.broker.oidc;
import static org.keycloak.common.util.UriUtils.checkUrl;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
/**
* @author Pedro Igor
@ -33,6 +38,10 @@ public class OIDCIdentityProviderConfig extends OAuth2IdentityProviderConfig {
super(identityProviderModel);
}
public OIDCIdentityProviderConfig() {
super();
}
public String getPrompt() {
return getConfig().get("prompt");
}
@ -122,4 +131,12 @@ public class OIDCIdentityProviderConfig extends OAuth2IdentityProviderConfig {
return 0;
}
}
@Override
public void validate(RealmModel realm) {
super.validate(realm);
SslRequired sslRequired = realm.getSslRequired();
checkUrl(sslRequired, getJwksUrl(), "jwks_url");
checkUrl(sslRequired, getLogoutUrl(), "logout_url");
}
}

View file

@ -43,6 +43,11 @@ public class OIDCIdentityProviderFactory extends AbstractIdentityProviderFactory
return new OIDCIdentityProvider(session, new OIDCIdentityProviderConfig(model));
}
@Override
public OIDCIdentityProviderConfig createConfig() {
return new OIDCIdentityProviderConfig();
}
@Override
public String getId() {
return PROVIDER_ID;
@ -60,7 +65,7 @@ public class OIDCIdentityProviderFactory extends AbstractIdentityProviderFactory
} catch (IOException e) {
throw new RuntimeException("failed to load openid connect metadata", e);
}
OIDCIdentityProviderConfig config = new OIDCIdentityProviderConfig(new IdentityProviderModel());
OIDCIdentityProviderConfig config = new OIDCIdentityProviderConfig();
config.setIssuer(rep.getIssuer());
config.setLogoutUrl(rep.getLogoutEndpoint());
config.setAuthorizationUrl(rep.getAuthorizationEndpoint());

View file

@ -16,8 +16,13 @@
*/
package org.keycloak.broker.saml;
import static org.keycloak.common.util.UriUtils.checkUrl;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.saml.SamlPrincipalType;
import org.keycloak.saml.common.util.XmlKeyInfoKeyNameTransformer;
@ -276,4 +281,11 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
getConfig().put(PRINCIPAL_ATTRIBUTE, principalAttribute);
}
@Override
public void validate(RealmModel realm) {
SslRequired sslRequired = realm.getSslRequired();
checkUrl(sslRequired, getSingleLogoutServiceUrl(), SINGLE_LOGOUT_SERVICE_URL);
checkUrl(sslRequired, getSingleSignOnServiceUrl(), SINGLE_SIGN_ON_SERVICE_URL);
}
}

View file

@ -58,6 +58,11 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
return new SAMLIdentityProvider(session, new SAMLIdentityProviderConfig(model), destinationValidator);
}
@Override
public SAMLIdentityProviderConfig createConfig() {
return new SAMLIdentityProviderConfig();
}
@Override
public Map<String, String> parseConfig(KeycloakSession session, InputStream inputStream) {
try {

View file

@ -72,7 +72,7 @@ public class IdentityProvidersPartialImport extends AbstractPartialImport<Identi
@Override
public void create(RealmModel realm, KeycloakSession session, IdentityProviderRepresentation idpRep) {
idpRep.setInternalId(KeycloakModelUtils.generateId());
IdentityProviderModel identityProvider = RepresentationToModel.toModel(realm, idpRep);
IdentityProviderModel identityProvider = RepresentationToModel.toModel(realm, idpRep, session);
realm.addIdentityProvider(identityProvider);
}

View file

@ -16,6 +16,8 @@
*/
package org.keycloak.services.resources.admin;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
import javax.ws.rs.NotFoundException;
@ -162,6 +164,8 @@ public class IdentityProviderResource {
adminEvent.operation(OperationType.UPDATE).resourcePath(session.getContext().getUri()).representation(providerRep).success();
return Response.noContent().build();
} catch (IllegalArgumentException e) {
return ErrorResponse.error("Invalid request", BAD_REQUEST);
} catch (ModelDuplicateException e) {
return ErrorResponse.exists("Identity Provider " + providerRep.getAlias() + " already exists");
}
@ -176,7 +180,7 @@ public class IdentityProviderResource {
lookUpProviderIdByAlias(realm, providerRep);
}
IdentityProviderModel updated = RepresentationToModel.toModel(realm, providerRep);
IdentityProviderModel updated = RepresentationToModel.toModel(realm, providerRep, session);
if (updated.getConfig() != null && ComponentRepresentation.SECRET_VALUE.equals(updated.getConfig().get("clientSecret"))) {
updated.getConfig().put("clientSecret", identityProviderModel.getConfig() != null ? identityProviderModel.getConfig().get("clientSecret") : null);

View file

@ -183,7 +183,7 @@ public class IdentityProvidersResource {
this.auth.realm().requireManageIdentityProviders();
try {
IdentityProviderModel identityProvider = RepresentationToModel.toModel(realm, representation);
IdentityProviderModel identityProvider = RepresentationToModel.toModel(realm, representation, session);
this.realm.addIdentityProvider(identityProvider);
representation.setInternalId(identityProvider.getInternalId());
@ -191,6 +191,8 @@ public class IdentityProvidersResource {
.representation(StripSecretsUtils.strip(representation)).success();
return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(representation.getAlias()).build()).build();
} catch (IllegalArgumentException e) {
return ErrorResponse.error("Invalid request", BAD_REQUEST);
} catch (ModelDuplicateException e) {
return ErrorResponse.exists("Identity Provider " + representation.getAlias() + " already exists");
}

View file

@ -39,6 +39,11 @@ public class BitbucketIdentityProviderFactory extends AbstractIdentityProviderFa
return new BitbucketIdentityProvider(session, new OAuth2IdentityProviderConfig(model));
}
@Override
public OAuth2IdentityProviderConfig createConfig() {
return new OAuth2IdentityProviderConfig();
}
@Override
public String getId() {
return PROVIDER_ID;

View file

@ -39,6 +39,11 @@ public class FacebookIdentityProviderFactory extends AbstractIdentityProviderFac
return new FacebookIdentityProvider(session, new OAuth2IdentityProviderConfig(model));
}
@Override
public OAuth2IdentityProviderConfig createConfig() {
return new OAuth2IdentityProviderConfig();
}
@Override
public String getId() {
return PROVIDER_ID;

View file

@ -39,6 +39,11 @@ public class GitHubIdentityProviderFactory extends AbstractIdentityProviderFacto
return new GitHubIdentityProvider(session, new OAuth2IdentityProviderConfig(model));
}
@Override
public OAuth2IdentityProviderConfig createConfig() {
return new OAuth2IdentityProviderConfig();
}
@Override
public String getId() {
return PROVIDER_ID;

View file

@ -16,7 +16,6 @@
*/
package org.keycloak.social.gitlab;
import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
import org.keycloak.broker.social.SocialIdentityProviderFactory;
@ -40,6 +39,11 @@ public class GitLabIdentityProviderFactory extends AbstractIdentityProviderFacto
return new GitLabIdentityProvider(session, new OIDCIdentityProviderConfig(model));
}
@Override
public OIDCIdentityProviderConfig createConfig() {
return new OIDCIdentityProviderConfig();
}
@Override
public String getId() {
return PROVIDER_ID;

View file

@ -28,6 +28,10 @@ public class GoogleIdentityProviderConfig extends OIDCIdentityProviderConfig {
super(model);
}
public GoogleIdentityProviderConfig() {
}
public boolean isUserIp() {
String userIp = getConfig().get("userIp");
return userIp == null ? false : Boolean.valueOf(userIp);

View file

@ -16,7 +16,6 @@
*/
package org.keycloak.social.google;
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.broker.social.SocialIdentityProviderFactory;
@ -39,6 +38,11 @@ public class GoogleIdentityProviderFactory extends AbstractIdentityProviderFacto
return new GoogleIdentityProvider(session, new GoogleIdentityProviderConfig(model));
}
@Override
public GoogleIdentityProviderConfig createConfig() {
return new GoogleIdentityProviderConfig();
}
@Override
public String getId() {
return PROVIDER_ID;

View file

@ -39,6 +39,11 @@ public class InstagramIdentityProviderFactory extends AbstractIdentityProviderFa
return new InstagramIdentityProvider(session, new OAuth2IdentityProviderConfig(model));
}
@Override
public OAuth2IdentityProviderConfig createConfig() {
return new OAuth2IdentityProviderConfig();
}
@Override
public String getId() {
return PROVIDER_ID;

View file

@ -40,6 +40,11 @@ public class LinkedInIdentityProviderFactory extends AbstractIdentityProviderFac
return new LinkedInIdentityProvider(session, new OAuth2IdentityProviderConfig(model));
}
@Override
public OAuth2IdentityProviderConfig createConfig() {
return new OAuth2IdentityProviderConfig();
}
@Override
public String getId() {
return PROVIDER_ID;

View file

@ -39,6 +39,11 @@ public class MicrosoftIdentityProviderFactory extends AbstractIdentityProviderFa
return new MicrosoftIdentityProvider(session, new OAuth2IdentityProviderConfig(model));
}
@Override
public OAuth2IdentityProviderConfig createConfig() {
return new OAuth2IdentityProviderConfig();
}
@Override
public String getId() {
return PROVIDER_ID;

View file

@ -10,6 +10,10 @@ public class OpenshiftV3IdentityProviderConfig extends OAuth2IdentityProviderCon
super(identityProviderModel);
}
public OpenshiftV3IdentityProviderConfig() {
}
public String getBaseUrl() {
return getConfig().get(BASE_URL);
}

View file

@ -19,6 +19,11 @@ public class OpenshiftV3IdentityProviderFactory extends AbstractIdentityProvider
return new OpenshiftV3IdentityProvider(keycloakSession, new OpenshiftV3IdentityProviderConfig(identityProviderModel));
}
@Override
public OpenshiftV3IdentityProviderConfig createConfig() {
return new OpenshiftV3IdentityProviderConfig();
}
@Override
public String getId() {
return PROVIDER_ID;

View file

@ -19,6 +19,9 @@ public class OpenshiftV4IdentityProviderConfig extends OAuth2IdentityProviderCon
super(identityProviderModel);
}
public OpenshiftV4IdentityProviderConfig() {
}
private String trimTrailingSlash(String baseUrl) {
if (baseUrl != null && baseUrl.endsWith("/")) {
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);

View file

@ -30,4 +30,8 @@ public class OpenshiftV4IdentityProviderFactory extends AbstractIdentityProvider
return PROVIDER_ID;
}
@Override
public OpenshiftV4IdentityProviderConfig createConfig() {
return new OpenshiftV4IdentityProviderConfig();
}
}

View file

@ -28,6 +28,10 @@ public class PayPalIdentityProviderConfig extends OAuth2IdentityProviderConfig {
super(model);
}
public PayPalIdentityProviderConfig() {
}
public boolean targetSandbox() {
String sandbox = getConfig().get("sandbox");
return sandbox == null ? false : Boolean.valueOf(sandbox);

View file

@ -39,6 +39,11 @@ public class PayPalIdentityProviderFactory extends AbstractIdentityProviderFacto
return new PayPalIdentityProvider(session, new PayPalIdentityProviderConfig(model));
}
@Override
public PayPalIdentityProviderConfig createConfig() {
return new PayPalIdentityProviderConfig();
}
@Override
public String getId() {
return PROVIDER_ID;

View file

@ -28,6 +28,10 @@ public class StackOverflowIdentityProviderConfig extends OAuth2IdentityProviderC
super(model);
}
public StackOverflowIdentityProviderConfig() {
}
public String getKey() {
return getConfig().get("key");
}

View file

@ -40,6 +40,11 @@ public class StackoverflowIdentityProviderFactory extends
return new StackoverflowIdentityProvider(session, new StackOverflowIdentityProviderConfig(model));
}
@Override
public StackOverflowIdentityProviderConfig createConfig() {
return new StackOverflowIdentityProviderConfig();
}
@Override
public String getId() {
return PROVIDER_ID;

View file

@ -39,6 +39,11 @@ public class TwitterIdentityProviderFactory extends AbstractIdentityProviderFact
return new TwitterIdentityProvider(session, new OAuth2IdentityProviderConfig(model));
}
@Override
public OAuth2IdentityProviderConfig createConfig() {
return new OAuth2IdentityProviderConfig();
}
@Override
public String getId() {
return PROVIDER_ID;

View file

@ -17,9 +17,13 @@
package org.keycloak.testsuite.admin;
import org.hamcrest.Matchers;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.dom.saml.v2.metadata.EndpointType;
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
import org.keycloak.dom.saml.v2.metadata.IndexedEndpointType;
@ -35,12 +39,15 @@ import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperTypeRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.broker.OIDCIdentityProviderConfigRep;
import org.keycloak.testsuite.util.AdminEventPaths;
import org.w3c.dom.NodeList;
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@ -59,6 +66,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
@ -75,6 +83,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.arquillian.AuthServerTestEnricher.AUTH_SERVER_SSL_REQUIRED;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -102,6 +111,9 @@ public class IdentityProviderTest extends AbstractAdminTest {
+ "vOU8TyqfZF5jpv0IcrviLl/DoFrbjByeHR+pu/vClcAOjL/u7oQELuuTfNsBI4tpexUj5G8q/YbEz0gk7idf"
+ "LXrAUVcsR73oTngrhRfwUSmPrjjK0kjcRb6HL9V/+wh3R/6mEd59U08ExT8N38rhmn0CI3ehMdebReprP7U8=";
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Test
public void testFindAll() {
create(createRep("google", "google"));
@ -143,6 +155,71 @@ public class IdentityProviderTest extends AbstractAdminTest {
assertEquals(ComponentRepresentation.SECRET_VALUE, rep.getConfig().get("clientSecret"));
}
@Test
public void failCreateInvalidUrl() {
RealmRepresentation realmRep = realm.toRepresentation();
realmRep.setSslRequired(SslRequired.ALL.name());
try {
realm.update(realmRep);
IdentityProviderRepresentation newIdentityProvider = createRep("new-identity-provider", "oidc");
newIdentityProvider.getConfig().put("clientId", "clientId");
newIdentityProvider.getConfig().put("clientSecret", "some secret value");
OIDCIdentityProviderConfigRep oidcConfig = new OIDCIdentityProviderConfigRep(newIdentityProvider);
oidcConfig.setAuthorizationUrl("invalid://test");
try (Response response = this.realm.identityProviders().create(newIdentityProvider)) {
assertEquals(AUTH_SERVER_SSL_REQUIRED ? Response.Status.BAD_REQUEST.getStatusCode() :
Response.Status.CREATED.getStatusCode(), response.getStatus());
}
oidcConfig.setAuthorizationUrl(null);
oidcConfig.setTokenUrl("http://test");
try (Response response = this.realm.identityProviders().create(newIdentityProvider)) {
assertEquals(AUTH_SERVER_SSL_REQUIRED ? Response.Status.BAD_REQUEST.getStatusCode() :
Response.Status.CREATED.getStatusCode(), response.getStatus());
}
oidcConfig.setAuthorizationUrl(null);
oidcConfig.setTokenUrl(null);
oidcConfig.setJwksUrl("http://test");
try (Response response = this.realm.identityProviders().create(newIdentityProvider)) {
assertEquals(AUTH_SERVER_SSL_REQUIRED ? Response.Status.BAD_REQUEST.getStatusCode() :
Response.Status.CREATED.getStatusCode(), response.getStatus());
}
oidcConfig.setAuthorizationUrl(null);
oidcConfig.setTokenUrl(null);
oidcConfig.setJwksUrl(null);
oidcConfig.setLogoutUrl("http://test");
try (Response response = this.realm.identityProviders().create(newIdentityProvider)) {
assertEquals(AUTH_SERVER_SSL_REQUIRED ? Response.Status.BAD_REQUEST.getStatusCode() :
Response.Status.CREATED.getStatusCode(), response.getStatus());
}
oidcConfig.setAuthorizationUrl(null);
oidcConfig.setTokenUrl(null);
oidcConfig.setJwksUrl(null);
oidcConfig.setLogoutUrl(null);
oidcConfig.setUserInfoUrl("http://test");
try (Response response = this.realm.identityProviders().create(newIdentityProvider)) {
assertEquals(AUTH_SERVER_SSL_REQUIRED ? Response.Status.BAD_REQUEST.getStatusCode() :
Response.Status.CREATED.getStatusCode(), response.getStatus());
}
} finally {
realmRep.setSslRequired(SslRequired.NONE.name());
realm.update(realmRep);
}
}
@Test
public void testCreateWithBasicAuth() {
@ -258,6 +335,84 @@ public class IdentityProviderTest extends AbstractAdminTest {
assertEquals("${vault.key}", testingClient.testing("admin-client-test").getIdentityProviderConfig("changed-alias").get("clientSecret"));
}
@Test
public void failUpdateInvalidUrl() {
RealmRepresentation realmRep = realm.toRepresentation();
realmRep.setSslRequired(SslRequired.ALL.name());
try {
realm.update(realmRep);
IdentityProviderRepresentation representation = createRep(UUID.randomUUID().toString(), "oidc");
representation.getConfig().put("clientId", "clientId");
representation.getConfig().put("clientSecret", "some secret value");
try (Response response = realm.identityProviders().create(representation)) {
assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
}
IdentityProviderResource resource = this.realm.identityProviders().get(representation.getAlias());
representation = resource.toRepresentation();
OIDCIdentityProviderConfigRep oidcConfig = new OIDCIdentityProviderConfigRep(representation);
oidcConfig.setAuthorizationUrl("invalid://test");
this.expectedException.expect(
Matchers.allOf(Matchers.instanceOf(ClientErrorException.class), Matchers.hasProperty("response",
Matchers.hasProperty("status", Matchers.is(
Response.Status.BAD_REQUEST.getStatusCode())))));
resource.update(representation);
oidcConfig.setAuthorizationUrl(null);
oidcConfig.setTokenUrl("http://test");
this.expectedException.expect(
Matchers.allOf(Matchers.instanceOf(ClientErrorException.class), Matchers.hasProperty("response",
Matchers.hasProperty("status", Matchers.is(
Response.Status.BAD_REQUEST.getStatusCode())))));
resource.update(representation);
oidcConfig.setAuthorizationUrl(null);
oidcConfig.setTokenUrl(null);
oidcConfig.setJwksUrl("http://test");
this.expectedException.expect(
Matchers.allOf(Matchers.instanceOf(ClientErrorException.class), Matchers.hasProperty("response",
Matchers.hasProperty("status", Matchers.is(
Response.Status.BAD_REQUEST.getStatusCode())))));
resource.update(representation);
oidcConfig.setAuthorizationUrl(null);
oidcConfig.setTokenUrl(null);
oidcConfig.setJwksUrl(null);
oidcConfig.setLogoutUrl("http://test");
this.expectedException.expect(
Matchers.allOf(Matchers.instanceOf(ClientErrorException.class), Matchers.hasProperty("response",
Matchers.hasProperty("status", Matchers.is(
Response.Status.BAD_REQUEST.getStatusCode())))));
resource.update(representation);
oidcConfig.setAuthorizationUrl(null);
oidcConfig.setTokenUrl(null);
oidcConfig.setJwksUrl(null);
oidcConfig.setLogoutUrl(null);
oidcConfig.setUserInfoUrl("http://test");
this.expectedException.expect(
Matchers.allOf(Matchers.instanceOf(ClientErrorException.class), Matchers.hasProperty("response",
Matchers.hasProperty("status", Matchers.is(
Response.Status.BAD_REQUEST.getStatusCode())))));
resource.update(representation);
} finally {
realmRep.setSslRequired(SslRequired.NONE.name());
realm.update(realmRep);
}
}
@Test
public void testRemove() {
IdentityProviderRepresentation newIdentityProvider = createRep("remove-identity-provider", "saml");

View file

@ -1658,8 +1658,8 @@ public class PermissionsTest extends AbstractKeycloakTest {
}, Resource.IDENTITY_PROVIDER, false);
invoke(new InvocationWithResponse() {
public void invoke(RealmResource realm, AtomicReference<Response> response) {
response.set(realm.identityProviders().create(IdentityProviderBuilder.create().providerId("nosuch")
.displayName("nosuch-foo").alias("foo").build()));
response.set(realm.identityProviders().create(IdentityProviderBuilder.create().providerId("oidc")
.displayName("nosuch-foo").alias("foo").setAttribute("clientId", "foo").setAttribute("clientSecret", "foo").build()));
}
}, Resource.IDENTITY_PROVIDER, true);

View file

@ -708,7 +708,7 @@ public class UserTest extends AbstractAdminTest {
IdentityProviderRepresentation rep = new IdentityProviderRepresentation();
rep.setAlias("social-provider-id");
rep.setProviderId("social-provider-type");
rep.setProviderId("oidc");
realm.identityProviders().create(rep);
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.identityProviderPath(rep.getAlias()), rep, ResourceType.IDENTITY_PROVIDER);

View file

@ -279,7 +279,7 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
// Update identityProvider to some bad JWKS_URL
OIDCIdentityProviderConfigRep cfg = new OIDCIdentityProviderConfigRep(idpRep);
cfg.setJwksUrl("http://localhost:43214/non-existent");
cfg.setJwksUrl("https://localhost:43214/non-existent");
updateIdentityProvider(idpRep);
// Check that key is not cached anymore

View file

@ -27,7 +27,7 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
class OIDCIdentityProviderConfigRep extends OIDCIdentityProviderConfig {
public class OIDCIdentityProviderConfigRep extends OIDCIdentityProviderConfig {
private final IdentityProviderRepresentation rep;

View file

@ -43,8 +43,8 @@ public class KcAdmUpdateTest extends AbstractAdmCliTest {
.providerId(SAMLIdentityProviderFactory.PROVIDER_ID)
.alias("idpAlias")
.displayName("SAML")
.setAttribute(SAMLIdentityProviderConfig.SINGLE_SIGN_ON_SERVICE_URL, "http://saml.idp/saml")
.setAttribute(SAMLIdentityProviderConfig.SINGLE_LOGOUT_SERVICE_URL, "http://saml.idp/saml")
.setAttribute(SAMLIdentityProviderConfig.SINGLE_SIGN_ON_SERVICE_URL, "https://saml.idp/saml")
.setAttribute(SAMLIdentityProviderConfig.SINGLE_LOGOUT_SERVICE_URL, "https://saml.idp/saml")
.setAttribute(SAMLIdentityProviderConfig.NAME_ID_POLICY_FORMAT, "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
.setAttribute(SAMLIdentityProviderConfig.POST_BINDING_RESPONSE, "false")
.setAttribute(SAMLIdentityProviderConfig.POST_BINDING_AUTHN_REQUEST, "false")

View file

@ -91,7 +91,7 @@ public class BrokerTest extends AbstractSamlTest {
final ResponseType res = new SAML2LoginResponseBuilder()
.requestID(req.getID())
.destination(req.getAssertionConsumerServiceURL().toString())
.issuer("http://saml.idp/saml")
.issuer("https://saml.idp/saml")
.assertionExpiration(1000000)
.subjectExpiration(1000000)
.requestIssuer(getAuthServerRealmBase(REALM_NAME).toString())
@ -118,7 +118,7 @@ public class BrokerTest extends AbstractSamlTest {
AuthenticationExecutionInfoRepresentation reviewProfileAuthenticator = null;
String firstBrokerLoginFlowAlias = null;
try (IdentityProviderCreator idp = new IdentityProviderCreator(realm, addIdentityProvider("http://saml.idp/saml"))) {
try (IdentityProviderCreator idp = new IdentityProviderCreator(realm, addIdentityProvider("https://saml.idp/saml"))) {
IdentityProviderRepresentation idpRepresentation = idp.identityProvider().toRepresentation();
firstBrokerLoginFlowAlias = idpRepresentation.getFirstBrokerLoginFlowAlias();
List<AuthenticationExecutionInfoRepresentation> executions = realm.flows().getExecutions(firstBrokerLoginFlowAlias);
@ -168,7 +168,7 @@ public class BrokerTest extends AbstractSamlTest {
public void testRedirectQueryParametersPreserved() throws IOException {
final RealmResource realm = adminClient.realm(REALM_NAME);
try (IdentityProviderCreator idp = new IdentityProviderCreator(realm, addIdentityProvider("http://saml.idp/?service=name&serviceType=prod"))) {
try (IdentityProviderCreator idp = new IdentityProviderCreator(realm, addIdentityProvider("https://saml.idp/?service=name&serviceType=prod"))) {
SAMLDocumentHolder samlResponse = new SamlClientBuilder()
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, SAML_ASSERTION_CONSUMER_URL_SALES_POST, POST).build()
.login().idp(SAML_BROKER_ALIAS).build()
@ -178,7 +178,7 @@ public class BrokerTest extends AbstractSamlTest {
assertThat(samlResponse.getSamlObject(), Matchers.instanceOf(AuthnRequestType.class));
AuthnRequestType ar = (AuthnRequestType) samlResponse.getSamlObject();
assertThat(ar.getDestination(), Matchers.equalTo(URI.create("http://saml.idp/?service=name&serviceType=prod")));
assertThat(ar.getDestination(), Matchers.equalTo(URI.create("https://saml.idp/?service=name&serviceType=prod")));
Header[] headers = new SamlClientBuilder()
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, SAML_ASSERTION_CONSUMER_URL_SALES_POST, POST).build()
@ -187,7 +187,7 @@ public class BrokerTest extends AbstractSamlTest {
.executeAndTransform(resp -> resp.getHeaders(HttpHeaders.LOCATION));
assertThat(headers.length, Matchers.is(1));
assertThat(headers[0].getValue(), Matchers.containsString("http://saml.idp/?service=name&serviceType=prod"));
assertThat(headers[0].getValue(), Matchers.containsString("https://saml.idp/?service=name&serviceType=prod"));
assertThat(headers[0].getValue(), Matchers.containsString("SAMLRequest"));
}
}
@ -228,7 +228,7 @@ public class BrokerTest extends AbstractSamlTest {
private void assertExpired(XMLGregorianCalendar notBefore, XMLGregorianCalendar notOnOrAfter, boolean shouldPass) throws Exception {
Status expectedStatus = shouldPass ? Status.OK : Status.BAD_REQUEST;
final RealmResource realm = adminClient.realm(REALM_NAME);
try (IdentityProviderCreator idp = new IdentityProviderCreator(realm, addIdentityProvider("http://saml.idp/"))) {
try (IdentityProviderCreator idp = new IdentityProviderCreator(realm, addIdentityProvider("https://saml.idp/"))) {
new SamlClientBuilder()
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, SAML_ASSERTION_CONSUMER_URL_SALES_POST, POST).build()
.login().idp(SAML_BROKER_ALIAS).build()

View file

@ -210,7 +210,7 @@ public class IdpInitiatedLoginTest extends AbstractSamlTest {
IdentityProviderBuilder.create()
.alias("saml-idp")
.providerId("saml")
.setAttribute(SAMLIdentityProviderConfig.SINGLE_SIGN_ON_SERVICE_URL, "http://saml-idp-sso-service/")
.setAttribute(SAMLIdentityProviderConfig.SINGLE_SIGN_ON_SERVICE_URL, "https://saml-idp-sso-service/")
.setAttribute(SAMLIdentityProviderConfig.POST_BINDING_AUTHN_REQUEST, "true")
.build())) {
new SamlClientBuilder()

View file

@ -75,9 +75,9 @@ public class LogoutTest extends AbstractSamlTest {
private static final String SP_NAME_QUALIFIER = "spNameQualifier";
private static final String NAME_QUALIFIER = "nameQualifier";
private static final String BROKER_SIGN_ON_SERVICE_URL = "http://saml.idp/saml";
private static final String BROKER_LOGOUT_SERVICE_URL = "http://saml.idp/SLO/saml";
private static final String BROKER_SERVICE_ID = "http://saml.idp/saml";
private static final String BROKER_SIGN_ON_SERVICE_URL = "https://saml.idp/saml";
private static final String BROKER_LOGOUT_SERVICE_URL = "https://saml.idp/SLO/saml";
private static final String BROKER_SERVICE_ID = "https://saml.idp/saml";
private ClientRepresentation salesRep;
private ClientRepresentation sales2Rep;

View file

@ -13,9 +13,9 @@
"config" : {
"nameIDPolicyFormat" : "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
"postBindingResponse" : "false",
"singleLogoutServiceUrl" : "http://saml.idp/saml",
"singleLogoutServiceUrl" : "https://saml.idp/saml",
"postBindingAuthnRequest" : "false",
"singleSignOnServiceUrl" : "http://saml.idp/saml",
"singleSignOnServiceUrl" : "https://saml.idp/saml",
"backchannelSupported" : "false"
}
}