diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/mfa/AbstractMfaProviderConfig.java b/model/src/main/java/org/cloudfoundry/identity/uaa/mfa/AbstractMfaProviderConfig.java deleted file mode 100644 index 5950129053d..00000000000 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/mfa/AbstractMfaProviderConfig.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.mfa; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; - -import java.util.Objects; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonIgnoreProperties(ignoreUnknown = true) -public abstract class AbstractMfaProviderConfig { - private String issuer; - - public abstract void validate(); - - public String getIssuer() { - return issuer; - } - - public AbstractMfaProviderConfig setIssuer(String issuer) { - this.issuer = issuer; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - AbstractMfaProviderConfig that = (AbstractMfaProviderConfig) o; - - return Objects.equals(issuer, that.issuer); - } - - @Override - public int hashCode() { - return issuer != null ? issuer.hashCode() : 0; - } -} diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/mfa/GoogleMfaProviderConfig.java b/model/src/main/java/org/cloudfoundry/identity/uaa/mfa/GoogleMfaProviderConfig.java deleted file mode 100644 index 09ed796e0f8..00000000000 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/mfa/GoogleMfaProviderConfig.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.mfa; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; - -import java.util.Objects; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonIgnoreProperties(ignoreUnknown = true) -public class GoogleMfaProviderConfig extends AbstractMfaProviderConfig { - - private String providerDescription; - - public void validate() {} - - public String getProviderDescription() { - return providerDescription; - } - - public GoogleMfaProviderConfig setProviderDescription(String providerDescription) { - this.providerDescription = providerDescription; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - GoogleMfaProviderConfig that = (GoogleMfaProviderConfig) o; - - if (!Objects.equals(providerDescription, that.providerDescription)) - return false; - return super.equals(that); - } - - @Override - public int hashCode() { - int result = super.hashCode(); - result += providerDescription != null ? providerDescription.hashCode() : 0; - return result; - } -} \ No newline at end of file diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaProvider.java b/model/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaProvider.java deleted file mode 100644 index 4118c10c9d7..00000000000 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaProvider.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.mfa; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonValue; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.springframework.util.StringUtils; - -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import static org.cloudfoundry.identity.uaa.util.JsonUtils.getNodeAsDate; -import static org.cloudfoundry.identity.uaa.util.JsonUtils.getNodeAsString; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonIgnoreProperties(ignoreUnknown = true) -@JsonDeserialize(using = MfaProvider.MfaProviderDeserializer.class) -public class MfaProvider { - - public static final String FIELD_IDENTITY_ZONE_ID = "identityZoneId"; - public static final String FIELD_TYPE = "type"; - public static final String FIELD_NAME = "name"; - public static final String FIELD_CREATED = "created"; - public static final String FIELD_LAST_MODIFIED = "last_modified"; - public static final String FIELD_ID = "id"; - - private String id; - private String name; - private String identityZoneId; - - private AbstractMfaProviderConfig config; - - private MfaProviderType type; - private Date created; - @JsonProperty("last_modified") - private Date lastModified; - - public Date getCreated() { - return created; - } - - public MfaProvider setCreated(Date created) { - this.created = created; - return this; - } - - public Date getLastModified() { - return lastModified; - } - - public MfaProvider setLastModified(Date lastModified) { - this.lastModified = lastModified; - return this; - } - - - public String getIdentityZoneId() { - return identityZoneId; - } - - public MfaProvider setIdentityZoneId(String identityZoneId) { - this.identityZoneId = identityZoneId; - return this; - } - - public enum MfaProviderType { - GOOGLE_AUTHENTICATOR; - - private static Map namesMap = new HashMap(); - static { - namesMap.put("google-authenticator", GOOGLE_AUTHENTICATOR); - } - - @JsonCreator - public static MfaProviderType forValue(String value) { - return namesMap.get(value); - } - - @JsonValue - public String toValue() { - for (Map.Entry entry : namesMap.entrySet()) { - if (entry.getValue() == this) - return entry.getKey(); - } - - return null; // or fail - } - - public static Set getStringValues() { - return namesMap.keySet(); - } - } - - public T getConfig() { - return (T) config; - } - - public MfaProvider setConfig(T config) { - this.config = config; - return this; - } - - public String getId() { - return id; - } - - public MfaProvider setId(String id) { - this.id = id; - return this; - } - - public String getName() { - return name; - } - - public MfaProvider setName(String name) { - this.name = name; - return this; - } - - public MfaProviderType getType() { - return type; - } - - public MfaProvider setType(MfaProviderType type) { - this.type = type; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof MfaProvider)) return false; - - MfaProvider that = (MfaProvider) o; - - if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) return false; - return getIdentityZoneId() != null ? getIdentityZoneId().equals(that.getIdentityZoneId()) : that.getIdentityZoneId() == null; - } - - @Override - public int hashCode() { - int result = getName() != null ? getName().hashCode() : 0; - result = 31 * result + (getIdentityZoneId() != null ? getIdentityZoneId().hashCode() : 0); - return result; - } - - public static class MfaProviderDeserializer extends JsonDeserializer { - - @Override - public MfaProvider deserialize(JsonParser p, DeserializationContext ctxt) { - MfaProvider result = new MfaProvider(); - - JsonNode node = JsonUtils.readTree(p); - MfaProviderType type; - try { - type = MfaProviderType.forValue(getNodeAsString(node, FIELD_TYPE, "google-authenticator")); - } catch(IllegalArgumentException e) { - type = null; - } - //deserialize based on type - JsonNode configNode = node.get("config"); - String config = configNode != null ? (configNode.isTextual() ? configNode.textValue() : configNode.toString()) : null; - AbstractMfaProviderConfig definition = null; - if(type != null) { - if (type == MfaProviderType.GOOGLE_AUTHENTICATOR) { - definition = StringUtils.hasText(config) ? JsonUtils.readValue(config, GoogleMfaProviderConfig.class) : new GoogleMfaProviderConfig(); - } - } - - result.setConfig(definition); - result.setType(type); - result.setName(getNodeAsString(node, FIELD_NAME, null)); - result.setId(getNodeAsString(node, FIELD_ID, null)); - result.setIdentityZoneId(getNodeAsString(node, FIELD_IDENTITY_ZONE_ID, null)); - result.setCreated(getNodeAsDate(node, FIELD_CREATED)); - result.setLastModified(getNodeAsDate(node, FIELD_LAST_MODIFIED)); - - return result; - } - } - -} - - diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/mfa/UserGoogleMfaCredentials.java b/model/src/main/java/org/cloudfoundry/identity/uaa/mfa/UserGoogleMfaCredentials.java deleted file mode 100644 index f0d89728816..00000000000 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/mfa/UserGoogleMfaCredentials.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.mfa; - -import java.io.Serializable; -import java.util.List; - -public class UserGoogleMfaCredentials implements Serializable { - private String userId; - private String secretKey; - private List scratchCodes; - private int validationCode; - private String mfaProviderId; - private String ZoneId; - - - public UserGoogleMfaCredentials(String userId, String secretKey, int validationCode, List scratchCodes) { - this.userId = userId; - this.secretKey = secretKey; - this.scratchCodes = scratchCodes; - this.validationCode = validationCode; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getSecretKey() { - return secretKey; - } - - public void setSecretKey(String secretKey) { - this.secretKey = secretKey; - } - - public List getScratchCodes() { - return scratchCodes; - } - - public void setScratchCodes(List scratchCodes) { - this.scratchCodes = scratchCodes; - } - - public int getValidationCode() { - return validationCode; - } - - public void setValidationCode(int validationCode) { - this.validationCode = validationCode; - } - - public String getMfaProviderId() { - return mfaProviderId; - } - - public String getZoneId() { - return ZoneId; - } - - public UserGoogleMfaCredentials setZoneId(String zoneId) { - ZoneId = zoneId; - return this; - } - - public UserGoogleMfaCredentials setMfaProviderId(String mfaProviderId) { - this.mfaProviderId = mfaProviderId; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - UserGoogleMfaCredentials that = (UserGoogleMfaCredentials) o; - - if (validationCode != that.validationCode) return false; - if (!userId.equals(that.userId)) return false; - if (!secretKey.equals(that.secretKey)) return false; - return scratchCodes.equals(that.scratchCodes); - } - - @Override - public int hashCode() { - int result = userId.hashCode(); - result = 31 * result + secretKey.hashCode(); - result = 31 * result + scratchCodes.hashCode(); - result = 31 * result + validationCode; - return result; - } -} diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java index b941912bb78..79cfd45c6ef 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java @@ -40,7 +40,6 @@ public class IdentityZoneConfiguration { private BrandingInformation branding; private boolean accountChooserEnabled; private UserConfig userConfig = new UserConfig(); - private MfaConfig mfaConfig = new MfaConfig(); private String issuer; private String defaultIdentityProvider; @@ -113,15 +112,6 @@ public void setAccountChooserEnabled(boolean accountChooserEnabled) { this.accountChooserEnabled = accountChooserEnabled; } - public MfaConfig getMfaConfig() { - return mfaConfig; - } - - public IdentityZoneConfiguration setMfaConfig(MfaConfig mfaConfig) { - this.mfaConfig = mfaConfig; - return this; - } - public CorsPolicy getCorsPolicy() { return corsPolicy; } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/MfaConfig.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/MfaConfig.java deleted file mode 100644 index 80e4abd5bcc..00000000000 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/MfaConfig.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.cloudfoundry.identity.uaa.zone; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonIgnoreProperties(ignoreUnknown = true) -public class MfaConfig { - - private boolean enabled = false; - private String providerName; - private List identityProviders = new ArrayList<>(); - public static final List DEFAULT_MFA_IDENTITY_PROVIDERS = Arrays.asList("uaa", "ldap"); - - - @Override - public String toString() { - return "MfaConfig: {" + - "enabled:" + enabled + - ", providerName:\"" + providerName + '\"' + - ", identityProviders:" + Arrays.toString(identityProviders.toArray()) + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - MfaConfig that = (MfaConfig) o; - - if (enabled != that.enabled) return false; - return Objects.equals(providerName, that.providerName); - } - - @Override - public int hashCode() { - int result = (enabled ? 1 : 0); - result = 31 * result + (providerName != null ? providerName.hashCode() : 0); - return result; - } - - public boolean isEnabled() { - return enabled; - } - - public MfaConfig setEnabled(boolean enabled) { - this.enabled = enabled; - return this; - } - - public String getProviderName() { - return providerName; - } - - public MfaConfig setProviderName(String providerName) { - this.providerName = providerName; - return this; - } - - public List getIdentityProviders() { - if (identityProviders == null || identityProviders.isEmpty()) { - return DEFAULT_MFA_IDENTITY_PROVIDERS; - } - - return identityProviders; - } - - public void setIdentityProviders(List identityProviders) { - this.identityProviders = identityProviders; - } - -} diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/mfa/GoogleMfaProviderConfigTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/mfa/GoogleMfaProviderConfigTest.java deleted file mode 100644 index 3ab1a97c731..00000000000 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/mfa/GoogleMfaProviderConfigTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import org.junit.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; - - -public class GoogleMfaProviderConfigTest { - - GoogleMfaProviderConfig config; - - @Test - public void testDefaultConfig() { - config = new GoogleMfaProviderConfig(); - assertThat(config.getProviderDescription(), is(nullValue())); - assertThat(config.getIssuer(), is(nullValue())); - } -} \ No newline at end of file diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderTest.java deleted file mode 100644 index 75b8f8ec788..00000000000 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import com.fasterxml.jackson.databind.JsonNode; -import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import java.util.Date; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -public class MfaProviderTest { - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Test - public void testSerialize() { - - MfaProvider provider = createValidGoogleMfaProvider(); - provider.setCreated(new Date()); - provider.setLastModified(new Date()); - String string = JsonUtils.writeValueAsString(provider); - JsonNode output = JsonUtils.readTree(JsonUtils.writeValueAsString(provider)); - assertEquals(output.get("type").textValue(), MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR.toValue()); - JsonNode config = output.get("config"); - assertEquals(config.get("issuer").textValue(), "current-zone"); - assertEquals(config.get("providerDescription").textValue(), "config description"); - } - - @Test - public void testDeserialize() { - String json = "{\n" + - " \"type\" : \"google-authenticator\",\n" + - " \"config\" : {\n" + - " \"providerDescription\" : \"ddd\",\n" + - " \"issuer\": \"issuer\",\n" + - " \"algorithm\": \"SHA256\",\n" + - " \"digits\": 8, \n" + - " \"duration\": 32 \n" + - " },\n" + - " \"name\" : \"UAA Provider\", \n" + - " \"active\" : true\n" + - "}"; - - MfaProvider provider = JsonUtils.readValue(json, MfaProvider.class); - - assertEquals(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR, provider.getType()); - assertEquals("UAA Provider", provider.getName()); - GoogleMfaProviderConfig config = provider.getConfig(); - assertEquals("issuer", config.getIssuer()); - assertEquals("ddd", config.getProviderDescription()); - } - - - @Test - public void testDeserializeInvalidType() { - String json = "{\n" + - " \"type\" : \"invalid-type\",\n" + - " \"config\" : {\n" + - " \"providerDescription\" : \"ddd\",\n" + - " \"issuer\": \"issuer\",\n" + - " \"algorithm\": \"SHA256\",\n" + - " \"digits\": 8, \n" + - " \"duration\": 32 \n" + - " },\n" + - " \"name\" : \"UAA Provider\" \n" + - "}"; - - MfaProvider provider = JsonUtils.readValue(json, MfaProvider.class); - - assertNull(provider.getType()); - assertEquals("UAA Provider", provider.getName()); - assertNull(provider.getConfig()); - } - - @Test - public void validateProviderActiveSetDefaultToTrue() { - MfaProvider provider = createValidGoogleMfaProvider(); - } - - private MfaProvider createValidGoogleMfaProvider() { - MfaProvider res = new MfaProvider(); - res.setName(new RandomValueStringGenerator(5).generate()) - .setConfig(createValidGoogleMfaConfig()) - .setIdentityZoneId(IdentityZone.getUaaZoneId()) - .setType(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR); - return res; - } - - private GoogleMfaProviderConfig createValidGoogleMfaConfig() { - return (GoogleMfaProviderConfig) new GoogleMfaProviderConfig() - .setProviderDescription("config description") - .setIssuer("current-zone"); - } -} \ No newline at end of file diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/mfa/RandomValueStringGenerator.java b/model/src/test/java/org/cloudfoundry/identity/uaa/mfa/RandomValueStringGenerator.java deleted file mode 100644 index 00fdedf92b5..00000000000 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/mfa/RandomValueStringGenerator.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import java.security.SecureRandom; -import java.util.Random; - -/** - * Utility that generates a random-value ASCII string. - * - * @author Ryan Heaton - * @author Dave Syer - */ -public class RandomValueStringGenerator { - - private static final char[] DEFAULT_CODEC = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - .toCharArray(); - - private Random random = new SecureRandom(); - - private int length; - - /** - * Create a generator with the default length (6). - */ - public RandomValueStringGenerator() { - this(6); - } - - /** - * Create a generator of random strings of the length provided - * - * @param length the length of the strings generated - */ - public RandomValueStringGenerator(int length) { - this.length = length; - } - - public String generate() { - byte[] verifierBytes = new byte[length]; - random.nextBytes(verifierBytes); - return getAuthorizationCodeString(verifierBytes); - } - - /** - * Convert these random bytes to a verifier string. The length of the byte array can be - * {@link #setLength(int) configured}. The default implementation mods the bytes to fit into the - * ASCII letters 1-9, A-Z, a-z . - * - * @param verifierBytes The bytes. - * @return The string. - */ - protected String getAuthorizationCodeString(byte[] verifierBytes) { - char[] chars = new char[verifierBytes.length]; - for (int i = 0; i < verifierBytes.length; i++) { - chars[i] = DEFAULT_CODEC[((verifierBytes[i] & 0xFF) % DEFAULT_CODEC.length)]; - } - return new String(chars); - } - - /** - * The random value generator used to create token secrets. - * - * @param random The random value generator used to create token secrets. - */ - public void setRandom(Random random) { - this.random = random; - } - - /** - * The length of string to generate. - * - * @param length the length to set - */ - public void setLength(int length) { - this.length = length; - } - -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java index e032d2b9f9f..681ab790979 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java @@ -60,8 +60,8 @@ public enum AuditEventType { TokenRevocationEvent(36), IdentityProviderAuthenticationSuccess(37), IdentityProviderAuthenticationFailure(38), - MfaAuthenticationSuccess(39), - MfaAuthenticationFailure(40), + MfaAuthenticationSuccess(39), // This is unused, as MFA is feature is removed, but removing this event results in [this test failure](https://github.com/cloudfoundry/uaa/blob/8a4ca068aa6f4faeb3e83765ead5900ceb159121/server/src/test/java/org/cloudfoundry/identity/uaa/audit/AuditEventTypeTests.java); fixing the test would require changing the event code number, which [this comment](https://github.com/cloudfoundry/uaa/blob/8a4ca068aa6f4faeb3e83765ead5900ceb159121/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java#L23) says we cannot. So leaving this unused event here for now. + MfaAuthenticationFailure(40), // This is unused, as MFA is feature is removed, but removing this event results in [this test failure](https://github.com/cloudfoundry/uaa/blob/8a4ca068aa6f4faeb3e83765ead5900ceb159121/server/src/test/java/org/cloudfoundry/identity/uaa/audit/AuditEventTypeTests.java); fixing the test would require changing the event code number, which [this comment](https://github.com/cloudfoundry/uaa/blob/8a4ca068aa6f4faeb3e83765ead5900ceb159121/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java#L23) says we cannot. So leaving this unused event here for now. ClientJwtChangeSuccess(41), ClientJwtChangeFailure(42); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/JdbcUnsuccessfulLoginCountingAuditService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/JdbcUnsuccessfulLoginCountingAuditService.java index e1aaaa169cd..689fd873954 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/JdbcUnsuccessfulLoginCountingAuditService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/JdbcUnsuccessfulLoginCountingAuditService.java @@ -9,7 +9,6 @@ import java.time.Duration; import java.util.concurrent.atomic.AtomicLong; -import static org.cloudfoundry.identity.uaa.audit.AuditEventType.MfaAuthenticationFailure; import static org.cloudfoundry.identity.uaa.audit.AuditEventType.UserAuthenticationFailure; /** @@ -40,20 +39,14 @@ public JdbcUnsuccessfulLoginCountingAuditService( @Override public void log(AuditEvent auditEvent, String zoneId) { switch (auditEvent.getType()) { - case MfaAuthenticationSuccess: - resetAuthenticationEvents(auditEvent, zoneId, MfaAuthenticationFailure); - - break; case UserAuthenticationSuccess: case PasswordChangeSuccess: resetAuthenticationEvents(auditEvent, zoneId, UserAuthenticationFailure); break; case UserAccountUnlockedEvent: resetAuthenticationEvents(auditEvent, zoneId, UserAuthenticationFailure); - resetAuthenticationEvents(auditEvent, zoneId, MfaAuthenticationFailure); break; case UserAuthenticationFailure: - case MfaAuthenticationFailure: periodicDelete(); super.log(auditEvent, zoneId); break; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/SystemDeletable.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/SystemDeletable.java index 0e2a2b1e008..7c5adb8af3b 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/SystemDeletable.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/SystemDeletable.java @@ -16,7 +16,6 @@ package org.cloudfoundry.identity.uaa.audit.event; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.user.UaaUser; @@ -64,10 +63,6 @@ default void onApplicationEvent(EntityDeletedEvent event) { String zoneId = ((ScimUser) event.getDeleted()).getZoneId(); getLogger().debug(String.format("Received SCIM user deletion event for zone_id:%s and user:%s", zoneId, userId)); deleteByUser(userId, zoneId); - } else if (event.getDeleted() instanceof MfaProvider) { - String providerId = ((MfaProvider) event.getDeleted()).getId(); - String zoneId = IdentityZoneHolder.get().getId(); - deleteByMfaProvider(providerId, zoneId); } else { getLogger().debug("Unsupported deleted event for deletion of object:" + event.getDeleted()); } @@ -95,9 +90,5 @@ default int deleteByUser(String userId, String zoneId) { return 0; } - default int deleteByMfaProvider(String id, String zoneId) { - return 0; - } - Logger getLogger(); } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/PasswordChangeUiRequiredFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/PasswordChangeUiRequiredFilter.java index 709b8024756..90858ee80b7 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/PasswordChangeUiRequiredFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/PasswordChangeUiRequiredFilter.java @@ -18,18 +18,15 @@ public class PasswordChangeUiRequiredFilter extends OncePerRequestFilter { private final static String MATCH_PATH = "/force_password_change"; - private final static String IGNORE_PATH = "/login/mfa/**"; private final static String COMPLETED_PATH = "/force_password_change_completed"; private final AntPathRequestMatcher matchPath; - private final AntPathRequestMatcher ignorePath; private final AntPathRequestMatcher completedPath; private final UaaSavedRequestCache cache; public PasswordChangeUiRequiredFilter(final UaaSavedRequestCache cache) { this.cache = cache; this.matchPath = new AntPathRequestMatcher(MATCH_PATH); - this.ignorePath = new AntPathRequestMatcher(IGNORE_PATH); this.completedPath = new AntPathRequestMatcher(COMPLETED_PATH); } @@ -38,10 +35,7 @@ protected void doFilterInternal( final @NonNull HttpServletRequest request, final @NonNull HttpServletResponse response, final @NonNull FilterChain filterChain) throws ServletException, IOException { - if (isIgnored(request)) { - //pass through even though 'change' is required request - filterChain.doFilter(request, response); - } else if (isCompleted(request)) { + if (isCompleted(request)) { logger.debug("Forced password change has been completed."); SavedRequest savedRequest = cache.getRequest(request, response); if (savedRequest != null) { @@ -63,10 +57,6 @@ protected void doFilterInternal( } } - protected boolean isIgnored(HttpServletRequest request) { - return ignorePath.matches(request); - } - private boolean isAuthenticated() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); return authentication != null && authentication.isAuthenticated(); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/MfaAuthenticationFailureEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/MfaAuthenticationFailureEvent.java deleted file mode 100644 index ad7b2779618..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/MfaAuthenticationFailureEvent.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.cloudfoundry.identity.uaa.authentication.event; - -import org.cloudfoundry.identity.uaa.audit.AuditEvent; -import org.cloudfoundry.identity.uaa.audit.AuditEventType; -import org.cloudfoundry.identity.uaa.user.UaaUser; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -public class MfaAuthenticationFailureEvent extends AbstractUaaAuthenticationEvent { - private final UaaUser user; - private final String type; - - public MfaAuthenticationFailureEvent(UaaUser user, Authentication authentication, String type, String zoneId) { - super(authentication, zoneId); - this.user = user; - this.type = type; - } - - @Override - public AuditEvent getAuditEvent() { - Assert.notNull(user, "UaaUser cannot be null"); - return createAuditRecord(user.getId(), AuditEventType.MfaAuthenticationFailure, - getOrigin(getAuthenticationDetails()), user.getUsername(), type, null); - } - - public UaaUser getUser() { - return user; - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/MfaAuthenticationSuccessEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/MfaAuthenticationSuccessEvent.java deleted file mode 100644 index 1af419259a8..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/MfaAuthenticationSuccessEvent.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.cloudfoundry.identity.uaa.authentication.event; - -import org.cloudfoundry.identity.uaa.audit.AuditEvent; -import org.cloudfoundry.identity.uaa.audit.AuditEventType; -import org.cloudfoundry.identity.uaa.user.UaaUser; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -public class MfaAuthenticationSuccessEvent extends AbstractUaaAuthenticationEvent { - private final UaaUser user; - private final String type; - - public MfaAuthenticationSuccessEvent(UaaUser user, Authentication authentication, String type, String zoneId) { - super(authentication, zoneId); - this.user = user; - this.type = type; - } - - @Override - public AuditEvent getAuditEvent() { - Assert.notNull(user, "UaaUser cannot be null"); - return createAuditRecord(user.getId(), AuditEventType.MfaAuthenticationSuccess, - getOrigin(getAuthenticationDetails()), user.getUsername(), type, null); - } - - public UaaUser getUser() { - return user; - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/AuthenticationSuccessListener.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/AuthenticationSuccessListener.java index 29610c8e20c..c8100e7781f 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/AuthenticationSuccessListener.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/AuthenticationSuccessListener.java @@ -16,9 +16,7 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.event.AbstractUaaAuthenticationEvent; import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; -import org.cloudfoundry.identity.uaa.authentication.event.MfaAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; -import org.cloudfoundry.identity.uaa.mfa.MfaChecker; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.user.UaaUser; @@ -32,13 +30,10 @@ public class AuthenticationSuccessListener implements ApplicationListener, ApplicationEventPublisherAware { private final ScimUserProvisioning scimUserProvisioning; - private final MfaChecker checker; private ApplicationEventPublisher publisher; - public AuthenticationSuccessListener(ScimUserProvisioning scimUserProvisioning, - MfaChecker checker) { + public AuthenticationSuccessListener(ScimUserProvisioning scimUserProvisioning) { this.scimUserProvisioning = scimUserProvisioning; - this.checker = checker; } @Override @@ -51,15 +46,6 @@ public void onApplicationEvent(AbstractUaaAuthenticationEvent event) { passwordAuthEvent.getUser(), (Authentication) passwordAuthEvent.getSource(), IdentityZoneHolder.getCurrentZoneId() ); - if (!checker.isMfaEnabledForZoneId(userEvent.getIdentityZoneId())) { - publisher.publishEvent(userEvent); - } - } else if (event instanceof MfaAuthenticationSuccessEvent) { - MfaAuthenticationSuccessEvent mfaEvent = (MfaAuthenticationSuccessEvent) event; - UserAuthenticationSuccessEvent userEvent = new UserAuthenticationSuccessEvent( - mfaEvent.getUser(), - (Authentication) mfaEvent.getSource(), IdentityZoneHolder.getCurrentZoneId() - ); publisher.publishEvent(userEvent); } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicy.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicy.java index 2974c03bc17..f18ee6594a5 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicy.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicy.java @@ -33,12 +33,9 @@ public class PeriodLockoutPolicy implements AccountLoginPolicy { private final Logger logger = LoggerFactory.getLogger(getClass()); private final LoginPolicy loginPolicy; - private final LoginPolicy mfaPolicy; - - public PeriodLockoutPolicy(LoginPolicy loginPolicy, LoginPolicy mfaPolicy) { + public PeriodLockoutPolicy(LoginPolicy loginPolicy) { this.loginPolicy = loginPolicy; - this.mfaPolicy = mfaPolicy; } public LockoutPolicy getDefaultLockoutPolicy() { @@ -48,13 +45,11 @@ public LockoutPolicy getDefaultLockoutPolicy() { @Override public boolean isAllowed(UaaUser user, Authentication a) throws AuthenticationException { Result loginResult = loginPolicy.isAllowed(user.getId()); - Result mfaResult = mfaPolicy.isAllowed(user.getId()); - if (loginResult.isAllowed() && mfaResult.isAllowed()) { + if (loginResult.isAllowed()) { return true; } logger.warn("User " + user.getUsername() + " and id " + user.getId() + " has " - + loginResult.getFailureCount() + " failed user logins within the last checking period." - + " and " + mfaResult.getFailureCount() + " failed mfa attempts within the last checking period."); + + loginResult.getFailureCount() + " failed user logins within the last checking period."); return false; } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java index dc7a96b73ee..b084344967b 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityZoneConfigurationBootstrap.java @@ -44,8 +44,6 @@ public class IdentityZoneConfigurationBootstrap implements InitializingBean { private boolean selfServiceLinksEnabled = true; private String homeRedirect = null; private Map selfServiceLinks; - private boolean mfaEnabled; - private String mfaProviderName; private List logoutRedirectWhitelist; private String logoutRedirectParameterName; private String logoutDefaultRedirectUrl; @@ -93,8 +91,6 @@ public void afterPropertiesSet() throws InvalidIdentityZoneDetailsException { definition.getSamlConfig().setDisableInResponseToCheck(disableSamlInResponseToCheck); definition.setIdpDiscoveryEnabled(idpDiscoveryEnabled); definition.setAccountChooserEnabled(accountChooserEnabled); - definition.getMfaConfig().setEnabled(mfaEnabled); - definition.getMfaConfig().setProviderName(mfaProviderName); definition.setDefaultIdentityProvider(defaultIdentityProvider); samlKeys = ofNullable(samlKeys).orElse(EMPTY_MAP); @@ -149,22 +145,6 @@ public void setClientSecretPolicy(ClientSecretPolicy clientSecretPolicy) { this.clientSecretPolicy = clientSecretPolicy; } - public void setMfaEnabled(boolean mfaEnabled) { - this.mfaEnabled = mfaEnabled; - } - - public void setMfaProviderName(String mfaProviderName) { - this.mfaProviderName = mfaProviderName; - } - - public String getMfaProviderName() { - return mfaProviderName; - } - - public boolean isMfaEnabled() { - return mfaEnabled; - } - public IdentityZoneConfigurationBootstrap setSamlKeys(Map> samlKeys) { this.samlKeys = samlKeys; return this; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java index f69c28ef69f..1d4625a884b 100755 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java @@ -59,7 +59,6 @@ import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.mfa.MfaChecker; import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.provider.AbstractExternalOAuthIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; @@ -106,8 +105,6 @@ public class LoginInfoEndpoint { private static Logger logger = LoggerFactory.getLogger(LoginInfoEndpoint.class); - private static final String MFA_CODE = "mfaCode"; - private static final String CREATE_ACCOUNT_LINK = "createAccountLink"; private static final String FORGOT_PASSWORD_LINK = "forgotPasswordLink"; private static final String LINK_CREATE_ACCOUNT_SHOW = "linkCreateAccountShow"; @@ -133,7 +130,6 @@ public class LoginInfoEndpoint { private final IdentityProviderProvisioning providerProvisioning; private final ExternalOAuthProviderConfigurator externalOAuthProviderConfigurator; private final Links globalLinks; - private final MfaChecker mfaChecker; private final String entityID; private static final MapCollector idpsMapCollector = @@ -147,7 +143,6 @@ public LoginInfoEndpoint( final @Qualifier("codeStore") ExpiringCodeStore expiringCodeStore, final @Value("${login.url:''}") String externalLoginUrl, final @Qualifier("uaaUrl") String baseUrl, - final @Qualifier("mfaChecker") MfaChecker mfaChecker, final @Qualifier("externalOAuthProviderConfigurator") ExternalOAuthProviderConfigurator externalOAuthProviderConfigurator, final @Qualifier("identityProviderProvisioning") IdentityProviderProvisioning providerProvisioning, final @Qualifier("samlEntityID") String entityID, @@ -158,7 +153,6 @@ public LoginInfoEndpoint( this.expiringCodeStore = expiringCodeStore; this.externalLoginUrl = externalLoginUrl; this.baseUrl = baseUrl; - this.mfaChecker = mfaChecker; this.externalOAuthProviderConfigurator = externalOAuthProviderConfigurator; this.providerProvisioning = providerProvisioning; this.entityID = entityID; @@ -222,7 +216,7 @@ public String loginForHtml(Model model, model.addAttribute("savedAccounts", savedAccounts); - return login(model, principal, Arrays.asList(PASSCODE, MFA_CODE), false, request); + return login(model, principal, Arrays.asList(PASSCODE), false, request); } private static List getSavedAccounts(Cookie[] cookies, Class clazz) { @@ -743,14 +737,6 @@ private void populatePrompts( } map.put(prompt.getName(), details); } - if (mfaChecker.isMfaEnabled(IdentityZoneHolder.get())) { - Prompt p = new Prompt( - MFA_CODE, - "password", - "MFA Code ( Register at " + addSubdomainToUrl(baseUrl + " )", IdentityZoneHolder.get().getSubdomain()) - ); - map.putIfAbsent(p.getName(), p.getDetails()); - } for (String excludeThisPrompt : exclude) { map.remove(excludeThisPrompt); } @@ -836,9 +822,6 @@ private String goToPasswordPage(String email, Model model) { @ResponseBody public AutologinResponse generateAutologinCode(@RequestBody AutologinRequest request, @RequestHeader(value = "Authorization", required = false) String auth) throws Exception { - if (mfaChecker.isMfaEnabled(IdentityZoneHolder.get())) { - throw new BadCredentialsException("MFA is required"); - } if (auth == null || (!auth.startsWith("Basic"))) { throw new BadCredentialsException("No basic authorization client information in request"); @@ -882,9 +865,6 @@ public AutologinResponse generateAutologinCode(@RequestBody AutologinRequest req @RequestMapping(value = "/autologin", method = GET) public String performAutologin(HttpSession session) { - if (mfaChecker.isMfaEnabled(IdentityZoneHolder.get())) { - throw new BadCredentialsException("MFA is required"); - } String redirectLocation = "home"; SavedRequest savedRequest = SessionUtils.getSavedRequestSession(session); if (savedRequest != null && savedRequest.getRedirectUrl() != null) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/login/TotpMfaEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/login/TotpMfaEndpoint.java deleted file mode 100644 index fb78418bcb1..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/login/TotpMfaEndpoint.java +++ /dev/null @@ -1,227 +0,0 @@ -package org.cloudfoundry.identity.uaa.login; - -import com.google.zxing.WriterException; -import com.warrenstrange.googleauth.GoogleAuthenticatorException; -import org.cloudfoundry.identity.uaa.authentication.AuthenticationPolicyRejectionException; -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.authentication.event.MfaAuthenticationFailureEvent; -import org.cloudfoundry.identity.uaa.authentication.event.MfaAuthenticationSuccessEvent; -import org.cloudfoundry.identity.uaa.authentication.manager.CommonLoginPolicy; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; -import org.cloudfoundry.identity.uaa.mfa.MfaProviderProvisioning; -import org.cloudfoundry.identity.uaa.mfa.UserGoogleMfaCredentials; -import org.cloudfoundry.identity.uaa.mfa.UserGoogleMfaCredentialsProvisioning; -import org.cloudfoundry.identity.uaa.user.UaaUser; -import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; -import org.cloudfoundry.identity.uaa.util.SessionUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.bind.support.SessionStatus; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.view.RedirectView; - -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -/* TOTP = Time-based, One-Time Password - MFA = Multi-Factor Authentication */ -@Controller -@SessionAttributes("uaaMfaCredentials") -@RequestMapping("/login/mfa") -public class TotpMfaEndpoint implements ApplicationEventPublisherAware { - - private final UserGoogleMfaCredentialsProvisioning mfaCredentialsProvisioning; - private final MfaProviderProvisioning mfaProviderProvisioning; - private Logger logger = LoggerFactory.getLogger(TotpMfaEndpoint.class); - - private final String mfaCompleteUrl; - private ApplicationEventPublisher eventPublisher; - private final UaaUserDatabase userDatabase; - private final CommonLoginPolicy mfaPolicy; - - public TotpMfaEndpoint( - final UserGoogleMfaCredentialsProvisioning mfaCredentialsProvisioning, - final MfaProviderProvisioning mfaProviderProvisioning, - final @Qualifier("mfaCompleteUrl") String mfaCompleteUrl, - final UaaUserDatabase userDatabase, - final @Qualifier("mfaGlobalUserLoginPolicy") CommonLoginPolicy mfaPolicy) { - this.mfaCredentialsProvisioning = mfaCredentialsProvisioning; - this.mfaProviderProvisioning = mfaProviderProvisioning; - this.mfaCompleteUrl = mfaCompleteUrl; - this.userDatabase = userDatabase; - this.mfaPolicy = mfaPolicy; - } - - @ModelAttribute("uaaMfaCredentials") - public UserGoogleMfaCredentials getUaaMfaCredentials() throws UaaPrincipalIsNotInSession { - UaaPrincipal principal = getSessionAuthPrincipal(); - UserGoogleMfaCredentials result = mfaCredentialsProvisioning.getUserGoogleMfaCredentials(principal.getId()); - if (result == null) { - result = mfaCredentialsProvisioning.createUserCredentials(principal.getId()); - result.setMfaProviderId(getMfaProvider().getId()); - } - return result; - } - - @RequestMapping(value = {"/register"}, method = RequestMethod.GET) - public String generateQrUrl(Model model, - @ModelAttribute("uaaMfaCredentials") UserGoogleMfaCredentials credentials) - throws WriterException, IOException, UaaPrincipalIsNotInSession { - UaaPrincipal uaaPrincipal = getSessionAuthPrincipal(); - MfaProvider provider = getMfaProvider(); - if (mfaCredentialsProvisioning.activeUserCredentialExists(uaaPrincipal.getId(), provider.getId())) { - return "redirect:/login/mfa/verify"; - } else { - String url = mfaCredentialsProvisioning.getOtpAuthURL(provider.getConfig().getIssuer(), credentials, uaaPrincipal.getName()); - model.addAttribute("qrurl", url); - model.addAttribute("identity_zone", IdentityZoneHolder.get().getName()); - return "mfa/qr_code"; - } - } - - @RequestMapping(value = {"/manual"}, method = RequestMethod.GET) - public String manualRegistration( - Model model, - @ModelAttribute("uaaMfaCredentials") UserGoogleMfaCredentials credentials - ) throws UaaPrincipalIsNotInSession { - UaaPrincipal uaaPrincipal = getSessionAuthPrincipal(); - MfaProvider provider = getMfaProvider(); - - if (mfaCredentialsProvisioning.activeUserCredentialExists(uaaPrincipal.getId(), provider.getId())) { - return "redirect:/login/mfa/verify"; - } else { - model.addAttribute("issuer", provider.getConfig().getIssuer()); - model.addAttribute("username", uaaPrincipal.getName()); - model.addAttribute("mfa_secret", credentials.getSecretKey()); - model.addAttribute("identity_zone", IdentityZoneHolder.get().getName()); - return "mfa/manual_registration"; - } - - } - - @RequestMapping(value = {"/verify"}, method = RequestMethod.GET) - public ModelAndView totpAuthorize(Model model) throws UaaPrincipalIsNotInSession { - UaaPrincipal uaaPrincipal = getSessionAuthPrincipal(); - return renderEnterCodePage(model, uaaPrincipal); - - } - - @RequestMapping(value = {"/verify.do"}, method = RequestMethod.POST) - public ModelAndView validateCode(Model model, - @RequestParam("code") String code, - @ModelAttribute("uaaMfaCredentials") UserGoogleMfaCredentials credentials, - HttpServletRequest request, - SessionStatus sessionStatus) - throws UaaPrincipalIsNotInSession { - UaaAuthentication uaaAuth = getUaaAuthentication(); - UaaPrincipal uaaPrincipal = getSessionAuthPrincipal(); - - if (!this.mfaPolicy.isAllowed(uaaPrincipal.getId()).isAllowed()) { - throw new AuthenticationPolicyRejectionException("Your account has been locked because of too many failed attempts to login."); - } - - try { - Integer codeValue = Integer.valueOf(code); - if (mfaCredentialsProvisioning.isValidCode(credentials, codeValue)) { - if (mfaCredentialsProvisioning.getUserGoogleMfaCredentials(uaaPrincipal.getId()) == null) { - mfaCredentialsProvisioning.saveUserCredentials(credentials); - } - Set authMethods = new HashSet<>(uaaAuth.getAuthenticationMethods()); - authMethods.addAll(Arrays.asList("otp", "mfa")); - uaaAuth.setAuthenticationMethods(authMethods); - publish(new MfaAuthenticationSuccessEvent(getUaaUser(uaaPrincipal), uaaAuth, getMfaProvider().getType().toValue(), IdentityZoneHolder.getCurrentZoneId())); - sessionStatus.setComplete(); - SessionUtils.setSecurityContext(request.getSession(), SecurityContextHolder.getContext()); - return new ModelAndView(new RedirectView(mfaCompleteUrl, true)); - } - logger.debug("Code authorization failed for user: " + uaaPrincipal.getId()); - publish(new MfaAuthenticationFailureEvent(getUaaUser(uaaPrincipal), uaaAuth, getMfaProvider().getType().toValue(), IdentityZoneHolder.getCurrentZoneId())); - model.addAttribute("error", "Incorrect code, please try again."); - } catch (NumberFormatException | GoogleAuthenticatorException e) { - logger.debug("Error validating the code for user: " + uaaPrincipal.getId() + ". Error: " + e.getMessage()); - publish(new MfaAuthenticationFailureEvent(getUaaUser(uaaPrincipal), uaaAuth, getMfaProvider().getType().toValue(), IdentityZoneHolder.getCurrentZoneId())); - model.addAttribute("error", "Incorrect code, please try again."); - } - return renderEnterCodePage(model, uaaPrincipal); - } - - @ExceptionHandler(UaaPrincipalIsNotInSession.class) - public ModelAndView handleUaaPrincipalIsNotInSession() { - return new ModelAndView("redirect:/login", Collections.emptyMap()); - } - - @ExceptionHandler(AuthenticationPolicyRejectionException.class) - public ModelAndView handleMFALockedOut() { - SecurityContextHolder.getContext().setAuthentication(null); - - return new ModelAndView("redirect:/login?error=account_locked", Collections.emptyMap()); - } - - private ModelAndView renderEnterCodePage(Model model, UaaPrincipal uaaPrincipal) { - model.addAttribute("is_first_time_user", mfaCredentialsProvisioning.isFirstTimeMFAUser(uaaPrincipal)); - model.addAttribute("identity_zone", IdentityZoneHolder.get().getName()); - return new ModelAndView("mfa/enter_code", model.asMap()); - } - - private UaaPrincipal getSessionAuthPrincipal() throws UaaPrincipalIsNotInSession { - UaaAuthentication uaaAuth = getUaaAuthentication(); - if (uaaAuth != null) { - UaaPrincipal principal = uaaAuth.getPrincipal(); - if (principal != null) { - return principal; - } - } - throw new UaaPrincipalIsNotInSession(); - } - - private UaaAuthentication getUaaAuthentication() { - Authentication a = SecurityContextHolder.getContext().getAuthentication(); - return a instanceof UaaAuthentication ? (UaaAuthentication) a : null; - } - - private MfaProvider getMfaProvider() { - String providerName = IdentityZoneHolder.get().getConfig().getMfaConfig().getProviderName(); - return mfaProviderProvisioning.retrieveByName(providerName, IdentityZoneHolder.get().getId()); - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.eventPublisher = applicationEventPublisher; - } - - private void publish(ApplicationEvent event) { - if (eventPublisher != null) { - eventPublisher.publishEvent(event); - } - } - - private UaaUser getUaaUser(UaaPrincipal principal) { - try { - UaaUser user = userDatabase.retrieveUserByName(principal.getName(), principal.getOrigin()); - if (user != null) { - return user; - } - } catch (UsernameNotFoundException ignored) { - } - return null; - } - - public class UaaPrincipalIsNotInSession extends Exception { - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/GeneralMfaProviderValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/GeneralMfaProviderValidator.java deleted file mode 100644 index eecfe480a70..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/GeneralMfaProviderValidator.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import org.cloudfoundry.identity.uaa.mfa.exception.InvalidMfaProviderException; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; - -import java.util.regex.Pattern; - -public class GeneralMfaProviderValidator implements MfaProviderValidator{ - - private static final Pattern MFA_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9]+[\\sa-zA-Z0-9]{0,256}$", Pattern.UNICODE_CHARACTER_CLASS); - - @Override - public void validate(MfaProvider mfaProvider) { - if(mfaProvider.getName() == null || ObjectUtils.isEmpty(mfaProvider.getName().trim())) { - throw new InvalidMfaProviderException("Provider name is required"); - } - mfaProvider.setName(mfaProvider.getName().trim()); - if(mfaProvider.getName().length() > 256) { - throw new InvalidMfaProviderException("Provider name cannot be longer than 256 characters"); - } - if(!MFA_NAME_PATTERN.matcher(mfaProvider.getName()).matches()) { - throw new InvalidMfaProviderException("Provider name must be alphanumeric"); - } - if(mfaProvider.getType() == null) { - throw new InvalidMfaProviderException("Provider type is required. Must be one of " + MfaProvider.MfaProviderType.getStringValues()); - } - if(mfaProvider.getConfig() == null) { - throw new InvalidMfaProviderException("Provider config is required"); - } - if(mfaProvider.getConfig().getIssuer() != null && mfaProvider.getConfig().getIssuer().contains(":")) { - throw new InvalidMfaProviderException("Provider config contains an invalid issuer. Issuer must not contain a colon"); - } - if(!StringUtils.hasText(mfaProvider.getIdentityZoneId())){ - throw new InvalidMfaProviderException("Provider must belong to a zone"); - } - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/JdbcMfaProviderProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/JdbcMfaProviderProvisioning.java deleted file mode 100644 index cd51e958505..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/JdbcMfaProviderProvisioning.java +++ /dev/null @@ -1,150 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.cloudfoundry.identity.uaa.audit.event.SystemDeletable; -import org.cloudfoundry.identity.uaa.mfa.exception.MfaAlreadyExistsException; -import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.springframework.dao.DuplicateKeyException; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.util.StringUtils; - -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.util.List; -import java.util.UUID; - -public class JdbcMfaProviderProvisioning implements MfaProviderProvisioning, SystemDeletable { - - private static Logger logger = LoggerFactory.getLogger(JdbcMfaProviderProvisioning.class); - public static final String TABLE_NAME = "mfa_providers"; - public static final String MFA_PROVIDER_FIELDS = "id,name,type,config,identity_zone_id,created,lastmodified"; - public static final String CREATE_PROVIDER_SQL = "insert into " + TABLE_NAME + "(" + MFA_PROVIDER_FIELDS + ") values (?,?,?,?,?,?,?)"; - - public static final String MFA_PROVIDER_UPDATE_FIELDS = "name,type,config,identity_zone_id,lastmodified".replace(",","=?,")+"=?"; - - public static final String UPDATE_PROVIDER_SQL = "update " + TABLE_NAME + " set " + MFA_PROVIDER_UPDATE_FIELDS + " where id=? and identity_zone_id=?"; - - - public static final String MFA_PROVIDER_BY_ID_QUERY = "select " + MFA_PROVIDER_FIELDS + " from " + TABLE_NAME + " where id=? and identity_zone_id=?"; - public static final String MFA_PROVIDER_BY_NAME_QUERY = "select " + MFA_PROVIDER_FIELDS + " from " + TABLE_NAME + " where name=? and identity_zone_id=?"; - public static final String MFA_PROVIDERS_QUERY = "select " + MFA_PROVIDER_FIELDS + " from " + TABLE_NAME + " where identity_zone_id=?"; - public static final String MFA_PROVIDER_DELETE_BY_ID = "delete from " + TABLE_NAME + " where id =? and identity_zone_id=?"; - public static final String MFA_PROVIDER_DELETE_BY_ZONE_ID = "delete from " + TABLE_NAME + " where identity_zone_id=?"; - - protected final JdbcTemplate jdbcTemplate; - private MfaProviderValidator mfaProviderValidator; - private MfaProviderMapper mapper = new MfaProviderMapper(); - - public JdbcMfaProviderProvisioning(JdbcTemplate jdbcTemplate, MfaProviderValidator mfaProviderValidator) { - this.jdbcTemplate = jdbcTemplate; - this.mfaProviderValidator = mfaProviderValidator; - } - - @Override - public MfaProvider create(MfaProvider provider, String zoneId) { - mfaProviderValidator.validate(provider); - final String id = UUID.randomUUID().toString(); - try { - jdbcTemplate.update(CREATE_PROVIDER_SQL, ps -> { - int pos = 1; - ps.setString(pos++, id); - ps.setString(pos++, provider.getName()); - ps.setString(pos++, provider.getType().toValue()); - ps.setString(pos++, JsonUtils.writeValueAsString(provider.getConfig())); - ps.setString(pos++, zoneId); - ps.setTimestamp(pos++, new Timestamp(System.currentTimeMillis())); - ps.setTimestamp(pos++, new Timestamp(System.currentTimeMillis())); - }); - } catch (DuplicateKeyException e) { - String message = e.getMostSpecificCause().getMessage(); - if (message.toUpperCase().contains("IDX_MFA_UNIQUE_NAME")) { - message = "An MFA Provider with that name already exists."; - } - throw new MfaAlreadyExistsException(message); - } - return retrieve(id, zoneId); - } - - @Override - public MfaProvider update(MfaProvider provider, String zoneId) { - try { - jdbcTemplate.update(UPDATE_PROVIDER_SQL, ps -> { - int pos = 1; - ps.setString(pos++, provider.getName()); - ps.setString(pos++, provider.getType().toValue()); - ps.setString(pos++, JsonUtils.writeValueAsString(provider.getConfig())); - ps.setString(pos++, zoneId); - ps.setTimestamp(pos++, new Timestamp(System.currentTimeMillis())); - - ps.setString(pos++, provider.getId().trim()); - ps.setString(pos++, zoneId); - - }); - } catch (DuplicateKeyException e) { - String message = e.getMostSpecificCause().getMessage(); - if (message.toUpperCase().contains("IDX_MFA_UNIQUE_NAME")) { - message = "An MFA Provider with that name already exists."; - } - throw new MfaAlreadyExistsException(message); - } - - return retrieve(provider.getId(), zoneId); - } - - @Override - public MfaProvider retrieve(String id, String zoneId) { - return jdbcTemplate.queryForObject(MFA_PROVIDER_BY_ID_QUERY, mapper, id, zoneId); - } - - @Override - public MfaProvider retrieveByName(String name, String zoneId) { - return jdbcTemplate.queryForObject(MFA_PROVIDER_BY_NAME_QUERY, mapper, name, zoneId); - } - - @Override - public List retrieveAll(String zoneId) { - return jdbcTemplate.query(MFA_PROVIDERS_QUERY, mapper, zoneId); - } - - @Override - public int deleteByMfaProvider(String providerId, String zoneId) { - return jdbcTemplate.update(MFA_PROVIDER_DELETE_BY_ID, providerId, zoneId); - } - - @Override - public int deleteByIdentityZone(String zoneId) { - return jdbcTemplate.update(MFA_PROVIDER_DELETE_BY_ZONE_ID, zoneId); - } - - @Override - public Logger getLogger() { - return logger; - } - - private static final class MfaProviderMapper implements RowMapper { - @Override - public MfaProvider mapRow(ResultSet rs, int rowNum) throws SQLException { - MfaProvider result = new MfaProvider(); - int pos = 1; - - result.setId(rs.getString(pos++).trim()); - result.setName(rs.getString(pos++)); - result.setType(MfaProvider.MfaProviderType.forValue(rs.getString(pos++))); - //deserialize based on type - String config = rs.getString(pos++); - AbstractMfaProviderConfig definition = null; - if (result.getType() == MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR) { - definition = StringUtils.hasText(config) ? JsonUtils.readValue(config, GoogleMfaProviderConfig.class) : new GoogleMfaProviderConfig(); - } - result.setConfig(definition); - result.setIdentityZoneId(rs.getString(pos++)); - result.setCreated(rs.getTimestamp(pos++)); - result.setLastModified(rs.getTimestamp(pos++)); - - return result; - } - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/JdbcUserGoogleMfaCredentialsProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/JdbcUserGoogleMfaCredentialsProvisioning.java deleted file mode 100644 index ad7b4495fad..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/JdbcUserGoogleMfaCredentialsProvisioning.java +++ /dev/null @@ -1,189 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import org.apache.commons.lang3.StringUtils; -import org.cloudfoundry.identity.uaa.audit.event.SystemDeletable; -import org.cloudfoundry.identity.uaa.cypto.EncryptionKeyService; -import org.cloudfoundry.identity.uaa.cypto.EncryptionServiceException; -import org.cloudfoundry.identity.uaa.mfa.exception.UnableToPersistMfaException; -import org.cloudfoundry.identity.uaa.mfa.exception.UnableToRetrieveMfaException; -import org.cloudfoundry.identity.uaa.mfa.exception.UserMfaConfigAlreadyExistsException; -import org.cloudfoundry.identity.uaa.mfa.exception.UserMfaConfigDoesNotExistException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.dao.DuplicateKeyException; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.util.Base64Utils; - -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import static java.lang.Integer.valueOf; - -public class JdbcUserGoogleMfaCredentialsProvisioning implements SystemDeletable, UserMfaCredentialsProvisioning { - - private static Logger logger = LoggerFactory.getLogger(JdbcUserGoogleMfaCredentialsProvisioning.class); - - private static final String CREATE_USER_MFA_CONFIG_SQL = - "INSERT INTO user_google_mfa_credentials (user_id, secret_key, encrypted_validation_code, scratch_codes, mfa_provider_id, zone_id, encryption_key_label) VALUES (?,?,?,?,?,?,?)"; - - private static final String UPDATE_USER_MFA_CONFIG_SQL = - "UPDATE user_google_mfa_credentials SET secret_key=?, encrypted_validation_code=?, scratch_codes=?, mfa_provider_id=?, zone_id=? WHERE user_id=?"; - - private static final String QUERY_USER_MFA_CONFIG_ALL_SQL = "SELECT * FROM user_google_mfa_credentials WHERE user_id=? AND mfa_provider_id=?"; - - private static final String DELETE_USER_MFA_CONFIG_SQL = "DELETE FROM user_google_mfa_credentials WHERE user_id=?"; - - private static final String DELETE_PROVIDER_MFA_CONFIG_SQL = "DELETE FROM user_google_mfa_credentials WHERE mfa_provider_id=?"; - - private static final String DELETE_ZONE_MFA_CONFIG_SQL = "DELETE FROM user_google_mfa_credentials WHERE zone_id=?"; - - private JdbcTemplate jdbcTemplate; - private UserMfaCredentialsMapper mapper; - private EncryptionKeyService encryptionKeyService; - - public JdbcUserGoogleMfaCredentialsProvisioning(JdbcTemplate jdbcTemplate, EncryptionKeyService encryptionKeyService) { - this.jdbcTemplate = jdbcTemplate; - this.mapper = new UserMfaCredentialsMapper(encryptionKeyService); - this.encryptionKeyService = encryptionKeyService; - } - - private String encrypt(String value) throws EncryptionServiceException { - return Base64Utils.encodeToString(encryptionKeyService.getActiveKey().encrypt(value)); - } - - @Override - public void save(UserGoogleMfaCredentials credentials, String zoneId) { - try { - jdbcTemplate.update(CREATE_USER_MFA_CONFIG_SQL, ps -> { - int pos = 1; - ps.setString(pos++, credentials.getUserId()); - try { - ps.setString(pos++, encrypt(credentials.getSecretKey())); - ps.setString(pos++, encrypt(String.valueOf(credentials.getValidationCode()))); - ps.setString(pos++, encrypt(toCSScratchCode(credentials.getScratchCodes()))); - } catch (EncryptionServiceException e) { - logger.error("Unable to encrypt MFA credentials", e); - throw new UnableToPersistMfaException(e); - } - - ps.setString(pos++, credentials.getMfaProviderId()); - ps.setString(pos++, zoneId); - ps.setString(pos++, encryptionKeyService.getActiveKey().getLabel()); - }); - } catch (DuplicateKeyException e) { - throw new UserMfaConfigAlreadyExistsException(e.getMostSpecificCause().getMessage()); - } - } - - @Override - public void update(UserGoogleMfaCredentials credentials, String zoneId) { - jdbcTemplate.update(UPDATE_USER_MFA_CONFIG_SQL, ps -> { - int pos = 1; - try { - ps.setString(pos++, encrypt(credentials.getSecretKey())); - ps.setString(pos++, encrypt(String.valueOf(credentials.getValidationCode()))); - ps.setString(pos++, encrypt(toCSScratchCode(credentials.getScratchCodes()))); - } catch (EncryptionServiceException e) { - logger.error("Unable to encrypt MFA credentials", e); - throw new UnableToPersistMfaException(e); - } - ps.setString(pos++, credentials.getMfaProviderId()); - ps.setString(pos++, zoneId); - ps.setString(pos++, credentials.getUserId()); - }); - retrieve(credentials.getUserId(), credentials.getMfaProviderId()); - } - - @Override - public UserGoogleMfaCredentials retrieve(String userId, String mfaProviderId) { - try { - return jdbcTemplate.queryForObject(QUERY_USER_MFA_CONFIG_ALL_SQL, mapper, userId, mfaProviderId); - } catch (EmptyResultDataAccessException e) { - throw new UserMfaConfigDoesNotExistException("No Creds for user " + userId); - } - } - - @Override - public int deleteByUser(String userId, String zoneId) { - return delete(userId); - } - - @Override - public int deleteByMfaProvider(String mfaProviderId, String zoneId) { - return jdbcTemplate.update(DELETE_PROVIDER_MFA_CONFIG_SQL, mfaProviderId); - } - - @Override - public int deleteByIdentityZone(String zoneId) { - return jdbcTemplate.update(DELETE_ZONE_MFA_CONFIG_SQL, zoneId); - } - - @Override - public int delete(String userId) { - return jdbcTemplate.update(DELETE_USER_MFA_CONFIG_SQL, userId); - } - - - @Override - public Logger getLogger() { - return LoggerFactory.getLogger(JdbcUserGoogleMfaCredentialsProvisioning.class); - } - - private String toCSScratchCode(List scratchCodes) { - return StringUtils.join(scratchCodes, ","); - } - - private static final class UserMfaCredentialsMapper implements RowMapper { - private EncryptionKeyService encryptionKeyService; - - public UserMfaCredentialsMapper(EncryptionKeyService encryptionKeyService) { - this.encryptionKeyService = encryptionKeyService; - } - - @Override - public UserGoogleMfaCredentials mapRow(ResultSet rs, int rowNum) throws SQLException { - UserGoogleMfaCredentials userGoogleMfaCredentials = null; - String encryptionKeyLabel = rs.getString("encryption_key_label"); - - if (StringUtils.isEmpty(encryptionKeyLabel)) { - userGoogleMfaCredentials = new UserGoogleMfaCredentials( - rs.getString("user_id"), - rs.getString("secret_key"), - rs.getInt("validation_code"), - fromSCString(rs.getString("scratch_codes"))); - } else { - try { - EncryptionKeyService.EncryptionKey encryptionKey = encryptionKeyService.getKey(encryptionKeyLabel).orElseGet(() -> { - RuntimeException cause = new RuntimeException("Attempted to retrieve record with an unknown decryption key"); - logger.error(String.format("Couldn't decrypt with unknown key label : %s", encryptionKeyLabel), cause); - throw new UnableToRetrieveMfaException(cause); - }); - - userGoogleMfaCredentials = new UserGoogleMfaCredentials( - rs.getString("user_id"), - new String(encryptionKey.decrypt(Base64Utils.decodeFromString(rs.getString("secret_key")))), - valueOf(new String(encryptionKey.decrypt(Base64Utils.decodeFromString(rs.getString("encrypted_validation_code"))))), - fromSCString(new String(encryptionKey.decrypt(Base64Utils.decodeFromString(rs.getString("scratch_codes"))))) - ); - } catch (EncryptionServiceException e) { - logger.error("Unable to decrypt MFA credentials", e); - throw new UnableToRetrieveMfaException(e); - } - } - - userGoogleMfaCredentials.setMfaProviderId(rs.getString("mfa_provider_id")); - userGoogleMfaCredentials.setZoneId(rs.getString("zone_id")); - - return userGoogleMfaCredentials; - } - - private List fromSCString(String csString) { - return Arrays.stream(csString.split(",")).map(s -> Integer.parseInt(s)).collect(Collectors.toList()); - } - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaChecker.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaChecker.java deleted file mode 100644 index 526cebc8035..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaChecker.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; - -public class MfaChecker { - - private final IdentityZoneProvisioning identityZoneProvisioning; - - public MfaChecker(IdentityZoneProvisioning identityZoneProvisioning) { - this.identityZoneProvisioning = identityZoneProvisioning; - } - - public boolean isMfaEnabled(IdentityZone zone) { - return zone.getConfig().getMfaConfig().isEnabled(); - } - - public boolean isMfaEnabledForZoneId(String zoneId) { - return isMfaEnabled(identityZoneProvisioning.retrieve(zoneId)); - } - - public boolean isRequired(IdentityZone zone, String originKey) { - return zone.getConfig().getMfaConfig().getIdentityProviders().contains(originKey); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderBootstrap.java deleted file mode 100644 index 703f23fc847..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderBootstrap.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.dao.EmptyResultDataAccessException; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; - -public class MfaProviderBootstrap implements InitializingBean { - private List mfaProviders = new ArrayList<>(); - private MfaProviderProvisioning provisioning; - - public MfaProviderBootstrap(MfaProviderProvisioning provisioning) { - this.provisioning = provisioning; - } - - @Override - public void afterPropertiesSet() { - for(MfaProvider provider : mfaProviders) { - MfaProvider existing; - try { - existing = provisioning.retrieveByName(provider.getName(), "uaa"); - } catch (EmptyResultDataAccessException x){ - provisioning.create(provider, "uaa"); - continue; - } - - provider.setId(existing.getId()); - provider.setCreated(existing.getCreated()); - provider.setLastModified(new Date(System.currentTimeMillis())); - provisioning.update(provider, "uaa"); - } - } - - public List getMfaProviders() { - return mfaProviders; - } - - public void setMfaProviders(Map> mfaProviderYaml) { - mfaProviders.clear(); - if (mfaProviderYaml == null) { - return; - } - for(Map.Entry> mfaProvider : mfaProviderYaml.entrySet()) { - String name = mfaProvider.getKey(); - String type = (String) mfaProvider.getValue().get("type"); - - MfaProvider def = new MfaProvider(); - def.setType(MfaProvider.MfaProviderType.forValue(type)); - def.setName(name); - def.setIdentityZoneId("uaa"); - - if(def.getType() == MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR) { - Map config = (Map) mfaProvider.getValue().get("config"); - String providerDescription = (String) config.get("providerDescription"); - Integer digits = (Integer) config.get("digits"); - Integer duration = (Integer) config.get("duration"); - String algorithm = (String) config.get("algorithm"); - String issuer = (String) config.get("issuer"); - - GoogleMfaProviderConfig defGoogleConfig = new GoogleMfaProviderConfig(); - defGoogleConfig.setIssuer(issuer); - defGoogleConfig.setProviderDescription(providerDescription); - def.setConfig(defGoogleConfig); - } - - mfaProviders.add(def); - } - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderConfigValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderConfigValidator.java deleted file mode 100644 index 057abcf3c1e..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderConfigValidator.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -public interface MfaProviderConfigValidator{ - void validate(T mfaProviderConfig); -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderEndpoints.java deleted file mode 100644 index 3b7d44c7eb8..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderEndpoints.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent; -import org.cloudfoundry.identity.uaa.mfa.exception.InvalidMfaProviderException; -import org.cloudfoundry.identity.uaa.mfa.exception.MfaAlreadyExistsException; -import org.cloudfoundry.identity.uaa.mfa.exception.MfaProviderUpdateIsNotAllowed; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; -import org.cloudfoundry.identity.uaa.zone.MfaConfig; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -import static org.springframework.web.bind.annotation.RequestMethod.*; - -@RequestMapping("/mfa-providers") -@RestController -public class MfaProviderEndpoints implements ApplicationEventPublisherAware{ - protected static Logger logger = LoggerFactory.getLogger(MfaProviderEndpoints.class); - private ApplicationEventPublisher publisher; - private MfaProviderProvisioning mfaProviderProvisioning; - private MfaProviderValidator mfaProviderValidator; - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { - this.publisher = publisher; - } - - @RequestMapping(method = POST) - public ResponseEntity createMfaProvider(@RequestBody MfaProvider body) { - String zoneId = IdentityZoneHolder.get().getId(); - body.setIdentityZoneId(zoneId); - mfaProviderValidator.validate(body); - if(!StringUtils.hasText(body.getConfig().getIssuer())){ - body.getConfig().setIssuer(IdentityZoneHolder.get().getName()); - } - MfaProvider created = mfaProviderProvisioning.create(body,zoneId); - return new ResponseEntity<>(created, HttpStatus.CREATED); - } - - @RequestMapping(value = "{id}", method = PUT) - public ResponseEntity updateMfaProvider() throws MfaProviderUpdateIsNotAllowed { - throw new MfaProviderUpdateIsNotAllowed(); - } - - @RequestMapping(method = GET) - public ResponseEntity> retrieveMfaProviders() { - String zoneId = IdentityZoneHolder.get().getId(); - List providers = mfaProviderProvisioning.retrieveAll(zoneId); - return new ResponseEntity<>(providers, HttpStatus.OK); - } - - @RequestMapping(value = "{id}", method = GET) - public ResponseEntity retrieveMfaProviderById(@PathVariable String id) { - String zoneId = IdentityZoneHolder.get().getId(); - MfaProvider provider = mfaProviderProvisioning.retrieve(id, zoneId); - return new ResponseEntity<>(provider, HttpStatus.OK); - } - - @RequestMapping(value = "{id}", method = DELETE) - public ResponseEntity deleteMfaProviderById(@PathVariable String id) { - MfaProvider existing = mfaProviderProvisioning.retrieve(id, IdentityZoneHolder.get().getId()); - MfaConfig currentMfaConfig = IdentityZoneHolder.get().getConfig().getMfaConfig(); - if(currentMfaConfig.isEnabled() && currentMfaConfig.getProviderName().equals(existing.getName())) { - throw new MfaAlreadyExistsException("MFA provider is currently active on zone: " + IdentityZoneHolder.get().getId() + ". Please deactivate it from the zone or set another MFA provider"); - } - publisher.publishEvent(new EntityDeletedEvent<>(existing, SecurityContextHolder.getContext().getAuthentication(), IdentityZoneHolder.getCurrentZoneId())); - return new ResponseEntity<>(existing, HttpStatus.OK); - } - - @ExceptionHandler(InvalidMfaProviderException.class) - public ResponseEntity handleInvalidMfaProviderException(InvalidMfaProviderException e) { - return new ResponseEntity<>(e, HttpStatus.UNPROCESSABLE_ENTITY); - } - - @ExceptionHandler(MfaAlreadyExistsException.class) - public ResponseEntity handleInvalidMfaProviderException(MfaAlreadyExistsException e) { - return new ResponseEntity<>(new InvalidMfaProviderException(e.getMessage()), HttpStatus.CONFLICT); - } - - - @ExceptionHandler(EmptyResultDataAccessException.class) - public ResponseEntity handleEmptyResultDataAccessException(EmptyResultDataAccessException e) { - return new ResponseEntity<>(HttpStatus.NOT_FOUND); - } - - @ExceptionHandler(MfaProviderUpdateIsNotAllowed.class) - public ResponseEntity handleMfaUpdatingNameOfActiveProvider(MfaProviderUpdateIsNotAllowed e) { - return new ResponseEntity<>(HttpStatus.METHOD_NOT_ALLOWED); - } - - public MfaProviderProvisioning getMfaProviderProvisioning() { - return mfaProviderProvisioning; - } - - public void setMfaProviderProvisioning(MfaProviderProvisioning mfaProviderProvisioning) { - this.mfaProviderProvisioning = mfaProviderProvisioning; - } - - public void setMfaProviderValidator(MfaProviderValidator mfaProviderValidator) { - this.mfaProviderValidator = mfaProviderValidator; - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderProvisioning.java deleted file mode 100644 index bad0c824a1a..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderProvisioning.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import java.util.List; - -public interface MfaProviderProvisioning { - MfaProvider create(MfaProvider provider, String zoneId); - - MfaProvider update(MfaProvider provider, String zoneId); - - MfaProvider retrieve(String id, String zoneId); - - MfaProvider retrieveByName(String name, String zoneId); - - List retrieveAll(String zoneId); -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderValidator.java deleted file mode 100644 index e401e092a14..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderValidator.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -public interface MfaProviderValidator { - void validate(MfaProvider mfaProvider); -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaRegisterQRGenerator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaRegisterQRGenerator.java deleted file mode 100644 index d9917667a28..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaRegisterQRGenerator.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.WriterException; -import com.google.zxing.client.j2se.MatrixToImageWriter; -import com.google.zxing.common.BitMatrix; -import com.google.zxing.qrcode.QRCodeWriter; -import org.springframework.util.StringUtils; -import org.springframework.web.util.UriUtils; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Base64; - -public class MfaRegisterQRGenerator { - - private static final String OTPAUTH_TOTP_URI = "otpauth://totp/%s:%s?secret=%s&issuer=%s"; - - private static final String STRING_ENCODING = "UTF-8"; - - public static String getQRCode(String issuer, - String accountName, - String secretKey) throws WriterException, IOException { - if(!StringUtils.hasText(issuer) || issuer.contains(":")) { - throw new IllegalArgumentException("invalid issuer"); - } - if(!StringUtils.hasText(accountName) || accountName.contains(":")) { - throw new IllegalArgumentException("invalid account name"); - } - - QRCodeWriter writer = new QRCodeWriter(); - BitMatrix qrBitMatrix = writer.encode(String.format( - OTPAUTH_TOTP_URI, - UriUtils.encode(issuer, STRING_ENCODING), - UriUtils.encode(accountName, STRING_ENCODING), - secretKey, - UriUtils.encode(issuer, STRING_ENCODING)), - BarcodeFormat.QR_CODE, 200, 200); - BufferedImage qrImage = MatrixToImageWriter.toBufferedImage(qrBitMatrix); - ByteArrayOutputStream os = new ByteArrayOutputStream(); - ImageIO.write(qrImage, "png", os); - return Base64.getEncoder().encodeToString(os.toByteArray()); - } - - public static String getQRCodePngDataUri(String issuer, - String accountName, - String secretKey) throws WriterException, IOException { - return "data:image/png;base64," + getQRCode(issuer, accountName, secretKey); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaRequiredFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaRequiredFilter.java deleted file mode 100644 index be4c35c1af0..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaRequiredFilter.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2018] Pivotal Software, Inc. All Rights Reserved. - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.mfa; - -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.mfa.exception.MfaRequiredException; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.web.filter.GenericFilterBean; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Set; - -public class MfaRequiredFilter extends GenericFilterBean { - private static Logger logger = LoggerFactory.getLogger(MfaRequiredFilter.class); - - private final MfaChecker checker; - private final AuthenticationEntryPoint entryPoint; - - public MfaRequiredFilter(MfaChecker checker, AuthenticationEntryPoint entryPoint) { - this.checker = checker; - this.entryPoint = entryPoint; - } - - @Override - public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { - HttpServletRequest request = (HttpServletRequest) req; - HttpServletResponse response = (HttpServletResponse) res; - if (isMfaRequiredAndMissing()) { - logger.debug("MFA is configured, but missing in authentication. Invoking entry point"); - entryPoint.commence(request, response, new MfaRequiredException("Multi-factor authentication required.")); - } else { - chain.doFilter(request, response); - } - } - - - protected boolean isMfaRequiredAndMissing() { - Authentication a = SecurityContextHolder.getContext().getAuthentication(); - if (a == null || a instanceof AnonymousAuthenticationToken) { - return false; - } - if (!(a instanceof UaaAuthentication)) { - return false; - } - UaaAuthentication uaaAuth = (UaaAuthentication) a; - if (!mfaRequired()) { - return false; - } - - Set methods = uaaAuth.getAuthenticationMethods(); - return methods == null || !methods.contains("mfa"); - } - - protected boolean mfaRequired() { - return checker.isMfaEnabled(IdentityZoneHolder.get()); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaUiRequiredFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaUiRequiredFilter.java deleted file mode 100644 index 483033d7d45..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/MfaUiRequiredFilter.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.mfa; - -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.savedrequest.RequestCache; -import org.springframework.security.web.savedrequest.SavedRequest; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.web.filter.GenericFilterBean; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -public class MfaUiRequiredFilter extends GenericFilterBean { - private static Logger logger = LoggerFactory.getLogger(MfaUiRequiredFilter.class); - - private final AntPathRequestMatcher inProgressMatcher; - private final AntPathRequestMatcher completedMatcher; - private final AntPathRequestMatcher logoutMatcher; - private final String redirect; - private final RequestCache cache; - private final MfaChecker checker; - - public MfaUiRequiredFilter(String urlFilter, - String redirect, - RequestCache cache, - String mfaCompleteUrl, - AntPathRequestMatcher logoutMatcher, - MfaChecker checker) { - inProgressMatcher = new AntPathRequestMatcher(urlFilter); - this.redirect = redirect; - this.cache = cache; - this.completedMatcher = new AntPathRequestMatcher(mfaCompleteUrl); - this.checker = checker; - this.logoutMatcher = logoutMatcher; - } - - @Override - public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { - HttpServletRequest request = (HttpServletRequest) req; - HttpServletResponse response = (HttpServletResponse) res; - - switch (getNextStep(request)) { - case INVALID_AUTH: - logger.debug("Unrecognized authentication object:" + getAuthenticationLogInfo()); - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid authentication object for UI operations."); - break; - case NOT_AUTHENTICATED: - case MFA_IN_PROGRESS: - case MFA_NOT_REQUIRED: - case MFA_OK: - chain.doFilter(request, response); - break; - case MFA_REQUIRED: - logger.debug("Request requires MFA, redirecting to MFA flow for " + getAuthenticationLogInfo()); - if (cache.getRequest(request, response) == null) { - cache.saveRequest(request, response); - } - sendRedirect(redirect, request, response); - break; - case MFA_COMPLETED: - logger.debug("MFA has been completed for " + getAuthenticationLogInfo()); - SavedRequest savedRequest = cache.getRequest(request, response); - if (savedRequest != null) { - logger.debug("Redirecting request to " + savedRequest.getRedirectUrl()); - sendRedirect(savedRequest.getRedirectUrl(), request, response); - } else { - logger.debug("Redirecting request to /"); - sendRedirect("/", request, response); - } - break; - } - } - - protected String getAuthenticationLogInfo() { - Authentication a = SecurityContextHolder.getContext().getAuthentication(); - if (a == null) { - return null; - } - StringBuilder result = new StringBuilder(); - if (a instanceof UaaAuthentication) { - UaaPrincipal principal = ((UaaAuthentication) a).getPrincipal(); - result - .append("Username:") - .append(principal.getName()) - .append(" User-ID:") - .append(principal.getId()); - } else { - result - .append("Unknown Auth=") - .append(a) - .append(" Principal=") - .append(a.getPrincipal()); - } - return result.toString(); - } - - public enum MfaNextStep { - NOT_AUTHENTICATED, - MFA_IN_PROGRESS, - MFA_REQUIRED, - MFA_OK, - MFA_NOT_REQUIRED, - MFA_COMPLETED, - INVALID_AUTH - } - - protected MfaNextStep getNextStep(HttpServletRequest request) { - Authentication a = SecurityContextHolder.getContext().getAuthentication(); - if (a == null || a instanceof AnonymousAuthenticationToken) { - return MfaNextStep.NOT_AUTHENTICATED; - } - if (!(a instanceof UaaAuthentication)) { - return MfaNextStep.INVALID_AUTH; - } - UaaAuthentication uaaAuth = (UaaAuthentication) a; - if (!mfaRequired(uaaAuth.getPrincipal().getOrigin()) || logoutInProgress(request)) { - return MfaNextStep.MFA_NOT_REQUIRED; - } - - if (completedMatcher.matches(request) && uaaAuth.getAuthenticationMethods().contains("mfa")) { - return MfaNextStep.MFA_COMPLETED; - } - if (inProgressMatcher.matches(request) && !uaaAuth.getAuthenticationMethods().contains("mfa")) { - return MfaNextStep.MFA_IN_PROGRESS; - } - if (!inProgressMatcher.matches(request) && !uaaAuth.getAuthenticationMethods().contains("mfa")) { - return MfaNextStep.MFA_REQUIRED; - } - if (uaaAuth.getAuthenticationMethods().contains("mfa")) { - return MfaNextStep.MFA_OK; - } else { - return MfaNextStep.INVALID_AUTH; - } - } - - protected void sendRedirect(String redirectUrl, HttpServletRequest request, HttpServletResponse response) throws IOException { - StringBuilder url = new StringBuilder( - redirectUrl.startsWith("/") ? request.getContextPath() : "" - ); - url.append(redirectUrl); - response.sendRedirect(url.toString()); - } - - protected boolean mfaRequired(String origin) { - return checker.isMfaEnabled(IdentityZoneHolder.get()) && checker.isRequired(IdentityZoneHolder.get(), origin); - } - - private boolean logoutInProgress(HttpServletRequest request) { - return logoutMatcher.matches(request); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/StatelessMfaAuthenticationFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/StatelessMfaAuthenticationFilter.java deleted file mode 100644 index c2922a8c7de..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/StatelessMfaAuthenticationFilter.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2018] Pivotal Software, Inc. All Rights Reserved. - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.mfa; - -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.event.MfaAuthenticationFailureEvent; -import org.cloudfoundry.identity.uaa.authentication.event.MfaAuthenticationSuccessEvent; -import org.cloudfoundry.identity.uaa.authentication.manager.CommonLoginPolicy; -import org.cloudfoundry.identity.uaa.mfa.exception.InvalidMfaCodeException; -import org.cloudfoundry.identity.uaa.mfa.exception.MissingMfaCodeException; -import org.cloudfoundry.identity.uaa.mfa.exception.UserMfaConfigDoesNotExistException; -import org.cloudfoundry.identity.uaa.user.UaaUser; -import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; -import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.security.authentication.InsufficientAuthenticationException; -import org.springframework.security.authentication.ProviderNotFoundException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.util.StringUtils; -import org.springframework.web.filter.OncePerRequestFilter; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import static org.springframework.security.oauth2.common.util.OAuth2Utils.GRANT_TYPE; - -public class StatelessMfaAuthenticationFilter extends OncePerRequestFilter implements ApplicationEventPublisherAware { - - private final UserGoogleMfaCredentialsProvisioning provisioning; - private final Set supportedGrantTypes; - private final MfaProviderProvisioning mfaProvider; - private final UaaUserDatabase userDb; - private final CommonLoginPolicy commonLoginPolicy; - - private ApplicationEventPublisher publisher; - - public StatelessMfaAuthenticationFilter(UserGoogleMfaCredentialsProvisioning provisioning, - Set supportedGrantTypes, - MfaProviderProvisioning mfaProvider, - UaaUserDatabase userDb, - CommonLoginPolicy commonLoginPolicy) { - this.provisioning = provisioning; - this.supportedGrantTypes = supportedGrantTypes; - this.mfaProvider = mfaProvider; - this.userDb = userDb; - this.commonLoginPolicy = commonLoginPolicy; -} - - public boolean isGrantTypeSupported(String type) { - return supportedGrantTypes.contains(type); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - MfaProvider provider = null; - try { - if (isGrantTypeSupported(request.getParameter(GRANT_TYPE))) { - provider = checkMfaCode(request); - UaaUser user = getUaaUser(); - if (provider != null) { - publishEvent(new MfaAuthenticationSuccessEvent(user, getAuthentication(), provider.getType().toValue(), IdentityZoneHolder.getCurrentZoneId())); - } - } - filterChain.doFilter(request, response); - } catch (InsufficientAuthenticationException x) { - handleException(new JsonError(400, "invalid_request", x.getMessage()), response); - } catch (MissingMfaCodeException | UserMfaConfigDoesNotExistException e) { - UaaUser user = getUaaUser(); - publishEvent(new MfaAuthenticationFailureEvent(user, getAuthentication(), provider != null ? provider.getType().toValue() : "null", IdentityZoneHolder.getCurrentZoneId())); - handleException(new JsonError(400, "invalid_request", e.getMessage()), response); - } catch (InvalidMfaCodeException e) { - UaaUser user = getUaaUser(); - publishEvent(new MfaAuthenticationFailureEvent(user, getAuthentication(), provider != null ? provider.getType().toValue() : "null", IdentityZoneHolder.getCurrentZoneId())); - handleException(new JsonError(401, "unauthorized", "Bad credentials"), response); - } - } - - protected void handleException(JsonError error, HttpServletResponse response) throws IOException { - response.setStatus(error.getStatus()); - response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); - response.getWriter().write(JsonUtils.writeValueAsString(error)); - } - - protected MfaProvider checkMfaCode(HttpServletRequest request) { - IdentityZone zone = IdentityZoneHolder.get(); - MfaProvider provider = null; - UaaAuthentication authentication = getAuthentication(); - - if (isMfaEnabled(zone)) { - if (!commonLoginPolicy.isAllowed(authentication.getPrincipal().getId()).isAllowed()) { - throw new RuntimeException(); - } - - try { - provider = mfaProvider.retrieveByName(zone.getConfig().getMfaConfig().getProviderName(), zone.getId()); - } catch (EmptyResultDataAccessException x) { - throw new ProviderNotFoundException("Unable to find MFA provider for zone:" + zone.getSubdomain()); - } - Integer code = getMfaCode(request); - UserGoogleMfaCredentials credentials = provisioning.getUserGoogleMfaCredentials(authentication.getPrincipal().getId(), provider.getId()); - if (credentials == null) { - throw new UserMfaConfigDoesNotExistException("User must register a multi-factor authentication token"); - } - if (!provisioning.isValidCode(credentials, code)) { - throw new InvalidMfaCodeException("Invalid multi-factor authentication code"); - } - HashSet authMethods = new HashSet<>(authentication.getAuthenticationMethods()); - authMethods.add("otp"); - authMethods.add("mfa"); - authentication.setAuthenticationMethods(authMethods); - } - return provider; - } - - private void publishEvent(ApplicationEvent event) { - if (publisher != null) { - publisher.publishEvent(event); - } - } - - private Integer getMfaCode(HttpServletRequest request) { - String code = request.getParameter("mfaCode"); - if (StringUtils.isEmpty(code)) { - throw new MissingMfaCodeException("A multi-factor authentication code is required to complete the request"); - } - try { - return Integer.valueOf(code); - } catch (NumberFormatException x) { - throw new InvalidMfaCodeException("Bad credentials"); - } - } - - private boolean isMfaEnabled(IdentityZone zone) { - return zone.getConfig().getMfaConfig().isEnabled(); - } - - private UaaUser getUaaUser() { - return userDb.retrieveUserById(getAuthentication().getPrincipal().getId()); - } - - private UaaAuthentication getAuthentication() { - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - if (auth == null) { - throw new InsufficientAuthenticationException("User authentication missing"); - } else if (!(auth instanceof OAuth2Authentication)) { - throw new InsufficientAuthenticationException("Unrecognizable authentication"); - } - Authentication userAuth = ((OAuth2Authentication) auth).getUserAuthentication(); - if (!(userAuth instanceof UaaAuthentication)) { - throw new InsufficientAuthenticationException("Unrecognizable user authentication"); - } - return (UaaAuthentication) userAuth; - } - - public Set getSupportedGrantTypes() { - return Collections.unmodifiableSet(supportedGrantTypes); - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.publisher = applicationEventPublisher; - } - - public static class JsonError { - private final int status; - private final String error; - private final String error_description; - - private JsonError(int status, String error, String error_description) { - this.status = status; - this.error = error; - this.error_description = error_description; - } - - public String getError() { - return error; - } - - public String getError_description() { - return error_description; - } - - public int getStatus() { - return status; - } - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/UserGoogleMfaCredentialsProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/UserGoogleMfaCredentialsProvisioning.java deleted file mode 100644 index b06d6c434e5..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/UserGoogleMfaCredentialsProvisioning.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - - -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.mfa.exception.UserMfaConfigDoesNotExistException; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; - -import com.google.zxing.WriterException; -import com.warrenstrange.googleauth.GoogleAuthenticator; -import com.warrenstrange.googleauth.GoogleAuthenticatorKey; -import com.warrenstrange.googleauth.ICredentialRepository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.List; - -public class UserGoogleMfaCredentialsProvisioning implements ICredentialRepository { - private static Logger logger = LoggerFactory.getLogger(UserGoogleMfaCredentialsProvisioning.class); - private MfaProviderProvisioning mfaProviderProvisioning; - private GoogleAuthenticator authenticator; - private UserMfaCredentialsProvisioning jdbcProvisioner; - - public void setAuthenticator(GoogleAuthenticator authenticator) { - this.authenticator = authenticator; - } - - public UserGoogleMfaCredentials getUserGoogleMfaCredentials(String userId) { - MfaProvider provider = mfaProviderProvisioning.retrieveByName( - IdentityZoneHolder.get().getConfig().getMfaConfig().getProviderName(), - IdentityZoneHolder.get().getId()); - return getUserGoogleMfaCredentials(userId, provider.getId()); - } - - public UserGoogleMfaCredentials getUserGoogleMfaCredentials(String userId, String providerId) { - try { - return jdbcProvisioner.retrieve(userId, providerId); - } catch (UserMfaConfigDoesNotExistException e) { - logger.debug("Unable to find MFA config for user:"+userId); - } - return null; - - } - - @Override - public String getSecretKey(String userId) { - throw new UnsupportedOperationException(); - } - - public String getOtpAuthURL(String qrIssuer, UserGoogleMfaCredentials credentials, String userName) throws IOException, WriterException { - return MfaRegisterQRGenerator.getQRCodePngDataUri(qrIssuer, userName, credentials.getSecretKey()); - } - - public UserGoogleMfaCredentials createUserCredentials(String userId) { - GoogleAuthenticatorKey credentials = authenticator.createCredentials(userId); - return new UserGoogleMfaCredentials(userId, - credentials.getKey(), - credentials.getVerificationCode(), - credentials.getScratchCodes()); - } - - public boolean isValidCode(UserGoogleMfaCredentials credentials, Integer code) { - return authenticator.authorize(credentials.getSecretKey(), code); - } - - @Override - public void saveUserCredentials(String userId, String secretKey, int validationCode, List scratchCodes) { - //no op - } - - public void saveUserCredentials(UserGoogleMfaCredentials credentials) { - IdentityZone zone = IdentityZoneHolder.get(); - jdbcProvisioner.save(credentials, zone.getId()); - } - - public boolean activeUserCredentialExists(String userId, String providerId) { - return getUserGoogleMfaCredentials(userId, providerId)!=null; - } - - - public boolean isFirstTimeMFAUser(UaaPrincipal uaaPrincipal) { - if (uaaPrincipal == null) throw new RuntimeException("User information is not present in session."); - return getUserGoogleMfaCredentials(uaaPrincipal.getId()) == null; - } - - public void setJdbcProvisioner(UserMfaCredentialsProvisioning jdbcProvisioner) { - this.jdbcProvisioner = jdbcProvisioner; - } - - public void setMfaProviderProvisioning(MfaProviderProvisioning mfaProviderProvisioning) { - this.mfaProviderProvisioning = mfaProviderProvisioning; - } - -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/UserMfaCredentialsProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/UserMfaCredentialsProvisioning.java deleted file mode 100644 index 716655f8eda..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/UserMfaCredentialsProvisioning.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -public interface UserMfaCredentialsProvisioning { - void save(T credentials, String zoneId); - void update(T credentials, String zoneId); - T retrieve(String userId, String mfaProviderId); - int delete(String userId); -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/InvalidMfaCodeException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/InvalidMfaCodeException.java deleted file mode 100644 index d7bde2d2942..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/InvalidMfaCodeException.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa.exception; - -import org.springframework.security.core.AuthenticationException; - -public class InvalidMfaCodeException extends AuthenticationException { - public InvalidMfaCodeException(String msg, Throwable t) { - super(msg, t); - } - - public InvalidMfaCodeException(String msg) { - super(msg); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/InvalidMfaProviderConfigException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/InvalidMfaProviderConfigException.java deleted file mode 100644 index 4a0d4f7a912..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/InvalidMfaProviderConfigException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa.exception; - -public class InvalidMfaProviderConfigException extends Exception { - public InvalidMfaProviderConfigException(String message) { - super(message); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/InvalidMfaProviderException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/InvalidMfaProviderException.java deleted file mode 100644 index 2c92baeffe0..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/InvalidMfaProviderException.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa.exception; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.cloudfoundry.identity.uaa.error.UaaException; -import org.springframework.http.HttpStatus; - -public class InvalidMfaProviderException extends UaaException { - - public final Logger logger = LoggerFactory.getLogger(InvalidMfaProviderException.class); - public InvalidMfaProviderException(String message) { - super("invalid_mfa_provider", message, HttpStatus.UNPROCESSABLE_ENTITY.value()); - logger.debug("MfaProvider validation error. " + message); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/MfaAlreadyExistsException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/MfaAlreadyExistsException.java deleted file mode 100644 index 40ecbf797b1..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/MfaAlreadyExistsException.java +++ /dev/null @@ -1,22 +0,0 @@ -/******************************************************************************* - * Cloud Foundry - * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - *******************************************************************************/ -package org.cloudfoundry.identity.uaa.mfa.exception; - -import org.cloudfoundry.identity.uaa.error.UaaException; - -public class MfaAlreadyExistsException extends UaaException { - - public MfaAlreadyExistsException(String msg) { - super("mfa_exists", msg, 409); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/MfaProviderUpdateIsNotAllowed.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/MfaProviderUpdateIsNotAllowed.java deleted file mode 100644 index 923a8993aff..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/MfaProviderUpdateIsNotAllowed.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa.exception; - -public class MfaProviderUpdateIsNotAllowed extends Exception { - public MfaProviderUpdateIsNotAllowed() { - super("Updating an MFA provider is not allowed."); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/MfaRequiredException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/MfaRequiredException.java deleted file mode 100644 index cc96dbae748..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/MfaRequiredException.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2018] Pivotal Software, Inc. All Rights Reserved. - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.mfa.exception; - -import org.cloudfoundry.identity.uaa.oauth.InteractionRequiredException; - -public class MfaRequiredException extends InteractionRequiredException { - - public MfaRequiredException(String msg) { - super(msg); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/MissingMfaCodeException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/MissingMfaCodeException.java deleted file mode 100644 index 63a6de372d9..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/MissingMfaCodeException.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2018] Pivotal Software, Inc. All Rights Reserved. - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.mfa.exception; - -import org.springframework.security.core.AuthenticationException; - -public class MissingMfaCodeException extends AuthenticationException { - public MissingMfaCodeException(String explanation) { - super(explanation); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/UnableToPersistMfaException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/UnableToPersistMfaException.java deleted file mode 100644 index 9ffde998fdd..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/UnableToPersistMfaException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa.exception; - -public class UnableToPersistMfaException extends RuntimeException { - public UnableToPersistMfaException(Throwable cause) { - super(cause); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/UnableToRetrieveMfaException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/UnableToRetrieveMfaException.java deleted file mode 100644 index fd86e9411e1..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/UnableToRetrieveMfaException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa.exception; - -public class UnableToRetrieveMfaException extends RuntimeException { - public UnableToRetrieveMfaException(Throwable cause) { - super(cause); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/UserMfaConfigAlreadyExistsException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/UserMfaConfigAlreadyExistsException.java deleted file mode 100644 index ec2f8210868..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/UserMfaConfigAlreadyExistsException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa.exception; - -public class UserMfaConfigAlreadyExistsException extends RuntimeException { - public UserMfaConfigAlreadyExistsException(String message) { - super(message); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/UserMfaConfigDoesNotExistException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/UserMfaConfigDoesNotExistException.java deleted file mode 100644 index a1764975738..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa/exception/UserMfaConfigDoesNotExistException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa.exception; - -public class UserMfaConfigDoesNotExistException extends RuntimeException { - public UserMfaConfigDoesNotExistException(String message) { - super(message); - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpoints.java index 0271b100ffc..afd48303c4a 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpoints.java @@ -10,7 +10,6 @@ import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.UaaException; -import org.cloudfoundry.identity.uaa.mfa.UserMfaCredentialsProvisioning; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.resources.AttributeNameMapper; @@ -119,7 +118,6 @@ public class ScimUserEndpoints implements InitializingBean, ApplicationEventPubl private final Map, HttpStatus> statuses; private final PasswordValidator passwordValidator; private final ExpiringCodeStore codeStore; - private final UserMfaCredentialsProvisioning mfaCredentialsProvisioning; private final ApprovalStore approvalStore; private final ScimGroupMembershipManager membershipManager; private final int userMaxCount; @@ -142,7 +140,6 @@ public ScimUserEndpoints( final @Qualifier("exceptionToStatusMap") Map, HttpStatus> statuses, final PasswordValidator passwordValidator, final ExpiringCodeStore codeStore, - final UserMfaCredentialsProvisioning mfaCredentialsProvisioning, final ApprovalStore approvalStore, final ScimGroupMembershipManager membershipManager, final @Value("${userMaxCount:500}") int userMaxCount) { @@ -160,7 +157,6 @@ public ScimUserEndpoints( this.statuses = statuses; this.passwordValidator = passwordValidator; this.codeStore = codeStore; - this.mfaCredentialsProvisioning = mfaCredentialsProvisioning; this.approvalStore = approvalStore; this.userMaxCount = userMaxCount; this.membershipManager = membershipManager; @@ -470,14 +466,6 @@ public UserAccountStatus updateAccountStatus(@RequestBody UserAccountStatus stat return status; } - @RequestMapping(value = "/Users/{userId}/mfa", method = RequestMethod.DELETE) - @ResponseStatus(HttpStatus.OK) - public void deleteMfaRegistration(@PathVariable String userId) { - ScimUser user = scimUserProvisioning.retrieve(userId, identityZoneManager.getCurrentIdentityZoneId()); - - mfaCredentialsProvisioning.delete(user.getId()); - } - private ScimUser syncGroups(ScimUser user) { if (user == null) { return user; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneConfigurationValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneConfigurationValidator.java index 12535ae4295..207135ab8a2 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneConfigurationValidator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneConfigurationValidator.java @@ -14,11 +14,7 @@ @Component public class GeneralIdentityZoneConfigurationValidator implements IdentityZoneConfigurationValidator { - private final MfaConfigValidator mfaConfigValidator; - - public GeneralIdentityZoneConfigurationValidator(final MfaConfigValidator mfaConfigValidator) { - this.mfaConfigValidator = mfaConfigValidator; - } + public GeneralIdentityZoneConfigurationValidator() {} @Override public IdentityZoneConfiguration validate(IdentityZone zone, IdentityZoneValidator.Mode mode) throws InvalidIdentityZoneConfigurationException { @@ -86,10 +82,6 @@ public IdentityZoneConfiguration validate(IdentityZone zone, IdentityZoneValidat BannerValidator.validate(config.getBranding().getBanner()); } - if (config.getMfaConfig() != null) { - mfaConfigValidator.validate(config.getMfaConfig(), zone.getId()); - } - return config; } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/MfaConfigValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/MfaConfigValidator.java deleted file mode 100644 index 9988c9097e8..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/MfaConfigValidator.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.cloudfoundry.identity.uaa.zone; - -import org.cloudfoundry.identity.uaa.mfa.MfaProviderProvisioning; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; - -@Component -public class MfaConfigValidator { - private static Logger logger = LoggerFactory.getLogger(MfaConfigValidator.class); - - private final MfaProviderProvisioning mfaProviderProvisioning; - - public MfaConfigValidator( - final MfaProviderProvisioning mfaProviderProvisioning) { - this.mfaProviderProvisioning = mfaProviderProvisioning; - } - - public void validate(MfaConfig config, String zoneId) throws InvalidIdentityZoneConfigurationException { - if (config.isEnabled() || StringUtils.hasText(config.getProviderName())) { - try { - mfaProviderProvisioning.retrieveByName(config.getProviderName(), zoneId); - } catch (EmptyResultDataAccessException e) { - logger.debug(String.format("Provider with name %s not found", config.getProviderName())); - throw new InvalidIdentityZoneConfigurationException("Active MFA Provider not found with name: " + config.getProviderName()); - } - } - } -} diff --git a/server/src/main/resources/spring/login-ui.xml b/server/src/main/resources/spring/login-ui.xml index e0823e46542..bbc836f510f 100644 --- a/server/src/main/resources/spring/login-ui.xml +++ b/server/src/main/resources/spring/login-ui.xml @@ -189,18 +189,6 @@ - - - - - - - - - - - - - @@ -266,7 +253,6 @@ xmlns="http://www.springframework.org/schema/security"> - @@ -285,7 +271,6 @@ authentication-details-source-ref="authenticationDetailsSource" default-target-url="/"/> - @@ -309,8 +294,6 @@ value="/login?error=account_not_precreated"/> - @@ -520,28 +503,6 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/server/src/main/resources/templates/web/layouts/pui-mfa-main.html b/server/src/main/resources/templates/web/layouts/pui-mfa-main.html deleted file mode 100644 index 6f5bbcb92e0..00000000000 --- a/server/src/main/resources/templates/web/layouts/pui-mfa-main.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - -
-
-
-
- -

Cloud Foundry

-
-
-
-
-
- - - - - - diff --git a/server/src/main/resources/templates/web/mfa/enter_code.html b/server/src/main/resources/templates/web/mfa/enter_code.html deleted file mode 100644 index 594307d983e..00000000000 --- a/server/src/main/resources/templates/web/mfa/enter_code.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - -
- -
-
-

The MFA Feature of UAA is disabled. This page does not function properly.

-

Please create an issue in UAA Github repository if you need this feature.

-

-

Enter your Google Authentication Code

-
-

- Enter the 6-digit code displayed in Google Authenticator app: -

-
-
- - -
-
-
- - - -
-
- - \ No newline at end of file diff --git a/server/src/main/resources/templates/web/mfa/manual_registration.html b/server/src/main/resources/templates/web/mfa/manual_registration.html deleted file mode 100644 index 5866eac9563..00000000000 --- a/server/src/main/resources/templates/web/mfa/manual_registration.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - -
-

-
-

Manual Setup Instructions

-
    -
  1. Install Google Authenticator on your mobile device from the App Store on your iPhone or Google Play on your Android.
  2. -
  3. Open Google Authenticator on your mobile device.
  4. -
  5. Tap the "+" button.
  6. -
  7. Tap "Manual Entry".
  8. -
  9. Enter the following in the 'Account' field: -

    -
  10. -
  11. Enter the following in the 'Key' field: -

    -
  12. -
  13. Check that 'Time based' is selected.
  14. -
  15. Tap the 'Done' button on your phone.
  16. -
- -
-
- - \ No newline at end of file diff --git a/server/src/main/resources/templates/web/mfa/qr_code.html b/server/src/main/resources/templates/web/mfa/qr_code.html deleted file mode 100644 index 8a0ea1abcfb..00000000000 --- a/server/src/main/resources/templates/web/mfa/qr_code.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - -
-

-
-

Setup Multifactor Authentication

-
    -
  1. Install Google Authenticator on your mobile device from the App Store on your iPhone or Google Play on your Android.
  2. -
  3. Open Google Authenticator on your mobile device.
  4. -
  5. Tap the "+" button.
  6. -
  7. Tap "Scan barcode".
  8. -
  9. Scan this barcode:
  10. -
- error getting qr code -

Can't scan barcode? See manual setup instructions.

- -
-
- - \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/audit/JdbcUnsuccessfulLoginCountingAuditServiceTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/JdbcUnsuccessfulLoginCountingAuditServiceTests.java index 69f6b8745b0..5fe7e46f0ef 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/audit/JdbcUnsuccessfulLoginCountingAuditServiceTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/JdbcUnsuccessfulLoginCountingAuditServiceTests.java @@ -15,8 +15,6 @@ import java.util.List; import static org.cloudfoundry.identity.uaa.audit.AuditEventType.ClientAuthenticationFailure; -import static org.cloudfoundry.identity.uaa.audit.AuditEventType.MfaAuthenticationFailure; -import static org.cloudfoundry.identity.uaa.audit.AuditEventType.MfaAuthenticationSuccess; import static org.cloudfoundry.identity.uaa.audit.AuditEventType.PasswordChangeSuccess; import static org.cloudfoundry.identity.uaa.audit.AuditEventType.UserAccountUnlockedEvent; import static org.cloudfoundry.identity.uaa.audit.AuditEventType.UserAuthenticationFailure; @@ -146,41 +144,9 @@ void findMethodOnlyReturnsEventsWithinRequestedPeriod() { assertEquals(0, clientEvents.size()); } - @Test - void mfaFailedEventsAreLogged() { - String principalId = "1"; - AuditEvent mfaFailureEvent = new AuditEvent(MfaAuthenticationFailure, principalId, authDetails, "joe", System.currentTimeMillis(), IdentityZone.getUaaZoneId(), null, null); - auditService.log(mfaFailureEvent, mfaFailureEvent.getIdentityZoneId()); - - assertThat(auditService.find(principalId, 0, mfaFailureEvent.getIdentityZoneId()), is(hasSize(1))); - } - - @Test - void mfaAuthenticationSuccessResetsData() { - AuditEvent mfaFailureEvent = new AuditEvent(MfaAuthenticationFailure, "1", authDetails, "joe", System.currentTimeMillis(), IdentityZone.getUaaZoneId(), null, null); - auditService.log(mfaFailureEvent, mfaFailureEvent.getIdentityZoneId()); - - AuditEvent mfaSuccessEvent = new AuditEvent(MfaAuthenticationSuccess, "1", authDetails, "joe", System.currentTimeMillis(), IdentityZone.getUaaZoneId(), null, null); - auditService.log(mfaSuccessEvent, mfaSuccessEvent.getIdentityZoneId()); - assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='1'", Integer.class), is(0)); - } - - @Test - void mfaAuthenticationSuccessResetsOnlyMfaAuthenticationFailures() { - AuditEvent mfaFailureEvent = new AuditEvent(MfaAuthenticationFailure, "1", authDetails, "joe", System.currentTimeMillis(), IdentityZone.getUaaZoneId(), null, null); - auditService.log(mfaFailureEvent, mfaFailureEvent.getIdentityZoneId()); - - AuditEvent loginFailureEvent = new AuditEvent(UserAuthenticationFailure, "1", authDetails, "joe", System.currentTimeMillis(), IdentityZone.getUaaZoneId(), null, null); - auditService.log(loginFailureEvent, loginFailureEvent.getIdentityZoneId()); - - AuditEvent mfaSuccessEvent = new AuditEvent(MfaAuthenticationSuccess, "1", authDetails, "joe", System.currentTimeMillis(), IdentityZone.getUaaZoneId(), null, null); - auditService.log(mfaSuccessEvent, mfaSuccessEvent.getIdentityZoneId()); - assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='1'", Integer.class), is(1)); - } - @Test void nontAuthSuccessesShouldNotThrowAnException() { - EnumSet userAuthenticationSuccess = EnumSet.of(UserAuthenticationSuccess, PasswordChangeSuccess, UserAccountUnlockedEvent, MfaAuthenticationSuccess); + EnumSet userAuthenticationSuccess = EnumSet.of(UserAuthenticationSuccess, PasswordChangeSuccess, UserAccountUnlockedEvent); EnumSet complementOfUserAuthenticationSuccess = EnumSet.complementOf(userAuthenticationSuccess); for (AuditEventType ofUserAuthenticationSuccess : complementOfUserAuthenticationSuccess) { @@ -190,17 +156,14 @@ void nontAuthSuccessesShouldNotThrowAnException() { } @Test - void userUnlockShouldResetBothUserandMfaAuthentication() { - AuditEvent mfaFailureEvent = new AuditEvent(MfaAuthenticationFailure, "1", authDetails, "joe", System.currentTimeMillis(), IdentityZone.getUaaZoneId(), null, null); - auditService.log(mfaFailureEvent, mfaFailureEvent.getIdentityZoneId()); - + void userUnlockShouldResetUserAuthentication() { AuditEvent loginFailureEvent = new AuditEvent(UserAuthenticationFailure, "1", authDetails, "joe", System.currentTimeMillis(), IdentityZone.getUaaZoneId(), null, null); auditService.log(loginFailureEvent, loginFailureEvent.getIdentityZoneId()); AuditEvent unlockEvent = new AuditEvent(UserAccountUnlockedEvent, "1", authDetails, "joe", System.currentTimeMillis(), IdentityZone.getUaaZoneId(), null, null); auditService.log(unlockEvent, unlockEvent.getIdentityZoneId()); - assertThat(auditService.find("1", 0, mfaFailureEvent.getIdentityZoneId()), is(empty())); + assertThat(auditService.find("1", 0, loginFailureEvent.getIdentityZoneId()), is(empty())); } private AuditEvent getAuditEvent(AuditEventType type, String principal, String data) { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/audit/event/SystemDeletableTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/event/SystemDeletableTest.java index 9f8f18299c0..2208120f383 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/audit/event/SystemDeletableTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/event/SystemDeletableTest.java @@ -1,7 +1,5 @@ package org.cloudfoundry.identity.uaa.audit.event; -import org.cloudfoundry.identity.uaa.mfa.GoogleMfaProviderConfig; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.extensions.PollutionPreventionExtension; @@ -45,7 +43,6 @@ void ignoreUnknownEvents() { verify(deletable, never()).deleteByOrigin(any(), any()); verify(deletable, never()).deleteByClient(any(), any()); verify(deletable, never()).deleteByUser(any(), any()); - verify(deletable, never()).deleteByMfaProvider(any(), any()); } @Test @@ -56,7 +53,6 @@ void uaaDefaultZoneIsIgnored() { verify(deletable, never()).deleteByOrigin(any(), any()); verify(deletable, never()).deleteByClient(any(), any()); verify(deletable, never()).deleteByUser(any(), any()); - verify(deletable, never()).deleteByMfaProvider(any(), any()); } @Test @@ -69,7 +65,6 @@ void identityZoneEventReceived() { verify(deletable, never()).deleteByOrigin(any(), any()); verify(deletable, never()).deleteByClient(any(), any()); verify(deletable, never()).deleteByUser(any(), any()); - verify(deletable, never()).deleteByMfaProvider(any(), any()); } @Test @@ -82,7 +77,6 @@ void identityProviderEventReceived() { verify(deletable, times(1)).deleteByOrigin("origin", "other-zone-id"); verify(deletable, never()).deleteByClient(any(), any()); verify(deletable, never()).deleteByUser(any(), any()); - verify(deletable, never()).deleteByMfaProvider(any(), any()); } @Test @@ -97,7 +91,6 @@ void clientDetailsEventReceived() { verify(deletable, never()).deleteByIdentityZone(any()); verify(deletable, never()).deleteByOrigin(any(), any()); verify(deletable, never()).deleteByUser(any(), any()); - verify(deletable, never()).deleteByMfaProvider(any(), any()); } @Test @@ -115,7 +108,6 @@ void uaaUserEventReceived() { verify(deletable, never()).deleteByOrigin(any(), any()); verify(deletable, never()).deleteByClient(any(), any()); verify(deletable, times(1)).deleteByUser("uaaUser-id", "other-zone-id"); - verify(deletable, never()).deleteByMfaProvider(any(), any()); } @Test @@ -133,18 +125,5 @@ void scimUserEventReceived() { verify(deletable, never()).deleteByOrigin(any(), any()); verify(deletable, never()).deleteByClient(any(), any()); verify(deletable, times(1)).deleteByUser("scimUserId", "zoneId"); - verify(deletable, never()).deleteByMfaProvider(any(), any()); - } - - @Test - void mfaEventReceived() { - MfaProvider mfaProvider = new MfaProvider().setId("provider1"); - EntityDeletedEvent event = new EntityDeletedEvent<>(mfaProvider, authentication, null); - deletable.onApplicationEvent(event); - verify(deletable, never()).deleteByIdentityZone(any()); - verify(deletable, never()).deleteByOrigin(any(), any()); - verify(deletable, never()).deleteByClient(any(), any()); - verify(deletable, never()).deleteByUser(any(), any()); - verify(deletable, times(1)).deleteByMfaProvider(eq("provider1"), any()); } } \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/PasswordChangeUiRequiredFilterTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/PasswordChangeUiRequiredFilterTest.java index 29596ee55ff..5e98c6f7943 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/PasswordChangeUiRequiredFilterTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/PasswordChangeUiRequiredFilterTest.java @@ -68,24 +68,6 @@ void tearDown() { SecurityContextHolder.clearContext(); } - @Test - void isIgnored() { - for (String s : Arrays.asList("/login/mfa", "/login/mfa/register", "/login/mfa/verify.do")) { - mockHttpServletRequest.setPathInfo(s); - assertThat("Is ignored:" + s, passwordChangeUiRequiredFilter.isIgnored(mockHttpServletRequest), is(true)); - } - } - - @Test - void requestToMfa() throws Exception { - SecurityContextHolder.getContext().setAuthentication(mockUaaAuthentication); - mockHttpServletRequest.setPathInfo("/login/mfa/register"); - passwordChangeUiRequiredFilter.doFilterInternal(mockHttpServletRequest, mockHttpServletResponse, mockFilterChain); - verify(mockFilterChain, times(1)).doFilter(same(mockHttpServletRequest), same(mockHttpServletResponse)); - verifyNoInteractions(mockHttpServletResponse); - verifyNoInteractions(mockRequestCache); - } - @Test void notAuthenticated() throws Exception { passwordChangeUiRequiredFilter.doFilterInternal(mockHttpServletRequest, mockHttpServletResponse, mockFilterChain); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/listener/AuthenticationSuccessListenerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/listener/AuthenticationSuccessListenerTests.java index 799197dd291..4fc5df5d852 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/listener/AuthenticationSuccessListenerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/listener/AuthenticationSuccessListenerTests.java @@ -2,10 +2,8 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; -import org.cloudfoundry.identity.uaa.authentication.event.MfaAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.mfa.MfaChecker; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.user.UaaUser; @@ -22,7 +20,6 @@ class AuthenticationSuccessListenerTests { private AuthenticationSuccessListener listener; private ScimUserProvisioning mockScimUserProvisioning; private UaaAuthentication mockUaaAuthentication; - private MfaChecker mockMfaChecker; private ApplicationEventPublisher mockApplicationEventPublisher; private String id; private UaaUserPrototype userPrototype; @@ -32,9 +29,8 @@ class AuthenticationSuccessListenerTests { void setUp() { mockUaaAuthentication = mock(UaaAuthentication.class); mockApplicationEventPublisher = mock(ApplicationEventPublisher.class); - mockMfaChecker = mock(MfaChecker.class); mockScimUserProvisioning = mock(ScimUserProvisioning.class); - listener = new AuthenticationSuccessListener(mockScimUserProvisioning, mockMfaChecker); + listener = new AuthenticationSuccessListener(mockScimUserProvisioning); listener.setApplicationEventPublisher(mockApplicationEventPublisher); id = "user-id"; userPrototype = new UaaUserPrototype() @@ -96,7 +92,6 @@ void previousLoginIsSetOnTheAuthentication() { @Test void provider_authentication_success_triggers_user_authentication_success() { - when(mockMfaChecker.isMfaEnabledForZoneId(anyString())).thenReturn(false); IdentityProviderAuthenticationSuccessEvent event = new IdentityProviderAuthenticationSuccessEvent( user, mockUaaAuthentication, @@ -106,29 +101,6 @@ void provider_authentication_success_triggers_user_authentication_success() { verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(UserAuthenticationSuccessEvent.class)); } - @Test - void provider_authentication_success_does_not_trigger_user_authentication_success() { - when(mockMfaChecker.isMfaEnabledForZoneId(anyString())).thenReturn(true); - IdentityProviderAuthenticationSuccessEvent event = new IdentityProviderAuthenticationSuccessEvent( - user, - mockUaaAuthentication, - OriginKeys.UAA, IdentityZoneHolder.getCurrentZoneId() - ); - listener.onApplicationEvent(event); - verifyNoInteractions(mockApplicationEventPublisher); - } - - @Test - void mfa_authentication_success_triggers_user_authentication_success() { - MfaAuthenticationSuccessEvent event = new MfaAuthenticationSuccessEvent( - user, - mockUaaAuthentication, - "mfa-type", IdentityZoneHolder.getCurrentZoneId() - ); - listener.onApplicationEvent(event); - verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(UserAuthenticationSuccessEvent.class)); - } - private UserAuthenticationSuccessEvent getEvent() { user = new UaaUser(userPrototype); return new UserAuthenticationSuccessEvent(user, mockUaaAuthentication, IdentityZoneHolder.getCurrentZoneId()); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java index 9ac8f9c1378..d9baec1f916 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java @@ -97,11 +97,6 @@ void setUp() { mgr.setAccountLoginPolicy(mockAccountLoginPolicy); } - @AfterEach - void cleanUp() { - IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(false); - } - private UaaUserPrototype getPrototype() { String id = new RandomValueStringGenerator().generate(); return new UaaUserPrototype() diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PasswordGrantAuthenticationManagerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PasswordGrantAuthenticationManagerTest.java index b817b10bafa..cb96df37235 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PasswordGrantAuthenticationManagerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PasswordGrantAuthenticationManagerTest.java @@ -479,7 +479,6 @@ void testOIDCPasswordGrantWithPrompts() { when(auth.getCredentials()).thenReturn("koala"); UaaAuthenticationDetails uaaAuthDetails = mock(UaaAuthenticationDetails.class); Map params = new HashMap<>(); - params.put("mfacode", new String[]{"123456"}); params.put("multivalue", new String[]{"123456","654321"}); params.put("emptyvalue", new String[0]); params.put("emptystring", new String[]{""}); @@ -492,7 +491,6 @@ void testOIDCPasswordGrantWithPrompts() { prompts.add(new Prompt("username","text", "Email")); prompts.add(new Prompt("password","password", "Password")); prompts.add(new Prompt("passcode","password", "Temporary Authentication Code")); - prompts.add(new Prompt("mfacode","password", "TOTP-Code")); prompts.add(new Prompt("multivalue","password", "TOTP-Code")); prompts.add(new Prompt("emptyvalue","password", "TOTP-Code")); prompts.add(new Prompt("emptystring","password", "TOTP-Code")); @@ -520,12 +518,11 @@ void testOIDCPasswordGrantWithPrompts() { assertTrue(httpEntity.hasBody()); assertTrue(httpEntity.getBody() instanceof MultiValueMap); MultiValueMap body = (MultiValueMap)httpEntity.getBody(); - assertEquals(5, body.size()); + assertEquals(4, body.size()); assertEquals(Collections.singletonList("password"), body.get("grant_type")); assertEquals(Collections.singletonList("id_token"), body.get("response_type")); assertEquals(Collections.singletonList("marissa"), body.get("username")); assertEquals(Collections.singletonList("koala"), body.get("password")); - assertEquals(Collections.singletonList("123456"), body.get("mfacode")); assertNull(body.get("passcode")); assertNull(body.get("multivalue")); assertNull(body.get("emptyvalue")); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java index 5a5edf46d25..dc35999ff21 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java @@ -31,7 +31,6 @@ import java.util.Arrays; -import static org.cloudfoundry.identity.uaa.audit.AuditEventType.MfaAuthenticationFailure; import static org.cloudfoundry.identity.uaa.audit.AuditEventType.UserAuthenticationFailure; import static org.cloudfoundry.identity.uaa.audit.AuditEventType.UserAuthenticationSuccess; import static org.hamcrest.CoreMatchers.is; @@ -53,9 +52,7 @@ public class PeriodLockoutPolicyTests { private long now; private PeriodLockoutPolicy policy; private CommonLoginPolicy innerPolicy; - private CommonLoginPolicy mfaInnerPolicy; private LockoutPolicyRetriever policyRetriever; - private LockoutPolicyRetriever mfaPolicyRetriever; private IdentityProviderProvisioning providerProvisioning; @Before @@ -71,14 +68,11 @@ public void setUp() { lockoutPolicy.setLockoutPeriodSeconds(ONE_HOUR); when(providerProvisioning.retrieveByOrigin(anyString(), anyString())).thenReturn(new IdentityProvider()); policyRetriever = new UserLockoutPolicyRetriever(providerProvisioning); - mfaPolicyRetriever = new UserLockoutPolicyRetriever(providerProvisioning); innerPolicy = new CommonLoginPolicy(as, policyRetriever, AuditEventType.UserAuthenticationSuccess, AuditEventType.UserAuthenticationFailure, timeService, true); - mfaInnerPolicy = new CommonLoginPolicy(as, policyRetriever, AuditEventType.MfaAuthenticationSuccess, AuditEventType.MfaAuthenticationFailure, timeService, true); policyRetriever.setDefaultLockoutPolicy(lockoutPolicy); - mfaPolicyRetriever.setDefaultLockoutPolicy(lockoutPolicy); - policy = new PeriodLockoutPolicy(innerPolicy, mfaInnerPolicy); + policy = new PeriodLockoutPolicy(innerPolicy); } @Test @@ -93,18 +87,6 @@ public void loginIsDeniedIfAllowedFailuresIsExceeded() { assertFalse(policy.isAllowed(joe, mock(Authentication.class))); } - @Test - public void loginIsDeniedIfAllowedMFAFailuresIsExceeded() { - String zoneId = IdentityZoneHolder.get().getId(); - when(as.find(eq("1"), anyLong(), eq(zoneId))).thenReturn(Arrays.asList( - new AuditEvent(MfaAuthenticationFailure, "joe", "", "", now - 1, IdentityZone.getUaaZoneId(), null, null), - new AuditEvent(MfaAuthenticationFailure, "joe", "", "", now - 2, IdentityZone.getUaaZoneId(), null, null) - )); - - mfaPolicyRetriever.getDefaultLockoutPolicy().setLockoutAfterFailures(2); - assertThat(policy.isAllowed(joe, mock(Authentication.class)), is(false)); - } - @Test public void loginIsAllowedIfSuccessfulLoginIntercedesExcessiveFailures() { String zoneId = IdentityZoneHolder.get().getId(); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java index df6beb52ad4..be259b6b3f2 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java @@ -4,11 +4,6 @@ import org.cloudfoundry.identity.uaa.annotations.WithDatabaseContext; import org.cloudfoundry.identity.uaa.impl.config.IdentityZoneConfigurationBootstrap; import org.cloudfoundry.identity.uaa.login.Prompt; -import org.cloudfoundry.identity.uaa.mfa.GeneralMfaProviderValidator; -import org.cloudfoundry.identity.uaa.mfa.GoogleMfaProviderConfig; -import org.cloudfoundry.identity.uaa.mfa.JdbcMfaProviderProvisioning; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; -import org.cloudfoundry.identity.uaa.mfa.MfaProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlTestUtils; import org.cloudfoundry.identity.uaa.test.TestUtils; import org.cloudfoundry.identity.uaa.zone.ClientSecretPolicy; @@ -19,7 +14,6 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.InvalidIdentityZoneDetailsException; import org.cloudfoundry.identity.uaa.zone.JdbcIdentityZoneProvisioning; -import org.cloudfoundry.identity.uaa.zone.MfaConfigValidator; import org.cloudfoundry.identity.uaa.zone.SamlConfig; import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.junit.jupiter.api.BeforeEach; @@ -76,19 +70,7 @@ void configureProvisioning(@Autowired JdbcTemplate jdbcTemplate) throws SQLExcep provisioning = new JdbcIdentityZoneProvisioning(jdbcTemplate); bootstrap = new IdentityZoneConfigurationBootstrap(provisioning); - GeneralMfaProviderValidator mfaProviderValidator = new GeneralMfaProviderValidator(); - MfaProviderProvisioning mfaProvisoning = new JdbcMfaProviderProvisioning(jdbcTemplate, mfaProviderValidator); - - MfaProvider provider = new MfaProvider<>(); - provider.setName("testProvider"); - provider.setType(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR); - provider.setConfig(new GoogleMfaProviderConfig()); - provider.setIdentityZoneId("uaa"); - mfaProvisoning.create(provider, "uaa"); - - MfaConfigValidator mfaConfigValidator = new MfaConfigValidator(mfaProvisoning); - - GeneralIdentityZoneConfigurationValidator configValidator = new GeneralIdentityZoneConfigurationValidator(mfaConfigValidator); + GeneralIdentityZoneConfigurationValidator configValidator = new GeneralIdentityZoneConfigurationValidator(); validator = new GeneralIdentityZoneValidator(configValidator); bootstrap.setValidator(validator); @@ -297,32 +279,4 @@ void idpDiscoveryEnabled() throws Exception { IdentityZoneConfiguration config = provisioning.retrieve(IdentityZone.getUaaZoneId()).getConfig(); assertTrue(config.isIdpDiscoveryEnabled()); } - - @Test - void testMfaDisabledByDefault() { - assertFalse(bootstrap.isMfaEnabled()); - } - - @Test - void testMfaDisabledWithInvalidName() throws Exception { - bootstrap.setMfaProviderName("NotExistingProvider"); - assertThrows(InvalidIdentityZoneDetailsException.class, () -> bootstrap.afterPropertiesSet()); - } - - @Test - void testMfaEnabledValidName() throws Exception { - bootstrap.setMfaProviderName("testProvider"); - bootstrap.setMfaEnabled(true); - bootstrap.afterPropertiesSet(); - IdentityZoneConfiguration config = provisioning.retrieve(IdentityZone.getUaaZoneId()).getConfig(); - assertEquals("testProvider", config.getMfaConfig().getProviderName()); - assertTrue(bootstrap.isMfaEnabled()); - } - - @Test - void testMfaEnabledInvalidName() throws Exception { - bootstrap.setMfaProviderName("InvalidProvider"); - bootstrap.setMfaEnabled(true); - assertThrows(InvalidIdentityZoneDetailsException.class, () -> bootstrap.afterPropertiesSet()); - } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java index fda80e8b5f7..b9ffe2e779c 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java @@ -23,7 +23,6 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.Links; -import org.cloudfoundry.identity.uaa.zone.MfaConfig; import org.cloudfoundry.identity.uaa.zone.SamlConfig; import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.cloudfoundry.identity.uaa.zone.UserConfig; @@ -105,12 +104,6 @@ public void deserializeClientSecretPolicyJSON_withUnknownProperties_doesNotFail( JsonUtils.readValue(config, ClientSecretPolicy.class); } - @Test - public void deserializeMfaConfigJSON_withUnknownProperties_doesNotFail() { - String config = "{ \"unknown-property\": \"unknown-value\"}"; - JsonUtils.readValue(config, MfaConfig.class); - } - @Test public void deserializeLinksJSON_withUnknownProperties_doesNotFail() { String config = "{ \"unknown-property\": \"unknown-value\"}"; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/ChangePasswordControllerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ChangePasswordControllerTest.java index ec6013c44a9..9ea72b0af2d 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/ChangePasswordControllerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ChangePasswordControllerTest.java @@ -59,7 +59,7 @@ void setUp() { Collections.singletonList(UaaAuthority.UAA_USER), new UaaAuthenticationDetails(false, null, UAA, "12345") ); - authMethods = asList("pwd", "mfa", "otp"); + authMethods = asList("pwd", "otp"); authentication.setAuthenticationMethods(new LinkedHashSet<>(authMethods)); SecurityContextHolder.getContext().setAuthentication(authentication); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java index c915d02b501..14c8887d9ee 100755 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java @@ -35,7 +35,6 @@ import org.cloudfoundry.identity.uaa.codestore.InMemoryExpiringCodeStore; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.extensions.PollutionPreventionExtension; -import org.cloudfoundry.identity.uaa.mfa.MfaChecker; import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.provider.AbstractExternalOAuthIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; @@ -113,7 +112,6 @@ class LoginInfoEndpointTests { private IdentityProvider uaaIdentityProvider; private IdentityZoneConfiguration originalConfiguration; private ExternalOAuthProviderConfigurator configurator; - private MfaChecker spiedMfaChecker; @BeforeEach void setUp() { @@ -137,7 +135,6 @@ void setUp() { UaaRandomStringUtil randomStringUtil = mock(UaaRandomStringUtil.class); when(randomStringUtil.getSecureRandom(anyInt())).thenReturn("01234567890123456789012345678901234567890123456789"); configurator = new ExternalOAuthProviderConfigurator(mockIdentityProviderProvisioning, mockOidcMetadataFetcher, randomStringUtil); - spiedMfaChecker = spy(new MfaChecker(mock(IdentityZoneProvisioning.class))); extendedModelMap = new ExtendedModelMap(); } @@ -482,30 +479,6 @@ void use_login_url_if_present_in_zone() { check_links_urls(zone); } - private void mfa_prompt(IdentityZone zone) { - zone.getConfig().getMfaConfig().setEnabled(true); - IdentityZoneHolder.set(zone); - String baseUrl = check_links_urls(zone); - Map mapPrompts = (Map) extendedModelMap.get("prompts"); - assertNotNull(mapPrompts.get("mfaCode")); - assertEquals( - "MFA Code ( Register at " + addSubdomainToUrl(baseUrl, IdentityZoneHolder.get().getSubdomain()) + " )", - ((String[]) mapPrompts.get("mfaCode"))[1] - ); - } - - @Test - void mfa_prompt_in_default_zone() { - IdentityZone zone = IdentityZone.getUaa(); - mfa_prompt(zone); - } - - @Test - void mfa_prompt_in_non_default_zone() { - IdentityZone zone = MultitenancyFixture.identityZone("test", "test"); - mfa_prompt(zone); - } - private String check_links_urls(IdentityZone zone) { IdentityZoneHolder.set(zone); String baseUrl = "http://uaa.domain.com"; @@ -623,7 +596,6 @@ void no_usernamePasswordBoxes_if_internalAuth_and_ldap_disabled() throws Excepti @Test void promptLogic() throws Exception { LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get()); - IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true); endpoint.loginForHtml(extendedModelMap, null, new MockHttpServletRequest("GET", "http://someurl"), singletonList(MediaType.TEXT_HTML)); assertNotNull("prompts attribute should be present", extendedModelMap.get("prompts")); assertTrue("prompts should be a Map for Html content", extendedModelMap.get("prompts") instanceof Map); @@ -632,17 +604,15 @@ void promptLogic() throws Exception { assertNotNull(mapPrompts.get("username")); assertNotNull(mapPrompts.get("password")); assertNull(mapPrompts.get("passcode")); - assertNull(mapPrompts.get("mfaCode")); extendedModelMap.clear(); endpoint.infoForJson(extendedModelMap, null, new MockHttpServletRequest("GET", "http://someurl")); assertNotNull("prompts attribute should be present", extendedModelMap.get("prompts")); assertTrue("prompts should be a Map for JSON content", extendedModelMap.get("prompts") instanceof Map); mapPrompts = (Map) extendedModelMap.get("prompts"); - assertEquals("there should be two prompts for html", 3, mapPrompts.size()); + assertEquals("there should be two prompts for html", 2, mapPrompts.size()); assertNotNull(mapPrompts.get("username")); assertNotNull(mapPrompts.get("password")); - assertNotNull(mapPrompts.get("mfaCode")); assertNull(mapPrompts.get("passcode")); //add a SAML IDP, should make the passcode prompt appear @@ -652,11 +622,10 @@ void promptLogic() throws Exception { assertNotNull("prompts attribute should be present", extendedModelMap.get("prompts")); assertTrue("prompts should be a Map for JSON content", extendedModelMap.get("prompts") instanceof Map); mapPrompts = (Map) extendedModelMap.get("prompts"); - assertEquals("there should be three prompts for html", 4, mapPrompts.size()); + assertEquals("there should be three prompts for html", 3, mapPrompts.size()); assertNotNull(mapPrompts.get("username")); assertNotNull(mapPrompts.get("password")); assertNotNull(mapPrompts.get("passcode")); - assertNotNull(mapPrompts.get("mfaCode")); when(mockSamlIdentityProviderConfigurator.getIdentityProviderDefinitions((List) isNull(), eq(IdentityZone.getUaa()))).thenReturn(idps); @@ -994,31 +963,6 @@ void loginWithInvalidMediaType() { () -> endpoint.loginForHtml(extendedModelMap, null, new MockHttpServletRequest(), singletonList(MediaType.TEXT_XML))); } - @Test - void generateAutologinCodeFailsWhenMfaRequired() { - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get()); - - doReturn(true).when(spiedMfaChecker).isMfaEnabled(any(IdentityZone.class)); - - assertThrowsWithMessageThat( - BadCredentialsException.class, - () -> endpoint.generateAutologinCode(mock(AutologinRequest.class), "Basic 1234"), - is("MFA is required") - ); - } - - @Test - void performAutologinFailsWhenMfaRequired() { - LoginInfoEndpoint endpoint = getEndpoint(IdentityZoneHolder.get()); - doReturn(true).when(spiedMfaChecker).isMfaEnabled(any(IdentityZone.class)); - - assertThrowsWithMessageThat( - BadCredentialsException.class, - () -> endpoint.performAutologin(new MockHttpSession()), - is("MFA is required") - ); - } - @Test void loginHintEmailDomain() throws Exception { MockHttpServletRequest mockHttpServletRequest = getMockHttpServletRequest(); @@ -1800,7 +1744,6 @@ private LoginInfoEndpoint getEndpoint( new InMemoryExpiringCodeStore(new TimeServiceImpl()), externalLoginUrl, baseUrl, - spiedMfaChecker, configurator, mockIdentityProviderProvisioning, "", diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/TotpMfaEndpointTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/TotpMfaEndpointTest.java deleted file mode 100644 index 2cdef6124aa..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/TotpMfaEndpointTest.java +++ /dev/null @@ -1,374 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.login; - - -import com.warrenstrange.googleauth.GoogleAuthenticatorException; -import org.cloudfoundry.identity.uaa.audit.event.AbstractUaaEvent; -import org.cloudfoundry.identity.uaa.authentication.AuthenticationPolicyRejectionException; -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.authentication.event.MfaAuthenticationFailureEvent; -import org.cloudfoundry.identity.uaa.authentication.event.MfaAuthenticationSuccessEvent; -import org.cloudfoundry.identity.uaa.authentication.manager.CommonLoginPolicy; -import org.cloudfoundry.identity.uaa.authentication.manager.LoginPolicy; -import org.cloudfoundry.identity.uaa.login.util.RandomValueStringGenerator; -import org.cloudfoundry.identity.uaa.mfa.GoogleMfaProviderConfig; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; -import org.cloudfoundry.identity.uaa.mfa.MfaProviderProvisioning; -import org.cloudfoundry.identity.uaa.mfa.UserGoogleMfaCredentials; -import org.cloudfoundry.identity.uaa.mfa.UserGoogleMfaCredentialsProvisioning; -import org.cloudfoundry.identity.uaa.user.UaaUser; -import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; -import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatchers; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; -import org.springframework.ui.Model; -import org.springframework.web.bind.support.SessionStatus; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.view.RedirectView; - -import java.util.List; - -import static org.hamcrest.Matchers.instanceOf; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - -public class TotpMfaEndpointTest { - private String userId; - private TotpMfaEndpoint endpoint; - private UserGoogleMfaCredentialsProvisioning userGoogleMfaCredentialsProvisioning; - private MfaProviderProvisioning mfaProviderProvisioning; - private UaaAuthentication uaaAuthentication; - - @Rule - public ExpectedException exception = ExpectedException.none(); - private MfaProvider mfaProvider; - private MfaProvider otherMfaProvider; - private SavedRequestAwareAuthenticationSuccessHandler mockSuccessHandler; - private ApplicationEventPublisher publisher; - private ArgumentCaptor eventCaptor; - private UaaUserDatabase userDb; - private CommonLoginPolicy mockMfaPolicy; - - @Before - public void setup() { - userId = new RandomValueStringGenerator(5).generate(); - - userGoogleMfaCredentialsProvisioning = mock(UserGoogleMfaCredentialsProvisioning.class); - mfaProviderProvisioning = mock(MfaProviderProvisioning.class); - uaaAuthentication = mock(UaaAuthentication.class); - - mfaProvider = new MfaProvider(); - mfaProvider.setName("provider-name"); - mfaProvider.setId("provider_id1"); - mfaProvider.setConfig(new GoogleMfaProviderConfig()); - mfaProvider.setType(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR); - - otherMfaProvider = new MfaProvider(); - otherMfaProvider.setName("other-provider-name"); - otherMfaProvider.setId("provider_id2"); - otherMfaProvider.setConfig(new GoogleMfaProviderConfig()); - otherMfaProvider.setType(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR); - - mockSuccessHandler = mock(SavedRequestAwareAuthenticationSuccessHandler.class); - - SecurityContextHolder.getContext().setAuthentication(uaaAuthentication); - - publisher = mock(ApplicationEventPublisher.class); - eventCaptor = ArgumentCaptor.forClass(ApplicationEvent.class); - doNothing().when(publisher).publishEvent(eventCaptor.capture()); - - userDb = mock(UaaUserDatabase.class); - mockMfaPolicy = mock(CommonLoginPolicy.class); - when(mockMfaPolicy.isAllowed(anyString())).thenReturn(new LoginPolicy.Result(true, 0)); - - endpoint = new TotpMfaEndpoint( - userGoogleMfaCredentialsProvisioning, - mfaProviderProvisioning, - "/login/mfa/completed", - userDb, - mockMfaPolicy); - endpoint.setApplicationEventPublisher(publisher); - } - - @After - public void cleanUp() { - IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(false).setProviderName(null); - SecurityContextHolder.clearContext(); - } - - @Test - public void testGenerateQrUrl() throws Exception{ - when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, null, null, null), null, null); - when(userGoogleMfaCredentialsProvisioning.activeUserCredentialExists(userId, mfaProvider.getId())).thenReturn(false); - - when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); - IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); - - String returnView = endpoint.generateQrUrl(mock(Model.class), null); - - assertEquals("mfa/qr_code", returnView); - } - - @Test - public void testGenerateQrUrlForNewUserRegistration() throws Exception{ - when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, null, null, null), null, null); - - when(userGoogleMfaCredentialsProvisioning.activeUserCredentialExists(userId, mfaProvider.getId())).thenReturn(true); - IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); - when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); - - String returnView = endpoint.generateQrUrl(mock(Model.class), null); - - assertEquals("redirect:/login/mfa/verify", returnView); - } - - @Test - public void testGenerateQrUrlAfterMfaProviderSwitch() throws Exception{ - when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, null, null, null), null, null); - - when(userGoogleMfaCredentialsProvisioning.activeUserCredentialExists(userId, mfaProvider.getId())).thenReturn(true); - when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); - when(mfaProviderProvisioning.retrieveByName(otherMfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(otherMfaProvider); - - IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(otherMfaProvider.getName()); - - String returnView = endpoint.generateQrUrl(mock(Model.class), null); - - assertEquals("mfa/qr_code", returnView); - } - - @Test(expected = TotpMfaEndpoint.UaaPrincipalIsNotInSession.class) - public void testTotpAuthorizePageNoAuthentication() throws Exception{ - when(uaaAuthentication.getPrincipal()).thenReturn(null); - endpoint.totpAuthorize(mock(Model.class)); - } - - @Test - public void testTotpAuthorizePage() throws Exception{ - when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, null, null, null), null, null); - - ModelAndView returnView = endpoint.totpAuthorize(mock(Model.class)); - assertEquals("mfa/enter_code", returnView.getViewName()); - } - - - @Test - public void testValidOTPTakesToHomePage() throws Exception{ - int code = 1234; - when(userGoogleMfaCredentialsProvisioning.isValidCode(ArgumentMatchers.any(UserGoogleMfaCredentials.class), eq(code))).thenReturn(true); - when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, "uaa", null, null), null, null); - when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); - when(userDb.retrieveUserByName("Marissa", "uaa")).thenReturn(new UaaUser(new UaaUserPrototype().withUsername("Marissa").withOrigin("uaa").withId("1234").withEmail("marissa@example.com"))); - IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); - - SessionStatus sessionStatus = mock(SessionStatus.class); - ModelAndView returnView = endpoint.validateCode( - mock(Model.class), - Integer.toString(code), - mock(UserGoogleMfaCredentials.class), - new MockHttpServletRequest(), - sessionStatus); - - assertEquals("/login/mfa/completed", ((RedirectView)returnView.getView()).getUrl()); - verify(sessionStatus, times(1)).setComplete(); - verifyMfaEvent(MfaAuthenticationSuccessEvent.class); - } - - @Test - public void testValidOTPActivatesUser() throws Exception { - int code = 1234; - when(userGoogleMfaCredentialsProvisioning.isValidCode(ArgumentMatchers.any(UserGoogleMfaCredentials.class), eq(code))).thenReturn(true); - when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, "uaa", null, null), null, null); - when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); - when(userDb.retrieveUserByName("Marissa", "uaa")).thenReturn(new UaaUser(new UaaUserPrototype().withUsername("Marissa").withOrigin("uaa").withId("1234").withEmail("marissa@example.com"))); - IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); - - SessionStatus sessionStatus = mock(SessionStatus.class); - endpoint.validateCode(mock(Model.class), - Integer.toString(code), - mock(UserGoogleMfaCredentials.class), - new MockHttpServletRequest(), - sessionStatus - ); - verify(userGoogleMfaCredentialsProvisioning).saveUserCredentials(ArgumentMatchers.any(UserGoogleMfaCredentials.class)); - verify(sessionStatus).setComplete(); - verifyMfaEvent(MfaAuthenticationSuccessEvent.class); - } - - @Test - public void testInvalidOTPReturnsError() throws Exception{ - int code = 1234; - when(userGoogleMfaCredentialsProvisioning.isValidCode(ArgumentMatchers.any(UserGoogleMfaCredentials.class), eq(code))).thenReturn(false); - when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, "uaa", null, null), null, null); - when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); - when(userDb.retrieveUserByName("Marissa", "uaa")).thenReturn(new UaaUser(new UaaUserPrototype().withUsername("Marissa").withOrigin("uaa").withId("1234").withEmail("marissa@example.com"))); - IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); - SessionStatus sessionStatus = mock(SessionStatus.class); - ModelAndView returnView = endpoint.validateCode( - mock(Model.class), - Integer.toString(code), - mock(UserGoogleMfaCredentials.class), - new MockHttpServletRequest(), - sessionStatus - ); - - assertEquals("mfa/enter_code", returnView.getViewName()); - verifyNoInteractions(sessionStatus); - verifyMfaEvent(MfaAuthenticationFailureEvent.class); - } - - @Test - public void testValidOTPReturnsErrorWhenLockedOut() throws Exception{ - exception.expect(AuthenticationPolicyRejectionException.class); - int code = 1234; - - - when(mockMfaPolicy.isAllowed(anyString())).thenReturn(new LoginPolicy.Result(false, 0)); - - when(userGoogleMfaCredentialsProvisioning.isValidCode(ArgumentMatchers.any(UserGoogleMfaCredentials.class), eq(code))).thenReturn(true); - when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, "uaa", null, null), null, null); - when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); - when(userDb.retrieveUserByName("Marissa", "uaa")).thenReturn(new UaaUser(new UaaUserPrototype().withUsername("Marissa").withOrigin("uaa").withId("1234").withEmail("marissa@example.com"))); - IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); - SessionStatus sessionStatus = mock(SessionStatus.class); - - endpoint.validateCode( - mock(Model.class), - Integer.toString(code), - mock(UserGoogleMfaCredentials.class), - new MockHttpServletRequest(), - sessionStatus - ); - - verifyNoInteractions(sessionStatus); - verifyMfaEvent(MfaAuthenticationFailureEvent.class); - } - - @Test - public void testValidateCodeThrowsException() throws Exception{ - int code = 1234; - when(userGoogleMfaCredentialsProvisioning.isValidCode(ArgumentMatchers.any(UserGoogleMfaCredentials.class), eq(code))).thenThrow(new GoogleAuthenticatorException("Thou shall not pass")); - when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, "uaa", null, null), null, null); - when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); - when(userDb.retrieveUserByName("Marissa", "uaa")).thenReturn(new UaaUser(new UaaUserPrototype().withUsername("Marissa").withOrigin("uaa").withId("1234").withEmail("marissa@example.com"))); - IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); - SessionStatus sessionStatus = mock(SessionStatus.class); - ModelAndView returnView = endpoint.validateCode( - mock(Model.class), - Integer.toString(code), - mock(UserGoogleMfaCredentials.class), - new MockHttpServletRequest(), - sessionStatus - ); - - assertEquals("mfa/enter_code", returnView.getViewName()); - verifyNoInteractions(sessionStatus); - verifyMfaEvent(MfaAuthenticationFailureEvent.class); - } - - @Test - public void testEmptyOTP() throws Exception{ - when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, "uaa", null, null), null, null); - when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); - when(userDb.retrieveUserByName("Marissa", "uaa")).thenReturn(new UaaUser(new UaaUserPrototype().withUsername("Marissa").withOrigin("uaa").withId("1234").withEmail("marissa@example.com"))); - IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); - SessionStatus sessionStatus = mock(SessionStatus.class); - ModelAndView returnView = endpoint.validateCode( - mock(Model.class), - "", - mock(UserGoogleMfaCredentials.class), - new MockHttpServletRequest(), - sessionStatus - ); - assertEquals("mfa/enter_code", returnView.getViewName()); - verifyNoInteractions(sessionStatus); - verifyMfaEvent(MfaAuthenticationFailureEvent.class); - } - - @Test - public void testNonNumericOTP() throws Exception{ - when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, "uaa", null, null), null, null); - when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); - when(userDb.retrieveUserByName("Marissa", "uaa")).thenReturn(new UaaUser(new UaaUserPrototype().withUsername("Marissa").withOrigin("uaa").withId("1234").withEmail("marissa@example.com"))); - IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); - SessionStatus sessionStatus = mock(SessionStatus.class); - ModelAndView returnView = endpoint.validateCode( - mock(Model.class), - "asdf123", - mock(UserGoogleMfaCredentials.class), - new MockHttpServletRequest(), - sessionStatus - ); - assertEquals("mfa/enter_code", returnView.getViewName()); - verifyNoInteractions(sessionStatus); - verifyMfaEvent(MfaAuthenticationFailureEvent.class); - } - - @Test - public void testManualRegistration() throws Exception { - when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, null, null, null), null, null); - when(userGoogleMfaCredentialsProvisioning.activeUserCredentialExists(userId, mfaProvider.getId())).thenReturn(false); - when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); - IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); - String returnValue = endpoint.manualRegistration(mock(Model.class), mock(UserGoogleMfaCredentials.class)); - assertEquals("mfa/manual_registration", returnValue); - } - - @Test - public void testManualRegistrationExistingCredential() throws Exception { - when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, null, null, null), null, null); - when(userGoogleMfaCredentialsProvisioning.activeUserCredentialExists(userId, mfaProvider.getId())).thenReturn(true); - when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); - IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); - - String returnValue = endpoint.manualRegistration( - mock(Model.class), - mock(UserGoogleMfaCredentials.class) - ); - - assertEquals("redirect:/login/mfa/verify", returnValue); - } - - private void verifyMfaEvent(Class eventClass) { - List values = eventCaptor.getAllValues(); - assertEquals(1, values.size()); - ApplicationEvent event = values.get(0); - assertThat(event, instanceOf(eventClass)); - AbstractUaaEvent mfaEvent = (AbstractUaaEvent) event; - assertEquals("google-authenticator", mfaEvent.getAuditEvent().getAuthenticationType()); - } -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/UaaAuthenticationFailureHandlerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/UaaAuthenticationFailureHandlerTests.java index 23f20b6e30c..03179d9c9d2 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/UaaAuthenticationFailureHandlerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/UaaAuthenticationFailureHandlerTests.java @@ -54,7 +54,6 @@ public void setup() { failureHandler = new ExceptionMappingAuthenticationFailureHandler(); Map errorMap = new HashMap<>(); errorMap.put("org.cloudfoundry.identity.uaa.authentication.PasswordChangeRequiredException", "/force_password_change"); - errorMap.put("org.cloudfoundry.identity.uaa.authentication.MfaAuthenticationRequiredException", "/login/mfa/register"); failureHandler.setExceptionMappings(errorMap); failureHandler = spy(failureHandler); cookieFactory = new CurrentUserCookieFactory(1234, false); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/GeneralMfaProviderValidatorTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/GeneralMfaProviderValidatorTest.java deleted file mode 100644 index 80bbe96c789..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/GeneralMfaProviderValidatorTest.java +++ /dev/null @@ -1,136 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import org.cloudfoundry.identity.uaa.mfa.exception.InvalidMfaProviderException; -import org.cloudfoundry.identity.uaa.util.AlphanumericRandomValueStringGenerator; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import static org.junit.Assert.assertEquals; - -public class GeneralMfaProviderValidatorTest { - - GeneralMfaProviderValidator validator; - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Before - public void setup() { - validator = new GeneralMfaProviderValidator(); - } - - @Test - public void validateProviderNullConfig() { - expectedException.expect(InvalidMfaProviderException.class); - expectedException.expectMessage("Provider config is required"); - MfaProvider provider = createValidGoogleMfaProvider() - .setConfig(null); - validator.validate(provider); - } - - @Test - public void validateProviderConfigWithInvalidIssuer() { - expectedException.expect(InvalidMfaProviderException.class); - expectedException.expectMessage("Provider config contains an invalid issuer. Issuer must not contain a colon"); - MfaProvider provider = createValidGoogleMfaProvider() - .setConfig(createValidGoogleMfaConfig().setIssuer("invalid:issuer")); - validator.validate(provider); - } - - @Test - public void validateProviderConfigWithMissingIssuer() { - MfaProvider provider = createValidGoogleMfaProvider() - .setConfig(createValidGoogleMfaConfig().setIssuer(null)); - validator.validate(provider); - } - - @Test - public void validateProviderEmptyName() { - expectedException.expect(InvalidMfaProviderException.class); - expectedException.expectMessage("Provider name is required"); - MfaProvider provider = createValidGoogleMfaProvider() - .setName(""); - validator.validate(provider); - } - - @Test - public void validateProviderInvalidNameTooLong() { - expectedException.expect(InvalidMfaProviderException.class); - expectedException.expectMessage("Provider name cannot be longer than 256 characters"); - MfaProvider provider = createValidGoogleMfaProvider() - .setName(new AlphanumericRandomValueStringGenerator(257).generate()); - validator.validate(provider); - } - @Test - public void validateProviderInvalidName() { - expectedException.expect(InvalidMfaProviderException.class); - expectedException.expectMessage("Provider name is required"); - MfaProvider provider = createValidGoogleMfaProvider() - .setName(" "); - validator.validate(provider); - } - - @Test - public void validateProviderWhitespaceInName() { - String name = "This is a valid name"; - MfaProvider provider = createValidGoogleMfaProvider() - .setName(name); - validator.validate(provider); - } - - @Test - public void validateProviderTrimsName() { - String name = " This is also a valid name "; - MfaProvider provider = createValidGoogleMfaProvider() - .setName(name); - provider.setName(" " + name + " "); - validator.validate(provider); - assertEquals(name.trim(), provider.getName()); - } - - @Test - public void validateProviderInvalidNameSpecialChars() { - expectedException.expect(InvalidMfaProviderException.class); - expectedException.expectMessage("Provider name must be alphanumeric"); - MfaProvider provider = createValidGoogleMfaProvider() - .setName("invalidName$"); - validator.validate(provider); - } - - - @Test - public void validateProviderNullType() { - expectedException.expect(InvalidMfaProviderException.class); - expectedException.expectMessage("Provider type is required. Must be one of " + MfaProvider.MfaProviderType.getStringValues()); - MfaProvider provider = createValidGoogleMfaProvider() - .setType(null); - validator.validate(provider); - } - - @Test - public void validateProviderEmptyZone() { - expectedException.expect(InvalidMfaProviderException.class); - expectedException.expectMessage("Provider must belong to a zone"); - MfaProvider provider = createValidGoogleMfaProvider() - .setIdentityZoneId(""); - validator.validate(provider); - } - - private MfaProvider createValidGoogleMfaProvider() { - MfaProvider res = new MfaProvider(); - res.setName(new AlphanumericRandomValueStringGenerator(5).generate()) - .setConfig(createValidGoogleMfaConfig()) - .setIdentityZoneId(IdentityZone.getUaaZoneId()) - .setType(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR); - return res; - } - - private GoogleMfaProviderConfig createValidGoogleMfaConfig() { - return (GoogleMfaProviderConfig) new GoogleMfaProviderConfig() - .setProviderDescription("config description") - .setIssuer("current-zone"); - } -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/JdbcMfaProviderProvisioningTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/JdbcMfaProviderProvisioningTest.java deleted file mode 100644 index 3faf984e82f..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/JdbcMfaProviderProvisioningTest.java +++ /dev/null @@ -1,178 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import org.cloudfoundry.identity.uaa.annotations.WithDatabaseContext; -import org.cloudfoundry.identity.uaa.login.util.RandomValueStringGenerator; -import org.cloudfoundry.identity.uaa.mfa.exception.MfaAlreadyExistsException; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.jdbc.core.JdbcTemplate; - -import java.util.List; - -import static org.cloudfoundry.identity.uaa.util.AssertThrowsWithMessage.assertThrowsWithMessageThat; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; - -@WithDatabaseContext -class JdbcMfaProviderProvisioningTest { - - private JdbcMfaProviderProvisioning mfaProviderProvisioning; - private MfaProviderValidator mfaProviderValidator; - - @Autowired - private JdbcTemplate jdbcTemplate; - - @BeforeEach - void setUp() { - mfaProviderValidator = mock(GeneralMfaProviderValidator.class); - mfaProviderProvisioning = new JdbcMfaProviderProvisioning(jdbcTemplate, mfaProviderValidator); - } - - @Test - void createAndRetrieve() { - MfaProvider mfaProvider = constructGoogleProvider(); - String zoneId = IdentityZoneHolder.get().getId(); - assertEquals(0, (int) jdbcTemplate.queryForObject("select count(*) from mfa_providers where identity_zone_id=? and name=?", new Object[]{zoneId, mfaProvider.getName()}, Integer.class)); - doNothing().when(mfaProviderValidator); - - MfaProvider created = mfaProviderProvisioning.create(mfaProvider, zoneId); - assertNotNull(created); - assertEquals(1, (int) jdbcTemplate.queryForObject("select count(*) from mfa_providers where identity_zone_id=? and id=?", new Object[]{zoneId, created.getId()}, Integer.class)); - - MfaProvider retrieved = mfaProviderProvisioning.retrieve(created.getId(), zoneId); - assertEquals(mfaProvider.getName(), retrieved.getName()); - assertEquals(mfaProvider.getConfig(), retrieved.getConfig()); - } - - @Test - void createDuplicate() { - MfaProvider mfaProvider = constructGoogleProvider(); - String zoneId = IdentityZoneHolder.get().getId(); - assertEquals(0, (int) jdbcTemplate.queryForObject("select count(*) from mfa_providers where identity_zone_id=? and name=?", new Object[]{zoneId, mfaProvider.getName()}, Integer.class)); - doNothing().when(mfaProviderValidator); - mfaProviderProvisioning.create(mfaProvider, zoneId); - assertThrowsWithMessageThat(MfaAlreadyExistsException.class, - () -> mfaProviderProvisioning.create(mfaProvider, zoneId), - is("An MFA Provider with that name already exists.")); - } - - @Test - void createDuplicateWorksAcrossZones() { - MfaProvider mfaProvider = constructGoogleProvider(); - String zoneId = IdentityZoneHolder.get().getId(); - assertEquals(0, (int) jdbcTemplate.queryForObject("select count(*) from mfa_providers where identity_zone_id=? and name=?", new Object[]{zoneId, mfaProvider.getName()}, Integer.class)); - doNothing().when(mfaProviderValidator); - mfaProviderProvisioning.create(mfaProvider, zoneId); - mfaProviderProvisioning.create(mfaProvider, zoneId + "-other-zone"); - } - - @Test - void updateDuplicate() { - MfaProvider firstProvider = mfaProviderProvisioning.create(constructGoogleProvider(), IdentityZoneHolder.get().getId()); - MfaProvider secondProvider = mfaProviderProvisioning.create(constructGoogleProvider(), IdentityZoneHolder.get().getId()); - - secondProvider.setName(firstProvider.getName()); - - assertThrowsWithMessageThat(MfaAlreadyExistsException.class, - () -> mfaProviderProvisioning.update(secondProvider, IdentityZoneHolder.get().getId()), - is("An MFA Provider with that name already exists.")); - } - - @Test - void createAndUpdate() { - MfaProvider mfaProvider = constructGoogleProvider(); - String zoneId = IdentityZoneHolder.get().getId(); - assertEquals(0, (int) jdbcTemplate.queryForObject("select count(*) from mfa_providers where identity_zone_id=? and name=?", new Object[]{zoneId, mfaProvider.getName()}, Integer.class)); - - MfaProvider created = mfaProviderProvisioning.create(mfaProvider, zoneId); - assertNotNull(created); - assertEquals(1, (int) jdbcTemplate.queryForObject("select count(*) from mfa_providers where identity_zone_id=? and id=?", new Object[]{zoneId, created.getId()}, Integer.class)); - - mfaProvider = created; - mfaProvider.setName(new RandomValueStringGenerator(5).generate()); - mfaProvider.getConfig().setIssuer("new issuer"); - - MfaProvider updated = mfaProviderProvisioning.update(created, zoneId); - assertNotNull(updated); - - MfaProvider retrieved = mfaProviderProvisioning.retrieve(created.getId(), zoneId); - assertEquals(mfaProvider.getName(), retrieved.getName()); - assertEquals(mfaProvider.getConfig().getIssuer(), retrieved.getConfig().getIssuer()); - } - - @Test - void retrieveAll() { - String zoneId = IdentityZoneHolder.get().getId(); - List providers = mfaProviderProvisioning.retrieveAll(zoneId); - doNothing().when(mfaProviderValidator); - int beforeCount = providers.size(); - - MfaProvider mfaProvider = constructGoogleProvider(); - mfaProviderProvisioning.create(mfaProvider, zoneId); - - providers = mfaProviderProvisioning.retrieveAll(zoneId); - int afterCount = providers.size(); - assertEquals(1, afterCount - beforeCount); - } - - @Test - void retrieve() { - MfaProvider mfaProvider = constructGoogleProvider(); - doNothing().when(mfaProviderValidator); - String zoneId = IdentityZoneHolder.get().getId(); - MfaProvider created = mfaProviderProvisioning.create(mfaProvider, zoneId); - assertEquals(mfaProvider.getName(), created.getName()); - assertNotNull(created.getId()); - } - - @Test - void retrieveByName() { - MfaProvider createdProvider = mfaProviderProvisioning.create(constructGoogleProvider(), IdentityZoneHolder.get().getId()); - assertEquals( - createdProvider.getId(), - mfaProviderProvisioning.retrieveByName(createdProvider.getName(), createdProvider.getIdentityZoneId()).getId() - ); - } - - @Test - void delete() { - String zoneId = IdentityZoneHolder.get().getId(); - doNothing().when(mfaProviderValidator); - MfaProvider mfaProvider = mfaProviderProvisioning.create(constructGoogleProvider(), zoneId); - assertNotNull(mfaProviderProvisioning.retrieve(mfaProvider.getId(), zoneId)); - - mfaProviderProvisioning.deleteByMfaProvider(mfaProvider.getId(), zoneId); - - assertThrows(EmptyResultDataAccessException.class, - () -> mfaProviderProvisioning.retrieve(mfaProvider.getId(), zoneId)); - } - - @Test - void deleteByIdentityZone() { - String zoneId = IdentityZoneHolder.get().getId(); - doNothing().when(mfaProviderValidator); - MfaProvider mfaProvider = mfaProviderProvisioning.create(constructGoogleProvider(), zoneId); - assertNotNull(mfaProviderProvisioning.retrieve(mfaProvider.getId(), zoneId)); - - mfaProviderProvisioning.deleteByIdentityZone(zoneId); - - assertThrows(EmptyResultDataAccessException.class, - () -> mfaProviderProvisioning.retrieve(mfaProvider.getId(), zoneId)); - } - - private static MfaProvider constructGoogleProvider() { - return new MfaProvider() - .setName(new RandomValueStringGenerator(10).generate()) - .setType(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR) - .setIdentityZoneId(IdentityZoneHolder.get().getId()) - .setConfig(new GoogleMfaProviderConfig()); - } - -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/JdbcUserGoogleMfaCredentialsProvisioningTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/JdbcUserGoogleMfaCredentialsProvisioningTest.java deleted file mode 100644 index ad6c7e8ca01..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/JdbcUserGoogleMfaCredentialsProvisioningTest.java +++ /dev/null @@ -1,359 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import com.google.common.collect.Lists; -import org.apache.commons.lang3.StringUtils; -import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; -import org.cloudfoundry.identity.uaa.annotations.WithDatabaseContext; -import org.cloudfoundry.identity.uaa.cypto.EncryptionKeyService; -import org.cloudfoundry.identity.uaa.cypto.EncryptionServiceException; -import org.cloudfoundry.identity.uaa.mfa.exception.UnableToPersistMfaException; -import org.cloudfoundry.identity.uaa.mfa.exception.UnableToRetrieveMfaException; -import org.cloudfoundry.identity.uaa.mfa.exception.UserMfaConfigAlreadyExistsException; -import org.cloudfoundry.identity.uaa.mfa.exception.UserMfaConfigDoesNotExistException; -import org.cloudfoundry.identity.uaa.test.RandomStringGetter; -import org.cloudfoundry.identity.uaa.test.RandomStringGetterExtension; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Answers; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.util.Base64Utils; - -import java.security.Security; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import static org.cloudfoundry.identity.uaa.util.AssertThrowsWithMessage.assertThrowsWithMessageThat; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -@WithDatabaseContext -@ExtendWith(RandomStringGetterExtension.class) -class JdbcUserGoogleMfaCredentialsProvisioningTest { - private JdbcUserGoogleMfaCredentialsProvisioning db; - private String activeKeyLabel; - private String inactiveKeyLabel; - private EncryptionKeyService encryptionKeyService; - private EncryptionKeyService.EncryptionKey activeEncryptionKey; - private EncryptionKeyService.EncryptionKey inActiveEncryptionKey; - - @Autowired - private JdbcTemplate jdbcTemplate; - - @BeforeAll - static void key() { - Security.addProvider(new BouncyCastleFipsProvider()); - Security.setProperty("crypto.policy", "unlimited"); - } - - private String MFA_ID; - private String zoneId; - - @BeforeEach - void initJdbcScimUserProvisioningTests( - final RandomStringGetter mfaId, - final RandomStringGetter zoneId) { - this.MFA_ID = StringUtils.rightPad(mfaId.get(), 36); - this.zoneId = StringUtils.rightPad(zoneId.get(), 36); - activeKeyLabel = "key-1"; - inactiveKeyLabel = "key-2"; - - activeEncryptionKey = new EncryptionKeyService.EncryptionKey() {{ - put("label", activeKeyLabel); - put("passphrase", "some-password"); - }}; - inActiveEncryptionKey = new EncryptionKeyService.EncryptionKey() {{ - put("label", inactiveKeyLabel); - put("passphrase", "some-other-password"); - }}; - - encryptionKeyService = new EncryptionKeyService(activeKeyLabel, Lists.newArrayList(activeEncryptionKey, inActiveEncryptionKey)); - - db = new JdbcUserGoogleMfaCredentialsProvisioning(jdbcTemplate, encryptionKeyService); - } - - @AfterEach - void clear() { - jdbcTemplate.execute("delete from user_google_mfa_credentials"); - } - - @Test - void saveUserGoogleMfaCredentials() throws EncryptionServiceException { - assertEquals(0, jdbcTemplate.queryForList("SELECT * FROM user_google_mfa_credentials").size()); - UserGoogleMfaCredentials userGoogleMfaCredentials = new UserGoogleMfaCredentials("jabbahut", - "very_sercret_key", - 74718234, - Arrays.asList(1, 22)).setMfaProviderId(MFA_ID); - - db.save(userGoogleMfaCredentials, zoneId); - List> credentials = jdbcTemplate.queryForList("SELECT * FROM user_google_mfa_credentials"); - assertEquals(1, credentials.size()); - Map record = credentials.get(0); - assertEquals("jabbahut", record.get("user_id")); - - assertEquals("very_sercret_key", new String( - activeEncryptionKey.decrypt(Base64Utils.decodeFromString((String) record.get("secret_key")))) - ); - assertEquals(74718234, Integer.parseInt(new String( - activeEncryptionKey.decrypt(Base64Utils.decodeFromString(String.valueOf(record.get("encrypted_validation_code")))))) - ); - assertEquals("1,22", new String( - activeEncryptionKey.decrypt(Base64Utils.decodeFromString(String.valueOf(record.get("scratch_codes"))))) - ); - - assertEquals(MFA_ID, record.get("mfa_provider_id")); - assertEquals(zoneId, record.get("zone_id")); - assertEquals(activeKeyLabel, record.get("encryption_key_label")); - } - - // db.save is a jdbcProvisioner method and should throw error when creating duplicate - @Test - void save_whenExistsForUser() { - assertEquals(0, jdbcTemplate.queryForList("SELECT * FROM user_google_mfa_credentials").size()); - UserGoogleMfaCredentials userGoogleMfaCredentials = new UserGoogleMfaCredentials("jabbahut", - "very_sercret_key", - 74718234, - Arrays.asList(1, 22)).setMfaProviderId(MFA_ID); - - db.save(userGoogleMfaCredentials, zoneId); - assertThrows(UserMfaConfigAlreadyExistsException.class, () -> db.save(userGoogleMfaCredentials, zoneId)); - } - - @Test - void updateUserGoogleMfaCredentials_noUser() { - assertEquals(0, jdbcTemplate.queryForList("SELECT * FROM user_google_mfa_credentials").size()); - UserGoogleMfaCredentials userGoogleMfaCredentials = new UserGoogleMfaCredentials("jabbahut", - "very_sercret_key", - 74718234, - Arrays.asList(1, 22)); - assertThrows(UserMfaConfigDoesNotExistException.class, () -> db.update(userGoogleMfaCredentials, zoneId)); - } - - @Test - void updateUserGoogleMfaCredentials() throws EncryptionServiceException { - assertEquals(0, jdbcTemplate.queryForList("SELECT * FROM user_google_mfa_credentials").size()); - UserGoogleMfaCredentials userGoogleMfaCredentials = new UserGoogleMfaCredentials("jabbahut", - "very_sercret_key", - 74718234, - Arrays.asList(1, 22)); - userGoogleMfaCredentials.setMfaProviderId(MFA_ID); - - db.save(userGoogleMfaCredentials, zoneId); - userGoogleMfaCredentials.setSecretKey("new_secret_key"); - userGoogleMfaCredentials.setValidationCode(84718234); - userGoogleMfaCredentials.setScratchCodes(Arrays.asList(2, 22)); - db.update(userGoogleMfaCredentials, zoneId); - - List> credentials = jdbcTemplate.queryForList("SELECT * FROM user_google_mfa_credentials"); - assertEquals(1, credentials.size()); - Map record = credentials.get(0); - - assertEquals("new_secret_key", new String( - activeEncryptionKey.decrypt(Base64Utils.decodeFromString((String) record.get("secret_key")))) - ); - assertEquals(84718234, Integer.parseInt(new String( - activeEncryptionKey.decrypt(Base64Utils.decodeFromString(String.valueOf(record.get("encrypted_validation_code")))))) - ); - assertEquals("2,22", new String( - activeEncryptionKey.decrypt(Base64Utils.decodeFromString((String) record.get("scratch_codes")))) - ); - assertEquals(activeKeyLabel, record.get("encryption_key_label")); - } - - @Test - void retrieveExisting() { - db.save(new UserGoogleMfaCredentials("user1", "secret", 12345, Collections.singletonList(123)).setMfaProviderId(MFA_ID), zoneId); - UserGoogleMfaCredentials creds = db.retrieve("user1", MFA_ID); - assertEquals("user1", creds.getUserId()); - assertEquals("secret", creds.getSecretKey()); - assertEquals(12345, creds.getValidationCode()); - assertEquals(Collections.singletonList(123), creds.getScratchCodes()); - assertEquals(MFA_ID, creds.getMfaProviderId()); - assertEquals(zoneId, creds.getZoneId()); - } - - @Test - void retrieveExistingWithANonActiveEncryptionKey() { - encryptionKeyService = new EncryptionKeyService(inactiveKeyLabel, Lists.newArrayList(activeEncryptionKey, inActiveEncryptionKey)); - db = new JdbcUserGoogleMfaCredentialsProvisioning(jdbcTemplate, - encryptionKeyService); - db.save(new UserGoogleMfaCredentials("user1", "secret", 12345, Collections.singletonList(123)).setMfaProviderId(MFA_ID), zoneId); - - - encryptionKeyService = new EncryptionKeyService(activeKeyLabel, Lists.newArrayList(activeEncryptionKey, inActiveEncryptionKey)); - db = new JdbcUserGoogleMfaCredentialsProvisioning(jdbcTemplate, encryptionKeyService); - - UserGoogleMfaCredentials creds = db.retrieve("user1", MFA_ID); - assertThat(creds.getUserId(), is("user1")); - assertThat(creds.getSecretKey(), is("secret")); - assertThat(creds.getValidationCode(), is(12345)); - assertThat(creds.getScratchCodes(), containsInAnyOrder(123)); - } - - @Test - void retrieveExistingDifferentMfaProvider() { - db.save(new UserGoogleMfaCredentials("user1", "secret", 12345, Collections.singletonList(123)).setMfaProviderId(MFA_ID), zoneId); - assertThrows(UserMfaConfigDoesNotExistException.class, () -> db.retrieve("user1", "otherMfa")); - } - - @Test - void retrieveNotExisting() { - assertThrows(UserMfaConfigDoesNotExistException.class, () -> db.retrieve("user1", MFA_ID)); - } - - @Test - void delete() { - assertEquals(0, jdbcTemplate.queryForList("SELECT * FROM user_google_mfa_credentials").size()); - db.save(new UserGoogleMfaCredentials("user1", "secret", 12345, Collections.singletonList(123)).setMfaProviderId(MFA_ID), zoneId); - assertEquals(1, jdbcTemplate.queryForList("SELECT * FROM user_google_mfa_credentials").size()); - - db.delete("user1"); - assertEquals(0, jdbcTemplate.queryForList("SELECT * FROM user_google_mfa_credentials").size()); - } - - @Test - void deleteByProvider() { - assertEquals(0, jdbcTemplate.queryForList("SELECT * FROM user_google_mfa_credentials").size()); - db.save(new UserGoogleMfaCredentials("user1", "secret", 12345, Collections.singletonList(123)).setMfaProviderId(MFA_ID), zoneId); - assertEquals(1, jdbcTemplate.queryForList("SELECT * FROM user_google_mfa_credentials").size()); - - db.deleteByMfaProvider(MFA_ID, zoneId); - assertEquals(0, jdbcTemplate.queryForList("SELECT * FROM user_google_mfa_credentials").size()); - } - - @Test - void deleteByZone() { - assertEquals(0, jdbcTemplate.queryForList("SELECT * FROM user_google_mfa_credentials").size()); - db.save(new UserGoogleMfaCredentials("user1", "secret", 12345, Collections.singletonList(123)).setMfaProviderId(MFA_ID), zoneId); - assertEquals(1, jdbcTemplate.queryForList("SELECT * FROM user_google_mfa_credentials").size()); - - db.deleteByIdentityZone(zoneId); - assertEquals(0, jdbcTemplate.queryForList("SELECT * FROM user_google_mfa_credentials").size()); - - } - - @Test - void whenSaving_AndFailsToEncrypt_ShouldThrowAMeaningfulException() throws EncryptionServiceException { - EncryptionKeyService mockEncryptionKeyService = mock(EncryptionKeyService.class, Answers.RETURNS_DEEP_STUBS); - when(mockEncryptionKeyService.getActiveKey().encrypt(any())) - .thenThrow(new EncryptionServiceException(new RuntimeException("message should match"))); - db = new JdbcUserGoogleMfaCredentialsProvisioning(jdbcTemplate, mockEncryptionKeyService); - - final UserGoogleMfaCredentials userGoogleMfaCredentials = new UserGoogleMfaCredentials("user1", "secret", 12345, Collections.singletonList(123)); - - assertThrowsWithMessageThat( - UnableToPersistMfaException.class, - () -> db.save(userGoogleMfaCredentials.setMfaProviderId(MFA_ID), zoneId), - containsString("message should match") - ); - } - - @Test - void whenUpdating_AndFailsToEncrypt_ShouldThrowAMeaningfulException() throws EncryptionServiceException { - UserGoogleMfaCredentials userGoogleMfaCredentials = new UserGoogleMfaCredentials( - "user1", - "secret", - 12345, - Collections.singletonList(123) - ).setMfaProviderId(MFA_ID); - - db.save(userGoogleMfaCredentials, zoneId); - - EncryptionKeyService mockEncryptionKeyService = mock(EncryptionKeyService.class, Answers.RETURNS_DEEP_STUBS); - when(mockEncryptionKeyService.getActiveKey().encrypt(any())) - .thenThrow(new EncryptionServiceException(new RuntimeException("message should match"))); - db = new JdbcUserGoogleMfaCredentialsProvisioning(jdbcTemplate, mockEncryptionKeyService); - - userGoogleMfaCredentials.setSecretKey("new_secret"); - - assertThrowsWithMessageThat( - UnableToPersistMfaException.class, - () -> db.update(userGoogleMfaCredentials, zoneId), - containsString("message should match") - ); - } - - @Test - void whenReading_AndFailsToDecrypt_ShouldThrowAMeaningfulException() throws EncryptionServiceException { - UserGoogleMfaCredentials userGoogleMfaCredentials = new UserGoogleMfaCredentials( - "user1", - "secret", - 12345, - Collections.singletonList(123) - ).setMfaProviderId(MFA_ID); - db.save(userGoogleMfaCredentials, zoneId); - - EncryptionKeyService mockEncryptionKeyService = mock(EncryptionKeyService.class, Answers.RETURNS_DEEP_STUBS); - when(mockEncryptionKeyService.getKey(any()).orElseGet(any()).decrypt(any())) - .thenThrow(new EncryptionServiceException(new RuntimeException("message should match"))); - db = new JdbcUserGoogleMfaCredentialsProvisioning(jdbcTemplate, mockEncryptionKeyService); - - assertThrowsWithMessageThat( - UnableToRetrieveMfaException.class, - () -> db.retrieve(userGoogleMfaCredentials.getUserId(), MFA_ID), - containsString("message should match") - ); - } - - @Test - void whenDecryptingWithNonExistentKey_ShouldThrowMeaningfulException() { - encryptionKeyService = new EncryptionKeyService(inactiveKeyLabel, Lists.newArrayList(activeEncryptionKey, inActiveEncryptionKey)); - db = new JdbcUserGoogleMfaCredentialsProvisioning(jdbcTemplate, - encryptionKeyService); - db.save(new UserGoogleMfaCredentials("user1", "secret", 12345, Collections.singletonList(123)).setMfaProviderId(MFA_ID), zoneId); - - - encryptionKeyService = new EncryptionKeyService(activeKeyLabel, Lists.newArrayList(activeEncryptionKey)); - db = new JdbcUserGoogleMfaCredentialsProvisioning(jdbcTemplate, encryptionKeyService); - - assertThrowsWithMessageThat( - UnableToRetrieveMfaException.class, - () -> db.retrieve("user1", MFA_ID), - containsString("Attempted to retrieve record with an unknown decryption key") - ); - } - - @Test - void whenDecryptingRecordWithNoKeyLabel_ShouldNotAttemptToDecrypt() { - String userId = "user1"; - int numInsertedRecords = jdbcTemplate.update("INSERT INTO user_google_mfa_credentials (user_id, secret_key, validation_code, scratch_codes, mfa_provider_id, zone_id, encryption_key_label) VALUES (?,?,?,?,?,?,?)", - userId, "secret_key", 123456, "123", MFA_ID, zoneId, null); - assertThat(numInsertedRecords, is(1)); - - UserGoogleMfaCredentials user = db.retrieve(userId, MFA_ID); - assertThat(user, is(notNullValue())); - assertThat(user.getUserId(), is(userId)); - assertThat(user.getSecretKey(), is("secret_key")); - assertThat(user.getValidationCode(), is(123456)); - assertThat(user.getScratchCodes(), containsInAnyOrder(123)); - } - - @Test - void whenDecryptingRecordWithEmptyKeyLabel_ShouldNotAttemptToDecrypt() { - String userId = "user1"; - int numInsertedRecords = jdbcTemplate.update("INSERT INTO user_google_mfa_credentials (user_id, secret_key, validation_code, scratch_codes, mfa_provider_id, zone_id, encryption_key_label) VALUES (?,?,?,?,?,?,?)", - userId, "secret_key", 123456, "123", MFA_ID, zoneId, ""); - assertThat(numInsertedRecords, is(1)); - - UserGoogleMfaCredentials user = db.retrieve(userId, MFA_ID); - assertThat(user, is(notNullValue())); - assertThat(user.getUserId(), is(userId)); - assertThat(user.getSecretKey(), is("secret_key")); - assertThat(user.getValidationCode(), is(123456)); - assertThat(user.getScratchCodes(), containsInAnyOrder(123)); - } - -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaCheckerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaCheckerTests.java deleted file mode 100644 index 7932167b169..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaCheckerTests.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import com.google.common.collect.Lists; -import org.cloudfoundry.identity.uaa.login.util.RandomValueStringGenerator; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; -import org.cloudfoundry.identity.uaa.zone.MfaConfig; -import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.junit.jupiter.params.provider.ArgumentsSource; - -import java.util.Arrays; -import java.util.stream.Stream; - -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.*; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.*; - -class MfaCheckerTests { - - private IdentityZone identityZone; - private MfaChecker mfaChecker; - private IdentityZoneProvisioning mockIdentityZoneProvisioning; - private RandomValueStringGenerator randomValueStringGenerator; - - @BeforeEach - void setUp() { - randomValueStringGenerator = new RandomValueStringGenerator(); - - identityZone = MultitenancyFixture.identityZone(randomValueStringGenerator.generate(), randomValueStringGenerator.generate()); - - mockIdentityZoneProvisioning = mock(IdentityZoneProvisioning.class); - when(mockIdentityZoneProvisioning.retrieve(any())).thenReturn(identityZone); - - mfaChecker = new MfaChecker(mockIdentityZoneProvisioning); - } - - static class BooleanArgumentsProvider implements ArgumentsProvider { - - @Override - public Stream provideArguments(ExtensionContext context) { - return Stream.of( - Arguments.of(true), - Arguments.of(false) - ); - } - } - - @ParameterizedTest - @ArgumentsSource(BooleanArgumentsProvider.class) - void isMfaEnabled(final boolean isMfaEnabled) { - identityZone.getConfig().getMfaConfig().setEnabled(isMfaEnabled); - assertEquals(isMfaEnabled, mfaChecker.isMfaEnabled(identityZone)); - } - - @ParameterizedTest - @ArgumentsSource(BooleanArgumentsProvider.class) - void isMfaEnabledForZoneId(final boolean isMfaEnabled) { - final String zoneId = randomValueStringGenerator.generate(); - identityZone.getConfig().getMfaConfig().setEnabled(isMfaEnabled); - assertEquals(isMfaEnabled, mfaChecker.isMfaEnabledForZoneId(zoneId)); - - verify(mockIdentityZoneProvisioning).retrieve(zoneId); - } - - @Test - void mfaIsRequiredWhenCorrectOriginsAreConfigured() { - final String randomIdp = randomValueStringGenerator.generate(); - identityZone.getConfig().getMfaConfig().setIdentityProviders( - Lists.newArrayList("uaa", "george", randomIdp)); - - assertThat(mfaChecker.isRequired(identityZone, "uaa"), is(true)); - assertThat(mfaChecker.isRequired(identityZone, "george"), is(true)); - assertThat(mfaChecker.isRequired(identityZone, randomIdp), is(true)); - - assertThat(mfaChecker.isRequired(identityZone, "other"), is(false)); - assertThat(mfaChecker.isRequired(identityZone, null), is(false)); - assertThat(mfaChecker.isRequired(identityZone, ""), is(false)); - assertThat(mfaChecker.isRequired(identityZone, randomValueStringGenerator.generate()), is(false)); - } - - @Test - void mfaConfig_getIdentityProviders_returnsUaaAndLdap() { - assertThat(MfaConfig.DEFAULT_MFA_IDENTITY_PROVIDERS, is(Arrays.asList(UAA, LDAP))); - - identityZone.getConfig().getMfaConfig().setIdentityProviders( - Lists.newArrayList()); - - assertThat(mfaChecker.isRequired(identityZone, UAA), is(true)); - assertThat(mfaChecker.isRequired(identityZone, LDAP), is(true)); - assertThat(mfaChecker.isRequired(identityZone, SAML), is(false)); - assertThat(mfaChecker.isRequired(identityZone, "other"), is(false)); - } -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderBootstrapTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderBootstrapTest.java deleted file mode 100644 index c550f4cff23..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderBootstrapTest.java +++ /dev/null @@ -1,134 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import org.cloudfoundry.identity.uaa.annotations.WithDatabaseContext; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.YamlMapFactoryBean; -import org.springframework.beans.factory.config.YamlProcessor; -import org.springframework.core.io.ByteArrayResource; -import org.springframework.core.io.Resource; -import org.springframework.jdbc.core.JdbcTemplate; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -@WithDatabaseContext -class MfaProviderBootstrapTest { - - private MfaProviderBootstrap bootstrap; - private MfaProviderProvisioning provisioning; - private Map> sampleData; - - private String sampleMfaYaml = "mfa-providers:\n" + - " provider-name1:\n" + - " type : google-authenticator\n" + - " config :\n" + - " providerDescription : mfa provider description\n" + - " digits: 6\n" + - " duration : 30\n" + - " algorithm : \"SHA256\"\n" + - " issuer: \"Issuer\"\n" + - " provider-name2:\n" + - " type : google-authenticator\n" + - " config :\n" + - " providerDescription : mfa provider description\n" + - " digits: 6\n" + - " duration : 30\n" + - " algorithm : \"SHA256\"\n" + - " issuer: \"Issuer\"\n"; - - private List> expectedGoogleProviders; - private MfaProvider unbootstrappedProvider; - - @BeforeEach - void setUp(@Autowired JdbcTemplate jdbcTemplate) { - provisioning = spy(new JdbcMfaProviderProvisioning(jdbcTemplate, mfaProvider -> { - })); - bootstrap = new MfaProviderBootstrap(provisioning); - sampleData = parseMfaYaml(sampleMfaYaml); - expectedGoogleProviders = new ArrayList<>(); - - GoogleMfaProviderConfig googleMfaProviderConfig = new GoogleMfaProviderConfig(); - googleMfaProviderConfig.setIssuer("Issuer"); - googleMfaProviderConfig.setProviderDescription("mfa provider description"); - - MfaProvider providerOne = new MfaProvider<>(); - providerOne.setName("provider-name1"); - providerOne.setType(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR); - providerOne.setIdentityZoneId("uaa"); - providerOne.setConfig(googleMfaProviderConfig); - - MfaProvider providerTwo = new MfaProvider<>(); - providerTwo.setName("provider-name2"); - providerTwo.setType(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR); - providerTwo.setIdentityZoneId("uaa"); - providerTwo.setConfig(googleMfaProviderConfig); - - unbootstrappedProvider = new MfaProvider<>(); - unbootstrappedProvider.setId("mfa-id"); - unbootstrappedProvider.setName("provider-name-not-bootstrapped"); - unbootstrappedProvider.setType(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR); - unbootstrappedProvider.setIdentityZoneId("uaa"); - unbootstrappedProvider.setConfig(googleMfaProviderConfig); - - expectedGoogleProviders.add(providerOne); - expectedGoogleProviders.add(providerTwo); - - jdbcTemplate.execute("delete from mfa_providers"); - } - - @Test - void parseMfaProviders() { - bootstrap.setMfaProviders(sampleData); - assertThat(bootstrap.getMfaProviders(), containsInAnyOrder(expectedGoogleProviders.toArray())); - } - - @Test - void afterPropertiesSet() { - bootstrap.setMfaProviders(sampleData); - bootstrap.afterPropertiesSet(); - verify(provisioning).create(expectedGoogleProviders.get(0), "uaa"); - verify(provisioning).create(expectedGoogleProviders.get(1), "uaa"); - } - - @Test - void bootstrapWithSomeExistingProviders() { - provisioning.create(expectedGoogleProviders.get(0), "uaa"); - reset(provisioning); - bootstrap.setMfaProviders(parseMfaYaml(sampleMfaYaml.replace("mfa provider description", "new description"))); - bootstrap.afterPropertiesSet(); - ArgumentCaptor captor = ArgumentCaptor.forClass(MfaProvider.class); - verify(provisioning).update(captor.capture(), eq("uaa")); - verify(provisioning).create(eq(expectedGoogleProviders.get(1)), eq("uaa")); - assertEquals("new description", ((GoogleMfaProviderConfig) captor.getValue().getConfig()).getProviderDescription()); - assertEquals("new description", ((GoogleMfaProviderConfig) provisioning.retrieveByName(expectedGoogleProviders.get(0).getName(), "uaa").getConfig()).getProviderDescription()); - } - - Map> parseMfaYaml(String sampleYaml) { - YamlMapFactoryBean factory = new YamlMapFactoryBean(); - factory.setResolutionMethod(YamlProcessor.ResolutionMethod.OVERRIDE_AND_IGNORE); - List resources = new ArrayList<>(); - ByteArrayResource resource = new ByteArrayResource(sampleYaml.getBytes()); - resources.add(resource); - factory.setResources(resources.toArray(new Resource[0])); - Map tmpdata = factory.getObject(); - Map> dataList = new HashMap<>(); - for (Map.Entry> entry : ((Map>) tmpdata.get("mfa-providers")).entrySet()) { - dataList.put(entry.getKey(), entry.getValue()); - } - return dataList; - } - -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderEndpointsTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderEndpointsTest.java deleted file mode 100644 index 509a97f87e0..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaProviderEndpointsTest.java +++ /dev/null @@ -1,131 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import org.cloudfoundry.identity.uaa.login.util.RandomValueStringGenerator; -import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent; -import org.cloudfoundry.identity.uaa.mfa.exception.MfaAlreadyExistsException; -import org.cloudfoundry.identity.uaa.mfa.exception.MfaProviderUpdateIsNotAllowed; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.http.ResponseEntity; - -import java.util.Collections; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - - -public class MfaProviderEndpointsTest { - - MfaProviderEndpoints endpoint = new MfaProviderEndpoints(); - MfaProviderProvisioning provisioning; - MfaProviderValidator validator; - - @Rule - public ExpectedException expection = ExpectedException.none(); - - @Before - public void setup() { - - provisioning = mock(JdbcMfaProviderProvisioning.class); - validator = mock(GeneralMfaProviderValidator.class); - endpoint.setMfaProviderProvisioning(provisioning); - endpoint.setMfaProviderValidator(validator); - IdentityZoneHolder.clear(); - } - - @Test - public void testCreateDefaultIssuer() { - MfaProvider mfaProvider = constructGoogleProvider(); - Mockito.when(provisioning.create(Mockito.any(), Mockito.anyString())).thenReturn(mfaProvider); - - - ResponseEntity mfaProviderResponseEntity = endpoint.createMfaProvider(mfaProvider); - assertEquals(IdentityZoneHolder.get().getName(), mfaProviderResponseEntity.getBody().getConfig().getIssuer()); - } - - @Test(expected = MfaProviderUpdateIsNotAllowed.class) - public void testUpdateProvider() throws MfaProviderUpdateIsNotAllowed { - endpoint.updateMfaProvider(); - } - - @Test - public void testGetMfaProviders() { - MfaProvider mockProviderResponse = constructGoogleProvider(); - when(provisioning.retrieveAll(anyString())).thenReturn(Collections.singletonList(mockProviderResponse)); - - ResponseEntity> mfaGetResponse = endpoint.retrieveMfaProviders(); - - assertEquals(mfaGetResponse.getBody().get(0), mockProviderResponse); - verify(provisioning, times(1)).retrieveAll(IdentityZoneHolder.get().getId()); - assertTrue("got response", mfaGetResponse.getStatusCode().is2xxSuccessful()); - - } - - @Test - public void testGetMfaProviderById() { - MfaProvider mockProviderResponse = constructGoogleProvider(); - String providerId = "1234"; - when(provisioning.retrieve(eq(providerId), anyString())).thenReturn(mockProviderResponse); - - ResponseEntity mfaGetResponse = endpoint.retrieveMfaProviderById(providerId); - - assertEquals(mockProviderResponse, mfaGetResponse.getBody()); - verify(provisioning, times(1)).retrieve(providerId, IdentityZoneHolder.get().getId()); - assertTrue("got response", mfaGetResponse.getStatusCode().is2xxSuccessful()); - - } - - @Test - public void testDeleteMFaProvider() { - ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class); - endpoint.setApplicationEventPublisher(publisher); - MfaProvider providerToDelete = constructGoogleProvider(); - String id = new RandomValueStringGenerator(5).generate(); - when(provisioning.retrieve(eq(id), anyString())).thenReturn(providerToDelete); - - ResponseEntity mfaDeleteResponse = endpoint.deleteMfaProviderById(id); - assertEquals(providerToDelete, mfaDeleteResponse.getBody()); - ArgumentCaptor entityDeletedCaptor = ArgumentCaptor.forClass(EntityDeletedEvent.class); - verify(provisioning, times(1)).retrieve(id, IdentityZoneHolder.get().getId()); - verify(publisher, times(1)).publishEvent(entityDeletedCaptor.capture()); - assertEquals(providerToDelete.getId(), ((MfaProvider)(entityDeletedCaptor.getAllValues().get(0)).getDeleted()).getId()); - } - - @Test - public void testDeleteActiveProviderThrowsException() { - MfaProvider providerToDelete = constructGoogleProvider(); - String id = new RandomValueStringGenerator(5).generate(); - when(provisioning.retrieve(eq(id), anyString())).thenReturn(providerToDelete); - IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(providerToDelete.getName()); - - expection.expect(MfaAlreadyExistsException.class); - expection.expectMessage("MFA provider is currently active on zone: " + IdentityZoneHolder.get().getId() + ". Please deactivate it from the zone or set another MFA provider"); - endpoint.deleteMfaProviderById(id); - - IdentityZoneHolder.get().getConfig().getMfaConfig().setProviderName(null); - } - - private MfaProvider constructGoogleProvider() { - return new MfaProvider() - .setName(new RandomValueStringGenerator(5).generate()) - .setType(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR) - .setConfig(constructGoogleProviderConfiguration()); - } - - private GoogleMfaProviderConfig constructGoogleProviderConfiguration() { - return new GoogleMfaProviderConfig(); - } -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaRegisterQRGeneratorTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaRegisterQRGeneratorTest.java deleted file mode 100644 index b5f73fcfac6..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaRegisterQRGeneratorTest.java +++ /dev/null @@ -1,109 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import com.google.zxing.BinaryBitmap; -import com.google.zxing.ChecksumException; -import com.google.zxing.DecodeHintType; -import com.google.zxing.FormatException; -import com.google.zxing.NotFoundException; -import com.google.zxing.client.j2se.BufferedImageLuminanceSource; -import com.google.zxing.common.HybridBinarizer; -import com.google.zxing.qrcode.QRCodeReader; -import com.warrenstrange.googleauth.GoogleAuthenticator; -import com.warrenstrange.googleauth.GoogleAuthenticatorKey; -import org.junit.Test; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.Arrays; -import java.util.Base64; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class MfaRegisterQRGeneratorTest { - - @Test - public void testQRCode() throws Exception { - GoogleAuthenticator authenticator = new GoogleAuthenticator(); - GoogleAuthenticatorKey key = authenticator.createCredentials(); - String encodedQrCode = MfaRegisterQRGenerator.getQRCode("testIssuer", "accountName", key.getKey()); - String result = decodeQrPng(encodedQrCode); - - String[] split = result.split("\\?"); - assertEquals("otpauth://totp/testIssuer:accountName", split[0]); - List list = Arrays.asList(split[1].split("&")); - assertTrue(list.contains("issuer=testIssuer")); - assertTrue(list.contains("secret=" + key.getKey())); - assertEquals(2, list.size()); - } - - @Test - public void testQrWithSpecialChars() throws Exception { - GoogleAuthenticator authenticator = new GoogleAuthenticator(); - GoogleAuthenticatorKey key = authenticator.createCredentials(); - String encodedQrCode = MfaRegisterQRGenerator.getQRCode("test=Issuer","account?&#Name", key.getKey()); - String result = decodeQrPng(encodedQrCode); - - String[] split = result.split("\\?"); - assertEquals("otpauth://totp/test%3DIssuer:account%3F%26%23Name", split[0]); - List list = Arrays.asList(split[1].split("&")); - assertTrue(list.contains("issuer=test%3DIssuer")); - assertTrue(list.contains("secret=" + key.getKey())); - assertEquals(2, list.size()); - } - - @Test(expected = IllegalArgumentException.class) - public void testQrWithColonIssuer() throws Exception { - GoogleAuthenticator authenticator = new GoogleAuthenticator(); - GoogleAuthenticatorKey key = authenticator.createCredentials(); - String encodedQrCode = MfaRegisterQRGenerator.getQRCode("test:Issuer", "accountName", key.getKey()); - } - - @Test(expected = IllegalArgumentException.class) - public void testQrWithColonAccountName() throws Exception { - GoogleAuthenticator authenticator = new GoogleAuthenticator(); - GoogleAuthenticatorKey key = authenticator.createCredentials(); - String encodedQrCode = MfaRegisterQRGenerator.getQRCode("testIssuer", "accountName:", key.getKey()); - } - - @Test(expected = IllegalArgumentException.class) - public void testEmptyIssuer() throws Exception { - GoogleAuthenticator authenticator = new GoogleAuthenticator(); - GoogleAuthenticatorKey key = authenticator.createCredentials(); - String encodedQrCode = MfaRegisterQRGenerator.getQRCode("", "accountName", key.getKey()); - } - - @Test - public void testPngUrl() throws Exception { - GoogleAuthenticator authenticator = new GoogleAuthenticator(); - GoogleAuthenticatorKey key = authenticator.createCredentials(); - String encodedQrCode = MfaRegisterQRGenerator.getQRCodePngDataUri("testIssuer", "accountName", key.getKey()); - assertTrue(encodedQrCode.startsWith("data:image/png;base64,")); - String rawSplit = encodedQrCode.split(",")[1]; - String[] split = decodeQrPng(rawSplit).split("\\?"); - - assertEquals("otpauth://totp/testIssuer:accountName", split[0]); - List list = Arrays.asList(split[1].split("&")); - assertTrue("url did not contain issuer", list.contains("issuer=testIssuer")); - assertTrue("url did not contain secret", list.contains("secret=" + key.getKey())); - assertEquals(2, list.size()); - } - - private String decodeQrPng(String encodedQrCode) throws IOException, NotFoundException, ChecksumException, FormatException { - byte[] decodedByte = Base64.getDecoder().decode(encodedQrCode); - - BufferedImage image = ImageIO.read(new ByteArrayInputStream(decodedByte)); - BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(image); - BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); - QRCodeReader reader = new QRCodeReader(); - Map hintMap = new HashMap<>(); - hintMap.put(DecodeHintType.PURE_BARCODE, true); - return reader.decode(bitmap, hintMap).getText(); - } - -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaRequiredFilterTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaRequiredFilterTests.java deleted file mode 100644 index 988b7ee415e..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaRequiredFilterTests.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2018] Pivotal Software, Inc. All Rights Reserved. - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.mfa; - -import javax.servlet.FilterChain; -import java.util.Arrays; -import java.util.HashSet; - -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.AuthenticationEntryPoint; - -import static java.util.Collections.emptyList; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.same; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.when; - -public class MfaRequiredFilterTests { - private UaaAuthentication authentication; - private MfaRequiredFilter filter; - private MockHttpServletRequest request; - private MockHttpServletResponse response; - private MfaChecker mfaChecker; - private AuthenticationEntryPoint entryPoint; - private FilterChain chain; - - @Before - public void setup() { - authentication = new UaaAuthentication( - new UaaPrincipal("fake-id", "fake-username", "email@email.com", "origin", "", "uaa"), - emptyList(), - null - ); - authentication.setAuthenticationMethods(new HashSet<>()); - mfaChecker = mock(MfaChecker.class); - entryPoint = mock(AuthenticationEntryPoint.class); - chain = mock(FilterChain.class); - filter = new MfaRequiredFilter( - mfaChecker, - entryPoint - ); - when(mfaChecker.isMfaEnabled(any(IdentityZone.class))).thenReturn(true); - request = new MockHttpServletRequest(); - response = new MockHttpServletResponse(); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - - @After - public void teardown() { - SecurityContextHolder.clearContext(); - IdentityZoneHolder.clear(); - } - - @Test - public void mfa_required() throws Exception { - assertTrue(filter.isMfaRequiredAndMissing()); - filter.doFilter(request, response, chain); - Mockito.verify(chain, never()).doFilter(same(request), same(response)); - } - - @Test - public void authentication_missing() { - SecurityContextHolder.clearContext(); - assertFalse(filter.isMfaRequiredAndMissing()); - } - - @Test - public void anonymous_authentication() { - SecurityContextHolder.getContext().setAuthentication(mock(AnonymousAuthenticationToken.class)); - assertFalse(filter.isMfaRequiredAndMissing()); - } - - @Test - public void unknown_authentication() { - SecurityContextHolder.getContext().setAuthentication(mock(UsernamePasswordAuthenticationToken.class)); - assertFalse(filter.isMfaRequiredAndMissing()); - } - - @Test - public void mfa_present() throws Exception { - authentication.setAuthenticationMethods(new HashSet<>(Arrays.asList("pwd","mfa"))); - assertFalse(filter.isMfaRequiredAndMissing()); - filter.doFilter(request, response, chain); - Mockito.verify(chain, times(1)).doFilter(same(request), same(response)); - } - - @Test - public void mfa_not_enabled() { - when(mfaChecker.isMfaEnabled(any(IdentityZone.class))).thenReturn(false); - assertFalse(filter.isMfaRequiredAndMissing()); - } - - -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaUiRequiredFilterTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaUiRequiredFilterTests.java deleted file mode 100644 index 0172379763e..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/MfaUiRequiredFilterTests.java +++ /dev/null @@ -1,298 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import javax.servlet.FilterChain; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.Arrays; -import java.util.HashSet; - -import com.google.common.collect.Lists; -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.extensions.PollutionPreventionExtension; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; - -import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.savedrequest.RequestCache; -import org.springframework.security.web.savedrequest.SavedRequest; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; - -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.cloudfoundry.identity.uaa.mfa.MfaUiRequiredFilter.MfaNextStep.INVALID_AUTH; -import static org.cloudfoundry.identity.uaa.mfa.MfaUiRequiredFilter.MfaNextStep.MFA_COMPLETED; -import static org.cloudfoundry.identity.uaa.mfa.MfaUiRequiredFilter.MfaNextStep.MFA_IN_PROGRESS; -import static org.cloudfoundry.identity.uaa.mfa.MfaUiRequiredFilter.MfaNextStep.MFA_NOT_REQUIRED; -import static org.cloudfoundry.identity.uaa.mfa.MfaUiRequiredFilter.MfaNextStep.MFA_OK; -import static org.cloudfoundry.identity.uaa.mfa.MfaUiRequiredFilter.MfaNextStep.MFA_REQUIRED; -import static org.cloudfoundry.identity.uaa.mfa.MfaUiRequiredFilter.MfaNextStep.NOT_AUTHENTICATED; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.same; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - -@ExtendWith(PollutionPreventionExtension.class) -class MfaUiRequiredFilterTests { - - private RequestCache requestCache; - private MfaUiRequiredFilter spyFilter; - private MockHttpServletRequest request; - private UsernamePasswordAuthenticationToken usernameAuthentication; - private AnonymousAuthenticationToken anonymous; - private UaaAuthentication authentication; - private HttpServletResponse response; - private FilterChain chain; - private MfaUiRequiredFilter filter; - private AntPathRequestMatcher logoutMatcher; - private IdentityZone mfaEnabledZone; - - @BeforeEach - void setup() { - requestCache = mock(RequestCache.class); - logoutMatcher = new AntPathRequestMatcher("/logout.do"); - filter = new MfaUiRequiredFilter("/login/mfa/**", - "/login/mfa/register", - requestCache, - "/login/mfa/completed", - logoutMatcher, - new MfaChecker(mock(IdentityZoneProvisioning.class))); - spyFilter = spy(filter); - request = new MockHttpServletRequest(); - usernameAuthentication = new UsernamePasswordAuthenticationToken("fake-principal","fake-credentials"); - anonymous = new AnonymousAuthenticationToken("fake-key", "fake-principal", singletonList(new SimpleGrantedAuthority("test"))); - authentication = new UaaAuthentication( - new UaaPrincipal("fake-id", "fake-username", "email@email.com", "origin", "", "uaa"), - emptyList(), - null - ); - authentication.setAuthenticationMethods(new HashSet<>()); - response = mock(HttpServletResponse.class); - chain = mock(FilterChain.class); - mfaEnabledZone = new IdentityZone(); - mfaEnabledZone.getConfig().getMfaConfig().setEnabled(true); - mfaEnabledZone.getConfig().getMfaConfig().setIdentityProviders(Lists.newArrayList("origin")); - } - - @AfterEach - void teardown() { - SecurityContextHolder.clearContext(); - } - - @Test - void authentication_log_info_null() { - assertNull(spyFilter.getAuthenticationLogInfo()); - } - - @Test - void authentication_log_info_uaa() { - SecurityContextHolder.getContext().setAuthentication(authentication); - assertThat(spyFilter.getAuthenticationLogInfo(), containsString("fake-id")); - assertThat(spyFilter.getAuthenticationLogInfo(), containsString("fake-username")); - } - - @Test - void authentication_log_info_unknown() { - SecurityContextHolder.getContext().setAuthentication(usernameAuthentication); - assertThat(spyFilter.getAuthenticationLogInfo(), containsString("Unknown Auth=UsernamePasswordAuthenticationToken")); - assertThat(spyFilter.getAuthenticationLogInfo(), containsString("fake-principal")); - } - - @Test - void next_step_not_authenticated() { - assertSame(NOT_AUTHENTICATED, spyFilter.getNextStep(request)); - } - - @Test - void next_step_mfa_not_needed_when_origin_key_does_not_match_valid_identity_provider() { - SecurityContextHolder.getContext().setAuthentication(authentication); - IdentityZone zone = new IdentityZone(); - zone.getConfig().getMfaConfig().setIdentityProviders(Lists.newArrayList("uaa", "ldap")); - zone.getConfig().getMfaConfig().setEnabled(true); - IdentityZoneHolder.set(zone); - assertThat(spyFilter.getNextStep(request), is(MFA_NOT_REQUIRED)); - } - - @Test - void next_step_mfa_needed_when_origin_key_matches_valid_identity_provider() { - UaaAuthentication auth = new UaaAuthentication( - new UaaPrincipal("fake-id", "fake-username", "email@email.com", "ldap", "", "uaa"), - emptyList(), - null - ); - auth.setAuthenticationMethods(new HashSet<>()); - SecurityContextHolder.getContext().setAuthentication(auth); - IdentityZone zone = new IdentityZone(); - zone.getConfig().getMfaConfig().setIdentityProviders(Lists.newArrayList("uaa", "ldap")); - zone.getConfig().getMfaConfig().setEnabled(true); - - IdentityZoneHolder.set(zone); - assertThat(spyFilter.getNextStep(request), is(MFA_REQUIRED)); - } - - @Test - void next_step_anonymous() { - SecurityContextHolder.getContext().setAuthentication(anonymous); - assertSame(NOT_AUTHENTICATED, spyFilter.getNextStep(request)); - } - - @Test - void next_step_unknown_authentication() { - SecurityContextHolder.getContext().setAuthentication(usernameAuthentication); - assertSame(INVALID_AUTH, spyFilter.getNextStep(request)); - } - - @Test - void next_step_mfa_not_needed() { - SecurityContextHolder.getContext().setAuthentication(authentication); - assertSame(MFA_NOT_REQUIRED, spyFilter.getNextStep(request)); - } - - @Test - void next_step_mfa_required() { - request.setServletPath("/"); - request.setPathInfo("oauth/authorize"); - - IdentityZoneHolder.set(mfaEnabledZone); - - SecurityContextHolder.getContext().setAuthentication(authentication); - assertSame(MFA_REQUIRED, spyFilter.getNextStep(request)); - } - - @Test - void next_step_mfa_in_progress() { - request.setServletPath("/"); - request.setPathInfo("login/mfa/register"); - - IdentityZoneHolder.set(mfaEnabledZone); - - SecurityContextHolder.getContext().setAuthentication(authentication); - assertSame(MFA_IN_PROGRESS, spyFilter.getNextStep(request)); - } - - @Test - void next_step_mfa_in_progress_when_completed_invoked() { - request.setServletPath("/"); - request.setPathInfo("login/mfa/completed"); - - IdentityZoneHolder.set(mfaEnabledZone); - - SecurityContextHolder.getContext().setAuthentication(authentication); - assertSame(MFA_IN_PROGRESS, spyFilter.getNextStep(request)); - } - - @Test - void next_step_mfa_completed() { - request.setServletPath("/"); - request.setPathInfo("login/mfa/completed"); - - IdentityZoneHolder.set(mfaEnabledZone); - - authentication.getAuthenticationMethods().addAll(Arrays.asList("pwd", "mfa")); - SecurityContextHolder.getContext().setAuthentication(authentication); - assertSame(MFA_COMPLETED, spyFilter.getNextStep(request)); - } - - @Test - void next_step_mfa_in_play() { - request.setServletPath("/"); - request.setPathInfo("oauth/authorize"); - - IdentityZoneHolder.set(mfaEnabledZone); - - authentication.getAuthenticationMethods().addAll(Arrays.asList("pwd", "mfa")); - SecurityContextHolder.getContext().setAuthentication(authentication); - assertSame(MFA_OK, spyFilter.getNextStep(request)); - } - - @Test - void send_redirect() throws Exception { - request.setServletPath("/"); - request.setContextPath("/uaa"); - spyFilter.sendRedirect("/login/mfa/register", request, response); - verify(response, times(1)).sendRedirect("/uaa/login/mfa/register"); - } - - @Test - void do_filter_invalid_auth() throws Exception { - when(spyFilter.getNextStep(any(HttpServletRequest.class))).thenReturn(INVALID_AUTH); - spyFilter.doFilter(request, response, chain); - verify(response, times(1)).sendError(401, "Invalid authentication object for UI operations."); - } - - @Test - void do_filter_not_authenticated() throws Exception { - when(spyFilter.getNextStep(any(HttpServletRequest.class))).thenReturn(NOT_AUTHENTICATED); - spyFilter.doFilter(request, response, chain); - verify(chain, times(1)).doFilter(same(request), same(response)); - verifyNoInteractions(requestCache); - } - - @Test - void do_filter_mfa_in_progress() throws Exception { - when(spyFilter.getNextStep(any(HttpServletRequest.class))).thenReturn(MFA_IN_PROGRESS); - spyFilter.doFilter(request, response, chain); - verify(chain, times(1)).doFilter(same(request), same(response)); - verifyNoInteractions(requestCache); - } - - @Test - void do_filter_mfa_ok() throws Exception { - when(spyFilter.getNextStep(any(HttpServletRequest.class))).thenReturn(MFA_OK); - spyFilter.doFilter(request, response, chain); - verify(chain, times(1)).doFilter(same(request), same(response)); - verifyNoInteractions(requestCache); - } - - @Test - void do_filter_mfa_completed_no_saved_request() throws Exception { - request.setContextPath("/uaa"); - when(spyFilter.getNextStep(any(HttpServletRequest.class))).thenReturn(MFA_COMPLETED); - spyFilter.doFilter(request, response, chain); - verify(requestCache, times(1)).getRequest(same(request), same(response)); - verify(spyFilter, times(1)).sendRedirect(eq("/"), same(request), same(response)); - } - - @Test - void do_filter_mfa_completed_with_saved_request() throws Exception { - SavedRequest savedRequest = mock(SavedRequest.class); - String redirect = "http://localhost:8080/uaa/oauth/authorize"; - when(savedRequest.getRedirectUrl()).thenReturn(redirect); - when(requestCache.getRequest(same(request), same(response))).thenReturn(savedRequest); - request.setContextPath("/uaa"); - when(spyFilter.getNextStep(any(HttpServletRequest.class))).thenReturn(MFA_COMPLETED); - spyFilter.doFilter(request, response, chain); - verify(requestCache, times(1)).getRequest(same(request), same(response)); - verify(spyFilter, times(1)).sendRedirect(eq(redirect), same(request), same(response)); - - } - - @Test - void do_filter_mfa_required() throws Exception { - request.setContextPath("/uaa"); - when(spyFilter.getNextStep(any(HttpServletRequest.class))).thenReturn(MFA_REQUIRED); - spyFilter.doFilter(request, response, chain); - verify(requestCache, times(1)).saveRequest(same(request), same(response)); - verify(spyFilter, times(1)).sendRedirect(eq("/login/mfa/register"), same(request), same(response)); - } - -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/StatelessMfaAuthenticationFilterTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/StatelessMfaAuthenticationFilterTests.java deleted file mode 100644 index dc25b5f59bf..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/StatelessMfaAuthenticationFilterTests.java +++ /dev/null @@ -1,399 +0,0 @@ -/* - * **************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2018] Pivotal Software, Inc. All Rights Reserved. - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * **************************************************************************** - */ - -package org.cloudfoundry.identity.uaa.mfa; - -import javax.servlet.FilterChain; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import com.google.common.collect.Lists; -import org.cloudfoundry.identity.uaa.audit.AuditEvent; -import org.cloudfoundry.identity.uaa.audit.AuditEventType; -import org.cloudfoundry.identity.uaa.audit.JdbcAuditService; -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.authentication.event.MfaAuthenticationFailureEvent; -import org.cloudfoundry.identity.uaa.authentication.event.MfaAuthenticationSuccessEvent; -import org.cloudfoundry.identity.uaa.authentication.manager.CommonLoginPolicy; -import org.cloudfoundry.identity.uaa.authentication.manager.LockoutPolicyRetriever; -import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.mfa.exception.InvalidMfaCodeException; -import org.cloudfoundry.identity.uaa.mfa.exception.MissingMfaCodeException; -import org.cloudfoundry.identity.uaa.mfa.exception.UserMfaConfigDoesNotExistException; -import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; -import org.cloudfoundry.identity.uaa.user.UaaUser; -import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; -import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; -import org.cloudfoundry.identity.uaa.util.TimeService; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; - -import com.jayway.jsonassert.JsonAssert; -import org.joda.time.DateTimeUtils; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.InsufficientAuthenticationException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.OAuth2Request; - -import static org.cloudfoundry.identity.uaa.mfa.MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.mockito.AdditionalMatchers.not; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.same; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; -import static org.springframework.security.oauth2.common.util.OAuth2Utils.GRANT_TYPE; - -public class StatelessMfaAuthenticationFilterTests { - - public static final String MFA_CODE = "mfaCode"; - - @Rule - public ExpectedException exception = ExpectedException.none(); - - private UserGoogleMfaCredentialsProvisioning googleAuthenticator; - private Set grantTypes; - private StatelessMfaAuthenticationFilter filter; - private FilterChain chain; - private MockHttpServletRequest request; - private MockHttpServletResponse response; - private OAuth2Authentication authentication; - private OAuth2Request storedOAuth2Request; - private UaaAuthentication uaaAuthentication; - private MfaProviderProvisioning mfaProvider; - private IdentityZone zone; - private UaaUserDatabase userDatabase; - private UaaUser user; - private ApplicationEventPublisher publisher; - private JdbcAuditService jdbcAuditServiceMock; - private LockoutPolicyRetriever lockoutPolicyRetriever; - private TimeService timeService; - private CommonLoginPolicy commonLoginPolicy; - - @After - public void teardown() { - IdentityZoneHolder.clear(); - } - - @Before - public void setup() { - zone = MultitenancyFixture.identityZone("id", "id"); - zone.getConfig().getMfaConfig().setEnabled(true).setProviderName("mfa-provider-name"); - IdentityZoneHolder.set(zone); - - storedOAuth2Request = mock(OAuth2Request.class); - UaaPrincipal uaaPrincipal = new UaaPrincipal("1", "marissa", "marissa@test.org", OriginKeys.UAA, null, zone.getId()); - uaaAuthentication = new UaaAuthentication(uaaPrincipal, Collections.emptyList(), mock(UaaAuthenticationDetails.class)); - uaaAuthentication.setAuthenticationMethods(new HashSet<>(Collections.singletonList("pwd"))); - authentication = new OAuth2Authentication(storedOAuth2Request, uaaAuthentication); - SecurityContextHolder.getContext().setAuthentication(authentication); - - googleAuthenticator = mock(UserGoogleMfaCredentialsProvisioning.class); - when(googleAuthenticator.activeUserCredentialExists(anyString(), anyString())).thenReturn(true); - when(googleAuthenticator.isValidCode(any(), eq(123456))).thenReturn(true); - when(googleAuthenticator.isValidCode(any(), not(eq(123456)))).thenReturn(false); - when(googleAuthenticator.getUserGoogleMfaCredentials(anyString(), anyString())).thenReturn(mock(UserGoogleMfaCredentials.class)); - grantTypes = new HashSet<>(Collections.singletonList("password")); - - mfaProvider = mock(MfaProviderProvisioning.class); - when(mfaProvider.retrieveByName(anyString(), anyString())).thenReturn( - new MfaProvider().setName("mfa-provider-name").setId("mfa-provider-id").setType(GOOGLE_AUTHENTICATOR) - ); - - userDatabase = mock(UaaUserDatabase.class); - user = new UaaUser( - new UaaUserPrototype() - .withUsername(uaaPrincipal.getName()) - .withEmail(uaaPrincipal.getEmail()) - .withId(uaaPrincipal.getId()) - ); - when(userDatabase.retrieveUserById(anyString())).thenReturn(user); - - publisher = mock(ApplicationEventPublisher.class); - jdbcAuditServiceMock = mock(JdbcAuditService.class); - - lockoutPolicyRetriever = mock(LockoutPolicyRetriever.class); - LockoutPolicy lockoutPolicy = new LockoutPolicy(0, 5, 60); - when(lockoutPolicyRetriever.getLockoutPolicy()).thenReturn(lockoutPolicy); - - timeService = mock(TimeService.class); - when(timeService.getCurrentTimeMillis()).thenReturn(1l); - - boolean mfaLockoutPolicyEnabled = true; - commonLoginPolicy = new CommonLoginPolicy(jdbcAuditServiceMock, lockoutPolicyRetriever, AuditEventType.MfaAuthenticationSuccess, AuditEventType.MfaAuthenticationFailure, timeService, mfaLockoutPolicyEnabled); - - filter = new StatelessMfaAuthenticationFilter(googleAuthenticator, grantTypes, mfaProvider, userDatabase, commonLoginPolicy); - filter.setApplicationEventPublisher(publisher); - - chain = mock(FilterChain.class); - request = new MockHttpServletRequest(); - response = new MockHttpServletResponse(); - - request.setParameter(GRANT_TYPE, "password"); - request.setParameter("client_id", "clientID"); - request.setParameter("client_secret", "secret"); - request.setParameter("username", "marissa"); - request.setParameter("password", "koala"); - request.setParameter(MFA_CODE, "123456"); - } - - @Test - public void only_password_grant_type() { - assertTrue(filter.isGrantTypeSupported("password")); - assertFalse(filter.isGrantTypeSupported("other")); - } - - @Test - public void non_password_grants_ignored() throws Exception { - request.setParameter(GRANT_TYPE, "other-than-password"); - filter.doFilterInternal(request, response, chain); - verifyNoInteractions(googleAuthenticator); - verify(chain).doFilter(same(request), same(response)); - verifyNoInteractions(publisher); - } - - @Test - public void authentication_missing() throws Exception { - exception.expect(InsufficientAuthenticationException.class); - exception.expectMessage("User authentication missing"); - SecurityContextHolder.clearContext(); - checkMfaCodeNoMfaInteraction(); - } - - private void checkMfaCodeNoMfaInteraction() { - try { - filter.checkMfaCode(request); - } catch (Exception e) { - verifyNoInteractions(chain); - verifyNoInteractions(googleAuthenticator); - throw e; - } - } - - @Test - public void authentication_wrong_type() throws Exception { - SecurityContextHolder.getContext().setAuthentication(mock(UaaAuthentication.class)); - exception.expect(InsufficientAuthenticationException.class); - exception.expectMessage("Unrecognizable authentication"); - checkMfaCodeNoMfaInteraction(); - - } - - @Test - public void user_authentication_wrong_type() throws Exception { - authentication = new OAuth2Authentication(storedOAuth2Request, mock(Authentication.class)); - SecurityContextHolder.getContext().setAuthentication(authentication); - exception.expect(InsufficientAuthenticationException.class); - exception.expectMessage("Unrecognizable user authentication"); - checkMfaCodeNoMfaInteraction(); - } - - @Test - public void mfa_validation_works() throws Exception { - filter.doFilterInternal(request, response, chain); - verify(googleAuthenticator).isValidCode(any(), eq(123456)); - verify(chain).doFilter(same(request), same(response)); - assertThat(uaaAuthentication.getAuthenticationMethods(), containsInAnyOrder("pwd", "otp", "mfa")); - verify(publisher, times(1)).publishEvent(any(MfaAuthenticationSuccessEvent.class)); - verify(publisher, times(1)).publishEvent(any(ApplicationEvent.class)); - - } - - @Test - public void mfa_code_missing() throws Exception { - request.removeParameter(MFA_CODE); - exception.expect(MissingMfaCodeException.class); - exception.expectMessage("A multi-factor authentication code is required to complete the request"); - checkMfaCodeNoMfaInteraction(); - } - - @Test - public void mfa_code_missing_returns_json_error() throws Exception { - request.removeParameter(MFA_CODE); - filter.doFilterInternal(request, response, chain); - assertThat(response.getStatus(), equalTo(400)); - JsonAssert.with(response.getContentAsString()) - .assertThat("error", equalTo("invalid_request")) - .assertThat("error_description", equalTo("A multi-factor authentication code is required to complete the request")); - verify(publisher, times(1)).publishEvent(any(MfaAuthenticationFailureEvent.class)); - verify(publisher, times(1)).publishEvent(any(ApplicationEvent.class)); - } - - @Test - public void invalid_mfa_code() { - request.setParameter(MFA_CODE, "54321"); - exception.expect(InvalidMfaCodeException.class); - checkMfaCode(); - } - - @Test - public void invalid_mfa_code_returns_json_bad_credentials() throws Exception { - request.setParameter(MFA_CODE, "54321"); - filter.doFilterInternal(request, response, chain); - assertThat(response.getStatus(), equalTo(401)); - JsonAssert.with(response.getContentAsString()) - .assertThat("error", equalTo("unauthorized")) - .assertThat("error_description", equalTo("Bad credentials")); - verify(publisher, times(1)).publishEvent(any(MfaAuthenticationFailureEvent.class)); - verify(publisher, times(1)).publishEvent(any(ApplicationEvent.class)); - } - - private void checkMfaCode() { - try { - filter.checkMfaCode(request); - } catch (Exception x) { - verifyNoInteractions(chain); - throw x; - } - } - - @Test - public void user_config_is_missing() { - when(googleAuthenticator.getUserGoogleMfaCredentials(anyString(), anyString())).thenReturn(null); - exception.expect(UserMfaConfigDoesNotExistException.class); - exception.expectMessage("User must register a multi-factor authentication token"); - checkMfaCode(); - } - - @Test - public void user_config_is_returning_error() throws Exception { - when(googleAuthenticator.getUserGoogleMfaCredentials(anyString(), anyString())).thenReturn(null); - filter.doFilterInternal(request, response, chain); - assertThat(response.getStatus(), equalTo(400)); - assertThat(response.getHeader(HttpHeaders.CONTENT_TYPE), equalTo(MediaType.APPLICATION_JSON_VALUE)); - assertNotNull(response.getContentAsString()); - JsonAssert.with(response.getContentAsString()) - .assertThat("error", equalTo("invalid_request")) - .assertThat("error_description", equalTo("User must register a multi-factor authentication token")); - verify(publisher, times(1)).publishEvent(any(MfaAuthenticationFailureEvent.class)); - verify(publisher, times(1)).publishEvent(any(ApplicationEvent.class)); - } - - @Test - public void no_mfa_configured() throws Exception { - zone.getConfig().getMfaConfig().setEnabled(false); - filter.doFilterInternal(request, response, chain); - verifyNoInteractions(googleAuthenticator); - verifyNoInteractions(mfaProvider); - verify(chain).doFilter(same(request), same(response)); - verifyNoInteractions(publisher); - } - - - @After - public void unfreezeTime() { - DateTimeUtils.setCurrentMillisSystem(); - } - - @Test - public void when_valid_mfa_auth_code_given_but_mfa_is_locked_out_should_fail() { - long fixedTime = 1L; - when(timeService.getCurrentTimeMillis()).thenReturn(fixedTime); - - MfaAuthenticationFailureEvent event = new MfaAuthenticationFailureEvent(user, authentication, GOOGLE_AUTHENTICATOR.toValue(), IdentityZoneHolder.getCurrentZoneId()); - when(jdbcAuditServiceMock.find(user.getId(), fixedTime, zone.getId())).thenReturn(Lists.newArrayList(event.getAuditEvent())); - - LockoutPolicy lockoutPolicy = new LockoutPolicy(0, 1, 5); - when(lockoutPolicyRetriever.getLockoutPolicy()).thenReturn(lockoutPolicy); - - request.setParameter(MFA_CODE, "123456"); - - exception.expect(RuntimeException.class); - filter.checkMfaCode(request); - } - - @Test - public void when_valid_mfa_auth_code_given_but_mfa_policy_is_disabled_should_not_fail() { - boolean mfaPolicyEnabled = false; - commonLoginPolicy = new CommonLoginPolicy(jdbcAuditServiceMock, lockoutPolicyRetriever, AuditEventType.MfaAuthenticationSuccess, AuditEventType.MfaAuthenticationFailure, timeService, mfaPolicyEnabled); - filter = new StatelessMfaAuthenticationFilter(googleAuthenticator, grantTypes, mfaProvider, userDatabase, commonLoginPolicy); - filter.setApplicationEventPublisher(publisher); - - long fixedTime = 1L; - when(timeService.getCurrentTimeMillis()).thenReturn(fixedTime); - - MfaAuthenticationFailureEvent event = new MfaAuthenticationFailureEvent(user, authentication, GOOGLE_AUTHENTICATOR.toValue(), IdentityZoneHolder.getCurrentZoneId()); - when(jdbcAuditServiceMock.find(user.getId(), fixedTime, zone.getId())).thenReturn(Lists.newArrayList(event.getAuditEvent())); - - LockoutPolicy lockoutPolicy = new LockoutPolicy(0, 1, 5); - when(lockoutPolicyRetriever.getLockoutPolicy()).thenReturn(lockoutPolicy); - - request.setParameter(MFA_CODE, "123456"); - - filter.checkMfaCode(request); - } - - @Test - public void when_valid_mfa_auth_code_given_with_previously_failed_mfa_auth_attempts_but_not_locked_out_should_pass() { - long fixedTime = 1L; - when(timeService.getCurrentTimeMillis()).thenReturn(fixedTime); - - MfaAuthenticationFailureEvent event = new MfaAuthenticationFailureEvent(user, authentication, GOOGLE_AUTHENTICATOR.toValue(), IdentityZoneHolder.getCurrentZoneId()); - when(jdbcAuditServiceMock.find(user.getId(), fixedTime, zone.getId())).thenReturn(Lists.newArrayList(event.getAuditEvent())); - - LockoutPolicy lockoutPolicy = new LockoutPolicy(1, 2, 5); - when(lockoutPolicyRetriever.getLockoutPolicy()).thenReturn(lockoutPolicy); - - request.setParameter(MFA_CODE, "123456"); - - filter.checkMfaCode(request); - } - - @Test - public void when_valid_mfa_auth_code_given_with_previously_failed_mfa_auth_attempts_interleaved_with_successful_mfa_auth_event_but_not_locked_out_should_pass() { - long fixedTime = 1L; - when(timeService.getCurrentTimeMillis()).thenReturn(fixedTime); - - - AuditEvent failedMfaEvent = new MfaAuthenticationFailureEvent(user, authentication, GOOGLE_AUTHENTICATOR.toValue(), IdentityZoneHolder.getCurrentZoneId()).getAuditEvent(); - AuditEvent successfulMfaEvent = new MfaAuthenticationSuccessEvent(user, authentication, GOOGLE_AUTHENTICATOR.toValue(), IdentityZoneHolder.getCurrentZoneId()).getAuditEvent(); - ArrayList events = Lists.newArrayList(failedMfaEvent, failedMfaEvent, successfulMfaEvent, failedMfaEvent); - when(jdbcAuditServiceMock.find(user.getId(), fixedTime, zone.getId())).thenReturn(events); - - LockoutPolicy lockoutPolicy = new LockoutPolicy(1, 3, 5); - when(lockoutPolicyRetriever.getLockoutPolicy()).thenReturn(lockoutPolicy); - - request.setParameter(MFA_CODE, "123456"); - - filter.checkMfaCode(request); - } - -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/UserGoogleMfaCredentialsProvisioningTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/UserGoogleMfaCredentialsProvisioningTest.java deleted file mode 100644 index 3118b973894..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa/UserGoogleMfaCredentialsProvisioningTest.java +++ /dev/null @@ -1,153 +0,0 @@ -package org.cloudfoundry.identity.uaa.mfa; - -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.mfa.exception.UserMfaConfigAlreadyExistsException; -import org.cloudfoundry.identity.uaa.mfa.exception.UserMfaConfigDoesNotExistException; -import org.cloudfoundry.identity.uaa.extensions.PollutionPreventionExtension; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.cloudfoundry.identity.uaa.zone.MfaConfig; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import java.util.Arrays; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.*; - -@ExtendWith(PollutionPreventionExtension.class) -class UserGoogleMfaCredentialsProvisioningTest { - - private UserGoogleMfaCredentialsProvisioning provisioner; - private JdbcUserGoogleMfaCredentialsProvisioning jdbcProvisioner; - private MfaProvider mfaProvider; - private MfaProvider otherMfaProvider; - - @BeforeEach - void setup() { - provisioner = new UserGoogleMfaCredentialsProvisioning(); - mfaProvider = new MfaProvider().setName("abc").setId("abc"); - otherMfaProvider = new MfaProvider().setName("abcd").setId("abcd"); - jdbcProvisioner = mock(JdbcUserGoogleMfaCredentialsProvisioning.class); - provisioner.setJdbcProvisioner(jdbcProvisioner); - MfaProviderProvisioning mfaProviderProvisioning = mock(MfaProviderProvisioning.class); - provisioner.setMfaProviderProvisioning(mfaProviderProvisioning); - when(mfaProviderProvisioning.retrieveByName(anyString(), anyString())).thenReturn(mfaProvider); - - IdentityZoneHolder.get().getConfig().setMfaConfig(new MfaConfig().setEnabled(true).setProviderName(mfaProvider.getName())); - } - - @Test - void testSavesCredentialsNotInDatabase() { - UserGoogleMfaCredentials creds = creds(); - provisioner.saveUserCredentials(creds.getUserId(), creds.getSecretKey(), creds.getValidationCode(), creds.getScratchCodes()); - verify(jdbcProvisioner, times(0)).save(any(), anyString()); - } - - @Test - void testSaveUserCredentials_updatesWhenUserExists() { - UserGoogleMfaCredentials creds = creds(); - provisioner.saveUserCredentials(creds.getUserId(), creds.getSecretKey(), creds.getValidationCode(), creds.getScratchCodes()); - - UserGoogleMfaCredentials updatedCreds = new UserGoogleMfaCredentials("jabbahut", - "different_key", - 45678, - Arrays.asList(1, 22)); - - provisioner.saveUserCredentials(updatedCreds.getUserId(), updatedCreds.getSecretKey(), updatedCreds.getValidationCode(), updatedCreds.getScratchCodes()); - verify(jdbcProvisioner, times(0)).save(any(), anyString()); - } - - @Test - void testPersist() { - UserGoogleMfaCredentials creds = creds(); - provisioner.saveUserCredentials(creds.getUserId(), creds.getSecretKey(), creds.getValidationCode(), creds.getScratchCodes()); - verify(jdbcProvisioner, times(0)).save(any(), anyString()); - provisioner.saveUserCredentials(creds); - verify(jdbcProvisioner, times(1)).save(creds, IdentityZoneHolder.get().getId()); - } - - @Test - void testPersist_emptySession() { - //provisioner.persistCredentials(); - verify(jdbcProvisioner, times(0)).save(any(), anyString()); - //assume that creds are already in database if session doesn't exist - } - - @Test - void testPersist_ErrorsIfAlreadyExists() { - UserGoogleMfaCredentials creds = creds(); - provisioner.saveUserCredentials(creds.getUserId(), creds.getSecretKey(), creds.getValidationCode(), creds.getScratchCodes()); - verify(jdbcProvisioner, times(0)).save(any(), anyString()); - provisioner.saveUserCredentials(creds); - doThrow(UserMfaConfigAlreadyExistsException.class).when(jdbcProvisioner).save(any(), anyString()); - - assertThrows(UserMfaConfigAlreadyExistsException.class, () -> provisioner.saveUserCredentials(creds)); - } - - @Test - void testActiveUserCredentialExists() { - UserGoogleMfaCredentials creds = creds(); - when(jdbcProvisioner.retrieve(anyString(), eq(mfaProvider.getId()))).thenReturn(creds); - when(jdbcProvisioner.retrieve(anyString(), eq(otherMfaProvider.getId()))).thenThrow(UserMfaConfigDoesNotExistException.class); - - provisioner.saveUserCredentials(creds.getUserId(), creds.getSecretKey(), creds.getValidationCode(), creds.getScratchCodes()); - - //provisioner.persistCredentials(); - - assertTrue("user not persisted for mfa provider", provisioner.activeUserCredentialExists("jabbahut", mfaProvider.getId())); - assertFalse("user persisted even though we switched mfaProvider", provisioner.activeUserCredentialExists("jabbahut", otherMfaProvider.getId())); - } - - @Test - void testActiveUserCredentialDoesNotExistAcrossProvider() { - UserGoogleMfaCredentials creds = creds(); - when(jdbcProvisioner.retrieve(anyString(), anyString())).thenThrow(UserMfaConfigDoesNotExistException.class).thenThrow(UserMfaConfigDoesNotExistException.class).thenReturn(creds); - - assertFalse("no user in db but activeCredentialExists returned true", provisioner.activeUserCredentialExists("jabbahut", mfaProvider.getId())); - - provisioner.saveUserCredentials(creds.getUserId(), creds.getSecretKey(), creds.getValidationCode(), creds.getScratchCodes()); - assertFalse("no user in db but activeCredentialExists returned true", provisioner.activeUserCredentialExists("jabbahut", mfaProvider.getId())); - - //provisioner.persistCredentials(); - assertTrue("user not shown as active after persisting", provisioner.activeUserCredentialExists("jabbahut", mfaProvider.getId())); - - } - - @Test - void testGetSecretKey() { - assertThrows(UnsupportedOperationException.class, () -> provisioner.getSecretKey("jabbahut")); - } - - - @Test - void isFirstTimeMFAUser() { - UaaPrincipal uaaPrincipal = mock(UaaPrincipal.class); - assertTrue(provisioner.isFirstTimeMFAUser(uaaPrincipal)); - } - - @Test - void isFirstTimeMFAUser_CredsAreNotInSession() { - UaaPrincipal uaaPrincipal = mock(UaaPrincipal.class); - UserGoogleMfaCredentials creds = creds(); - when(jdbcProvisioner.retrieve(any(),any())).thenReturn(creds); - assertFalse(provisioner.isFirstTimeMFAUser(uaaPrincipal)); - } - - @Test - void isFirstTimeMFAUser_failsIfNotParitallyLoggedIn() { - assertThrows(RuntimeException.class, () -> provisioner.isFirstTimeMFAUser(null)); - } - - private UserGoogleMfaCredentials creds() { - UserGoogleMfaCredentials res = new UserGoogleMfaCredentials("jabbahut", - "very_sercret_key", - 74718234, - Arrays.asList(1, 22)); - res.setMfaProviderId(mfaProvider.getId()); - return res; - } - -} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java index 7ed694d2265..c941f885883 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java @@ -106,7 +106,6 @@ void init() throws SQLException { null, null, null, - null, jdbcScimGroupMembershipManager, 5); IdentityZoneHolder.get().getConfig().getUserConfig().setDefaultGroups(emptyList()); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java index 268e18c2366..1526846def0 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java @@ -4,7 +4,6 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.impl.config.IdentityProviderBootstrap; import org.cloudfoundry.identity.uaa.impl.config.IdentityZoneConfigurationBootstrap; -import org.cloudfoundry.identity.uaa.mfa.MfaProviderBootstrap; import org.cloudfoundry.identity.uaa.provider.saml.BootstrapSamlIdentityProviderData; import org.cloudfoundry.identity.uaa.scim.bootstrap.ScimExternalGroupBootstrap; import org.cloudfoundry.identity.uaa.scim.bootstrap.ScimGroupBootstrap; @@ -108,7 +107,6 @@ private static void bootstrapDb(ApplicationContext applicationContext) { tryCallAfterPropertiesSet(applicationContext, ScimExternalGroupBootstrap.class); tryCallAfterPropertiesSet(applicationContext, BootstrapSamlIdentityProviderData.class); tryCallAfterPropertiesSet(applicationContext, IdentityProviderBootstrap.class); - tryCallAfterPropertiesSet(applicationContext, MfaProviderBootstrap.class); tryCallAfterPropertiesSet(applicationContext, ScimGroupBootstrap.class); tryCallAfterPropertiesSet(applicationContext, ScimUserBootstrap.class); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneConfigurationValidatorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneConfigurationValidatorTests.java index 2b957ce64e3..d93bfe5b76b 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneConfigurationValidatorTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneConfigurationValidatorTests.java @@ -39,7 +39,6 @@ public class GeneralIdentityZoneConfigurationValidatorTests { private IdentityZoneConfiguration zoneConfiguration; - private MfaConfigValidator mockMfaConfigValidator; @Parameterized.Parameters public static Object[][] parameters() { @@ -222,8 +221,7 @@ public void setUp() { samlConfig.setPrivateKeyPassword(legacyPassphrase); samlConfig.addKey("key-1", new SamlKey(key1, passphrase1, certificate1)); samlConfig.addKey("key-2", new SamlKey(key2, passphrase2, certificate2)); - mockMfaConfigValidator = mock(MfaConfigValidator.class); - validator = new GeneralIdentityZoneConfigurationValidator(mockMfaConfigValidator); + validator = new GeneralIdentityZoneConfigurationValidator(); zoneConfiguration = new IdentityZoneConfiguration(); BrandingInformation brandingInformation = new BrandingInformation(); zoneConfiguration.setBranding(brandingInformation); @@ -313,15 +311,6 @@ public void validate_no_keys() throws Exception { validator.validate(zone, mode); } - @Test - public void mfa_validation_exception_gets_thrown_back() throws Exception{ - doThrow(new InvalidIdentityZoneConfigurationException("Invalid MFA Config")).when(mockMfaConfigValidator).validate(any(), any()); - - expection.expect(InvalidIdentityZoneConfigurationException.class); - expection.expectMessage("Invalid MFA Config"); - validator.validate(zone, mode); - } - @Test public void validate_invalid_corsPolicy_xhrConfiguration_allowedUris() throws InvalidIdentityZoneConfigurationException { List invalidAllowedUris = List.of("https://google.com", "https://*.example.com", "^/uaa/userinfo(", "^/uaa/logout.do$"); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/zone/MfaConfigValidatorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/MfaConfigValidatorTests.java deleted file mode 100644 index 0bb2662dbee..00000000000 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/zone/MfaConfigValidatorTests.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.cloudfoundry.identity.uaa.zone; - -import org.cloudfoundry.identity.uaa.extensions.PollutionPreventionExtension; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; -import org.cloudfoundry.identity.uaa.mfa.MfaProviderProvisioning; -import org.hamcrest.Matchers; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.dao.EmptyResultDataAccessException; - -import static org.cloudfoundry.identity.uaa.util.AssertThrowsWithMessage.assertThrowsWithMessageThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.matches; -import static org.mockito.Mockito.when; - -@ExtendWith(PollutionPreventionExtension.class) -@ExtendWith(MockitoExtension.class) -class MfaConfigValidatorTests { - - @Mock - private MfaProviderProvisioning mockJdbcMfaProviderProvisioning; - - @InjectMocks - private MfaConfigValidator mfaConfigValidator; - - @Test - void validateSuccessful() throws InvalidIdentityZoneConfigurationException { - when(mockJdbcMfaProviderProvisioning.retrieveByName(matches("some-provider"), anyString())).thenReturn(new MfaProvider()); - - MfaConfig configuration = new MfaConfig().setEnabled(true).setProviderName("some-provider"); - mfaConfigValidator.validate(configuration, "some-zone"); - } - - @Test - void validateDisabledNoProviderId() throws InvalidIdentityZoneConfigurationException { - MfaConfig configuration = new MfaConfig().setEnabled(false).setProviderName(""); - - mfaConfigValidator.validate(configuration, "some-zone"); - } - - @Test - void validateDisabledInvalidProvider() throws InvalidIdentityZoneConfigurationException { - when(mockJdbcMfaProviderProvisioning.retrieveByName(anyString(), anyString())).thenThrow(new EmptyResultDataAccessException(1)); - MfaConfig configuration = new MfaConfig().setEnabled(false).setProviderName("some-provider"); - - assertThrowsWithMessageThat( - InvalidIdentityZoneConfigurationException.class, - () -> mfaConfigValidator.validate(configuration, "some-zone"), - Matchers.is("Active MFA Provider not found with name: some-provider")); - } - - @Test - void validateNoAvailableProviders() throws Exception { - when(mockJdbcMfaProviderProvisioning.retrieveByName(anyString(), anyString())).thenThrow(new EmptyResultDataAccessException(1)); - String providerName = "some-provider"; - - MfaConfig configuration = new MfaConfig().setEnabled(true).setProviderName(providerName); - assertThrowsWithMessageThat( - InvalidIdentityZoneConfigurationException.class, - () -> mfaConfigValidator.validate(configuration, "some-zone"), - Matchers.is("Active MFA Provider not found with name: some-provider")); - - } -} \ No newline at end of file diff --git a/uaa/slateCustomizations/source/index.html.md.erb b/uaa/slateCustomizations/source/index.html.md.erb index b3197155a3f..2a0d77a5a47 100644 --- a/uaa/slateCustomizations/source/index.html.md.erb +++ b/uaa/slateCustomizations/source/index.html.md.erb @@ -230,26 +230,6 @@ _Response Fields_ <%= render('TokenEndpointDocs/getTokenUsingPasswordGrant/response-fields.md') %> -### Password Grant with MFA - - - -A password grant can be completed when multi-factor authentication is enabled. - - -<%= render('TokenEndpointDocs/getTokenUsingMfaPasswordGrant/curl-request.md') %> -<%= render('TokenEndpointDocs/getTokenUsingMfaPasswordGrant/http-request.md') %> -<%= render('TokenEndpointDocs/getTokenUsingMfaPasswordGrant/http-response.md') %> - -_Request Parameters_ - -<%= render('TokenEndpointDocs/getTokenUsingMfaPasswordGrant/request-parameters.md') %> - -_Response Fields_ - -<%= render('TokenEndpointDocs/getTokenUsingMfaPasswordGrant/response-fields.md') %> ### One-time Passcode @@ -1223,134 +1203,6 @@ _Error Codes_ | 403 | Forbidden - Insufficient scope | | 422 | Unprocessable Entity - Invalid config | -# MFA Providers - -## Create - - - -<%= render('MfaProviderEndpointDocs/testCreateGoogleMfaProvider/curl-request.md') %> -<%= render('MfaProviderEndpointDocs/testCreateGoogleMfaProvider/http-request.md') %> -<%= render('MfaProviderEndpointDocs/testCreateGoogleMfaProvider/http-response.md') %> - -_Request Headers_ - -<%= render('MfaProviderEndpointDocs/testCreateGoogleMfaProvider/request-headers.md') %> - -_Request Fields_ - -<%= render('MfaProviderEndpointDocs/testCreateGoogleMfaProvider/request-fields.md') %> - - -_Response Fields_ - -<%= render('MfaProviderEndpointDocs/testCreateGoogleMfaProvider/response-fields.md') %> - -_Error Codes_ - -| Error Code | Description | -|------------|--------------------------------------------------------------------------------------------------------| -| 400 | Bad Request - JSON body was malformed or missing fields | -| 401 | Unauthorized - Invalid token | -| 403 | Forbidden - Insufficient scope (`uaa.admin` or `zones..admin` is required to create a MFA provider)| -| 422 | Unprocessable Entity - Some values in the MFA configuration are invalid | - - -## Update - - - - - - -_Error Codes_ - -| Error Code | Description | -|------------|--------------------------------------------------------------------------------------------------------| -| 405 | Method Not Allowed | - - -## Get - - - -<%= render('MfaProviderEndpointDocs/testGetMfaProvider/curl-request.md') %> -<%= render('MfaProviderEndpointDocs/testGetMfaProvider/http-request.md') %> -<%= render('MfaProviderEndpointDocs/testGetMfaProvider/http-response.md') %> - -_Request Headers_ - -<%= render('MfaProviderEndpointDocs/testGetMfaProvider/request-headers.md') %> - -_Response Fields_ - -<%= render('MfaProviderEndpointDocs/testGetMfaProvider/response-fields.md') %> - -_Error Codes_ - -| Error Code | Description | -|------------|--------------------------------------------------------------------------------------------------------| -| 401 | Unauthorized - Invalid token | -| 403 | Forbidden - Insufficient scope (`uaa.admin` or `zones..admin` is required to create a MFA provider)| -| 404 | Not Found - Provider id not found | - - -## Delete - - - -<%= render('MfaProviderEndpointDocs/testDeleteMfaProvider/curl-request.md') %> -<%= render('MfaProviderEndpointDocs/testDeleteMfaProvider/http-request.md') %> -<%= render('MfaProviderEndpointDocs/testDeleteMfaProvider/http-response.md') %> - -_Request Headers_ - -<%= render('MfaProviderEndpointDocs/testDeleteMfaProvider/request-headers.md') %> - -_Response Fields_ - -<%= render('MfaProviderEndpointDocs/testDeleteMfaProvider/response-fields.md') %> - -_Error Codes_ - -| Error Code | Description | -|------------|--------------------------------------------------------------------------------------------------------| -| 401 | Unauthorized - Invalid token | -| 403 | Forbidden - Insufficient scope (`uaa.admin` or `zones..admin` is required to create a MFA provider)| -| 404 | Not Found - Provider id not found | - -## List - - - -<%= render('MfaProviderEndpointDocs/testListMfaProviders/curl-request.md') %> -<%= render('MfaProviderEndpointDocs/testListMfaProviders/http-request.md') %> -<%= render('MfaProviderEndpointDocs/testListMfaProviders/http-response.md') %> - -_Request Headers_ - -<%= render('MfaProviderEndpointDocs/testListMfaProviders/request-headers.md') %> - -_Response Fields_ - -<%= render('MfaProviderEndpointDocs/testListMfaProviders/response-fields.md') %> - -_Error Codes_ - -| Error Code | Description | -|------------|--------------------------------------------------------------------------------------------------------| -| 401 | Unauthorized - Invalid token | -| 403 | Forbidden - Insufficient scope (`uaa.admin` or `zones..admin` is required to create a MFA provider)| # Users @@ -1797,27 +1649,6 @@ _Error Codes_ | 403 | Forbidden - Insufficient scope or internal user management disabled | | 404 | Not Found - User not found | -## Delete MFA registration - -<%= render('ScimUserEndpointDocs/deleteMfaRegistration/curl-request.md') %> -<%= render('ScimUserEndpointDocs/deleteMfaRegistration/http-request.md') %> -<%= render('ScimUserEndpointDocs/deleteMfaRegistration/http-response.md') %> - -_Path Parameters_ - -<%= render('ScimUserEndpointDocs/deleteMfaRegistration/path-parameters.md') %> - -_Request Headers_ - -<%= render('ScimUserEndpointDocs/deleteMfaRegistration/request-headers.md') %> - -_Error Codes_ - -| Error Code | Description | -|------------|-----------------------------------------------------------------------| -| 403 | Forbidden - Insufficient scope or internal user management disabled | -| 404 | Not Found - User not found | - ## Lookup User IDs/Usernames <%= render('UserIdConversionEndpointDocs/lookUpIds/curl-request.md') %> diff --git a/uaa/src/main/resources/uaa.yml b/uaa/src/main/resources/uaa.yml index 0b026ed5c9e..e0eeb9d7cc4 100755 --- a/uaa/src/main/resources/uaa.yml +++ b/uaa/src/main/resources/uaa.yml @@ -370,16 +370,6 @@ login: # idpDiscoveryEnabled: true # accountChooserEnabled: true -# mfa: -# enabled: true -# providerName: customProviderName -# providers: -# customProviderName: -# type: google-authenticator -# config: -# issuer: "custom issuer" -# providerDescription: "Default authenticator for uaa zone" - # SAML Key Configuration # The location and credentials of the certificate for this SP # See README.md for details on how to create this. diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index 10b82ab9e20..80944484569 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -338,7 +338,6 @@ - @@ -455,8 +454,7 @@ + class="org.cloudfoundry.identity.uaa.impl.config.IdentityZoneConfigurationBootstrap"> @@ -485,8 +483,6 @@ @config['login']['saml']['keys']}"/> - - @@ -498,7 +494,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml index f49a20d9a89..b27d4731fd2 100755 --- a/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml @@ -191,7 +191,6 @@ - @@ -257,19 +256,6 @@ - - - - - password - - - - - - - @@ -373,11 +359,6 @@
- - - - - @@ -395,7 +376,6 @@ request-matcher-ref="promptOauthAuthorizeApiRequestMatcher" create-session="never" xmlns="http://www.springframework.org/schema/security"> - @@ -670,30 +650,14 @@ value="${authentication.policy.global.lockoutPeriodSeconds:300}"/> - - - - - - - - - - - - @@ -705,15 +669,6 @@ - - - - - - - - - diff --git a/uaa/src/main/webapp/WEB-INF/spring/scim-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/scim-endpoints.xml index 90626f42280..afffae47f2c 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/scim-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/scim-endpoints.xml @@ -208,9 +208,6 @@ - diff --git a/uaa/src/main/webapp/resources/oss/stylesheets/mfa.css b/uaa/src/main/webapp/resources/oss/stylesheets/mfa.css deleted file mode 100644 index 8607dfbc225..00000000000 --- a/uaa/src/main/webapp/resources/oss/stylesheets/mfa.css +++ /dev/null @@ -1,10 +0,0 @@ -.panel.mfa { - min-width: 320px; - width: 50%; - max-width: 600px; - border-radius: 5px; -} - -h2.mfa-header { - font-size: 24px; -} \ No newline at end of file diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/MfaProviderEndpointsIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/MfaProviderEndpointsIntegrationTests.java deleted file mode 100644 index 9c28031f556..00000000000 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/MfaProviderEndpointsIntegrationTests.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.cloudfoundry.identity.uaa.integration; - -import org.cloudfoundry.identity.uaa.ServerRunning; -import org.cloudfoundry.identity.uaa.integration.feature.DefaultIntegrationTestConfig; -import org.cloudfoundry.identity.uaa.integration.feature.TestClient; -import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; -import org.cloudfoundry.identity.uaa.mfa.GoogleMfaProviderConfig; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.openqa.selenium.WebDriver; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.oauth2.client.test.TestAccounts; -import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; -import org.springframework.security.oauth2.provider.client.BaseClientDetails; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.util.StringUtils; -import org.springframework.web.client.RestTemplate; - -import static org.junit.Assert.assertTrue; - -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = DefaultIntegrationTestConfig.class) -public class MfaProviderEndpointsIntegrationTests { - - @Autowired - WebDriver webDriver; - - @Value("${integration.test.base_url}") - String baseUrl; - - @Autowired - TestAccounts testAccounts; - - @Autowired - TestClient testClient; - - ServerRunning serverRunning = ServerRunning.isRunning(); - private String adminToken; - private MfaProvider mfaProvider; - - @Before - public void setup() throws Exception { - adminToken = IntegrationTestUtils.getZoneAdminToken(baseUrl, serverRunning); - - mfaProvider = new MfaProvider(); - mfaProvider.setConfig(new GoogleMfaProviderConfig()); - mfaProvider.setType(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR); - mfaProvider.setName("testMfaProvider"); - } - - @Test - public void createMfaProvider() { - MfaProvider result = IntegrationTestUtils.createGoogleMfaProvider(baseUrl, adminToken, mfaProvider, ""); - assertTrue("id is not empty", StringUtils.hasText(result.getId())); - } - - @Test - public void createMfaProviderInZone() throws Exception { - ClientCredentialsResourceDetails adminResource = IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret"); - RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( - adminResource); - - IdentityZone mfaZone = IntegrationTestUtils.createZoneOrUpdateSubdomain(adminClient, baseUrl, "testzone1", "testzone1", null); - String zoneUrl = baseUrl.replace("localhost", mfaZone.getSubdomain() + ".localhost"); - - String zoneAdminToken = IntegrationTestUtils.getZoneAdminToken(baseUrl, serverRunning, mfaZone.getId()); - BaseClientDetails zoneClient = new BaseClientDetails("mfaAdmin", null, "", "client_credentials", "uaa.admin"); - zoneClient.setClientSecret("secret"); - IntegrationTestUtils.createClientAsZoneAdmin(zoneAdminToken, baseUrl, mfaZone.getId(), zoneClient); - - String inZoneAdminToken = IntegrationTestUtils.getClientCredentialsToken(zoneUrl, "mfaAdmin", "secret"); - - MfaProvider result = IntegrationTestUtils.createGoogleMfaProvider(zoneUrl, inZoneAdminToken, mfaProvider, ""); - assertTrue("id is not empty", StringUtils.hasText(result.getId())); - - - } - -} diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/TotpMfaEndpointIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/TotpMfaEndpointIntegrationTests.java deleted file mode 100644 index b0c1c9a2f00..00000000000 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/TotpMfaEndpointIntegrationTests.java +++ /dev/null @@ -1,415 +0,0 @@ -package org.cloudfoundry.identity.uaa.integration; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.cloudfoundry.identity.uaa.ServerRunning; -import org.cloudfoundry.identity.uaa.integration.feature.DefaultIntegrationTestConfig; -import org.cloudfoundry.identity.uaa.integration.feature.TestClient; -import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; -import org.cloudfoundry.identity.uaa.login.util.RandomValueStringGenerator; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; -import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; -import org.cloudfoundry.identity.uaa.scim.ScimUser; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; - -import com.dumbster.smtp.SimpleSmtpServer; -import com.google.zxing.BinaryBitmap; -import com.google.zxing.DecodeHintType; -import com.google.zxing.client.j2se.BufferedImageLuminanceSource; -import com.google.zxing.common.HybridBinarizer; -import com.google.zxing.qrcode.QRCodeReader; -import com.warrenstrange.googleauth.GoogleAuthenticator; -import com.warrenstrange.googleauth.GoogleAuthenticatorConfig; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.openqa.selenium.By; -import org.openqa.selenium.WebDriver; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.security.oauth2.client.http.OAuth2ErrorHandler; -import org.springframework.security.oauth2.client.test.OAuth2ContextSetup; -import org.springframework.security.oauth2.client.test.TestAccounts; -import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.web.client.RestTemplate; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.springframework.http.HttpMethod.GET; -import static org.springframework.http.HttpMethod.POST; - -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = DefaultIntegrationTestConfig.class) -public class TotpMfaEndpointIntegrationTests { - - @Autowired - WebDriver webDriver; - - @Value("${integration.test.base_url}") - String baseUrl; - - @Autowired - TestAccounts testAccounts; - - @Autowired - SimpleSmtpServer simpleSmtpServer; - - @Autowired - TestClient testClient; - - private static final String USER_PASSWORD = "sec3Tas"; - - @Rule - public ServerRunning serverRunning = ServerRunning.isRunning(); - - @Rule - public OAuth2ContextSetup context = OAuth2ContextSetup.withTestAccounts(serverRunning, testAccounts); - - private IdentityZone mfaZone; - private RestTemplate adminClient; - private String zoneUrl; - private String username; - private MfaProvider mfaProvider; - private String zoneAdminToken; - private String adminAccessToken; - private ScimUser user; - - @Before - public void setup() throws Exception { - ClientCredentialsResourceDetails adminResource = IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret"); - adminClient = IntegrationTestUtils.getClientCredentialsTemplate( - adminResource); - - mfaZone = IntegrationTestUtils.createZoneOrUpdateSubdomain(adminClient, baseUrl, "testzone1", "testzone1", null); - - zoneUrl = baseUrl.replace("localhost", mfaZone.getSubdomain() + ".localhost"); - adminAccessToken = testClient.getOAuthAccessToken("admin", "adminsecret", "client_credentials", "uaa.admin"); - zoneAdminToken = IntegrationTestUtils.getZoneAdminToken(baseUrl, serverRunning, mfaZone.getId()); - user = createRandomUser(); - username = user.getUserName(); - mfaProvider = enableMfaInZone(zoneAdminToken); - webDriver.get(zoneUrl + "/logout.do"); - } - - @After - public void cleanup() { - webDriver.get(zoneUrl + "/logout.do"); - webDriver.manage().deleteAllCookies(); - webDriver.get(baseUrl + "/logout.do"); - webDriver.manage().deleteAllCookies(); - mfaZone.getConfig().getMfaConfig().setEnabled(false).setProviderName(null); - IntegrationTestUtils.createZoneOrUpdateSubdomain(adminClient, baseUrl, mfaZone.getId(), mfaZone.getSubdomain(), mfaZone.getConfig()); - } - - @Test - public void testQRCodeScreen() throws Exception { - performLogin(username); - assertEquals(zoneUrl + "/login/mfa/register", webDriver.getCurrentUrl()); - - String imageSrc = webDriver.findElement(By.id("qr")).getAttribute("src"); - - String secretKey = getSecretFromQrImageString(imageSrc); - - webDriver.findElement(By.id("Next")).click(); - verifyCodeOnRegistration(secretKey, "/"); - } - - @Test - public void force_password_happens_after_MFA() throws Exception { - IntegrationTestUtils.updateUserToForcePasswordChange( - getRestTemplate(), - baseUrl, - adminAccessToken, - user.getId(), - mfaZone.getId() - ); - - performLogin(username); - assertEquals(zoneUrl + "/login/mfa/register", webDriver.getCurrentUrl()); - - String imageSrc = webDriver.findElement(By.id("qr")).getAttribute("src"); - - String secretKey = getSecretFromQrImageString(imageSrc); - - webDriver.findElement(By.id("Next")).click(); - verifyCodeOnRegistration(secretKey, "/force_password_change"); - - - } - - @Test - public void testQRCodeScreenAfterRegistrationDeletion() throws Exception { - // register mfa for user and logout - testQRCodeScreen(); - webDriver.get(zoneUrl + "/logout.do"); - - // retrieve user id and delete mfa registration - RestTemplate client = getRestTemplate(); - HttpHeaders headers = new HttpHeaders(); - headers.add("Authorization", "Bearer " + zoneAdminToken); - headers.add("X-Identity-Zone-Id", mfaZone.getId()); - headers.add("Content-Type", "application/json"); - Map uriParams = new HashMap<>(); - uriParams.put("filter","userName eq \""+username+"\""); - ResponseEntity exchange = client.exchange(serverRunning.getUrl("/Users?attributes=id&filter={filter}"), HttpMethod.GET, new HttpEntity( - headers), Map.class, uriParams); - String userId = (String) ((Map)((java.util.List) exchange.getBody().get("resources")).get(0)).get("id"); - - client.exchange(serverRunning.getUrl("/Users/{userId}/mfa"), HttpMethod.DELETE, new HttpEntity( - headers), Map.class, userId); - - // user login should end up at mfa registration page - performLogin(username); - assertEquals(zoneUrl + "/login/mfa/register", webDriver.getCurrentUrl()); - } - - private RestTemplate getRestTemplate() { - RestTemplate client = (RestTemplate) serverRunning.getRestTemplate(); - client.setErrorHandler(new OAuth2ErrorHandler(context.getResource()) { - // Pass errors through in response entity for status code analysis - @Override - public boolean hasError(ClientHttpResponse response) { - return false; - } - - @Override - public void handleError(ClientHttpResponse response) { - } - }); - return client; - } - - @Test - public void testMfaRegisterPageWithoutLoggingIn() { - webDriver.get(zoneUrl + "/logout.do"); - webDriver.get(zoneUrl + "/login/mfa/register"); - assertEquals(zoneUrl + "/login", webDriver.getCurrentUrl()); - } - - @Test - public void testMfaVerifyPageWithoutLoggingIn() { - webDriver.get(zoneUrl + "/logout.do"); - webDriver.get(zoneUrl + "/login/mfa/verify"); - assertEquals(zoneUrl + "/login", webDriver.getCurrentUrl()); - } - - private String qrCodeText(String dataUrl) throws Exception { - QRCodeReader reader = new QRCodeReader(); - String[] rawSplit = dataUrl.split(","); - assertEquals("data:image/png;base64", rawSplit[0]); - byte[] decodedByte = Base64.getDecoder().decode(rawSplit[1]); - BufferedImage image = ImageIO.read(new ByteArrayInputStream(decodedByte)); - BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(image); - Map hintMap = new HashMap<>(); - hintMap.put(DecodeHintType.PURE_BARCODE, true); - - BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); - return reader.decode(bitmap, hintMap).getText(); - } - - @Test - public void testQRCodeValidation() { - performLogin(username); - assertEquals(zoneUrl + "/login/mfa/register", webDriver.getCurrentUrl()); - - webDriver.findElement(By.id("Next")).click(); - assertEquals(zoneUrl + "/login/mfa/verify", webDriver.getCurrentUrl()); - webDriver.findElement(By.name("code")).sendKeys("1111111111111111112222"); - - webDriver.findElement(By.id("verify_code_btn")).click(); - assertEquals("Incorrect code, please try again.", webDriver.findElement(By.cssSelector("form .error-color")).getText()); - } - - @Test - public void checkAccessForTotpPage() { - webDriver.get(zoneUrl + "/logout.do"); - webDriver.manage().deleteAllCookies(); - webDriver.get(zoneUrl + "/login/mfa/register"); - assertEquals(zoneUrl + "/login", webDriver.getCurrentUrl()); - } - - @Test - public void testDisplayIdentityZoneNameOnRegisterPage() { - performLogin(username); - assertEquals(zoneUrl + "/login/mfa/register", webDriver.getCurrentUrl()); - - assertEquals(webDriver.findElement(By.id("mfa-identity-zone")).getText(), mfaZone.getName()); - } - - @Test - public void testDisplayIdentityZoneNameOnVerifyPage() { - performLogin(username); - webDriver.findElement(By.id("Next")).click(); - - assertEquals(zoneUrl + "/login/mfa/verify", webDriver.getCurrentUrl()); - assertEquals(webDriver.findElement(By.id("mfa-identity-zone")).getText(), mfaZone.getName()); - - webDriver.findElement(By.id("verify_code_btn")).click(); - assertEquals(webDriver.findElement(By.id("mfa-identity-zone")).getText(), mfaZone.getName()); - } - - @Test - public void testManualMfaRegistrationFlow() { - performLogin(username); - assertEquals(zoneUrl + "/login/mfa/register", webDriver.getCurrentUrl()); - - webDriver.findElement(By.linkText("manual setup instructions")).click(); - - assertEquals(zoneUrl + "/login/mfa/manual", webDriver.getCurrentUrl()); - - String key = webDriver.findElement(By.id("key")).getText(); - String account = webDriver.findElement(By.id("account")).getText(); - assertFalse("secret not found", key.isEmpty()); - assertFalse("account not found", account.isEmpty()); - - webDriver.findElement(By.id("Next")).click(); - assertEquals(zoneUrl + "/login/mfa/verify", webDriver.getCurrentUrl()); - - verifyCodeOnRegistration(key, "/"); - } - - private void verifyCodeOnRegistration(String key, String expectedUrlPath) { - GoogleAuthenticator authenticator = new GoogleAuthenticator(new GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder().build()); - int verificationCode = authenticator.getTotpPassword(key); - webDriver.findElement(By.name("code")).sendKeys(Integer.toString(verificationCode)); - webDriver.findElement(By.cssSelector("form button")).click(); - - assertEquals(zoneUrl + expectedUrlPath, webDriver.getCurrentUrl()); - } - - @Test - public void testQRCodeScreen_ClickManualAndReturn() throws Exception{ - performLogin(username); - assertEquals(zoneUrl + "/login/mfa/register", webDriver.getCurrentUrl()); - - webDriver.findElement(By.linkText("manual setup instructions")).click(); - assertEquals(zoneUrl + "/login/mfa/manual", webDriver.getCurrentUrl()); - - webDriver.findElement(By.id("Back")).click(); - assertEquals(zoneUrl + "/login/mfa/register", webDriver.getCurrentUrl()); - - String imageSrc = webDriver.findElement(By.id("qr")).getAttribute("src"); - - String secretKey = getSecretFromQrImageString(imageSrc); - - webDriver.findElement(By.id("Next")).click(); - verifyCodeOnRegistration(secretKey, "/"); - } - - @Test - public void testManualMfaRegistrationFlow_ClickBackAndManual() { - performLogin(username); - assertEquals(zoneUrl + "/login/mfa/register", webDriver.getCurrentUrl()); - - webDriver.findElement(By.linkText("manual setup instructions")).click(); - assertEquals(zoneUrl + "/login/mfa/manual", webDriver.getCurrentUrl()); - - webDriver.findElement(By.id("Back")).click(); - assertEquals(zoneUrl + "/login/mfa/register", webDriver.getCurrentUrl()); - - webDriver.findElement(By.linkText("manual setup instructions")).click(); - assertEquals(zoneUrl + "/login/mfa/manual", webDriver.getCurrentUrl()); - - String key = webDriver.findElement(By.id("key")).getText(); - String account = webDriver.findElement(By.id("account")).getText(); - assertFalse("secret not found", key.isEmpty()); - assertFalse("account not found", account.isEmpty()); - - webDriver.findElement(By.id("Next")).click(); - assertEquals(zoneUrl + "/login/mfa/verify", webDriver.getCurrentUrl()); - - verifyCodeOnRegistration(key, "/"); - } - - @Test - public void testQRCodeScreen_ClickManualClickNextClickBack() throws Exception{ - performLogin(username); - assertEquals(zoneUrl + "/login/mfa/register", webDriver.getCurrentUrl()); - - webDriver.findElement(By.linkText("manual setup instructions")).click(); - assertEquals(zoneUrl + "/login/mfa/manual", webDriver.getCurrentUrl()); - - webDriver.findElement(By.id("Next")).click(); - assertEquals(zoneUrl + "/login/mfa/verify", webDriver.getCurrentUrl()); - - webDriver.findElement(By.id("Back")).click(); - assertEquals(zoneUrl + "/login/mfa/register", webDriver.getCurrentUrl()); - - String imageSrc = webDriver.findElement(By.id("qr")).getAttribute("src"); - - String secretKey = getSecretFromQrImageString(imageSrc); - - assertFalse("secret not found", secretKey.isEmpty()); - - webDriver.findElement(By.id("Next")).click(); - verifyCodeOnRegistration(secretKey, "/"); - } - - private String getSecretFromQrImageString(String imageSrc) throws Exception { - String[] qparams = qrCodeText(imageSrc).split("\\?")[1].split("&"); - for(String param : qparams) { - if(param.contains("issuer=")) { - assertEquals("issuer=" + mfaProvider.getConfig().getIssuer(), URLDecoder.decode(param, StandardCharsets.UTF_8)); - break; - } - } - String secretKey = ""; - for(String param: qparams) { - String[] keyVal = param.split("="); - if(keyVal[0].equals("secret")) { - secretKey = keyVal[1]; - break; - } - } - return secretKey; - } - - private void performLogin(String username) { - webDriver.get(zoneUrl + "/logout.do"); - webDriver.manage().deleteAllCookies(); - webDriver.get(zoneUrl + "/login"); - webDriver.findElement(By.name("username")).sendKeys(username); - webDriver.findElement(By.name("password")).sendKeys(USER_PASSWORD); - webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); - } - - private MfaProvider enableMfaInZone(String zoneAdminToken) { - MfaProvider provider = IntegrationTestUtils.createGoogleMfaProvider(baseUrl, zoneAdminToken, MockMvcUtils.constructGoogleMfaProvider(), mfaZone.getId()); - mfaZone.getConfig().getMfaConfig().setEnabled(true).setProviderName(provider.getName()); - mfaZone.getConfig().getCorsPolicy().getDefaultConfiguration(). - setAllowedMethods(List.of(GET.toString(), POST.toString())); - mfaZone = IntegrationTestUtils.createZoneOrUpdateSubdomain(adminClient, baseUrl, "testzone1", mfaZone.getSubdomain() , mfaZone.getConfig()); - return provider; - } - - private ScimUser createRandomUser() { - String username = new RandomValueStringGenerator(5).generate().toLowerCase(); - ScimUser user = new ScimUser(null, username, "first", "last"); - user.setPrimaryEmail(user.getUserName()); - user.setPassword(USER_PASSWORD); - user.setVerified(true); - return IntegrationTestUtils.createUser(adminAccessToken, - baseUrl, - user, - mfaZone.getId() - ); - } - -} diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/IdentityZoneNotAvailableIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/IdentityZoneNotAvailableIT.java index d0e757ec3bd..9863589fa7d 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/IdentityZoneNotAvailableIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/IdentityZoneNotAvailableIT.java @@ -88,14 +88,6 @@ public void testIdentityProvidersEndpoints() { checkNotFoundForEndpoint(HttpMethod.PATCH,zoneUrl + "/identity-providers/id/status"); } - @Test - public void testMfaProvidersEndpoints() { - checkNotFoundForEndpoint(HttpMethod.GET,zoneUrl + "/mfa-providers"); - checkNotFoundForEndpoint(HttpMethod.GET,zoneUrl + "/mfa-providers/id"); - checkNotFoundForEndpoint(HttpMethod.POST,zoneUrl + "/mfa-providers"); - checkNotFoundForEndpoint(HttpMethod.DELETE,zoneUrl + "/mfa-providers/id"); - } - @Test public void testUsersEndpoints() { checkNotFoundForEndpoint(HttpMethod.GET,zoneUrl + "/Users/id"); @@ -109,7 +101,6 @@ public void testUsersEndpoints() { checkNotFoundForEndpoint(HttpMethod.PATCH,zoneUrl + "/Users/id/status"); checkNotFoundForEndpoint(HttpMethod.GET,zoneUrl + "/Users/id/verify-link"); checkNotFoundForEndpoint(HttpMethod.GET,zoneUrl + "/Users/id/verify"); - checkNotFoundForEndpoint(HttpMethod.DELETE,zoneUrl + "/Users/id/mfa"); checkNotFoundForEndpoint(HttpMethod.GET,zoneUrl + "/ids/Users"); checkNotFoundForEndpoint(HttpMethod.POST,zoneUrl + "/invite_users"); } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java index ab05a136118..86194d764db 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java @@ -14,8 +14,6 @@ import org.cloudfoundry.identity.uaa.account.UserInfoResponse; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.integration.feature.TestClient; -import org.cloudfoundry.identity.uaa.mfa.GoogleMfaProviderConfig; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; import org.cloudfoundry.identity.uaa.oauth.jwt.JwtClientAuthentication; import org.cloudfoundry.identity.uaa.provider.AbstractExternalOAuthIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; @@ -188,29 +186,6 @@ public static void deleteZone(String baseUrl, String id, String adminToken) thro rest.exchange(request, Void.class); } - public static MfaProvider createGoogleMfaProvider(String url, String token, MfaProvider provider, String zoneSwitchId) { - RestTemplate template = new RestTemplate(); - MultiValueMap headers = new LinkedMultiValueMap<>(); - headers.add("Accept", APPLICATION_JSON_VALUE); - headers.add("Authorization", "bearer " + token); - headers.add("Content-Type", APPLICATION_JSON_VALUE); - if (hasText(zoneSwitchId)) { - headers.add(IdentityZoneSwitchingFilter.HEADER, zoneSwitchId); - } - HttpEntity getHeaders = new HttpEntity<>(provider, headers); - ResponseEntity providerResponse = template.exchange( - url + "/mfa-providers", - HttpMethod.POST, - getHeaders, - MfaProvider.class - ); - if (providerResponse.getStatusCode() == HttpStatus.CREATED) { - return providerResponse.getBody(); - } - throw new RuntimeException("Invalid return code:" + providerResponse.getStatusCode()); - - } - public static class RegexMatcher extends TypeSafeMatcher { private final String regex; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ForcePasswordChangeControllerMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ForcePasswordChangeControllerMockMvcTest.java index be6beba8b70..27ff22215a6 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ForcePasswordChangeControllerMockMvcTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ForcePasswordChangeControllerMockMvcTest.java @@ -4,7 +4,6 @@ import org.cloudfoundry.identity.uaa.account.UserAccountStatus; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.util.RandomValueStringGenerator; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; @@ -38,7 +37,6 @@ import java.util.stream.Stream; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; -import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.performMfaRegistrationInZone; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.springframework.http.MediaType.APPLICATION_JSON; @@ -56,7 +54,6 @@ class ForcePasswordChangeControllerMockMvcTest { private String token; private IdentityProviderProvisioning identityProviderProvisioning; private IdentityZoneConfiguration uaaZoneConfig; - private MfaProvider mfaProvider; @Autowired private WebApplicationContext webApplicationContext; @@ -73,12 +70,10 @@ void setup() throws Exception { token = MockMvcUtils.getClientCredentialsOAuthAccessToken(mockMvc, "admin", "adminsecret", null, null); user = MockMvcUtils.createUser(mockMvc, token, user); uaaZoneConfig = MockMvcUtils.getZoneConfiguration(webApplicationContext, "uaa"); - mfaProvider = MockMvcUtils.createMfaProvider(webApplicationContext, IdentityZone.getUaa()); } @AfterEach void cleanup() { - uaaZoneConfig.getMfaConfig().setEnabled(false).setProviderName(null); MockMvcUtils.setZoneConfiguration(webApplicationContext, "uaa", uaaZoneConfig); } @@ -143,51 +138,6 @@ void requires_user_to_change_password() throws Exception { assertFalse(SessionUtils.isPasswordChangeRequired(session)); } - @Nested - @DefaultTestContext - class WithMFA { - @BeforeEach - void setup() { - uaaZoneConfig.getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); - MockMvcUtils.setZoneConfiguration(webApplicationContext, "uaa", uaaZoneConfig); - } - - - @Test - @DisplayName("") - void requires_user_to_change_password() throws Exception { - MockHttpSession session = (MockHttpSession) performMfaRegistrationInZone( - user.getUserName(), - "secret", - mockMvc, - "localhost", - new String[]{"pwd"}, - new String[]{"pwd", "mfa", "otp"} - ).andExpect(status().isFound()) - .andExpect(redirectedUrl("/force_password_change")) - .andReturn() - .getRequest() - .getSession(false); - - MockHttpServletRequestBuilder validPost = post("/force_password_change") - .param("password", "test") - .param("password_confirmation", "test") - .session(session) - .with(cookieCsrf()); - mockMvc.perform(validPost) - .andExpect(status().isFound()) - .andExpect(redirectedUrl(("/force_password_change_completed"))); - assertTrue(((SecurityContext) ((HttpSession) session).getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)).getAuthentication().isAuthenticated()); - assertFalse(SessionUtils.isPasswordChangeRequired(session)); - - mockMvc.perform(get("/force_password_change_completed") - .session(session)) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/")); - assertTrue(((SecurityContext) ((HttpSession) session).getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)).getAuthentication().isAuthenticated()); - assertFalse(SessionUtils.isPasswordChangeRequired(session)); - } - } } @Nested diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java index 808aae86725..d739ed89315 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java @@ -7,7 +7,6 @@ import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.codestore.JdbcExpiringCodeStore; import org.cloudfoundry.identity.uaa.impl.config.IdentityZoneConfigurationBootstrap; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.provider.AbstractExternalOAuthIdentityProviderDefinition; @@ -104,7 +103,6 @@ import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.IdentityZoneCreationResult; -import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.constructGoogleMfaProvider; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.createOtherIdentityZone; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.getMarissaSecurityContext; import static org.cloudfoundry.identity.uaa.security.web.CorsFilter.X_REQUESTED_WITH; @@ -191,17 +189,6 @@ void setUpContext( String subdomain = new AlphanumericRandomValueStringGenerator(24).generate().toLowerCase(); identityZone = MockMvcUtils.createOtherIdentityZone(subdomain, mockMvc, webApplicationContext, false, IdentityZoneHolder.getCurrentZoneId()); - MfaProvider mfaProvider = constructGoogleMfaProvider(); - mfaProvider = JsonUtils.readValue(mockMvc.perform( - post("/mfa-providers") - .header("X-Identity-Zone-Id", identityZone.getId()) - .header("Authorization", "Bearer " + adminToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(mfaProvider))) - .andExpect(status().isCreated()) - .andReturn().getResponse().getContentAsByteArray(), MfaProvider.class); - - identityZone.getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); MockMvcUtils.updateIdentityZone(identityZone, webApplicationContext); originalLimitedModeStatusFile = MockMvcUtils.getLimitedModeStatusFile(webApplicationContext); @@ -368,31 +355,6 @@ void testLogin() throws Exception { .andExpect(content().string(containsString("/create_account"))); } - @Test - void testLoginMfaRedirect( - @Autowired ScimUserProvisioning scimUserProvisioning - ) throws Exception { - MockHttpSession session = new MockHttpSession(); - - ScimUser user = createUser(scimUserProvisioning, generator, identityZone.getId()); - - mockMvc.perform(post("/login.do") - .with(cookieCsrf()) - .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost")) - .session(session) - .param("username", user.getUserName()) - .param("password", user.getPassword())) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/")); - - mockMvc.perform(get("/") - .with(cookieCsrf()) - .with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost")) - .session(session)) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login/mfa/register")); - } - IdentityZone createZoneLinksZone() throws Exception { String subdomain = new RandomValueStringGenerator(24).generate().toLowerCase(); IdentityZone zone = MockMvcUtils.createOtherIdentityZone(subdomain, mockMvc, webApplicationContext, false, IdentityZoneHolder.getCurrentZoneId()); @@ -1263,34 +1225,6 @@ void testLoginWithRemoteUaaJsonPrompts() throws Exception { .andExpect(model().attribute("prompts", hasKey("password"))); } - @Test - void testDefaultMfaPrompt() throws Exception { - IdentityZone zone = createZoneLinksZone(); - zone.getConfig().getMfaConfig().setEnabled(true); - MockMvcUtils.updateIdentityZone(zone, webApplicationContext); - - mockMvc.perform( - get("/login") - .accept(APPLICATION_JSON) - .header("Host", zone.getSubdomain() + ".localhost") - ) - .andExpect(status().isOk()) - .andExpect(view().name("login")) - .andExpect(model().attribute("prompts", hasKey("mfaCode"))) - .andExpect(model().attribute("prompts", hasKey("username"))) - .andExpect(model().attribute("prompts", hasKey("password"))); - - mockMvc.perform( - get("/login") //default zone - .accept(APPLICATION_JSON) - ) - .andExpect(status().isOk()) - .andExpect(view().name("login")) - .andExpect(model().attribute("prompts", not(hasKey("mfaCode")))) - .andExpect(model().attribute("prompts", hasKey("username"))) - .andExpect(model().attribute("prompts", hasKey("password"))); - } - @Test void testInfoWithRemoteUaaJsonPrompts() throws Exception { mockMvc.perform(get("/info") diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/PasscodeMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/PasscodeMockMvcTests.java index d3ed314f4cc..93f36d6c9f3 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/PasscodeMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/PasscodeMockMvcTests.java @@ -253,7 +253,7 @@ void testLoginUsingPasscodeWithUnknownToken() throws Exception { .session(session); mockMvc.perform(get) - .andExpect(status().isUnauthorized()); + .andExpect(status().isForbidden()); } @Test diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/TokenEndpointDocs.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/TokenEndpointDocs.java index b8bd80bbb7d..65bc30912f3 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/TokenEndpointDocs.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/TokenEndpointDocs.java @@ -54,7 +54,6 @@ import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.MockSecurityContext; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.getClientCredentialsOAuthAccessToken; -import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.getMfaCodeFromCredentials; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.getUserOAuthAccessToken; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_AUTHORIZATION_CODE; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_CLIENT_CREDENTIALS; @@ -352,52 +351,6 @@ void getTokenUsingPasswordGrant() throws Exception { .andDo(document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()), requestParameters, responseFields)); } - @Test - void getTokenUsingMfaPasswordGrant() throws Exception { - - setupForMfaPasswordGrant(user.getId()); - - MockHttpServletRequestBuilder postForToken = post("/oauth/token") - .accept(APPLICATION_JSON) - .contentType(APPLICATION_FORM_URLENCODED) - .param(CLIENT_ID, "app") - .param("client_secret", "appclientsecret") - .param("client_assertion", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjU4ZDU1YzUwMGNjNmI1ODM3OTYxN2UwNmU3ZGVjNmNhIn0.eyJzdWIiOiJsb2dpbiIsImlzcyI6ImxvZ2luIiwianRpIjoiNThkNTVjNTAwY2M2YjU4Mzc5NjE3ZTA2ZTdhZmZlZSIsImV4cCI6MTIzNDU2NzgsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC91YWEvb2F1dGgvdG9rZW4ifQ.jwWw0OKZecd4ZjtwQ_ievqBVrh2SieqMF6vY74Oo5H6v-Ibcmumq96NLNtoUEwaAEQQOHb8MWcC8Gwi9dVQdCrtpomC86b_LKkihRBSKuqpw0udL9RMH5kgtC04ctsN0yZNifUWMP85VHn97Ual5eZ2miaBFob3H5jUe98CcBj1TSRehr64qBFYuwt9vD19q6U-ONhRt0RXBPB7ayHAOMYtb1LFIzGAiKvqWEy9f-TBPXSsETjKkAtSuM-WVWi4EhACMtSvI6iJN15f7qlverRSkGIdh1j2vPXpKKBJoRhoLw6YqbgcUC9vAr17wfa_POxaRHvh9JPty0ZXLA4XPtA") - .param("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") - .param(GRANT_TYPE, GRANT_TYPE_PASSWORD) - .param("username", user.getUserName()) - .param("password", user.getPassword()) - .param("mfaCode", String.valueOf(getMfaCodeFromCredentials(credentials))) - .param(REQUEST_TOKEN_FORMAT, OPAQUE.getStringValue()) - .param("login_hint", "{\"origin\":\"uaa\"}"); - - Snippet requestParameters = requestParameters( - clientIdParameter, - grantTypeParameter.description("the type of authentication being used to obtain the token, in this case `password`"), - clientSecretParameter, - clientAssertion, - clientAssertionType, - parameterWithName("username").required().type(STRING).description("the username for the user trying to get a token"), - parameterWithName("password").required().type(STRING).description("the password for the user trying to get a token"), - parameterWithName("mfaCode").required().type(NUMBER).description("A one time passcode from a registered multi-factor generator"), - opaqueFormatParameter, - loginHintParameter - ); - - Snippet responseFields = responseFields( - accessTokenFieldDescriptor, - idTokenFieldDescriptor, - tokenTypeFieldDescriptor, - expiresInFieldDescriptor, - scopeFieldDescriptorWhenUserToken, - refreshTokenFieldDescriptor, - jtiFieldDescriptor - ); - - mockMvc.perform(postForToken) - .andDo(document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()), requestParameters, responseFields)); - } - @Test void getTokenUsingUserTokenGrant() throws Exception { String token = MockMvcUtils.getUserOAuthAccessToken(mockMvc, diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/TotpMfaEndpointMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/TotpMfaEndpointMockMvcTests.java deleted file mode 100644 index 8503299193b..00000000000 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/TotpMfaEndpointMockMvcTests.java +++ /dev/null @@ -1,491 +0,0 @@ -package org.cloudfoundry.identity.uaa.login; - -import org.cloudfoundry.identity.uaa.DefaultTestContext; -import org.cloudfoundry.identity.uaa.audit.event.AbstractUaaEvent; -import org.cloudfoundry.identity.uaa.authentication.event.MfaAuthenticationFailureEvent; -import org.cloudfoundry.identity.uaa.authentication.event.MfaAuthenticationSuccessEvent; -import org.cloudfoundry.identity.uaa.login.util.RandomValueStringGenerator; -import org.cloudfoundry.identity.uaa.mfa.JdbcUserGoogleMfaCredentialsProvisioning; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; -import org.cloudfoundry.identity.uaa.mfa.UserGoogleMfaCredentials; -import org.cloudfoundry.identity.uaa.mfa.UserGoogleMfaCredentialsProvisioning; -import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; -import org.cloudfoundry.identity.uaa.oauth.client.ClientDetailsModification; -import org.cloudfoundry.identity.uaa.scim.ScimUser; -import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; -import org.cloudfoundry.identity.uaa.test.TestClient; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationListener; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.ResultActions; -import org.springframework.web.context.WebApplicationContext; - -import java.security.Security; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; -import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.createMfaProvider; -import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_AUTHORIZATION_CODE; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.Matchers.instanceOf; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -@DefaultTestContext -class TotpMfaEndpointMockMvcTests { - - private String adminToken; - @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") - @Autowired - private JdbcUserGoogleMfaCredentialsProvisioning jdbcUserGoogleMfaCredentialsProvisioning; - private IdentityZoneConfiguration uaaZoneConfig; - private MfaProvider mfaProvider; - private MfaProvider otherMfaProvider; - private String password; - @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") - @Autowired - private UserGoogleMfaCredentialsProvisioning userGoogleMfaCredentialsProvisioning; - private ScimUser scimUser; - private MockHttpSession mockHttpSession; - private ApplicationListener applicationListener; - - @Autowired - private MockMvc mockMvc; - - @Autowired - private WebApplicationContext webApplicationContext; - - @BeforeAll - static void key() { - Security.setProperty("crypto.policy", "unlimited"); - } - - @BeforeEach - void setup( - @Autowired TestClient testClient, - @Autowired ConfigurableApplicationContext configurableApplicationContext, - @Autowired ScimUserProvisioning scimUserProvisioning - ) throws Exception { - adminToken = testClient.getClientCredentialsOAuthAccessToken( - "admin", - "adminsecret", - "clients.read clients.write clients.secret clients.admin uaa.admin" - ); - - mfaProvider = createMfaProvider(webApplicationContext, IdentityZone.getUaa()); - otherMfaProvider = createMfaProvider(webApplicationContext, IdentityZone.getUaa()); - - uaaZoneConfig = MockMvcUtils.getZoneConfiguration(webApplicationContext, IdentityZone.getUaaZoneId()); - uaaZoneConfig.getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); - MockMvcUtils.setZoneConfiguration(webApplicationContext, IdentityZone.getUaaZoneId(), uaaZoneConfig); - - //noinspection unchecked - applicationListener = (ApplicationListener) mock(ApplicationListener.class); - configurableApplicationContext.addApplicationListener(applicationListener); - - password = "sec3Tas"; - scimUser = createUser(scimUserProvisioning, password); - mockHttpSession = new MockHttpSession(); - } - - @AfterEach - void cleanup() { - uaaZoneConfig.getMfaConfig().setEnabled(false).setProviderName(null); - MockMvcUtils.setZoneConfiguration(webApplicationContext, "uaa", uaaZoneConfig); - MockMvcUtils.removeEventListener(webApplicationContext, applicationListener); - } - - @Test - void testRedirectToMfaAfterLogin() throws Exception { - redirectToMFARegistration(mockMvc, mockHttpSession, scimUser, password); - - MockHttpServletResponse response = mockMvc.perform(get("/profile") - .session(mockHttpSession)).andReturn().getResponse(); - assertTrue(response.getRedirectedUrl().contains("/login")); - } - - @Test - void testRedirectToLoginPageAfterClickingBackFromMfaRegistrationPage() throws Exception { - redirectToMFARegistration(mockMvc, mockHttpSession, scimUser, password); - - MockHttpServletResponse response = mockMvc.perform(get("/logout.do") - .session(mockHttpSession)).andReturn().getResponse(); - - assertTrue(response.getRedirectedUrl().endsWith("/login")); - } - - @Test - void testGoogleAuthenticatorLoginFlow() throws Exception { - redirectToMFARegistration(mockMvc, mockHttpSession, scimUser, password); - - performGetMfaRegister(mockMvc, mockHttpSession) - .andDo(print()) - .andExpect(view().name("mfa/qr_code")); - - assertFalse(userGoogleMfaCredentialsProvisioning.activeUserCredentialExists(scimUser.getId(), mfaProvider.getId())); - - int code = MockMvcUtils.getMFACodeFromSession(mockHttpSession); - - String location = MockMvcUtils.performMfaPostVerifyWithCode(code, mockMvc, mockHttpSession); - - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(AbstractUaaEvent.class); - verify(applicationListener, atLeast(1)).onApplicationEvent(eventCaptor.capture()); - assertEquals(9, eventCaptor.getAllValues().size()); - assertThat(eventCaptor.getAllValues().get(7), instanceOf(MfaAuthenticationSuccessEvent.class)); - - mockMvc.perform(get(location) - .session(mockHttpSession)) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/")); - - mockHttpSession = new MockHttpSession(); - performLoginWithSession(mockMvc, mockHttpSession, scimUser, password); - MockMvcUtils.performMfaPostVerifyWithCode(code, mockMvc, mockHttpSession); - - eventCaptor = ArgumentCaptor.forClass(AbstractUaaEvent.class); - verify(applicationListener, atLeast(1)).onApplicationEvent(eventCaptor.capture()); - assertEquals(15, eventCaptor.getAllValues().size()); - assertThat(eventCaptor.getAllValues().get(13), instanceOf(MfaAuthenticationSuccessEvent.class)); - } - - @Test - void testLockedOutAfterExceededMfaAttempts() throws Exception { - redirectToMFARegistration(mockMvc, mockHttpSession, scimUser, password); - performGetMfaRegister(mockMvc, mockHttpSession) - .andDo(print()) - .andExpect(view().name("mfa/qr_code")); - - assertFalse(userGoogleMfaCredentialsProvisioning.activeUserCredentialExists(scimUser.getId(), mfaProvider.getId())); - int code = MockMvcUtils.getMFACodeFromSession(mockHttpSession); - - for (int i = 0; i < 5; i++) { - mockMvc.perform(post("/login/mfa/verify.do") - .param("code", Integer.toString(-1)) - .header("Host", "localhost") - .session(mockHttpSession) - .with(cookieCsrf())) - .andExpect(status().isOk()); - } - - - String location = mockMvc.perform(post("/login/mfa/verify.do") - .param("code", Integer.toString(code)) - .header("Host", "localhost") - .session(mockHttpSession) - .with(cookieCsrf())) - .andExpect(status().is3xxRedirection()) - .andReturn().getResponse().getRedirectedUrl(); - - assertThat(location, is(containsString("login?error=account_locked"))); - } - - @Test - void testMFARegistrationHonorsRedirectUri() throws Exception { - ClientDetailsModification client = - MockMvcUtils.getClientDetailsModification( - "auth-client-id", - "secret", - Collections.emptyList(), - Collections.singletonList("openid"), - Collections.singletonList(GRANT_TYPE_AUTHORIZATION_CODE), - "uaa.resource", - Collections.singleton("http://example.com")); - client.setAutoApproveScopes(Collections.singletonList("openid")); - Map information = new HashMap<>(); - information.put("autoapprove", "true"); - client.setAdditionalInformation(information); - - MockMvcUtils.createClient(mockMvc, adminToken, client, IdentityZone.getUaa(), status().isCreated()); - - //Not using param function because params won't end up in paramsMap. - String oauthUrl = "/oauth/authorize?client_id=auth-client-id&client_secret=secret&redirect_uri=http://example.com"; - mockMvc.perform(get(oauthUrl) - .session(mockHttpSession) - .with(cookieCsrf())) - .andExpect(status().is3xxRedirection()) - .andDo(print()) - .andExpect(redirectedUrl("http://localhost/login")); - - performLoginWithSession(mockMvc, mockHttpSession, scimUser, password).andExpect(redirectedUrl("http://localhost" + oauthUrl)); - - mockMvc.perform(get(oauthUrl) - .session(mockHttpSession) - .with(cookieCsrf())) - .andExpect(status().is3xxRedirection()) - .andDo(print()) - .andExpect(redirectedUrl("/login/mfa/register")); - - performGetMfaRegister(mockMvc, mockHttpSession); - - int code = MockMvcUtils.getMFACodeFromSession(mockHttpSession); - MockMvcUtils.performMfaPostVerifyWithCode(code, mockMvc, mockHttpSession); - - mockMvc.perform(get("/login/mfa/completed") - .session(mockHttpSession) - .with(cookieCsrf())) - .andExpect(status().is3xxRedirection()) - .andDo(print()) - .andExpect(redirectedUrl("http://localhost/oauth/authorize?client_id=auth-client-id&client_secret=secret&redirect_uri=http://example.com")); - } - - @Test - void testQRCodeCannotBeSubmittedWithoutLoggedInSession() throws Exception { - mockMvc.perform(post("/login/mfa/verify.do") - .param("code", "1234") - .with(cookieCsrf())) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/login")); - } - - @Test - void testOtpValidationFails() throws Exception { - redirectToMFARegistration(mockMvc, mockHttpSession, scimUser, password); - - assertFalse(userGoogleMfaCredentialsProvisioning.activeUserCredentialExists(scimUser.getId(), mfaProvider.getId())); - - performGetMfaManualRegister(mockMvc, mockHttpSession).andExpect((view().name("mfa/manual_registration"))); - - int code = MockMvcUtils.getMFACodeFromSession(mockHttpSession); - - String location = MockMvcUtils.performMfaPostVerifyWithCode(code, mockMvc, mockHttpSession); - assertEquals("/login/mfa/completed", location); - - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(AbstractUaaEvent.class); - verify(applicationListener, atLeast(1)).onApplicationEvent(eventCaptor.capture()); - assertEquals(9, eventCaptor.getAllValues().size()); - assertThat(eventCaptor.getAllValues().get(7), instanceOf(MfaAuthenticationSuccessEvent.class)); - - mockMvc.perform(get("/") - .session(mockHttpSession)) - .andExpect(status().isOk()) - .andExpect(view().name("home")); - - mockMvc.perform(get("/logout.do")).andReturn(); - - mockHttpSession = new MockHttpSession(); - performLoginWithSession(mockMvc, mockHttpSession, scimUser, password); - - mockMvc.perform(post("/login/mfa/verify.do") - .param("code", Integer.toString(code + 1)) - .header("Host", "localhost") - .session(mockHttpSession) - .with(cookieCsrf())) - .andExpect(status().is2xxSuccessful()) - .andExpect(view().name("mfa/enter_code")); - - eventCaptor = ArgumentCaptor.forClass(AbstractUaaEvent.class); - verify(applicationListener, atLeast(1)).onApplicationEvent(eventCaptor.capture()); - assertEquals(15, eventCaptor.getAllValues().size()); - assertThat(eventCaptor.getAllValues().get(13), instanceOf(MfaAuthenticationFailureEvent.class)); - - mockMvc.perform(post("/login/mfa/verify.do") - .param("code", "ABCDEF") - .header("Host", "localhost") - .session(mockHttpSession) - .with(cookieCsrf())) - .andExpect(status().is2xxSuccessful()) - .andExpect(view().name("mfa/enter_code")); - - eventCaptor = ArgumentCaptor.forClass(AbstractUaaEvent.class); - verify(applicationListener, atLeast(1)).onApplicationEvent(eventCaptor.capture()); - assertEquals(17, eventCaptor.getAllValues().size()); - assertThat(eventCaptor.getAllValues().get(15), instanceOf(MfaAuthenticationFailureEvent.class)); - } - - @Test - void testQRCodeRedirectIfCodeValidated() throws Exception { - - redirectToMFARegistration(mockMvc, mockHttpSession, scimUser, password); - - performGetMfaRegister(mockMvc, mockHttpSession).andExpect(view().name("mfa/qr_code")); - - int code = MockMvcUtils.getMFACodeFromSession(mockHttpSession); - - MockMvcUtils.performMfaPostVerifyWithCode(code, mockMvc, mockHttpSession); - - UserGoogleMfaCredentials activeCreds = jdbcUserGoogleMfaCredentialsProvisioning.retrieve(scimUser.getId(), mfaProvider.getId()); - assertNotNull(activeCreds); - assertEquals(mfaProvider.getId(), activeCreds.getMfaProviderId()); - mockMvc.perform(get("/logout.do")).andReturn(); - - mockHttpSession = new MockHttpSession(); - performLoginWithSession(mockMvc, mockHttpSession, scimUser, password); - - performGetMfaRegister(mockMvc, mockHttpSession).andExpect(redirectedUrl("/login/mfa/verify")); - } - - @Test - void testRegisterFlowWithMfaProviderSwitch() throws Exception { - - redirectToMFARegistration(mockMvc, mockHttpSession, scimUser, password); - - performGetMfaRegister(mockMvc, mockHttpSession).andExpect(view().name("mfa/qr_code")); - - int code = MockMvcUtils.getMFACodeFromSession(mockHttpSession); - - String location = MockMvcUtils.performMfaPostVerifyWithCode(code, mockMvc, mockHttpSession); - - location = mockMvc.perform( - get(location) - .session(mockHttpSession) - ) - .andExpect(status().isFound()) - .andReturn().getResponse().getRedirectedUrl(); - - mockMvc.perform( - get(location) - .session(mockHttpSession) - ) - .andExpect(status().isOk()) - .andExpect(view().name("home")); - - - UserGoogleMfaCredentials activeCreds = jdbcUserGoogleMfaCredentialsProvisioning.retrieve(scimUser.getId(), mfaProvider.getId()); - assertNotNull(activeCreds); - assertEquals(mfaProvider.getId(), activeCreds.getMfaProviderId()); - mockMvc.perform(get("/logout.do")).andReturn(); - - uaaZoneConfig = MockMvcUtils.getZoneConfiguration(webApplicationContext, "uaa"); - uaaZoneConfig.getMfaConfig().setProviderName(otherMfaProvider.getName()); - MockMvcUtils.setZoneConfiguration(webApplicationContext, "uaa", uaaZoneConfig); - - mockHttpSession = new MockHttpSession(); - performLoginWithSession(mockMvc, mockHttpSession, scimUser, password); - - performGetMfaRegister(mockMvc, mockHttpSession).andExpect(view().name("mfa/qr_code")); - - code = MockMvcUtils.getMFACodeFromSession(mockHttpSession); - - location = MockMvcUtils.performMfaPostVerifyWithCode(code, mockMvc, mockHttpSession); - - location = mockMvc.perform( - get(location) - .session(mockHttpSession) - ) - .andExpect(status().isFound()) - .andReturn().getResponse().getRedirectedUrl(); - - mockMvc.perform( - get(location) - .session(mockHttpSession) - ) - .andExpect(status().isOk()) - .andExpect(view().name("home")); - } - - @Test - void testQRCodeRedirectIfCodeNotValidated() throws Exception { - redirectToMFARegistration(mockMvc, mockHttpSession, scimUser, password); - - performGetMfaRegister(mockMvc, mockHttpSession).andExpect(view().name("mfa/qr_code")); - - UserGoogleMfaCredentials inActiveCreds = (UserGoogleMfaCredentials) mockHttpSession.getAttribute("uaaMfaCredentials"); - assertNotNull(inActiveCreds); - - performGetMfaRegister(mockMvc, mockHttpSession).andExpect(view().name("mfa/qr_code")); - } - - @Test - void testManualRegistrationFlow() throws Exception { - redirectToMFARegistration(mockMvc, mockHttpSession, scimUser, password); - - assertFalse(userGoogleMfaCredentialsProvisioning.activeUserCredentialExists(scimUser.getId(), mfaProvider.getId())); - - performGetMfaManualRegister(mockMvc, mockHttpSession).andExpect((view().name("mfa/manual_registration"))); - - int code = MockMvcUtils.getMFACodeFromSession(mockHttpSession); - - String location = MockMvcUtils.performMfaPostVerifyWithCode(code, mockMvc, mockHttpSession); - assertEquals("/login/mfa/completed", location); - - ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(AbstractUaaEvent.class); - verify(applicationListener, atLeast(1)).onApplicationEvent(eventCaptor.capture()); - assertEquals(9, eventCaptor.getAllValues().size()); - assertThat(eventCaptor.getAllValues().get(7), instanceOf(MfaAuthenticationSuccessEvent.class)); - - mockMvc.perform(get("/") - .session(mockHttpSession)) - .andExpect(status().isOk()) - .andExpect(view().name("home")); - - mockMvc.perform(get("/logout.do")).andReturn(); - - mockHttpSession = new MockHttpSession(); - performLoginWithSession(mockMvc, mockHttpSession, scimUser, password); - MockMvcUtils.performMfaPostVerifyWithCode(code, mockMvc, mockHttpSession); - - eventCaptor = ArgumentCaptor.forClass(AbstractUaaEvent.class); - verify(applicationListener, atLeast(1)).onApplicationEvent(eventCaptor.capture()); - assertEquals(16, eventCaptor.getAllValues().size()); - assertThat(eventCaptor.getAllValues().get(14), instanceOf(MfaAuthenticationSuccessEvent.class)); - } - - @Test - void testQRDoesNotChangeDuringOneSession() throws Exception { - redirectToMFARegistration(mockMvc, mockHttpSession, scimUser, password); - assertFalse(userGoogleMfaCredentialsProvisioning.activeUserCredentialExists(scimUser.getId(), mfaProvider.getId())); - - MvcResult res = performGetMfaRegister(mockMvc, mockHttpSession).andExpect(view().name("mfa/qr_code")).andReturn(); - String qrUrl = (String) res.getModelAndView().getModel().get("qrurl"); - - performGetMfaRegister(mockMvc, mockHttpSession) - .andExpect(view().name("mfa/qr_code")) - .andExpect(model().attribute("qrurl", qrUrl)); - } - - private static ScimUser createUser(ScimUserProvisioning scimUserProvisioning, String password) { - ScimUser user = new ScimUser(null, new RandomValueStringGenerator(5).generate(), "first", "last"); - - user.setPrimaryEmail(user.getUserName()); - user.setPassword(password); - user = scimUserProvisioning.createUser(user, user.getPassword(), IdentityZoneHolder.getUaaZone().getId()); - return user; - } - - private static ResultActions performLoginWithSession(MockMvc mockMvc, MockHttpSession session, ScimUser user, String password) throws Exception { - return mockMvc.perform(post("/login.do") - .session(session) - .param("username", user.getUserName()) - .param("password", password) - .with(cookieCsrf())) - .andDo(print()) - .andExpect(status().isFound()); - } - - private static ResultActions performGetMfaRegister(MockMvc mockMvc, MockHttpSession session) throws Exception { - return mockMvc.perform(get("/login/mfa/register") - .session(session)); - } - - private static ResultActions performGetMfaManualRegister(MockMvc mockMvc, MockHttpSession session) throws Exception { - return mockMvc.perform(get("/login/mfa/manual") - .session(session) - ); - } - - private static void redirectToMFARegistration(MockMvc mockMvc, MockHttpSession session, ScimUser user, String password) throws Exception { - String location = performLoginWithSession(mockMvc, session, user, password).andReturn().getResponse().getHeader("Location"); - mockMvc.perform(get(location) - .session(session)) - .andExpect(redirectedUrl("/login/mfa/register")); - } -} diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/AbstractLdapMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/AbstractLdapMockMvcTest.java index 4c9372c7f78..7d397a1ff92 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/AbstractLdapMockMvcTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/AbstractLdapMockMvcTest.java @@ -11,9 +11,6 @@ import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.authentication.manager.DynamicZoneAwareAuthenticationManager; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.mfa.GoogleMfaProviderConfig; -import org.cloudfoundry.identity.uaa.mfa.JdbcMfaProviderProvisioning; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; import org.cloudfoundry.identity.uaa.mock.util.InterceptingLogger; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.oauth.jwt.Jwt; @@ -78,7 +75,6 @@ import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LDAP; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.createClient; -import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.performMfaRegistrationInZone; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_PASSWORD; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_REFRESH_TOKEN; import static org.hamcrest.MatcherAssert.assertThat; @@ -825,33 +821,6 @@ void test_username_with_space() throws Exception { } - @Test - void testLdapAuthenticationWithMfa() throws Exception { - String zoneId = zone.getZone().getIdentityZone().getId(); - // create mfa provider - MfaProvider mfaProvider = new MfaProvider(); - mfaProvider.setName(new AlphanumericRandomValueStringGenerator(5).generate()); - mfaProvider.setType(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR); - mfaProvider.setIdentityZoneId(zone.getZone().getIdentityZone().getId()); - mfaProvider.setConfig((GoogleMfaProviderConfig) new GoogleMfaProviderConfig().setIssuer("issuer")); - mfaProvider = getWebApplicationContext().getBean(JdbcMfaProviderProvisioning.class).create(mfaProvider, zoneId); - zone.getZone().getIdentityZone().getConfig().setMfaConfig(new MfaConfig().setEnabled(true).setProviderName(mfaProvider.getName())); - IdentityZone newZone = getWebApplicationContext().getBean(JdbcIdentityZoneProvisioning.class).update(zone.getZone().getIdentityZone()); - assertEquals(mfaProvider.getName(), newZone.getConfig().getMfaConfig().getProviderName()); - ResultActions actions = performMfaRegistrationInZone( - "marissa7", - "ldap7", - getMockMvc(), - host, - new String[]{"ext", "pwd"}, - new String[]{"ext", "pwd", "mfa", "otp"} - ); - actions - .andExpect(status().isOk()) - .andExpect(view().name("home")); - } - - void testSuccessfulLogin() throws Exception { getMockMvc().perform(post("/login.do").accept(TEXT_HTML_VALUE) .header(HOST, host) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/mfa_provider/MfaProviderEndpointDocs.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/mfa_provider/MfaProviderEndpointDocs.java deleted file mode 100644 index c026b427dc2..00000000000 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/mfa_provider/MfaProviderEndpointDocs.java +++ /dev/null @@ -1,203 +0,0 @@ -package org.cloudfoundry.identity.uaa.mock.mfa_provider; - -import org.apache.commons.lang3.ArrayUtils; -import org.cloudfoundry.identity.uaa.login.util.RandomValueStringGenerator; -import org.cloudfoundry.identity.uaa.mfa.GoogleMfaProviderConfig; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; -import org.cloudfoundry.identity.uaa.mock.EndpointDocs; -import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.http.HttpStatus; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.restdocs.headers.HeaderDescriptor; -import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; -import org.springframework.restdocs.payload.FieldDescriptor; -import org.springframework.restdocs.snippet.Snippet; -import org.springframework.test.web.servlet.ResultActions; - -import static org.cloudfoundry.identity.uaa.mfa.MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR; -import static org.cloudfoundry.identity.uaa.test.SnippetUtils.fieldWithPath; -import static org.cloudfoundry.identity.uaa.test.SnippetUtils.parameterWithName; -import static org.cloudfoundry.identity.uaa.test.SnippetUtils.subFields; -import static org.cloudfoundry.identity.uaa.util.JsonUtils.serializeExcludingProperties; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; -import static org.springframework.http.MediaType.APPLICATION_JSON; -import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; -import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; -import static org.springframework.restdocs.payload.JsonFieldType.OBJECT; -import static org.springframework.restdocs.payload.JsonFieldType.STRING; -import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; -import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -class MfaProviderEndpointDocs extends EndpointDocs { - - private static final String NAME_DESC = "Human-readable name for this provider. Must be alphanumeric."; - private static final String ID_DESC = "Unique identifier for this provider. This is a GUID generated by UAA."; - private static final String CREATED_DESC = "UAA sets the creation date."; - private static final String LAST_MODIFIED_DESC = "UAA sets the last modification date."; - private static final String IDENTITY_ZONE_ID_DESC = "Set to the zone that this provider will be active in. Determined either by the Host header or the zone switch header."; - private static final FieldDescriptor NAME = fieldWithPath("name").required().type(STRING).description(NAME_DESC); - private static final FieldDescriptor TYPE = fieldWithPath("type").required().type(STRING).description("Type of MFA provider. Available types include `google-authenticator`."); - private static final FieldDescriptor LAST_MODIFIED = fieldWithPath("last_modified").description(LAST_MODIFIED_DESC); - private static final FieldDescriptor ID = fieldWithPath("id").type(STRING).description(ID_DESC); - private static final FieldDescriptor CREATED = fieldWithPath("created").description(CREATED_DESC); - private static final FieldDescriptor IDENTITY_ZONE_ID = fieldWithPath("identityZoneId").type(STRING).description(IDENTITY_ZONE_ID_DESC); - private static final HeaderDescriptor IDENTITY_ZONE_ID_HEADER = headerWithName(IdentityZoneSwitchingFilter.HEADER).optional().description("If using a `zones..admin` scope/token, indicates what zone this request goes to by supplying a zone_id."); - private static final HeaderDescriptor IDENTITY_ZONE_SUBDOMAIN_HEADER = headerWithName(IdentityZoneSwitchingFilter.SUBDOMAIN_HEADER).optional().description("If using a `zones..admin` scope/token, indicates what zone this request goes to by supplying a subdomain."); - private static final HeaderDescriptor MFA_AUTHORIZATION_HEADER = headerWithName("Authorization").description("Bearer token containing `uaa.admin` or `zones..admin`"); - private FieldDescriptor[] commonProviderFields = { - NAME, - TYPE, - }; - private String adminToken; - - @BeforeEach - void setUp() throws Exception { - adminToken = testClient.getClientCredentialsOAuthAccessToken( - "admin", - "adminsecret", - ""); - } - - @Test - void testCreateGoogleMfaProvider() throws Exception { - MfaProvider mfaProvider = getGoogleMfaProvider(); - - FieldDescriptor[] idempotentFields = getGoogleMfaProviderFields(); - Snippet requestFields = requestFields(idempotentFields); - - Snippet responseFields = responseFields(getMfaProviderResponseFields(idempotentFields)); - mockMvc.perform(RestDocumentationRequestBuilders.post("/mfa-providers", mfaProvider.getId()) - .accept(APPLICATION_JSON) - .header("Authorization", "Bearer " + adminToken) - .contentType(APPLICATION_JSON) - .content(serializeExcludingProperties(mfaProvider, "id", "created", "last_modified", "identityZoneId"))) - .andExpect(status().isCreated()) - .andDo(document("{ClassName}/{methodName}", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - requestHeaders( - MFA_AUTHORIZATION_HEADER, - IDENTITY_ZONE_ID_HEADER - ), - requestFields, - responseFields) - ); - } - - private FieldDescriptor[] getGoogleMfaProviderFields() { - return (FieldDescriptor[]) ArrayUtils.addAll(commonProviderFields, new FieldDescriptor[]{ - fieldWithPath("config").optional(null).type(OBJECT).description("Human-readable provider description. Object with optional providerDescription and issue properties."), - fieldWithPath("config.providerDescription").optional(null).type(STRING).description("Human-readable provider description. Only for backend description purposes."), - fieldWithPath("config.issuer").optional(null).type(STRING).description("Human-readable tag for display purposes on MFA devices. Defaults to name of identity zone.") - }); - } - - private FieldDescriptor[] getMfaProviderResponseFields(FieldDescriptor[] idempotentFields) { - return (FieldDescriptor[]) ArrayUtils.addAll(idempotentFields, new FieldDescriptor[]{ - ID, - CREATED, - LAST_MODIFIED, - IDENTITY_ZONE_ID - }); - } - - private MfaProvider getGoogleMfaProvider() { - return new MfaProvider() - .setName("sampleGoogleMfaProvider" + new RandomValueStringGenerator(6).generate()) - .setType(GOOGLE_AUTHENTICATOR) - .setConfig(new GoogleMfaProviderConfig().setProviderDescription("Google MFA for default zone")); - } - - @Test - void testGetMfaProvider() throws Exception { - MfaProvider mfaProvider = getGoogleMfaProvider(); - mfaProvider = createMfaProviderHelper(mfaProvider); - - Snippet responseFields = responseFields(getMfaProviderResponseFields(getGoogleMfaProviderFields())); - - ResultActions getMFaResultAction = mockMvc.perform( - RestDocumentationRequestBuilders.get("/mfa-providers/{id}", mfaProvider.getId()) - .header("Authorization", "Bearer " + adminToken) - .accept(APPLICATION_JSON)); - - getMFaResultAction.andDo(document( - "{ClassName}/{methodName}", - preprocessResponse(prettyPrint()), - pathParameters(parameterWithName("id").required().description(ID_DESC)), - requestHeaders( - MFA_AUTHORIZATION_HEADER, - IDENTITY_ZONE_ID_HEADER - ), - responseFields - )); - } - - @Test - void testListMfaProviders() throws Exception { - MfaProvider mfaProvider = getGoogleMfaProvider(); - createMfaProviderHelper(mfaProvider); - - Snippet responseFields = responseFields((FieldDescriptor[]) - subFields("[]", getMfaProviderResponseFields(getGoogleMfaProviderFields()))); - - ResultActions listMfaProviderAction = mockMvc.perform(RestDocumentationRequestBuilders.get("/mfa-providers") - .header("Authorization", "Bearer " + adminToken) - .accept(APPLICATION_JSON)); - - listMfaProviderAction.andDo( - document("{ClassName}/{methodName}", - preprocessResponse(prettyPrint()), - requestHeaders( - MFA_AUTHORIZATION_HEADER, - IDENTITY_ZONE_ID_HEADER), - responseFields - )); - } - - @Test - void testDeleteMfaProvider() throws Exception { - MfaProvider mfaProvider = getGoogleMfaProvider(); - mfaProvider = createMfaProviderHelper(mfaProvider); - - Snippet responseFields = responseFields(getMfaProviderResponseFields(getGoogleMfaProviderFields())); - - ResultActions getMFaResultAction = mockMvc.perform( - RestDocumentationRequestBuilders.delete("/mfa-providers/{id}", mfaProvider.getId()) - .header("Authorization", "Bearer " + adminToken) - .accept(APPLICATION_JSON)); - - getMFaResultAction.andDo(document( - "{ClassName}/{methodName}", - preprocessResponse(prettyPrint()), - pathParameters(parameterWithName("id").required().description(ID_DESC)), - requestHeaders( - MFA_AUTHORIZATION_HEADER, - IDENTITY_ZONE_ID_HEADER - ), - responseFields - )); - } - - private MfaProvider createMfaProviderHelper(MfaProvider mfaProvider) throws Exception { - MockHttpServletResponse createResponse = mockMvc.perform( - post("/mfa-providers") - .header("Authorization", "Bearer " + adminToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(mfaProvider)) - .accept(APPLICATION_JSON)).andReturn().getResponse(); - assertThat(HttpStatus.CREATED.value(), is(createResponse.getStatus())); - return JsonUtils.readValue(createResponse.getContentAsString(), MfaProvider.class); - } - -} diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/mfa_provider/MfaProviderEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/mfa_provider/MfaProviderEndpointsMockMvcTests.java deleted file mode 100644 index e7039bfe801..00000000000 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/mfa_provider/MfaProviderEndpointsMockMvcTests.java +++ /dev/null @@ -1,337 +0,0 @@ -package org.cloudfoundry.identity.uaa.mock.mfa_provider; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import org.cloudfoundry.identity.uaa.DefaultTestContext; -import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent; -import org.cloudfoundry.identity.uaa.mfa.GoogleMfaProviderConfig; -import org.cloudfoundry.identity.uaa.mfa.JdbcMfaProviderProvisioning; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; -import org.cloudfoundry.identity.uaa.mfa.MfaProviderProvisioning; -import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; -import org.cloudfoundry.identity.uaa.util.AlphanumericRandomValueStringGenerator; -import org.cloudfoundry.identity.uaa.test.TestApplicationEventListener; -import org.cloudfoundry.identity.uaa.test.TestClient; -import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.http.HttpStatus; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.ResultActions; -import org.springframework.web.context.support.GenericWebApplicationContext; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.constructGoogleMfaProvider; -import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.springframework.http.MediaType.APPLICATION_JSON; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@DefaultTestContext -public class MfaProviderEndpointsMockMvcTests { - @Autowired - public GenericWebApplicationContext webApplicationContext; - private String adminToken; - private String nonAdminToken; - private TestApplicationEventListener eventListener; - - private MfaProviderProvisioning mfaProviderProvisioning; - - @Autowired - private MockMvc mockMvc; - - @BeforeEach - void setup() throws Exception { - TestClient testClient = new TestClient(mockMvc); - - mfaProviderProvisioning = webApplicationContext.getBean(JdbcMfaProviderProvisioning.class); - adminToken = testClient.getClientCredentialsOAuthAccessToken("admin", "adminsecret", - "clients.read clients.write clients.secret clients.admin uaa.admin"); - nonAdminToken = testClient.getClientCredentialsOAuthAccessToken("admin", "adminsecret", "clients.read clients.write clients.secret scim.read scim.write clients.admin"); - eventListener = MockMvcUtils.addEventListener(webApplicationContext, EntityDeletedEvent.class); - } - - @Test - void testCreateGoogleMfaProviderConfigDefaults() throws Exception { - MfaProvider mfaProvider = constructGoogleMfaProvider(); - mfaProvider.setConfig(null); - MvcResult mfaResponse = mockMvc.perform( - post("/mfa-providers") - .header("Authorization", "Bearer " + adminToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(mfaProvider))).andReturn(); - assertEquals(HttpStatus.CREATED.value(), mfaResponse.getResponse().getStatus()); - MfaProvider mfaProviderCreated = JsonUtils.readValue(mfaResponse.getResponse().getContentAsString(), MfaProvider.class); - assertEquals(IdentityZoneHolder.get().getName(), mfaProviderCreated.getConfig().getIssuer()); - assertEquals(IdentityZoneHolder.get().getId(), mfaProviderCreated.getIdentityZoneId()); - - } - - @Test - void testCreateGoogleMfaProviderInvalidType() throws Exception { - MfaProvider mfaProvider = constructGoogleMfaProvider(); - ObjectNode mfaAsJSON = (ObjectNode) JsonUtils.readTree(JsonUtils.writeValueAsString(mfaProvider)); - mfaAsJSON.put("type", "not-google-authenticator"); - ResultActions authorization = mockMvc.perform( - post("/mfa-providers") - .header("Authorization", "Bearer " + adminToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(mfaAsJSON))); - assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), authorization.andReturn().getResponse().getStatus()); - } - - @Test - void testCreateMfaProvider() throws Exception { - MfaProvider mfaProvider = constructGoogleMfaProvider(); - String name = new AlphanumericRandomValueStringGenerator(5).generate(); - mfaProvider.setName(name); - MvcResult mfaResponse = mockMvc.perform( - post("/mfa-providers") - .header("Authorization", "Bearer " + adminToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(mfaProvider))).andReturn(); - - mfaProvider = JsonUtils.readValue(mfaResponse.getResponse().getContentAsString(), MfaProvider.class); - - assertEquals(HttpStatus.CREATED.value(), mfaResponse.getResponse().getStatus()); - assertEquals(name, mfaProvider.getName()); - assertNotNull(mfaProvider.getId()); - } - - @Test - void testCreateMfaProviderInvalidIssuer() throws Exception { - GoogleMfaProviderConfig config = new GoogleMfaProviderConfig(); - config.setIssuer("invalid:issuer"); - MfaProvider mfaProvider = constructGoogleMfaProvider().setConfig(config); - String name = new AlphanumericRandomValueStringGenerator(5).generate(); - mfaProvider.setName(name); - MvcResult mfaResponse = mockMvc.perform( - post("/mfa-providers") - .header("Authorization", "Bearer " + adminToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(mfaProvider))).andReturn(); - - assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), mfaResponse.getResponse().getStatus()); - } - - @Test - void testCreateDuplicate() throws Exception { - MfaProvider mfaProvider = constructGoogleProvider(); - mfaProvider.setConfig(null); - mockMvc.perform( - post("/mfa-providers") - .header("Authorization", "Bearer " + adminToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(mfaProvider))).andReturn(); - - mockMvc.perform( - post("/mfa-providers") - .header("Authorization", "Bearer " + adminToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(mfaProvider))) - .andDo(print()) - .andExpect(status().isConflict()) - .andExpect(jsonPath("$.error").value("invalid_mfa_provider")) - .andExpect(jsonPath("$.error_description").value("An MFA Provider with that name already exists.")); - } - - @Test - void testCreateMfaForOtherZone() throws Exception { - IdentityZone identityZone = MockMvcUtils.createZoneUsingWebRequest(mockMvc, adminToken); - - MfaProvider mfaProvider = constructGoogleMfaProvider(); - MvcResult mfaResponse = mockMvc.perform( - post("/mfa-providers") - .header("Authorization", "Bearer " + adminToken) - .header(IdentityZoneSwitchingFilter.HEADER, identityZone.getId()) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(mfaProvider))).andReturn(); - assertEquals(HttpStatus.CREATED.value(), mfaResponse.getResponse().getStatus()); - } - - @Test - void testUpdateIsNotAllowed() throws Exception { - mockMvc.perform(put("/mfa-providers/invalid") - .header("Authorization", "bearer " + adminToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(new MfaProvider<>()))) - .andExpect(status().isMethodNotAllowed()); - } - - @Test - void testUpdateForbiddenNonAdmin() throws Exception { - mockMvc.perform(put("/mfa-providers/invalid") - .header("Authorization", "bearer " + nonAdminToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(new MfaProvider<>()))) - .andExpect(status().isForbidden()); - } - - @Test - void testRetrieveMfaProviders() throws Exception { - int mfaProvidersCount = mfaProviderProvisioning.retrieveAll(IdentityZoneHolder.get().getId()).size(); - MvcResult authorization = mockMvc.perform( - get("/mfa-providers") - .header("Authorization", "Bearer " + adminToken)).andReturn(); - - assertEquals(HttpStatus.OK.value(), authorization.getResponse().getStatus()); - List mfaProviders = JsonUtils.readValue(authorization.getResponse().getContentAsString(), List.class); - assertEquals(mfaProvidersCount, mfaProviders.size()); - } - - @Test - void testRetrieveMfaProviderById() throws Exception { - MfaProvider createdProvider = constructGoogleMfaProvider(); - createdProvider.setIdentityZoneId(IdentityZoneHolder.get().getId()); - createdProvider = mfaProviderProvisioning.create(createdProvider, IdentityZoneHolder.get().getId()); - MvcResult result = mockMvc.perform( - get("/mfa-providers/" + createdProvider.getId()) - .header("Authorization", "Bearer " + adminToken)).andReturn(); - - assertEquals(HttpStatus.OK.value(), result.getResponse().getStatus()); - assertEquals(JsonUtils.writeValueAsString(createdProvider), result.getResponse().getContentAsString()); - } - - @Test - void testGetMfaInOtherZone() throws Exception { - IdentityZone identityZone = MockMvcUtils.createZoneUsingWebRequest(mockMvc, adminToken); - - MfaProvider mfaProvider = constructGoogleMfaProvider(); - MvcResult createResult = mockMvc.perform( - post("/mfa-providers") - .header("Authorization", "Bearer " + adminToken) - .header(IdentityZoneSwitchingFilter.HEADER, identityZone.getId()) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(mfaProvider))).andReturn(); - mfaProvider = JsonUtils.readValue(createResult.getResponse().getContentAsString(), MfaProvider.class); - - - MvcResult mfaListResult = mockMvc.perform( - get("/mfa-providers") - .header("Authorization", "Bearer " + adminToken) - .header(IdentityZoneSwitchingFilter.HEADER, identityZone.getId())).andReturn(); - List mfaProviders = JsonUtils.readValue(mfaListResult.getResponse().getContentAsString(), List.class); - List providerIds = mfaProviders.stream().map(provider -> provider.get("id")).collect(Collectors.toList()); - assertTrue(providerIds.contains(mfaProvider.getId())); - } - - @Test - void testRetrieveMfaProviderByIdInvalid() throws Exception { - MvcResult authorization = mockMvc.perform( - get("/mfa-providers/abcd") - .header("Authorization", "Bearer " + adminToken)).andReturn(); - - assertEquals(HttpStatus.NOT_FOUND.value(), authorization.getResponse().getStatus()); - } - - @Test - void testDeleteMfaProvider() throws Exception { - MfaProvider provider = constructGoogleMfaProvider(); - MockHttpServletResponse createResponse = mockMvc.perform(post("/mfa-providers") - .header("Authorization", "Bearer " + adminToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(provider))).andReturn().getResponse(); - provider = JsonUtils.readValue(createResponse.getContentAsString(), MfaProvider.class); - - - mockMvc.perform(delete("/mfa-providers/" + provider.getId()) - .header("Authorization", "Bearer " + adminToken)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id").value(provider.getId())) - .andReturn(); - - assertEquals(1, eventListener.getEventCount()); - } - - @Test - void testDeleteZoneActiveMfaProviderShouldFail() throws Exception { - IdentityZone identityZone = MockMvcUtils.createZoneUsingWebRequest(mockMvc, adminToken); - - MfaProvider mfaProvider = constructGoogleMfaProvider(); - mfaProvider = JsonUtils.readValue(mockMvc.perform(post("/mfa-providers") - .header("Authorization", "Bearer " + adminToken) - .header("X-Identity-Zone-Id", identityZone.getId()) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(mfaProvider))).andReturn().getResponse().getContentAsString(), MfaProvider.class); - - identityZone.getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); - MockMvcUtils.updateIdentityZone(identityZone, webApplicationContext); - - String deleteResponse = mockMvc.perform(delete("/mfa-providers/" + mfaProvider.getId()) - .header("Authorization", "Bearer " + adminToken) - .header("X-Identity-Zone-Id", identityZone.getId())) - .andExpect(status().isConflict()).andReturn().getResponse().getContentAsString(); - - assertThat(deleteResponse, containsString("MFA provider is currently active on zone: " + identityZone.getId())); - - } - - @Test - void testNonExistentMfaProviderDelete() throws Exception { - mockMvc.perform(delete("/mfa-providers/invalid") - .header("Authorization", "Bearer " + adminToken)) - .andExpect(status().isNotFound()) - .andReturn(); - assertEquals(0, eventListener.getEventCount()); - } - - @Test - void testDeleteForbiddenForNonAdmin() throws Exception { - mockMvc.perform(delete("/mfa-providers/invalid") - .header("Authorization", "Bearer " + nonAdminToken)) - .andExpect(status().isForbidden()) - .andReturn(); - assertEquals(0, eventListener.getEventCount()); - } - - @Test - void testDeleteZoneAlsoDeletesMfaProviderInThatZone() throws Exception { - IdentityZone identityZone = MockMvcUtils.createZoneUsingWebRequest(mockMvc, adminToken); - - MfaProvider mfaProvider = constructGoogleMfaProvider(); - MockHttpServletResponse response = mockMvc.perform(post("/mfa-providers") - .header("Authorization", "Bearer " + adminToken) - .header("X-Identity-Zone-Id", identityZone.getId()) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(mfaProvider))).andReturn().getResponse(); - mfaProvider = JsonUtils.readValue(response.getContentAsString(), MfaProvider.class); - MfaProviderProvisioning providerProvisioning = webApplicationContext.getBean(JdbcMfaProviderProvisioning.class); - providerProvisioning.retrieve(mfaProvider.getId(), identityZone.getId()); - - MockMvcUtils.deleteIdentityZone(identityZone.getId(), mockMvc); - - final String mfaProviderId = mfaProvider.getId(); - assertThrows(EmptyResultDataAccessException.class, () -> providerProvisioning.retrieve(mfaProviderId, identityZone.getId())); - } - - private MfaProvider constructGoogleProvider() { - return new MfaProvider() - .setName(new AlphanumericRandomValueStringGenerator(10).generate()) - .setType(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR) - .setIdentityZoneId(IdentityZoneHolder.get().getId()) - .setConfig(constructGoogleProviderConfiguration()); - } - - private GoogleMfaProviderConfig constructGoogleProviderConfiguration() { - return new GoogleMfaProviderConfig(); - } -} \ No newline at end of file diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/AbstractTokenMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/AbstractTokenMockMvcTests.java index f47fc6ab3a9..d8b42027cb9 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/AbstractTokenMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/AbstractTokenMockMvcTests.java @@ -3,9 +3,6 @@ import org.cloudfoundry.identity.uaa.DefaultTestContext; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.util.RandomValueStringGenerator; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; -import org.cloudfoundry.identity.uaa.mfa.UserGoogleMfaCredentials; -import org.cloudfoundry.identity.uaa.mfa.UserGoogleMfaCredentialsProvisioning; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.oauth.UaaTokenServices; import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; @@ -35,7 +32,6 @@ import java.util.*; -import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.createMfaProvider; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.getClientCredentialsOAuthAccessToken; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_IMPLICIT; import static org.junit.Assert.assertNull; @@ -74,9 +70,7 @@ public abstract class AbstractTokenMockMvcTests { protected RandomValueStringGenerator generator = new RandomValueStringGenerator(); protected IdentityZone zone; - protected MfaProvider mfaProvider; private IdentityZoneConfiguration uaaZoneConfig; - protected UserGoogleMfaCredentials credentials; @Autowired protected MockMvc mockMvc; @@ -90,9 +84,6 @@ public abstract class AbstractTokenMockMvcTests { @Autowired protected UaaUserDatabase uaaUserDatabase; - @Autowired - protected UserGoogleMfaCredentialsProvisioning userGoogleMfaCredentialsProvisioning; - Set defaultAuthorities; @BeforeEach @@ -116,41 +107,10 @@ public void setUpContext( @AfterEach public void cleanup() { if (uaaZoneConfig != null) { - uaaZoneConfig.getMfaConfig().setEnabled(false).setProviderName(null); MockMvcUtils.setZoneConfiguration(webApplicationContext, IdentityZone.getUaaZoneId(), uaaZoneConfig); - deleteMfaRegistrations(); } } - void deleteMfaRegistrations() { - jdbcTemplate.update("DELETE FROM user_google_mfa_credentials"); - } - - public void setupForMfaPasswordGrant() throws Exception { - String userId = uaaUserDatabase.retrieveUserByName("marissa", OriginKeys.UAA).getId(); - setupForMfaPasswordGrant(userId); - } - - protected void setupForMfaPasswordGrant(String userId) throws Exception { - uaaZoneConfig = MockMvcUtils.getZoneConfiguration(webApplicationContext, IdentityZone.getUaaZoneId()); - - cleanup(); - - adminToken = testClient.getClientCredentialsOAuthAccessToken( - "admin", - "adminsecret", - "uaa.admin" - ); - mfaProvider = createMfaProvider(webApplicationContext, IdentityZone.getUaa()); - - uaaZoneConfig.getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); - MockMvcUtils.setZoneConfiguration(webApplicationContext, IdentityZone.getUaaZoneId(), uaaZoneConfig); - - credentials = userGoogleMfaCredentialsProvisioning.createUserCredentials(userId); - credentials.setMfaProviderId(mfaProvider.getId()); - userGoogleMfaCredentialsProvisioning.saveUserCredentials(credentials); - } - protected String createUserForPasswordGrant( final JdbcScimUserProvisioning jdbcScimUserProvisioning, final JdbcScimGroupMembershipManager jdbcScimGroupMembershipManager, diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/MfaPasswordGrantMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/MfaPasswordGrantMockMvcTests.java deleted file mode 100644 index 730ef6ddacb..00000000000 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/MfaPasswordGrantMockMvcTests.java +++ /dev/null @@ -1,153 +0,0 @@ -package org.cloudfoundry.identity.uaa.mock.token; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.cloudfoundry.identity.uaa.authentication.event.AbstractUaaAuthenticationEvent; -import org.cloudfoundry.identity.uaa.authentication.event.MfaAuthenticationFailureEvent; -import org.cloudfoundry.identity.uaa.authentication.event.MfaAuthenticationSuccessEvent; -import org.cloudfoundry.identity.uaa.mfa.StatelessMfaAuthenticationFilter; -import org.cloudfoundry.identity.uaa.test.TestApplicationEventListener; -import org.cloudfoundry.identity.uaa.user.UaaUser; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.security.oauth2.common.util.OAuth2Utils; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.context.support.GenericWebApplicationContext; - -import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.getMfaCodeFromCredentials; -import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.removeEventListener; -import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.REQUEST_TOKEN_FORMAT; -import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.TokenFormat.OPAQUE; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -public class MfaPasswordGrantMockMvcTests extends AbstractTokenMockMvcTests { - private TestApplicationEventListener listener; - - @Autowired - private StatelessMfaAuthenticationFilter statelessMfaAuthenticationFilter; - - @BeforeEach - public void setupForMfaPasswordGrant() throws Exception { - super.setupForMfaPasswordGrant(); - listener = TestApplicationEventListener.forEventClass(AbstractUaaAuthenticationEvent.class); - ((GenericWebApplicationContext) webApplicationContext).addApplicationListener(listener); - } - - @AfterEach - void clearListeners() { - if (listener!=null) { - removeEventListener(webApplicationContext, listener); - listener.clearEvents(); - } - } - - @Test - void filter_only_triggers_on_password_grant() { - assertThat(statelessMfaAuthenticationFilter.getSupportedGrantTypes(), containsInAnyOrder("password")); - } - - @Test - void mfa_happy_path() throws Exception { - listener.clearEvents(); - mockMvc.perform( - post("/oauth/token") - .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) - .param(OAuth2Utils.GRANT_TYPE, "password") - .param(OAuth2Utils.CLIENT_ID, "cf") - .param(REQUEST_TOKEN_FORMAT, OPAQUE.getStringValue()) - .param("client_secret", "") - .param("username", "marissa") - .param("password", "koala") - .param("mfaCode", String.valueOf(getMfaCodeFromCredentials(credentials))) - ) - .andDo(print()) - .andExpect(status().isOk()); - - - validateAuthEvents( - Collections.singletonList( - MfaAuthenticationSuccessEvent.class - ), "marissa" - ); - } - - private void validateAuthEvents(List> eventsExpected, String username) { - List events = new ArrayList(listener.getEvents()); - for (Class clazz : eventsExpected) { - for (AbstractUaaAuthenticationEvent auth : events) { - if (auth.getClass().equals(clazz)) { - UaaUser user = getUaaUser(auth); - assertEquals(username, user.getUsername()); - } - } - assertTrue(events.removeIf(e -> e.getClass().equals(clazz))); - } - } - - private UaaUser getUaaUser(AbstractUaaAuthenticationEvent auth) { - return (UaaUser)ReflectionTestUtils.getField(auth, "user"); - } - - @Test - void invalid_code() throws Exception { - mockMvc.perform( - post("/oauth/token") - .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) - .param(OAuth2Utils.GRANT_TYPE, "password") - .param(OAuth2Utils.CLIENT_ID, "cf") - .param(REQUEST_TOKEN_FORMAT, OPAQUE.getStringValue()) - .param("client_secret", "") - .param("username", "marissa") - .param("password", "koala") - .param("mfaCode", "1234") - ) - .andDo(print()) - .andExpect(status().isUnauthorized()) - .andExpect(jsonPath("error").value("unauthorized")) - .andExpect(jsonPath("error_description").value(containsString("Bad credentials"))); - validateAuthEvents( - Collections.singletonList( - MfaAuthenticationFailureEvent.class - ), "marissa" - ); - } - - @Test - void not_registered() throws Exception { - deleteMfaRegistrations(); - mockMvc.perform( - post("/oauth/token") - .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) - .param(OAuth2Utils.GRANT_TYPE, "password") - .param(OAuth2Utils.CLIENT_ID, "cf") - .param(REQUEST_TOKEN_FORMAT, OPAQUE.getStringValue()) - .param("client_secret", "") - .param("username", "marissa") - .param("password", "koala") - .param("mfaCode", "1234") - ) - .andDo(print()) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("error").value("invalid_request")) - .andExpect(jsonPath("error_description").value(containsString("register a multi-factor"))); - validateAuthEvents( - Collections.singletonList( - MfaAuthenticationFailureEvent.class - ), "marissa" - ); - } -} diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java index 8c0c946d905..996fcb4afb1 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java @@ -869,57 +869,6 @@ void authorizeEndpointWithPromptNone_WhenNotAuthenticated() throws Exception { assertTrue(url.startsWith(UaaUrlUtils.addQueryParameter(TEST_REDIRECT_URI, "error", "login_required"))); } - @Test - void testAuthorizeEndpointWithPromptNone_MfaRequired() throws Exception { - String clientId = "testclient" + generator.generate(); - BaseClientDetails clientDetails = new BaseClientDetails(clientId, null, "uaa.user,other.scope", "authorization_code,refresh_token", "uaa.resource", TEST_REDIRECT_URI); - clientDetails.setAutoApproveScopes(Collections.singletonList("uaa.user")); - clientDetails.setClientSecret("secret"); - clientDetails.addAdditionalInformation(ClientConstants.AUTO_APPROVE, Collections.singletonList("other.scope")); - clientDetails.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList("uaa")); - clientDetailsService.addClientDetails(clientDetails); - - String username = "testuser" + generator.generate(); - String userScopes = "uaa.user,other.scope"; - ScimUser developer = setUpUser(jdbcScimUserProvisioning, jdbcScimGroupMembershipManager, jdbcScimGroupProvisioning, username, userScopes, OriginKeys.UAA, IdentityZone.getUaaZoneId()); - super.setupForMfaPasswordGrant(developer.getId()); - - MockHttpSession session = getAuthenticatedSession(developer); - - String state = generator.generate(); - - MvcResult result = mockMvc.perform(get("/oauth/authorize") - .session(session) - .param(OAuth2Utils.RESPONSE_TYPE, "code") - .param(OAuth2Utils.STATE, state) - .param(OAuth2Utils.CLIENT_ID, clientId) - .param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI) - .param(ID_TOKEN_HINT_PROMPT, ID_TOKEN_HINT_PROMPT_NONE)) - .andDo(print()) - .andExpect(status().isFound()) - .andReturn(); - - String url = result.getResponse().getHeader("Location"); - assertTrue(url.startsWith(UaaUrlUtils.addQueryParameter(TEST_REDIRECT_URI, "error", "interaction_required"))); - - setAuthentication(session, developer, false, "mfa", "pwd", "otp"); - result = mockMvc.perform(get("/oauth/authorize") - .session(session) - .param(OAuth2Utils.RESPONSE_TYPE, "code") - .param(OAuth2Utils.STATE, state) - .param(OAuth2Utils.CLIENT_ID, clientId) - .param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI) - .param(ID_TOKEN_HINT_PROMPT, ID_TOKEN_HINT_PROMPT_NONE)) - .andDo(print()) - .andExpect(status().isFound()) - .andReturn(); - url = result.getResponse().getHeader("Location"); - assertThat(url, containsString(TEST_REDIRECT_URI)); - assertThat(url, not(containsString("error"))); - assertThat(url, not(containsString("login_required"))); - assertThat(url, not(containsString("interaction_required"))); - } - @Test void testAuthorizeEndpointWithPromptNone_ForcePasswordChangeRequired() throws Exception { String clientId = "testclient" + generator.generate(); @@ -934,7 +883,7 @@ void testAuthorizeEndpointWithPromptNone_ForcePasswordChangeRequired() throws Ex String userScopes = "uaa.user,other.scope"; ScimUser developer = setUpUser(jdbcScimUserProvisioning, jdbcScimGroupMembershipManager, jdbcScimGroupProvisioning, username, userScopes, OriginKeys.UAA, IdentityZone.getUaaZoneId()); MockHttpSession session = new MockHttpSession(); - setAuthentication(session, developer, true, "pwd", "mfa", "otp"); + setAuthentication(session, developer, true, "pwd", "otp"); String state = generator.generate(); @@ -952,7 +901,7 @@ void testAuthorizeEndpointWithPromptNone_ForcePasswordChangeRequired() throws Ex String url = result.getResponse().getHeader("Location"); assertTrue(url.startsWith(UaaUrlUtils.addQueryParameter(TEST_REDIRECT_URI, "error", "interaction_required"))); - setAuthentication(session, developer, false, "mfa", "pwd", "otp"); + setAuthentication(session, developer, false, "pwd", "otp"); result = mockMvc.perform(get("/oauth/authorize") .session(session) .param(OAuth2Utils.RESPONSE_TYPE, "code") diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java index e35102f571a..691fdc2217e 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java @@ -28,11 +28,6 @@ import org.cloudfoundry.identity.uaa.invitations.InvitationsRequest; import org.cloudfoundry.identity.uaa.invitations.InvitationsResponse; import org.cloudfoundry.identity.uaa.login.Prompt; -import org.cloudfoundry.identity.uaa.mfa.GoogleMfaProviderConfig; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; -import org.cloudfoundry.identity.uaa.mfa.MfaProviderProvisioning; -import org.cloudfoundry.identity.uaa.mfa.UserGoogleMfaCredentials; -import org.cloudfoundry.identity.uaa.mfa.exception.MfaAlreadyExistsException; import org.cloudfoundry.identity.uaa.oauth.client.ClientDetailsModification; import org.cloudfoundry.identity.uaa.oauth.token.TokenConstants; import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; @@ -193,113 +188,11 @@ public static T getEventOfType(ArgumentCaptor captor, Clas return null; } - public static String performMfaPostVerifyWithCode(int code, MockMvc mvc, MockHttpSession session) throws Exception { - return performMfaPostVerifyWithCode(code, mvc, session, "localhost"); - } - - public static String performMfaPostVerifyWithCode(int code, MockMvc mvc, MockHttpSession session, String host) throws Exception { - return mvc.perform(post("/login/mfa/verify.do") - .param("code", Integer.toString(code)) - .header("Host", host) - .session(session) - .with(cookieCsrf())) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/login/mfa/completed")) - .andReturn().getResponse().getRedirectedUrl(); - } - - public static int getMFACodeFromSession(MockHttpSession session) { - UserGoogleMfaCredentials activeCreds = (UserGoogleMfaCredentials) session.getAttribute("uaaMfaCredentials"); - return getMfaCodeFromCredentials(activeCreds); - } - - public static int getMfaCodeFromCredentials(UserGoogleMfaCredentials activeCreds) { - GoogleAuthenticator authenticator = new GoogleAuthenticator(new GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder().build()); - return authenticator.getTotpPassword(activeCreds.getSecretKey()); - } - public static UaaAuthentication getUaaAuthentication(HttpSession session) { SecurityContext context = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); return (UaaAuthentication) context.getAuthentication(); } - public static ResultActions performMfaRegistrationInZone(String username, String password, MockMvc mockMvc, String host, String[] firstAuthMethods, String[] afterMfaAuthMethods) throws Exception { - - //ldap login - MockHttpSession session = (MockHttpSession) mockMvc.perform( - post("/login.do") - .with(cookieCsrf()) - .header(HOST, host) - .accept(MediaType.TEXT_HTML) - .param("username", username) - .param("password", password) - ) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/")) - .andReturn().getRequest().getSession(false); - - assertTrue(getUaaAuthentication(session).isAuthenticated()); - assertThat(getUaaAuthentication(session).getAuthenticationMethods(), containsInAnyOrder(firstAuthMethods)); - - //successful login, follow redirect - mockMvc.perform( - get("/") - .header(HOST, host) - .accept(MediaType.TEXT_HTML) - .session(session) - ) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login/mfa/register")); - - //follow redirect to mfa register - mockMvc.perform( - get("/login/mfa/register") - .header(HOST, host) - .accept(MediaType.TEXT_HTML) - .session(session) - ) - .andExpect(status().isOk()) - .andExpect(view().name("mfa/qr_code")); - - //post MFA code - int code = MockMvcUtils.getMFACodeFromSession(session); - String location = MockMvcUtils.performMfaPostVerifyWithCode(code, mockMvc, session, host); - //follow redirect to completed - location = mockMvc.perform(get(location) - .session(session) - .header(HOST, host) - ) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://" + host + "/")) - .andReturn().getResponse().getRedirectedUrl(); - - ResultActions resultActions = mockMvc.perform(get(location) - .session(session) - .header(HOST, host) - ); - - assertTrue(getUaaAuthentication(session).isAuthenticated()); - assertThat(getUaaAuthentication(session).getAuthenticationMethods(), containsInAnyOrder(afterMfaAuthMethods)); - - return resultActions; - } - - public static MfaProvider createMfaProvider(ApplicationContext context, IdentityZone zone) { - String zoneId = zone.getId(); - MfaProvider provider = new MfaProvider(); - provider.setName(new AlphanumericRandomValueStringGenerator(5).generate().toLowerCase()); - provider.setType(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR); - provider.setIdentityZoneId(zoneId); - provider.setConfig(new GoogleMfaProviderConfig()); - provider.getConfig().setIssuer(zone.getName()); - MfaProviderProvisioning provisioning = context.getBean(MfaProviderProvisioning.class); - try { - return provisioning.create(provider, zoneId); - } catch (MfaAlreadyExistsException x) { - return provisioning.update(provider, zoneId); - } - } - public static File getLimitedModeStatusFile(ApplicationContext context) { return context.getBean(LimitedModeUaaFilter.class).getStatusFile(); } @@ -1451,18 +1344,6 @@ public String generate() { } } - public static MfaProvider constructGoogleMfaProvider() { - MfaProvider res = new MfaProvider(); - res.setName(new AlphanumericRandomValueStringGenerator(5).generate()); - res.setType(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR); - res.setConfig(constructGoogleProviderConfiguration()); - return res; - } - - public static GoogleMfaProviderConfig constructGoogleProviderConfiguration() { - return new GoogleMfaProviderConfig(); - } - public static IdentityZone updateZone(MockMvc mockMvc, IdentityZone updatedZone) throws Exception { String token = getClientCredentialsOAuthAccessToken(mockMvc, "admin", "adminsecret", "uaa.admin", null); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointDocs.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointDocs.java index a6f59255263..e27bf9d8095 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointDocs.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointDocs.java @@ -161,9 +161,6 @@ class IdentityZoneEndpointDocs extends EndpointDocs { private static final String DEFAULT_ZONE_GROUPS_DESC = "Default groups each user in the zone inherits."; private static final String ALLOWED_ZONE_GROUPS_DESC = "Allowed groups in the zone. Defaults to null (all groups allowed)"; private static final String SERVICE_PROVIDER_ID = "cloudfoundry-saml-login"; - private static final String MFA_CONFIG_ENABLED_DESC = "Set `true` to enable Multi-factor Authentication (MFA) for the current zone. Defaults to `false`"; - private static final String MFA_CONFIG_PROVIDER_NAME_DESC = "The unique `name` of the MFA provider to use for this zone."; - private static final String MFA_CONFIG_IDENTITY_PROVIDER_DESC = "Only trigger MFA when user is using an identity provider whose origin key matches one of these values"; private static final String ZONE_ISSUER_DESC = "Issuer of this zone. Must be a valid URL."; private static final String DEFAULT_IDP_DESC = "This value can be set to the origin key of an identity provider. If set, the user will be directed to this identity provider automatically if no other identity provider is discovered or selected via login_hint."; private static final String DEFAULT_ISSUER_URI = "http://localhost:8080/uaa"; @@ -315,10 +312,6 @@ void createIdentityZone() throws Exception { fieldWithPath("config.userConfig.maxUsers").description(USER_CONFIG_USER_LIMIT_DESCRIPTION).attributes(key("constraints").value(USER_CONFIG_USER_LIMIT_CONSTRAINT)).optional().type(NUMBER), fieldWithPath("config.userConfig.checkOriginEnabled").description(USER_CONFIG_CHECK_ORIGIN_ENABLED).attributes(key("constraints").value("Optional")).optional().type(BOOLEAN), - fieldWithPath("config.mfaConfig.enabled").description(MFA_CONFIG_ENABLED_DESC).attributes(key("constraints").value("Optional")), - fieldWithPath("config.mfaConfig.providerName").description(MFA_CONFIG_PROVIDER_NAME_DESC).attributes(key("constraints").value("Required when `config.mfaConfig.enabled` is `true`")).optional().type(STRING), - fieldWithPath("config.mfaConfig.identityProviders").description(MFA_CONFIG_IDENTITY_PROVIDER_DESC).attributes(key("constraints").value("Optional")).optional().type(ARRAY), - fieldWithPath("created").ignored(), fieldWithPath("last_modified").ignored() }; @@ -481,10 +474,6 @@ void getAllIdentityZones() throws Exception { fieldWithPath("[].config.userConfig.maxUsers").description(USER_CONFIG_USER_LIMIT_DESCRIPTION).attributes(key("constraints").value(USER_CONFIG_USER_LIMIT_CONSTRAINT)).optional().type(NUMBER), fieldWithPath("[].config.userConfig.checkOriginEnabled").description(USER_CONFIG_CHECK_ORIGIN_ENABLED).attributes(key("constraints").value("optional")).optional().type(BOOLEAN), - fieldWithPath("[].config.mfaConfig.enabled").optional().description(MFA_CONFIG_ENABLED_DESC).attributes(key("constraints").value("Optional")), - fieldWithPath("[].config.mfaConfig.providerName").optional().description(MFA_CONFIG_PROVIDER_NAME_DESC).attributes(key("constraints").value("Required when `config.mfaConfig.enabled` is `true`")).optional().type(STRING), - fieldWithPath("[].config.mfaConfig.identityProviders").optional().description(MFA_CONFIG_IDENTITY_PROVIDER_DESC).attributes(key("constraints").value("Optional")).optional().type(ARRAY), - fieldWithPath("[].created").ignored(), fieldWithPath("[].last_modified").ignored() ); @@ -631,10 +620,6 @@ void updateIdentityZone() throws Exception { fieldWithPath("config.userConfig.maxUsers").description(USER_CONFIG_USER_LIMIT_DESCRIPTION).attributes(key("constraints").value(USER_CONFIG_USER_LIMIT_CONSTRAINT)).optional().type(NUMBER), fieldWithPath("config.userConfig.checkOriginEnabled").description(USER_CONFIG_CHECK_ORIGIN_ENABLED).attributes(key("constraints").value("Optional")).optional().type(BOOLEAN), - fieldWithPath("config.mfaConfig.enabled").description(MFA_CONFIG_ENABLED_DESC).attributes(key("constraints").value("Optional")), - fieldWithPath("config.mfaConfig.providerName").description(MFA_CONFIG_PROVIDER_NAME_DESC).attributes(key("constraints").value("Required when `config.mfaConfig.enabled` is `true`")).optional().type(STRING), - fieldWithPath("config.mfaConfig.identityProviders").description(MFA_CONFIG_IDENTITY_PROVIDER_DESC).attributes(key("constraints").value("Optional")).optional().type(ARRAY), - fieldWithPath("created").ignored(), fieldWithPath("last_modified").ignored() ); @@ -818,9 +803,6 @@ private Snippet getResponseFields() { fieldWithPath("config.userConfig.maxUsers").description(USER_CONFIG_USER_LIMIT_DESCRIPTION), fieldWithPath("config.userConfig.checkOriginEnabled").description(USER_CONFIG_CHECK_ORIGIN_ENABLED).optional().type(BOOLEAN), - fieldWithPath("config.mfaConfig.enabled").description(MFA_CONFIG_ENABLED_DESC), - fieldWithPath("config.mfaConfig.providerName").description(MFA_CONFIG_PROVIDER_NAME_DESC).optional().type(STRING), - fieldWithPath("config.mfaConfig.identityProviders").description(MFA_CONFIG_IDENTITY_PROVIDER_DESC).optional().type(ARRAY), fieldWithPath("created").ignored(), fieldWithPath("last_modified").ignored() ); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index c29d1be61bc..a349fec8528 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -12,8 +12,6 @@ import org.cloudfoundry.identity.uaa.client.event.ClientCreateEvent; import org.cloudfoundry.identity.uaa.client.event.ClientDeleteEvent; import org.cloudfoundry.identity.uaa.login.util.RandomValueStringGenerator; -import org.cloudfoundry.identity.uaa.mfa.GoogleMfaProviderConfig; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.IdentityZoneCreationResult; import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; @@ -421,36 +419,6 @@ void testCreateZone() throws Exception { createZoneReturn(); } - @Test - void testCreateZoneWithMfaConfigWithIdentityProviders() throws Exception { - String id = generator.generate(); - - IdentityZoneConfiguration zoneConfiguration = new IdentityZoneConfiguration(); - zoneConfiguration.getMfaConfig().setIdentityProviders(Lists.newArrayList("uaa", "ldap")); - - IdentityZone zone = createZone(id, HttpStatus.CREATED, identityClientToken, zoneConfiguration); - - assertThat(zone.getConfig().getMfaConfig().getIdentityProviders(), hasItems("uaa", "ldap")); - - IdentityZone checkZone = getIdentityZone(zone.getId(), HttpStatus.OK, identityClientToken); - assertThat(checkZone.getConfig().getMfaConfig().getIdentityProviders(), hasItems("uaa", "ldap")); - } - - @Test - void testCreateZoneWithMfaConfigWithoutIdentityProviders_returnsDefaultProviders() throws Exception { - String id = generator.generate(); - - IdentityZoneConfiguration zoneConfiguration = new IdentityZoneConfiguration(); - zoneConfiguration.getMfaConfig().setIdentityProviders(null); - - IdentityZone zone = createZone(id, HttpStatus.CREATED, identityClientToken, zoneConfiguration); - - assertThat(zone.getConfig().getMfaConfig().getIdentityProviders(), hasItems("uaa", "ldap")); - - IdentityZone checkZone = getIdentityZone(zone.getId(), HttpStatus.OK, identityClientToken); - assertThat(checkZone.getConfig().getMfaConfig().getIdentityProviders(), hasItems("uaa", "ldap")); - } - @Test void updateZoneCreatesGroups() throws Exception { IdentityZone zone = createZoneReturn(); @@ -2072,46 +2040,6 @@ void userCanReadAZone_withZoneZoneIdReadToken() throws Exception { assertEquals("kid", zoneResult.getConfig().getTokenPolicy().getActiveKeyId()); } - @Test - void createZoneWithMfaConfigIsNotSupported() throws Exception { - MfaProvider mfaProvider = createGoogleMfaProvider(null); - String zoneId = new RandomValueStringGenerator(5).generate(); - String zoneContent = "{\"id\" : \"" + zoneId + "\", \"name\" : \"" + zoneId + "\", \"subdomain\" : \"" + zoneId + "\", \"config\" : { \"mfaConfig\" : {\"enabled\" : true, \"providerName\" : \"" + mfaProvider.getName() + "\"}}}"; - mockMvc.perform(post("/identity-zones") - .header("Authorization", "Bearer " + adminToken) - .contentType(APPLICATION_JSON) - .content(zoneContent)) - .andExpect(status().isUnprocessableEntity()) - .andReturn().getResponse(); - } - - @Test - void updateZoneWithValidMfaConfig() throws Exception { - IdentityZone identityZone = createZone(new RandomValueStringGenerator(5).generate(), HttpStatus.CREATED, adminToken, new IdentityZoneConfiguration()); - MfaProvider mfaProvider = createGoogleMfaProvider(identityZone.getId()); - identityZone.getConfig().setMfaConfig(new MfaConfig().setProviderName(mfaProvider.getName())); - - IdentityZone updatedZone = updateZone(identityZone, HttpStatus.OK, adminToken); - - assertEquals(mfaProvider.getName(), updatedZone.getConfig().getMfaConfig().getProviderName()); - assertFalse(updatedZone.getConfig().getMfaConfig().isEnabled()); - } - - @Test - void updateZoneWithValidMfaConfigWithoutIdInBody_Succeeds() throws Exception { - IdentityZone identityZone = createZone(new RandomValueStringGenerator(5).generate(), HttpStatus.CREATED, adminToken, new IdentityZoneConfiguration()); - MfaProvider mfaProvider = createGoogleMfaProvider(identityZone.getId()); - assert mfaProvider.getName() != null; - identityZone.getConfig().setMfaConfig(new MfaConfig().setEnabled(true).setProviderName(mfaProvider.getName())); - String id = identityZone.getId(); - identityZone.setId(null); - - IdentityZone updatedZone = updateZone(id, identityZone, HttpStatus.OK, adminToken); - - assertEquals(mfaProvider.getName(), updatedZone.getConfig().getMfaConfig().getProviderName()); - assertTrue(updatedZone.getConfig().getMfaConfig().isEnabled()); - } - @Test void updateZoneWithDifferentIdInBodyAndPath_fails() throws Exception { IdentityZone identityZone = createZone(new RandomValueStringGenerator(5).generate(), HttpStatus.CREATED, adminToken, new IdentityZoneConfiguration()); @@ -2122,14 +2050,6 @@ void updateZoneWithDifferentIdInBodyAndPath_fails() throws Exception { updateZone(id, identityZone, HttpStatus.UNPROCESSABLE_ENTITY, adminToken); } - @Test - void updateZoneWithInvalidMfaConfig() throws Exception { - IdentityZone identityZone = createZone(new RandomValueStringGenerator(5).generate(), HttpStatus.CREATED, adminToken, new IdentityZoneConfiguration()); - identityZone.getConfig().setMfaConfig(new MfaConfig().setProviderName("INVALID_NAME")); - - updateZone(identityZone, HttpStatus.UNPROCESSABLE_ENTITY, adminToken); - } - @Test void testCreateZone_withCustomIssuerAndSigningKeyWorks() throws Exception { IdentityZoneConfiguration identityZoneConfiguration = new IdentityZoneConfiguration(); @@ -2328,26 +2248,6 @@ private IdentityZone createZoneUsingToken(String token) throws Exception { new IdentityZoneConfiguration()); } - private MfaProvider createGoogleMfaProvider(String zoneId) throws Exception { - String providerName = new RandomValueStringGenerator(5).generate(); - final MfaProvider wantedMfaConfig = - new MfaProvider().setName(providerName); - MockHttpServletRequestBuilder createMfaRequest = post("/mfa-providers") - .header("Authorization", "Bearer " + adminToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(wantedMfaConfig)); - if (hasText(zoneId)) { - createMfaRequest.header("X-Identity-Zone-Id", zoneId); - } - MockHttpServletResponse mfaProviderResponse = mockMvc.perform(createMfaRequest) - .andDo(print()) - .andExpect(status().isCreated()) - .andReturn() - .getResponse(); - final MfaProvider createdMfaConfig = JsonUtils.readValue(mfaProviderResponse.getContentAsString(), MfaProvider.class); - return createdMfaConfig; - } - private IdentityZone getIdentityZone(String id, HttpStatus expect, String token) throws Exception { MvcResult result = mockMvc.perform( get("/identity-zones/" + id) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointDocs.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointDocs.java index 7a8e7d9d5bd..60cfa75dcdf 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointDocs.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointDocs.java @@ -781,32 +781,4 @@ void directlyVerifyUser() throws Exception { pathParameters, requestHeaders)) ; } - - @Test - void deleteMfaRegistration() throws Exception { - String accessToken = testClient.getClientCredentialsOAuthAccessToken("admin", "adminsecret", "uaa.admin"); - - String email = "tom.mugwort@example.com"; - ScimUser tommy = new ScimUser(null, email, "Tom", "Mugwort"); - tommy.setVerified(false); - tommy.addEmail(email); - tommy = userProvisioning.createUser(tommy, "pas5Word", IdentityZoneHolder.get().getId()); - - Snippet requestHeaders = requestHeaders(headerWithName("Authorization").description("Access token with `zones..admin` or `uaa.admin` required."), - IDENTITY_ZONE_ID_HEADER, - IDENTITY_ZONE_SUBDOMAIN_HEADER); - - Snippet pathParameters = pathParameters( - RequestDocumentation.parameterWithName("userId").description("Unique user identifier.") - ); - - MockHttpServletRequestBuilder delete = RestDocumentationRequestBuilders.delete("/Users/{userId}/mfa", tommy.getId()) - .header("Authorization", "Bearer " + accessToken); - - mockMvc.perform(delete) - .andExpect(status().isOk()) - .andDo(document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()), - pathParameters, requestHeaders)) - ; - } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java index 4b0e5907629..faaec51d849 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java @@ -12,13 +12,6 @@ import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.mfa.GoogleMfaProviderConfig; -import org.cloudfoundry.identity.uaa.mfa.JdbcMfaProviderProvisioning; -import org.cloudfoundry.identity.uaa.mfa.JdbcUserGoogleMfaCredentialsProvisioning; -import org.cloudfoundry.identity.uaa.mfa.MfaProvider; -import org.cloudfoundry.identity.uaa.mfa.MfaProviderProvisioning; -import org.cloudfoundry.identity.uaa.mfa.UserGoogleMfaCredentials; -import org.cloudfoundry.identity.uaa.mfa.exception.UserMfaConfigDoesNotExistException; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; @@ -39,7 +32,6 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; import org.cloudfoundry.identity.uaa.zone.JdbcIdentityZoneProvisioning; -import org.cloudfoundry.identity.uaa.zone.MfaConfig; import org.json.JSONObject; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -115,8 +107,6 @@ class ScimUserEndpointsMockMvcTests { private ScimUserProvisioning usersRepository; private JdbcIdentityProviderProvisioning identityProviderProvisioning; private ExpiringCodeStore codeStore; - private JdbcUserGoogleMfaCredentialsProvisioning mfaCredentialsProvisioning; - private MfaProviderProvisioning mfaProviderProvisioning; @Value("${userMaxCount}") private int usersMaxCount; @@ -129,8 +119,6 @@ class ScimUserEndpointsMockMvcTests { @Autowired private JdbcIdentityZoneProvisioning jdbcIdentityZoneProvisioning; - @Autowired - private JdbcMfaProviderProvisioning jdbcMfaProviderProvisioning; @BeforeEach void setUp() throws Exception { @@ -147,16 +135,9 @@ void setUp() throws Exception { usersRepository = webApplicationContext.getBean(ScimUserProvisioning.class); identityProviderProvisioning = webApplicationContext.getBean(JdbcIdentityProviderProvisioning.class); codeStore = webApplicationContext.getBean(ExpiringCodeStore.class); - mfaCredentialsProvisioning = webApplicationContext.getBean(JdbcUserGoogleMfaCredentialsProvisioning.class); - mfaProviderProvisioning = webApplicationContext.getBean(JdbcMfaProviderProvisioning.class); uaaAdminToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret, "uaa.admin"); } - @AfterEach - void tearDown() { - jdbcIdentityZoneProvisioning.retrieveAll().forEach(identityZone -> jdbcMfaProviderProvisioning.deleteByIdentityZone(identityZone.getId())); - } - @Test void unauthorized_put_returns_401() throws Exception { mockMvc.perform( @@ -1196,143 +1177,6 @@ void testCreateUserWithEmailDomainNotAllowedForOriginUaa() throws Exception { } } - @Test - void testDeleteMfaUserCredentials() throws Exception { - ScimUser user = createUser(uaaAdminToken); - MfaProvider provider = createMfaProvider(IdentityZoneHolder.get().getId()); - IdentityZoneHolder.get().getConfig().setMfaConfig(new MfaConfig().setEnabled(true).setProviderName("mfaProvider")); - UserGoogleMfaCredentials creds = new UserGoogleMfaCredentials(user.getId(), "ABCDEFGHIJKLMNOP", 1234, - Collections.singletonList(123456)).setMfaProviderId(provider.getId()); - mfaCredentialsProvisioning.save(creds, IdentityZoneHolder.get().getId()); - - assertNotNull(mfaCredentialsProvisioning.retrieve(user.getId(), provider.getId())); - - MockHttpServletRequestBuilder delete = delete("/Users/" + user.getId() + "/mfa") - .header("Authorization", "Bearer " + uaaAdminToken) - .contentType(APPLICATION_JSON); - - mockMvc.perform(delete) - .andExpect(status().isOk()); - - assertMfaCredentialsNotExisting(user, provider); - } - - @Test - void testDeleteMfaUserCredentialsWithZoneSwitching() throws Exception { - IdentityZone identityZone = getIdentityZone(); - String authorities = "zones." + identityZone.getId() + ".admin"; - clientDetails = MockMvcUtils.createClient(mockMvc, uaaAdminToken, "switchClientId", "switchClientSecret", null, null, Collections.singletonList("client_credentials"), authorities, null, IdentityZone.getUaa()); - String uaaAdminTokenFromOtherZone = testClient.getClientCredentialsOAuthAccessToken("switchClientId", "switchClientSecret", authorities); - ScimUser user = setUpScimUser(identityZone); - MfaProvider provider = createMfaProvider(identityZone.getId()); - identityZone.getConfig().setMfaConfig(new MfaConfig().setEnabled(true).setProviderName("mfaProvider")); - MockMvcUtils.updateIdentityZone(identityZone, webApplicationContext); - UserGoogleMfaCredentials creds = new UserGoogleMfaCredentials(user.getId(), "ABCDEFGHIJKLMNOP", 1234, - Collections.singletonList(123456)).setMfaProviderId(provider.getId()); - mfaCredentialsProvisioning.save(creds, identityZone.getId()); - - assertNotNull(mfaCredentialsProvisioning.retrieve(user.getId(), provider.getId())); - - MockHttpServletRequestBuilder delete = delete("/Users/" + user.getId() + "/mfa") - .header("Authorization", "Bearer " + uaaAdminTokenFromOtherZone) - .header(HEADER, identityZone.getId()) - .contentType(APPLICATION_JSON); - - mockMvc.perform(delete) - .andExpect(status().isOk()); - - assertMfaCredentialsNotExisting(user, provider); - } - - @Test - void testDeleteMfaUserCredentialsNotAuthorized() throws Exception { - ScimUser user = createUser(uaaAdminToken); - MockHttpServletRequestBuilder delete = delete("/Users/" + user.getId() + "/mfa") - .header("Authorization", "Bearer " + scimCreateToken) - .contentType(APPLICATION_JSON); - - mockMvc.perform(delete) - .andExpect(status().isForbidden()); - } - - @Test - void testDeleteMfaUserCredentialsUserDoesNotExist() throws Exception { - MfaProvider provider = createMfaProvider(IdentityZoneHolder.get().getId()); - IdentityZoneHolder.get().getConfig().setMfaConfig(new MfaConfig().setEnabled(true).setProviderName("mfaProvider")); - String userId = "invalidUserId"; - - MockHttpServletRequestBuilder delete = delete("/Users/" + userId + "/mfa") - .header("Authorization", "Bearer " + uaaAdminToken) - .contentType(APPLICATION_JSON); - - mockMvc.perform(delete) - .andExpect(status().isNotFound()); - - try { - mfaCredentialsProvisioning.retrieve(userId, provider.getId()); - fail(); - } catch (UserMfaConfigDoesNotExistException e) { - //no op - } - } - - @Test - void testDeleteMfaUserCredentialsUserNotRegistered() throws Exception { - ScimUser user = createUser(uaaAdminToken); - MfaProvider provider = createMfaProvider(IdentityZoneHolder.get().getId()); - IdentityZoneHolder.get().getConfig().setMfaConfig(new MfaConfig().setEnabled(true).setProviderName("mfaProvider")); - - assertMfaCredentialsNotExisting(user, provider); - - MockHttpServletRequestBuilder delete = delete("/Users/" + user.getId() + "/mfa") - .header("Authorization", "Bearer " + uaaAdminToken) - .contentType(APPLICATION_JSON); - - mockMvc.perform(delete) - .andExpect(status().isOk()); - - assertMfaCredentialsNotExisting(user, provider); - } - - @Test - void testDeleteMfaUserCredentialsMfaNotEnabled() throws Exception { - ScimUser user = createUser(uaaAdminToken); - MfaProvider provider = createMfaProvider(IdentityZoneHolder.get().getId()); - IdentityZoneHolder.get().getConfig().setMfaConfig(new MfaConfig().setEnabled(false)); - UserGoogleMfaCredentials creds = new UserGoogleMfaCredentials(user.getId(), "ABCDEFGHIJKLMNOP", 1234, - Collections.singletonList(123456)).setMfaProviderId(provider.getId()); - mfaCredentialsProvisioning.save(creds, IdentityZoneHolder.get().getId()); - - assertNotNull(mfaCredentialsProvisioning.retrieve(user.getId(), provider.getId())); - - MockHttpServletRequestBuilder delete = delete("/Users/" + user.getId() + "/mfa") - .header("Authorization", "Bearer " + uaaAdminToken) - .contentType(APPLICATION_JSON); - - mockMvc.perform(delete) - .andExpect(status().isOk()); - - assertMfaCredentialsNotExisting(user, provider); - } - - private MfaProvider createMfaProvider(String identityZoneId) { - String index = generator.generate(); - String mfaProviderId = "mfaProviderId" + index; - String mfaProviderName = "mfaProvider" + index; - MfaProvider provider = new MfaProvider().setName(mfaProviderName).setId(mfaProviderId).setType(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR).setConfig(new GoogleMfaProviderConfig()).setIdentityZoneId(identityZoneId); - mfaProviderProvisioning.create(provider, identityZoneId); - return provider; - } - - private void assertMfaCredentialsNotExisting(ScimUser user, MfaProvider provider) { - try { - mfaCredentialsProvisioning.retrieve(user.getId(), provider.getId()); - fail(); - } catch (UserMfaConfigDoesNotExistException e) { - //no op - } - } - private MockHttpServletRequestBuilder setUpVerificationLinkRequest(ScimUser user, String token) { return MockMvcRequestBuilders.get("/Users/" + user.getId() + "/verify-link") .header("Authorization", "Bearer " + token) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java index 99f3dce5e73..4faa121d036 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java @@ -7,7 +7,6 @@ import org.cloudfoundry.identity.uaa.approval.Approval; import org.cloudfoundry.identity.uaa.approval.ApprovalStore; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.mfa.JdbcUserGoogleMfaCredentialsProvisioning; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; @@ -37,7 +36,6 @@ import org.cloudfoundry.identity.uaa.test.ZoneSeederExtension; import org.cloudfoundry.identity.uaa.web.ConvertingExceptionView; import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.MfaConfig; import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManagerImpl; import org.junit.jupiter.api.BeforeEach; @@ -144,7 +142,6 @@ class ScimUserEndpointsTests { private ScimUser dale; private PasswordValidator mockPasswordValidator; - private JdbcUserGoogleMfaCredentialsProvisioning mockJdbcUserGoogleMfaCredentialsProvisioning; private JdbcIdentityProviderProvisioning mockJdbcIdentityProviderProvisioning; private ApprovalStore mockApprovalStore; @@ -185,7 +182,6 @@ void setUpAfterSeeding(final IdentityZone identityZone) { jdbcScimUserProvisioning.setQueryConverter(filterConverter); mockJdbcIdentityProviderProvisioning = mock(JdbcIdentityProviderProvisioning.class); - mockJdbcUserGoogleMfaCredentialsProvisioning = mock(JdbcUserGoogleMfaCredentialsProvisioning.class); mockPasswordValidator = mock(PasswordValidator.class); ApplicationEventPublisher mockApplicationEventPublisher = mock(ApplicationEventPublisher.class); @@ -210,7 +206,6 @@ void setUpAfterSeeding(final IdentityZone identityZone) { statuses, mockPasswordValidator, null, - mockJdbcUserGoogleMfaCredentialsProvisioning, mockApprovalStore, spiedScimGroupMembershipManager, 5); @@ -703,7 +698,7 @@ void findUsersApprovalsNotSyncedIfNotIncluded() { void whenSettingAnInvalidUserMaxCount_ScimUsersEndpointShouldThrowAnException() { assertThrowsWithMessageThat( IllegalArgumentException.class, - () -> new ScimUserEndpoints(null, null, null, null, null, null, null, null, null, null, null, 0), + () -> new ScimUserEndpoints(null, null, null, null, null, null, null, null, null, null, 0), containsString("Invalid \"userMaxCount\" value (got 0). Should be positive number.")); } @@ -711,7 +706,7 @@ void whenSettingAnInvalidUserMaxCount_ScimUsersEndpointShouldThrowAnException() void whenSettingANegativeValueUserMaxCount_ScimUsersEndpointShouldThrowAnException() { assertThrowsWithMessageThat( IllegalArgumentException.class, - () -> new ScimUserEndpoints(null, null, null, null, null, null, null, null, null, null, null, -1), + () -> new ScimUserEndpoints(null, null, null, null, null, null, null, null, null, null, -1), containsString("Invalid \"userMaxCount\" value (got -1). Should be positive number.")); } @@ -1148,32 +1143,6 @@ void userWithNoOriginGetsDefaultUaa() { assertEquals(OriginKeys.UAA, createdUser.getOrigin()); } - @Test - void deleteMfaRegistration() { - identityZone.getConfig().setMfaConfig(new MfaConfig().setEnabled(true).setProviderName("mfaProvider")); - scimUserEndpoints.deleteMfaRegistration(dale.getId()); - - verify(mockJdbcUserGoogleMfaCredentialsProvisioning).delete(dale.getId()); - } - - @Test - void deleteMfaRegistrationUserDoesNotExist() { - assertThrows(ScimResourceNotFoundException.class, () -> scimUserEndpoints.deleteMfaRegistration("invalidUserId")); - } - - @Test - void deleteMfaRegistrationNoMfaConfigured() { - identityZone.getConfig().setMfaConfig(new MfaConfig().setEnabled(true).setProviderName("mfaProvider")); - scimUserEndpoints.deleteMfaRegistration(dale.getId()); - } - - @Test - void deleteMfaRegistrationMfaNotEnabledInZone() { - identityZone.getConfig().setMfaConfig(new MfaConfig().setEnabled(false)); - - scimUserEndpoints.deleteMfaRegistration(dale.getId()); - } - private void validatePasswordForUaaOriginOnly(VerificationMode verificationMode, String origin, String expectedPassword) { ScimUser user = new ScimUser(null, generator.generate(), "GivenName", "FamilyName"); user.setOrigin(origin);