From 07b6e2f51e01ac7bc226e4c5a84fc6d796079939 Mon Sep 17 00:00:00 2001 From: Micah Silverman Date: Thu, 9 Mar 2017 22:18:29 -0800 Subject: [PATCH 1/2] WIP - bad password check still fails TCK --- .../sdk/directory/OktaPasswordPolicy.java | 23 ++++ .../sdk/directory/OktaPasswordPolicyList.java | 6 + .../ExternalAccountStoreModelFactory.java | 9 +- .../directory/DefaultOktaPasswordPolicy.java | 117 ++++++++++++++++++ .../DefaultOktaPasswordPolicyList.java | 39 ++++++ .../sdk/impl/directory/OktaDirectory.java | 85 ++++++++++++- .../sdk/impl/ds/JacksonMapMarshaller.java | 20 ++- 7 files changed, 293 insertions(+), 6 deletions(-) create mode 100644 api/src/main/java/com/stormpath/sdk/directory/OktaPasswordPolicy.java create mode 100644 api/src/main/java/com/stormpath/sdk/directory/OktaPasswordPolicyList.java create mode 100644 impl/src/main/java/com/stormpath/sdk/impl/directory/DefaultOktaPasswordPolicy.java create mode 100644 impl/src/main/java/com/stormpath/sdk/impl/directory/DefaultOktaPasswordPolicyList.java diff --git a/api/src/main/java/com/stormpath/sdk/directory/OktaPasswordPolicy.java b/api/src/main/java/com/stormpath/sdk/directory/OktaPasswordPolicy.java new file mode 100644 index 0000000000..a71bfd806e --- /dev/null +++ b/api/src/main/java/com/stormpath/sdk/directory/OktaPasswordPolicy.java @@ -0,0 +1,23 @@ +package com.stormpath.sdk.directory; + +import com.stormpath.sdk.resource.Resource; + +import java.util.Date; +import java.util.Map; + +public interface OktaPasswordPolicy extends Resource { + + String getType(); + String getId(); + String getStatus(); + String getName(); + String getDescription(); + int getPriority(); + boolean getSystem(); + Map getConditions(); + Date getCreated(); + Date getLastUpdated(); + Map getSettings(); + Map getDelegation(); + Map getRules(); +} diff --git a/api/src/main/java/com/stormpath/sdk/directory/OktaPasswordPolicyList.java b/api/src/main/java/com/stormpath/sdk/directory/OktaPasswordPolicyList.java new file mode 100644 index 0000000000..616ad27c7d --- /dev/null +++ b/api/src/main/java/com/stormpath/sdk/directory/OktaPasswordPolicyList.java @@ -0,0 +1,6 @@ +package com.stormpath.sdk.directory; + +import com.stormpath.sdk.resource.CollectionResource; + +public interface OktaPasswordPolicyList extends CollectionResource { +} diff --git a/extensions/servlet/src/main/java/com/stormpath/sdk/servlet/mvc/provider/ExternalAccountStoreModelFactory.java b/extensions/servlet/src/main/java/com/stormpath/sdk/servlet/mvc/provider/ExternalAccountStoreModelFactory.java index 625d1343c0..4a374fdbcc 100644 --- a/extensions/servlet/src/main/java/com/stormpath/sdk/servlet/mvc/provider/ExternalAccountStoreModelFactory.java +++ b/extensions/servlet/src/main/java/com/stormpath/sdk/servlet/mvc/provider/ExternalAccountStoreModelFactory.java @@ -59,11 +59,14 @@ public List getAccountStores(HttpServletRequest request) { AccountStoreModelVisitor visitor = new AccountStoreModelVisitor(accountStores, getAuthorizeBaseUri(request, app.getWebConfig())); - for (ApplicationAccountStoreMapping mapping : mappings) { + // TODO - if introduced for Okta. Need to deal with for real when we add social support + if (mappings.getHref() != null) { + for (ApplicationAccountStoreMapping mapping : mappings) { - final AccountStore accountStore = mapping.getAccountStore(); + final AccountStore accountStore = mapping.getAccountStore(); - accountStore.accept(visitor); + accountStore.accept(visitor); + } } return visitor.getAccountStores(); diff --git a/impl/src/main/java/com/stormpath/sdk/impl/directory/DefaultOktaPasswordPolicy.java b/impl/src/main/java/com/stormpath/sdk/impl/directory/DefaultOktaPasswordPolicy.java new file mode 100644 index 0000000000..d9580af526 --- /dev/null +++ b/impl/src/main/java/com/stormpath/sdk/impl/directory/DefaultOktaPasswordPolicy.java @@ -0,0 +1,117 @@ +package com.stormpath.sdk.impl.directory; + +import com.stormpath.sdk.directory.OktaPasswordPolicy; +import com.stormpath.sdk.impl.ds.InternalDataStore; +import com.stormpath.sdk.impl.resource.AbstractInstanceResource; +import com.stormpath.sdk.impl.resource.BooleanProperty; +import com.stormpath.sdk.impl.resource.DateProperty; +import com.stormpath.sdk.impl.resource.IntegerProperty; +import com.stormpath.sdk.impl.resource.MapProperty; +import com.stormpath.sdk.impl.resource.Property; +import com.stormpath.sdk.impl.resource.StringProperty; + +import java.util.Date; +import java.util.Map; + +public class DefaultOktaPasswordPolicy extends AbstractInstanceResource implements OktaPasswordPolicy { + + // SIMPLE PROPERTIES + static final StringProperty TYPE = new StringProperty("type"); + static final StringProperty ID = new StringProperty("id"); + static final StringProperty STATUS = new StringProperty("status"); + static final StringProperty NAME = new StringProperty("name"); + static final StringProperty DESCRIPTION = new StringProperty("description"); + static final IntegerProperty PRIORITY = new IntegerProperty("priority"); + static final BooleanProperty SYSTEM = new BooleanProperty("system"); + static final DateProperty CREATED = new DateProperty("created"); + static final DateProperty LAST_UPDATED = new DateProperty("lastUpdated"); + + // MAP Properties + static final MapProperty CONDITIONS = new MapProperty("conditions"); + static final MapProperty SETTINGS = new MapProperty("settings"); + static final MapProperty DELEGATION = new MapProperty("delegation"); + static final MapProperty RULES = new MapProperty("rules"); + + private static final Map PROPERTY_DESCRIPTORS = createPropertyDescriptorMap( + TYPE, ID, STATUS, NAME, DESCRIPTION, PRIORITY, SYSTEM, CREATED, LAST_UPDATED, CONDITIONS, SETTINGS, DELEGATION, RULES + ); + + public DefaultOktaPasswordPolicy(InternalDataStore dataStore) { + super(dataStore); + } + + public DefaultOktaPasswordPolicy(InternalDataStore dataStore, Map properties) { + super(dataStore, properties); + } + + @Override + public Map getPropertyDescriptors() { + return PROPERTY_DESCRIPTORS; + } + + + @Override + public String getType() { + return getString(TYPE); + } + + @Override + public String getId() { + return getString(ID); + } + + @Override + public String getStatus() { + return getString(STATUS); + } + + @Override + public String getName() { + return getString(NAME); + } + + @Override + public String getDescription() { + return getString(DESCRIPTION); + } + + @Override + public int getPriority() { + return getInt(PRIORITY); + } + + @Override + public boolean getSystem() { + return getBoolean(SYSTEM); + } + + @Override + public Map getConditions() { + return getMap(CONDITIONS); + } + + @Override + public Date getCreated() { + return getDateProperty(CREATED); + } + + @Override + public Date getLastUpdated() { + return getDateProperty(LAST_UPDATED); + } + + @Override + public Map getSettings() { + return getMap(SETTINGS); + } + + @Override + public Map getDelegation() { + return getMap(DELEGATION); + } + + @Override + public Map getRules() { + return getMap(RULES); + } +} diff --git a/impl/src/main/java/com/stormpath/sdk/impl/directory/DefaultOktaPasswordPolicyList.java b/impl/src/main/java/com/stormpath/sdk/impl/directory/DefaultOktaPasswordPolicyList.java new file mode 100644 index 0000000000..a0f18e639c --- /dev/null +++ b/impl/src/main/java/com/stormpath/sdk/impl/directory/DefaultOktaPasswordPolicyList.java @@ -0,0 +1,39 @@ +package com.stormpath.sdk.impl.directory; + +import com.stormpath.sdk.directory.OktaPasswordPolicy; +import com.stormpath.sdk.directory.OktaPasswordPolicyList; +import com.stormpath.sdk.impl.ds.InternalDataStore; +import com.stormpath.sdk.impl.resource.AbstractCollectionResource; +import com.stormpath.sdk.impl.resource.ArrayProperty; +import com.stormpath.sdk.impl.resource.Property; + +import java.util.Map; + +public class DefaultOktaPasswordPolicyList extends AbstractCollectionResource implements OktaPasswordPolicyList { + + private static final ArrayProperty ITEMS = new ArrayProperty<>("items", OktaPasswordPolicy.class); + + private static final Map PROPERTY_DESCRIPTORS = createPropertyDescriptorMap(OFFSET, LIMIT, ITEMS); + + public DefaultOktaPasswordPolicyList(InternalDataStore dataStore) { + super(dataStore); + } + + public DefaultOktaPasswordPolicyList(InternalDataStore dataStore, Map properties) { + super(dataStore, properties); + } + + public DefaultOktaPasswordPolicyList(InternalDataStore dataStore, Map properties, Map queryParams) { + super(dataStore, properties, queryParams); + } + + @Override + public Map getPropertyDescriptors() { + return PROPERTY_DESCRIPTORS; + } + + @Override + protected Class getItemType() { + return OktaPasswordPolicy.class; + } +} diff --git a/impl/src/main/java/com/stormpath/sdk/impl/directory/OktaDirectory.java b/impl/src/main/java/com/stormpath/sdk/impl/directory/OktaDirectory.java index 4f171cf81b..ee30e0b306 100644 --- a/impl/src/main/java/com/stormpath/sdk/impl/directory/OktaDirectory.java +++ b/impl/src/main/java/com/stormpath/sdk/impl/directory/OktaDirectory.java @@ -11,7 +11,10 @@ import com.stormpath.sdk.directory.Directory; import com.stormpath.sdk.directory.DirectoryOptions; import com.stormpath.sdk.directory.DirectoryStatus; +import com.stormpath.sdk.directory.OktaPasswordPolicy; import com.stormpath.sdk.directory.PasswordPolicy; +import com.stormpath.sdk.directory.OktaPasswordPolicyList; +import com.stormpath.sdk.directory.PasswordStrength; import com.stormpath.sdk.group.CreateGroupRequest; import com.stormpath.sdk.group.Group; import com.stormpath.sdk.group.GroupCriteria; @@ -21,6 +24,9 @@ import com.stormpath.sdk.impl.resource.AbstractResource; import com.stormpath.sdk.impl.resource.Property; import com.stormpath.sdk.lang.Assert; +import com.stormpath.sdk.mail.EmailStatus; +import com.stormpath.sdk.mail.ModeledEmailTemplateList; +import com.stormpath.sdk.mail.UnmodeledEmailTemplateList; import com.stormpath.sdk.organization.OrganizationAccountStoreMappingCriteria; import com.stormpath.sdk.organization.OrganizationAccountStoreMappingList; import com.stormpath.sdk.organization.OrganizationCriteria; @@ -187,7 +193,10 @@ public Provider getProvider() { @Override public PasswordPolicy getPasswordPolicy() { - throw new UnsupportedOperationException("Not implemented."); + String passwordPolicyHref = getHref() + "/policies?type=PASSWORD"; + OktaPasswordPolicyList policies = getDataStore().getResource(passwordPolicyHref, OktaPasswordPolicyList.class); + OktaPasswordPolicy oktaPasswordPolicy = policies.single(); + return transformOktaPasswordPolicy(oktaPasswordPolicy); } @Override @@ -234,4 +243,78 @@ public OrganizationAccountStoreMappingList getOrganizationAccountStoreMappings(O public Schema getAccountSchema() { throw new UnsupportedOperationException("Not implemented."); } + + @SuppressWarnings("unchecked") + private PasswordPolicy transformOktaPasswordPolicy(OktaPasswordPolicy oktaPasswordPolicy) { + final Map strengthMap = (Map) + ((Map)oktaPasswordPolicy.getSettings().get("password")).get("complexity"); + PasswordPolicy ret = new PasswordPolicy() { + @Override + public int getResetTokenTtlHours() { + return 0; + } + + @Override + public PasswordPolicy setResetTokenTtlHours(int resetTokenTtl) { + return null; + } + + @Override + public EmailStatus getResetEmailStatus() { + return null; + } + + @Override + public PasswordPolicy setResetEmailStatus(EmailStatus status) { + return null; + } + + @Override + public EmailStatus getResetSuccessEmailStatus() { + return null; + } + + @Override + public PasswordPolicy setResetSuccessEmailStatus(EmailStatus status) { + return null; + } + + @Override + public PasswordStrength getStrength() { + PasswordStrength p = new DefaultPasswordStrength(getDataStore()); + + p.setMinLength((Integer) strengthMap.get("minLength")); + p.setMinLowerCase((Integer) strengthMap.get("minLowerCase")); + p.setMinUpperCase((Integer) strengthMap.get("minUpperCase")); + p.setMinNumeric((Integer) strengthMap.get("minNumber")); + p.setMinSymbol((Integer) strengthMap.get("minSymbol")); + p.setMaxLength(1024); + p.setMinDiacritic(0); + p.setPreventReuse(0); + + return p; + } + + @Override + public ModeledEmailTemplateList getResetEmailTemplates() { + return null; + } + + @Override + public UnmodeledEmailTemplateList getResetSuccessEmailTemplates() { + return null; + } + + @Override + public String getHref() { + return "local"; + } + + @Override + public void save() { + + } + }; + return ret; + } } diff --git a/impl/src/main/java/com/stormpath/sdk/impl/ds/JacksonMapMarshaller.java b/impl/src/main/java/com/stormpath/sdk/impl/ds/JacksonMapMarshaller.java index d7e026c0d7..3f5da23aae 100644 --- a/impl/src/main/java/com/stormpath/sdk/impl/ds/JacksonMapMarshaller.java +++ b/impl/src/main/java/com/stormpath/sdk/impl/ds/JacksonMapMarshaller.java @@ -20,9 +20,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; /** @@ -74,11 +76,25 @@ public Map unmarshal(String marshalled) { } } + @SuppressWarnings("unchecked") @Override public Map unmarshall(InputStream marshalled) { try { - TypeReference> typeRef = new TypeReference>(){}; - return this.objectMapper.readValue(marshalled, typeRef); + Object resolvedObj = this.objectMapper.readValue(marshalled, Object.class); + if (resolvedObj instanceof Map) { + return (Map) resolvedObj; + } else if (resolvedObj instanceof List) { + List list = (List) resolvedObj; + Map ret = new LinkedHashMap<>(); + ret.put("items", list); + ret.put("offset", 0); + ret.put("limit", 100); + ret.put("size", list.size()); + ret.put("href", "local"); + return ret; + } + throw new MarshalingException("Unable to convert InputStream String to Map. " + + "Resolved Object is neither a Map or a List: " + resolvedObj.getClass()); } catch (IOException e) { throw new MarshalingException("Unable to convert InputStream String to Map.", e); } From 0d6a0637339fa74b7aa292657afb77eb4dcfd32f Mon Sep 17 00:00:00 2001 From: Micah Silverman Date: Fri, 10 Mar 2017 21:10:10 -0500 Subject: [PATCH 2/2] Added error handling for register when password spec is not met. --- .../sdk/impl/directory/OktaDirectory.java | 1 + .../sdk/impl/ds/DefaultDataStore.java | 20 +++- .../stormpath/sdk/impl/error/OktaError.java | 92 +++++++++++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 impl/src/main/java/com/stormpath/sdk/impl/error/OktaError.java diff --git a/impl/src/main/java/com/stormpath/sdk/impl/directory/OktaDirectory.java b/impl/src/main/java/com/stormpath/sdk/impl/directory/OktaDirectory.java index ee30e0b306..1f304672b9 100644 --- a/impl/src/main/java/com/stormpath/sdk/impl/directory/OktaDirectory.java +++ b/impl/src/main/java/com/stormpath/sdk/impl/directory/OktaDirectory.java @@ -246,6 +246,7 @@ public Schema getAccountSchema() { @SuppressWarnings("unchecked") private PasswordPolicy transformOktaPasswordPolicy(OktaPasswordPolicy oktaPasswordPolicy) { + // ref: http://developer.okta.com/docs/api/resources/policy.html#GroupPasswordPolicy final Map strengthMap = (Map) ((Map)oktaPasswordPolicy.getSettings().get("password")).get("complexity"); PasswordPolicy ret = new PasswordPolicy() { diff --git a/impl/src/main/java/com/stormpath/sdk/impl/ds/DefaultDataStore.java b/impl/src/main/java/com/stormpath/sdk/impl/ds/DefaultDataStore.java index 771616ebab..34c2ebf254 100644 --- a/impl/src/main/java/com/stormpath/sdk/impl/ds/DefaultDataStore.java +++ b/impl/src/main/java/com/stormpath/sdk/impl/ds/DefaultDataStore.java @@ -31,6 +31,7 @@ import com.stormpath.sdk.impl.ds.cache.ReadCacheFilter; import com.stormpath.sdk.impl.ds.cache.WriteCacheFilter; import com.stormpath.sdk.impl.error.DefaultError; +import com.stormpath.sdk.impl.error.OktaError; import com.stormpath.sdk.impl.http.CanonicalUri; import com.stormpath.sdk.impl.http.HttpHeaders; import com.stormpath.sdk.impl.http.HttpHeadersHolder; @@ -65,6 +66,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.servlet.http.HttpServletResponse; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; @@ -105,6 +107,8 @@ public class DefaultDataStore implements InternalDataStore { private static final boolean COLLECTION_CACHING_ENABLED = false; //EXPERIMENTAL - set to true only while developing. + private static boolean oktaEnabled; + private final RequestExecutor requestExecutor; private final ResourceFactory resourceFactory; private final MapMarshaller mapMarshaller; @@ -160,6 +164,12 @@ public DefaultDataStore(RequestExecutor requestExecutor, BaseUrlResolver baseUrl this.cacheResolver = new DefaultCacheResolver(this.cacheManager, new DefaultCacheRegionNameResolver()); this.apiKeyResolver = apiKeyResolver; + if (baseUrlResolver.getBaseUrl().toLowerCase().contains("okta")) { + oktaEnabled = true; + } else { + oktaEnabled = false; + } + ReferenceFactory referenceFactory = new ReferenceFactory(); this.resourceConverter = new DefaultResourceConverter(referenceFactory); @@ -600,7 +610,15 @@ private Response execute(Request request) throws ResourceException { body.put(DefaultError.REQUEST_ID.getName(), requestId); } - DefaultError error = new DefaultError(body); + com.stormpath.sdk.error.Error error; + if (oktaEnabled) { + OktaError oktaError = new OktaError(body); + // Okta Error response doesn't have status + oktaError.setProperty(OktaError.STATUS.getName(), response.getHttpStatus()); + error = oktaError; + } else { + error = new DefaultError(body); + } throw new ResourceException(error); } diff --git a/impl/src/main/java/com/stormpath/sdk/impl/error/OktaError.java b/impl/src/main/java/com/stormpath/sdk/impl/error/OktaError.java new file mode 100644 index 0000000000..14841ec234 --- /dev/null +++ b/impl/src/main/java/com/stormpath/sdk/impl/error/OktaError.java @@ -0,0 +1,92 @@ +/* + * Copyright 2013 Stormpath, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.stormpath.sdk.impl.error; + +import com.stormpath.sdk.error.Error; +import com.stormpath.sdk.impl.resource.AbstractResource; +import com.stormpath.sdk.impl.resource.IntegerProperty; +import com.stormpath.sdk.impl.resource.ListProperty; +import com.stormpath.sdk.impl.resource.Property; +import com.stormpath.sdk.impl.resource.StringProperty; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +/** + * @since 0.1 + */ +public class OktaError extends AbstractResource implements Error, Serializable { + + static final long serialVersionUID = 42L; + + public static final IntegerProperty STATUS = new IntegerProperty("status"); + static final StringProperty ERROR_CODE = new StringProperty("errorCode"); + static final StringProperty ERROR_SUMMARY = new StringProperty("errorSummary"); + static final ListProperty ERROR_CAUSES = new ListProperty("errorCauses"); + static final StringProperty ERROR_ID = new StringProperty("errorId"); + + private static final Map PROPERTY_DESCRIPTORS = createPropertyDescriptorMap( + STATUS, ERROR_CODE, ERROR_SUMMARY, ERROR_CAUSES, ERROR_ID + ); + + public OktaError(Map body) { + super(null, body); + } + + // Needed for this class to be serializable + public OktaError() { + super(null, null); + } + + @Override + public Map getPropertyDescriptors() { + return PROPERTY_DESCRIPTORS; + } + + @Override + public int getStatus() { + return getInt(STATUS); + } + + @Override + public int getCode() { + return 0; + } + + @Override + @SuppressWarnings("unchecked") + public String getMessage() { + List causes = getListProperty(ERROR_CAUSES.getName()); + return ((Map)causes.get(0)).get("errorSummary"); + } + + @Override + public String getDeveloperMessage() { + return ""; + } + + @Override + public String getMoreInfo() { + return getString(ERROR_SUMMARY); + } + + @Override + public String getRequestId() { + return getString(ERROR_ID); + } + +}