diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/AbstractMfaProviderConfig.java b/model/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/AbstractMfaProviderConfig.java index a0c84ee77d..2532ac2861 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/AbstractMfaProviderConfig.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/AbstractMfaProviderConfig.java @@ -26,4 +26,18 @@ public T setIssuer(String issuer) { return (T) 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 issuer != null ? issuer.equals(that.issuer) : that.issuer == null; + } + + @Override + public int hashCode() { + return issuer != null ? issuer.hashCode() : 0; + } } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/GoogleMfaProviderConfig.java b/model/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/GoogleMfaProviderConfig.java index 1c68778327..cb5686e925 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/GoogleMfaProviderConfig.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/GoogleMfaProviderConfig.java @@ -1,10 +1,6 @@ package org.cloudfoundry.identity.uaa.mfa_provider; -import org.springframework.util.StringUtils; - -import static org.springframework.util.StringUtils.hasText; - public class GoogleMfaProviderConfig extends AbstractMfaProviderConfig { public enum Algorithm { SHA256, SHA512 } @@ -52,4 +48,29 @@ public GoogleMfaProviderConfig setAlgorithm(Algorithm algorithm) { this.algorithm = algorithm; 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 (digits != that.digits) return false; + if (duration != that.duration) return false; + if (providerDescription != null ? !providerDescription.equals(that.providerDescription) : that.providerDescription != null) + return false; + if(algorithm != that.algorithm) return false; + return super.equals(that); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result += providerDescription != null ? providerDescription.hashCode() : 0; + result = 31 * result + digits; + result = 31 * result + duration; + result = 31 * result + (algorithm != null ? algorithm.hashCode() : 0); + return result; + } } \ No newline at end of file diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/MfaProvider.java b/model/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/MfaProvider.java index f270e8f748..6a9a19565e 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/MfaProvider.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/MfaProvider.java @@ -1,66 +1,113 @@ package org.cloudfoundry.identity.uaa.mfa_provider; +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.core.JsonProcessingException; 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 com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.provider.KeystoneIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.provider.RawXOAuthIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.springframework.util.StringUtils; -import javax.xml.bind.ValidationException; - import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.KEYSTONE; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LDAP; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OAUTH20; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OIDC10; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.SAML; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UNKNOWN; import static org.cloudfoundry.identity.uaa.util.JsonUtils.getNodeAsBoolean; +import static org.cloudfoundry.identity.uaa.util.JsonUtils.getNodeAsDate; import static org.cloudfoundry.identity.uaa.util.JsonUtils.getNodeAsString; -import static org.cloudfoundry.identity.uaa.util.JsonUtils.readValue; @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_ACTIVE = "active"; + 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 boolean active = true; + private AbstractMfaProviderConfig config; + private MfaProviderType type; + private Date created; + @JsonProperty("last_modified") + private Date lastModified; - enum MfaProviderType { - GOOGLE_AUTHENTICATOR + + public Date getCreated() { + return created; } - public AbstractMfaProviderConfig getConfig() { - return config; + public MfaProvider setCreated(Date created) { + this.created = created; + return this; } - public MfaProvider setConfig(T config) { + 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 T getConfig() { + return (T) config; + } + + public MfaProvider setConfig(T config) { this.config = config; return this; } @@ -69,11 +116,12 @@ public String getId() { return id; } - public void setId(String id) { + public MfaProvider setId(String id) { this.id = id; + return this; } - public Boolean getActive() { + public Boolean isActive() { return active; } @@ -86,7 +134,7 @@ public String getName() { return name; } - public MfaProvider setName(String name) { + public MfaProvider setName(String name) { this.name = name; return this; } @@ -95,7 +143,7 @@ public MfaProviderType getType() { return type; } - public MfaProvider setType(MfaProviderType type) { + public MfaProvider setType(MfaProviderType type) { this.type = type; return this; } @@ -113,6 +161,9 @@ public void validate() { if(config == null) { throw new IllegalArgumentException("Provider config must be set"); } + if(!StringUtils.hasText(identityZoneId)){ + throw new IllegalArgumentException("Provider must belong to a zone"); + } config.validate(); } @@ -125,7 +176,7 @@ public MfaProvider deserialize(JsonParser p, DeserializationContext ctxt) throws JsonNode node = JsonUtils.readTree(p); MfaProviderType type; try { - type = MfaProviderType.valueOf(getNodeAsString(node, FIELD_TYPE, MfaProviderType.GOOGLE_AUTHENTICATOR.name())); + type = MfaProviderType.forValue(getNodeAsString(node, FIELD_TYPE, "google-authenticator")); } catch(IllegalArgumentException e) { type = null; } @@ -148,9 +199,9 @@ public MfaProvider deserialize(JsonParser p, DeserializationContext ctxt) throws result.setName(getNodeAsString(node, FIELD_NAME, null)); result.setId(getNodeAsString(node, FIELD_ID, null)); result.setActive(getNodeAsBoolean(node, FIELD_ACTIVE, true)); - - - + 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/test/java/org/cloudfoundry/identity/uaa/mfa_provider/MfaProviderTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/mfa_provider/MfaProviderTest.java index 92e5be5db4..9e1761855a 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/mfa_provider/MfaProviderTest.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/mfa_provider/MfaProviderTest.java @@ -1,5 +1,7 @@ package org.cloudfoundry.identity.uaa.mfa_provider; +import com.fasterxml.jackson.databind.JsonNode; +import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -7,6 +9,9 @@ import javax.xml.bind.ValidationException; +import java.util.Date; + +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class MfaProviderTest { @@ -14,6 +19,52 @@ 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("algorithm").textValue(), GoogleMfaProviderConfig.Algorithm.SHA256.toString()); + assertEquals(config.get("digits").intValue(), 42); + assertEquals(config.get("issuer").textValue(), "current-zone"); + assertEquals(config.get("duration").intValue(), 13); + 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()); + assertEquals(true, provider.isActive()); + GoogleMfaProviderConfig config = provider.getConfig(); + assertEquals(GoogleMfaProviderConfig.Algorithm.SHA256, config.getAlgorithm()); + assertEquals(8, config.getDigits()); + assertEquals(32, config.getDuration()); + assertEquals("issuer", config.getIssuer()); + assertEquals("ddd", config.getProviderDescription()); + + } + @Test public void validateProviderNullConfig() throws ValidationException { expectedException.expect(IllegalArgumentException.class); @@ -68,10 +119,19 @@ public void validateProviderNullType() throws ValidationException { provider.validate(); } + @Test + public void validateProviderEmptyZone() throws ValidationException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Provider must belong to a zone"); + MfaProvider provider = createValidGoogleMfaProvider() + .setIdentityZoneId(""); + provider.validate(); + } + @Test public void validateProviderActiveSetDefaultToTrue() { MfaProvider provider = createValidGoogleMfaProvider(); - assertTrue(provider.getActive()); + assertTrue(provider.isActive()); } private MfaProvider createValidGoogleMfaProvider() { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/JdbcMfaProviderProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/JdbcMfaProviderProvisioning.java index 244d69a1ce..fbf403a76d 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/JdbcMfaProviderProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/JdbcMfaProviderProvisioning.java @@ -3,14 +3,28 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.audit.event.SystemDeletable; +import org.cloudfoundry.identity.uaa.provider.IdpAlreadyExistsException; +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.springframework.dao.DuplicateKeyException; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.PreparedStatementSetter; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.util.StringUtils; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.UUID; public class JdbcMfaProviderProvisioning implements MfaProviderProvisioning, SystemDeletable { private static Log logger = LogFactory.getLog(JdbcMfaProviderProvisioning.class); public static final String MFA_PROVIDER_FIELDS = "id,name,type,config,active,identity_zone_id,created,lastmodified"; public static final String CREATE_PROVIDER_SQL = "insert into mfa_providers(" + MFA_PROVIDER_FIELDS + ") values (?,?,?,?,?,?,?,?)"; + public static final String MFA_PROVIDER_BY_ID_QUERY = "select " + MFA_PROVIDER_FIELDS + " from mfa_providers " + "where id=? and identity_zone_id=?"; protected final JdbcTemplate jdbcTemplate; + private MfaProviderMapper mapper = new MfaProviderMapper(); public JdbcMfaProviderProvisioning(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; @@ -18,9 +32,37 @@ public JdbcMfaProviderProvisioning(JdbcTemplate jdbcTemplate) { @Override public MfaProvider create(MfaProvider provider, String zoneId) { - return null; + provider.validate(); + final String id = UUID.randomUUID().toString(); + try { + jdbcTemplate.update(CREATE_PROVIDER_SQL, new PreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps) throws SQLException { + 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.setBoolean(pos++, provider.isActive()); + ps.setString(pos++, zoneId); + ps.setTimestamp(pos++, new Timestamp(System.currentTimeMillis())); + ps.setTimestamp(pos++, new Timestamp(System.currentTimeMillis())); + } + }); + } catch (DuplicateKeyException e) { + throw new IdpAlreadyExistsException(e.getMostSpecificCause().getMessage()); + } + return retrieve(id, zoneId); } + @Override + public MfaProvider retrieve(String id, String zoneId) { + MfaProvider provider = jdbcTemplate.queryForObject(MFA_PROVIDER_BY_ID_QUERY, mapper, id, zoneId); + return provider; + } + + + @Override public int deleteByIdentityZone(String zoneId) { return 0; @@ -45,4 +87,33 @@ public int deleteByUser(String userId, String zoneId) { public Log 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; + switch(result.getType()) { + case GOOGLE_AUTHENTICATOR: + definition = StringUtils.hasText(config) ? JsonUtils.readValue(config, GoogleMfaProviderConfig.class) : new GoogleMfaProviderConfig(); + break; + default: + break; + } + result.setConfig(definition); + result.setActive(rs.getBoolean(pos++)); + 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_provider/MfaProviderEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/MfaProviderEndpoints.java index 08925c1395..637b629632 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/MfaProviderEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/MfaProviderEndpoints.java @@ -29,7 +29,9 @@ public void setApplicationEventPublisher(ApplicationEventPublisher applicationEv @RequestMapping(method = POST) public ResponseEntity createMfaProvider(@RequestBody MfaProvider provider) { + String zoneId = IdentityZoneHolder.get().getId(); try { + provider.setIdentityZoneId(zoneId); provider.validate(); if(!StringUtils.hasText(provider.getConfig().getIssuer())){ provider.getConfig().setIssuer(IdentityZoneHolder.get().getName()); @@ -38,7 +40,8 @@ public ResponseEntity createMfaProvider(@RequestBody MfaProvider pr logger.debug("MfaProvider [name"+provider.getName()+"] - Configuration validation error.", e); return new ResponseEntity<>(provider, UNPROCESSABLE_ENTITY); } - return new ResponseEntity<>(provider, HttpStatus.CREATED); + MfaProvider created = mfaProviderProvisioning.create(provider,zoneId); + return new ResponseEntity<>(created, HttpStatus.CREATED); } public MfaProviderProvisioning getMfaProviderProvisioning() { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/MfaProviderProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/MfaProviderProvisioning.java index 46a4997a88..063f456279 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/MfaProviderProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/mfa_provider/MfaProviderProvisioning.java @@ -2,4 +2,7 @@ public interface MfaProviderProvisioning { MfaProvider create(MfaProvider provider, String zoneId); + + MfaProvider retrieve(String id, String zoneId); + } diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V4_7_1__Add_Mfa_Providers_Table.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V4_7_1__Add_Mfa_Providers_Table.sql new file mode 100644 index 0000000000..71748fbd05 --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V4_7_1__Add_Mfa_Providers_Table.sql @@ -0,0 +1,10 @@ +CREATE TABLE mfa_providers ( + id CHAR(36) NOT NULL PRIMARY KEY, + active BOOLEAN NOT NULL, + created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + lastmodified TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + identity_zone_id varchar(36) NOT NULL, + name varchar(255) NOT NULL, + type varchar(255) NOT NULL, + config LONGVARCHAR +); diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V4_7_1__Add_Mfa_Providers_Table.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V4_7_1__Add_Mfa_Providers_Table.sql new file mode 100644 index 0000000000..6da4425080 --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V4_7_1__Add_Mfa_Providers_Table.sql @@ -0,0 +1,11 @@ +CREATE TABLE `mfa_providers` ( + `id` varchar(36) NOT NULL, + `created` TIMESTAMP default current_timestamp NOT NULL, + `lastModified` TIMESTAMP null, + `identity_zone_id` varchar(36) NOT NULL, + `name` varchar(255) NOT NULL, + `type` varchar(255) NOT NULL, + `config` longtext, + `active` BOOLEAN NOT NULL, + PRIMARY KEY (`id`) +); diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V4_7_1__Add_Mfa_Providers_Table.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V4_7_1__Add_Mfa_Providers_Table.sql new file mode 100644 index 0000000000..c3d9d3b70e --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V4_7_1__Add_Mfa_Providers_Table.sql @@ -0,0 +1,10 @@ +CREATE TABLE mfa_providers ( + id VARCHAR(36) NOT NULL PRIMARY KEY, + created TIMESTAMP default current_timestamp NOT NULL, + lastModified TIMESTAMP null, + identity_zone_id VARCHAR(36) NOT NULL, + name VARCHAR(255) NOT NULL, + type VARCHAR(255) NOT NULL, + config TEXT, + active BOOLEAN NOT NULL +); \ No newline at end of file diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/sqlserver/V4_7_1__Add_Mfa_Providers_Table.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/sqlserver/V4_7_1__Add_Mfa_Providers_Table.sql new file mode 100644 index 0000000000..7d7e03428c --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/sqlserver/V4_7_1__Add_Mfa_Providers_Table.sql @@ -0,0 +1,13 @@ +CREATE TABLE mfa_providers ( + id NVARCHAR(36) NOT NULL , + created TIMESTAMP default current_timestamp NOT NULL, + lastModified TIMESTAMP null, + identity_zone_id NVARCHAR(36) NOT NULL, + name NVARCHAR(255) NOT NULL, + type NVARCHAR(255) NOT NULL, + config TEXT, + active BIT not null, + PRIMARY KEY (id) +); + + diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa_provider/JdbcMfaProviderProvisioningTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/mfa_provider/JdbcMfaProviderProvisioningTest.java index 5d933ff009..6e1d7d8e01 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/mfa_provider/JdbcMfaProviderProvisioningTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/mfa_provider/JdbcMfaProviderProvisioningTest.java @@ -1,5 +1,7 @@ package org.cloudfoundry.identity.uaa.mfa_provider; +import org.cloudfoundry.identity.uaa.test.JdbcTestBase; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.Before; import org.junit.Test; import org.springframework.jdbc.core.JdbcTemplate; @@ -7,34 +9,49 @@ import java.sql.PreparedStatement; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -public class JdbcMfaProviderProvisioningTest { +public class JdbcMfaProviderProvisioningTest extends JdbcTestBase { JdbcMfaProviderProvisioning mfaProviderProvisioning; - JdbcTemplate jdbcTemplate; @Before public void setup() { - jdbcTemplate = mock(JdbcTemplate.class); mfaProviderProvisioning = new JdbcMfaProviderProvisioning(jdbcTemplate); } @Test - public void testCreate() { + public void testCreateAndRetrieve() { 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)); - mfaProviderProvisioning.create(mfaProvider, "uaa"); + 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)); - verify(jdbcTemplate).update(eq(JdbcMfaProviderProvisioning.CREATE_PROVIDER_SQL), any(PreparedStatement.class)); + MfaProvider retrieved = mfaProviderProvisioning.retrieve(created.getId(), zoneId); + assertEquals(mfaProvider.getName(), retrieved.getName()); + assertEquals(mfaProvider.getConfig(), retrieved.getConfig()); + } + + @Test + public void testRetrieve() { + MfaProvider mfaProvider = constructGoogleProvider(); + mfaProvider.setActive(false); + String zoneId = IdentityZoneHolder.get().getId(); + MfaProvider created = mfaProviderProvisioning.create(mfaProvider, zoneId); } private MfaProvider constructGoogleProvider() { - return new MfaProvider() - .setName(new RandomValueStringGenerator(5).generate()) + return new MfaProvider() + .setName(new RandomValueStringGenerator(10).generate()) .setType(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR) + .setIdentityZoneId(IdentityZoneHolder.get().getId()) .setConfig(constructGoogleProviderConfiguration()); } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/MfaIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/MfaIT.java new file mode 100644 index 0000000000..1bf74006e7 --- /dev/null +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/MfaIT.java @@ -0,0 +1,49 @@ +package org.cloudfoundry.identity.uaa.integration.feature; + +import org.cloudfoundry.identity.uaa.ServerRunning; +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; +import org.cloudfoundry.identity.uaa.mfa_provider.GoogleMfaProviderConfig; +import org.cloudfoundry.identity.uaa.mfa_provider.MfaProvider; +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.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.StringUtils; + +import static org.junit.Assert.assertTrue; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = DefaultIntegrationTestConfig.class) +public class MfaIT { + + @Autowired + WebDriver webDriver; + + @Value("${integration.test.base_url}") + String baseUrl; + + @Autowired + TestAccounts testAccounts; + + @Autowired + TestClient testClient; + + ServerRunning serverRunning = ServerRunning.isRunning(); + + @Test + public void createMfaProvider() throws Exception { + MfaProvider provider = new MfaProvider(); + provider.setConfig(new GoogleMfaProviderConfig()); + provider.setType(MfaProvider.MfaProviderType.GOOGLE_AUTHENTICATOR); + provider.setName("testMfaProvider"); + + String adminToken = IntegrationTestUtils.getZoneAdminToken(baseUrl, serverRunning); + MfaProvider result = IntegrationTestUtils.createGoogleMfaProvider(baseUrl, adminToken, provider, ""); + assertTrue("id is not empty", StringUtils.hasText(result.getId())); + } + +} 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 2e62b586bb..2695fcee8b 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 @@ -24,6 +24,8 @@ import org.cloudfoundry.identity.uaa.ServerRunning; import org.cloudfoundry.identity.uaa.account.UserInfoResponse; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.mfa_provider.GoogleMfaProviderConfig; +import org.cloudfoundry.identity.uaa.mfa_provider.MfaProvider; import org.cloudfoundry.identity.uaa.provider.AbstractXOAuthIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition; @@ -161,6 +163,29 @@ 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/mfa_provider/MfaProviderEndpointsMockMVCTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mfa_provider/MfaProviderEndpointsMockMVCTest.java index 645def77ee..1623ed50e1 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mfa_provider/MfaProviderEndpointsMockMVCTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mfa_provider/MfaProviderEndpointsMockMVCTest.java @@ -38,6 +38,8 @@ public void testCreateGoogleMfaProviderConfigDefaults() throws Exception { Assert.assertEquals(HttpStatus.CREATED.value(), mfaResponse.getResponse().getStatus()); MfaProvider mfaProviderCreated = JsonUtils.readValue(mfaResponse.getResponse().getContentAsString(), MfaProvider.class); Assert.assertEquals(IdentityZoneHolder.get().getName(), mfaProviderCreated.getConfig().getIssuer()); + Assert.assertEquals(IdentityZoneHolder.get().getId(), mfaProviderCreated.getIdentityZoneId()); + } @Test