From 83fdc01c7e0e0749880d71300919c3283dbd140d Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 10 Apr 2015 11:06:28 -0300 Subject: [PATCH 1/7] #175 Spring Security plugin moved to Java SDK repo --- changelog.md | 4 + extensions/pom.xml | 1 + extensions/spring-security/pom.xml | 57 +++ .../authz/CustomDataPermissionsEditor.java | 295 +++++++++++ .../security/authz/PermissionsEditor.java | 55 +++ .../authz/permission/DomainPermission.java | 159 ++++++ .../security/authz/permission/Permission.java | 89 ++++ .../authz/permission/WildcardPermission.java | 289 +++++++++++ .../WildcardPermissionEvaluator.java | 90 ++++ .../spring/security/cache/SpringCache.java | 68 +++ .../security/cache/SpringCacheManager.java | 62 +++ .../spring/security/client/ClientFactory.java | 190 ++++++++ .../AccountCustomDataPermissionResolver.java | 35 ++ .../AccountGrantedAuthorityResolver.java | 56 +++ .../provider/AccountPermissionResolver.java | 52 ++ .../provider/AuthenticationTokenFactory.java | 52 ++ .../CustomDataPermissionResolver.java | 205 ++++++++ .../DefaultGroupGrantedAuthorityResolver.java | 173 +++++++ .../GroupCustomDataPermissionResolver.java | 35 ++ .../GroupGrantedAuthorityResolver.java | 47 ++ .../provider/GroupPermissionResolver.java | 47 ++ .../InvalidPermissionStringException.java | 71 +++ .../security/provider/PermissionResolver.java | 51 ++ .../StormpathAuthenticationProvider.java | 459 ++++++++++++++++++ .../provider/StormpathUserDetails.java | 120 +++++ ...amePasswordAuthenticationTokenFactory.java | 41 ++ .../provider/WildcardPermissionResolver.java | 40 ++ .../spring/security/util/CollectionUtils.java | 52 ++ .../spring/security/util/StringUtils.java | 60 +++ .../CustomDataPermissionsEditorTest.groovy | 301 ++++++++++++ .../security/authz/MockCustomData.groovy | 120 +++++ .../permission/DomainPermissionTest.groovy | 261 ++++++++++ .../permission/WildcardPermissionTest.groovy | 243 ++++++++++ .../WildcardPermissionEvaluatorTest.groovy | 163 +++++++ .../cache/SpringCacheManagerTest.groovy | 52 ++ .../security/cache/SpringCacheTest.groovy | 124 +++++ .../security/client/ClientFactoryTest.groovy | 47 ++ ...ltGroupGrantedAuthorityResolverTest.groovy | 193 ++++++++ .../security/provider/SimpleError.groovy | 27 ++ ...StormpathAuthenticationProviderTest.groovy | 268 ++++++++++ .../provider/StormpathUserDetailsTest.groovy | 79 +++ ...swordAuthenticationTokenFactoryTest.groovy | 87 ++++ .../security/util/CollectionUtilsTest.groovy | 25 + .../security/util/StringUtilsTest.groovy | 34 ++ pom.xml | 13 + 45 files changed, 4992 insertions(+) create mode 100644 extensions/spring-security/pom.xml create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/CustomDataPermissionsEditor.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/PermissionsEditor.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/DomainPermission.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/Permission.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/WildcardPermission.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/evaluator/WildcardPermissionEvaluator.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/cache/SpringCache.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/cache/SpringCacheManager.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/client/ClientFactory.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AccountCustomDataPermissionResolver.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AccountGrantedAuthorityResolver.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AccountPermissionResolver.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AuthenticationTokenFactory.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/CustomDataPermissionResolver.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/DefaultGroupGrantedAuthorityResolver.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/GroupCustomDataPermissionResolver.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/GroupGrantedAuthorityResolver.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/GroupPermissionResolver.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/InvalidPermissionStringException.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/PermissionResolver.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/StormpathAuthenticationProvider.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/StormpathUserDetails.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/UsernamePasswordAuthenticationTokenFactory.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/WildcardPermissionResolver.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/util/CollectionUtils.java create mode 100644 extensions/spring-security/src/main/java/com/stormpath/spring/security/util/StringUtils.java create mode 100644 extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/CustomDataPermissionsEditorTest.groovy create mode 100644 extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/MockCustomData.groovy create mode 100644 extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/DomainPermissionTest.groovy create mode 100644 extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/WildcardPermissionTest.groovy create mode 100644 extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/evaluator/WildcardPermissionEvaluatorTest.groovy create mode 100644 extensions/spring-security/src/test/groovy/com/stormpath/spring/security/cache/SpringCacheManagerTest.groovy create mode 100644 extensions/spring-security/src/test/groovy/com/stormpath/spring/security/cache/SpringCacheTest.groovy create mode 100644 extensions/spring-security/src/test/groovy/com/stormpath/spring/security/client/ClientFactoryTest.groovy create mode 100644 extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/DefaultGroupGrantedAuthorityResolverTest.groovy create mode 100644 extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/SimpleError.groovy create mode 100644 extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/StormpathAuthenticationProviderTest.groovy create mode 100644 extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/StormpathUserDetailsTest.groovy create mode 100644 extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/UsernamePasswordAuthenticationTokenFactoryTest.groovy create mode 100644 extensions/spring-security/src/test/groovy/com/stormpath/spring/security/util/CollectionUtilsTest.groovy create mode 100644 extensions/spring-security/src/test/groovy/com/stormpath/spring/security/util/StringUtilsTest.groovy diff --git a/changelog.md b/changelog.md index 43a50bad8f..16585b939d 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,9 @@ ## Change Log ## +### 1.0 ## + +- [Issue 175](https://github.com/stormpath/stormpath-sdk-java/issues/175): The Spring Security Plugin now resides inside the Java SDK codebase. + ### 1.0.RC4.1 ## This release adds ID Site functionality for Spring Web and Spring Boot applications. diff --git a/extensions/pom.xml b/extensions/pom.xml index 2e0a7ebc8d..0547ec097c 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -35,6 +35,7 @@ servlet-plugin httpclient spring + spring-security diff --git a/extensions/spring-security/pom.xml b/extensions/spring-security/pom.xml new file mode 100644 index 0000000000..e72726ddf2 --- /dev/null +++ b/extensions/spring-security/pom.xml @@ -0,0 +1,57 @@ + + + + + 4.0.0 + + + com.stormpath.sdk + stormpath-sdk-root + 1.0-SNAPSHOT + ../../pom.xml + + + stormpath-spring-security + Stormpath Java SDK :: Extensions :: Stormpath Spring Security Plugin + + Stormpath Spring Security integration allows Spring Security applications to use Stormpath as + the backend for all of their security needs. + + jar + + + + com.stormpath.sdk + stormpath-sdk-api + + + com.stormpath.sdk + stormpath-sdk-httpclient + runtime + + + com.fasterxml.jackson.core + jackson-databind + test + + + org.springframework.security + spring-security-core + + + + \ No newline at end of file diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/CustomDataPermissionsEditor.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/CustomDataPermissionsEditor.java new file mode 100644 index 0000000000..541289b950 --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/CustomDataPermissionsEditor.java @@ -0,0 +1,295 @@ +/* + * 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.spring.security.authz; + +import com.stormpath.sdk.directory.CustomData; +import com.stormpath.sdk.lang.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.*; + +/** + * A {@code CustomDataPermissionsEditor} can read or modify a collection of permission Strings stored in a + * {@link com.stormpath.sdk.directory.CustomData} resource. This is used to support a common convention for Spring Security+Stormpath + * applications: you can assign Spring Security granted authority to an {@link com.stormpath.sdk.account.Account} or + * a {@link com.stormpath.sdk.group.Group} by storing those granted authorities in the respective account or + * group's {@link com.stormpath.sdk.directory.CustomData} map. Let's recall that a + * {@link com.stormpath.spring.security.authz.permission.Permission Permission} is also a {@link org.springframework.security.core.GrantedAuthority + * GrantedAuthority} with a customizable operation to carry out comparisons: + * {@link com.stormpath.spring.security.authz.permission.Permission#implies(com.stormpath.spring.security.authz.permission.Permission)}. + *

Usage

+ * You may use this component to 'wrap' a CustomData instance, and it will read or modify the CustomData's permission + * Set as necessary. This implementation assumes a single CustomData field that contains a Set of Strings. + *

+ * For example: + *

+ *     CustomData data = account.getCustomData();
+ *     new CustomDataPermissionsEditor(customData)
+ *         .append("user:1234:edit")
+ *         .append("document:*")
+ *         .remove("printer:*:print");
+ *     data.save();
+ * 
+ * Invoking this code would remove the first two permission and remove the 3rd. + *

+ * Note however that manipulating the permission Set only makes changes locally. You must call + * {@code customData.}{@link com.stormpath.sdk.directory.CustomData#save() save()} (or account.save() or group.save()) + * to persist the changes back to Stormpath. + * + *

Field Name

+ * This implementation assumes a CustomData field named {@code springSecurityPermissions} to store the Set of Spring Security + * granted authorities, implying the following CustomData JSON structure: + *
+ * {
+ *     ... any other of your own custom data properties ...,
+ *
+ *     "springSecurityPermissions": [
+ *         "perm1",
+ *         "perm2",
+ *         ...,
+ *         "permN"j
+ *     ]
+ * }
+ * 
+ * You can set the {@link #setFieldName(String) fieldName} property if you would like to change this name to something + * else. For example, if you changed the name to {@code myApplicationPermissions}, you would see the resulting + * CustomData JSON structure: + *
+ * {
+ *     ... any other of your own custom data properties ...,
+ *
+ *     "myApplicationPermissions": [
+ *         "perm1",
+ *         "perm2",
+ *         ...,
+ *         "permN"j
+ *     ]
+ * }
+ * 
+ * + * @since 0.2.0 + */ +public class CustomDataPermissionsEditor implements PermissionsEditor { + + public static final String DEFAULT_CUSTOM_DATA_FIELD_NAME = "springSecurityPermissions"; + + private final CustomData CUSTOM_DATA; + + private String fieldName = DEFAULT_CUSTOM_DATA_FIELD_NAME; + + /** + * Creates a new CustomDataPermissionsEditor that will delegate to the specified {@link com.stormpath.sdk.directory.CustomData} instance. + * + * @param customData the CustomData instance that may store a Set of spring security granted authority strings. + */ + public CustomDataPermissionsEditor(CustomData customData) { + Assert.notNull(customData, "CustomData argument cannot be null."); + this.CUSTOM_DATA = customData; + } + + /** + * Returns the name of the {@link com.stormpath.sdk.directory.CustomData} field used to store the {@code Set<String>} + * of permissions. The default name is + * {@code springSecurityPermissions}, implying a {@code CustomData} JSON representation as follows: + *
+     * {
+     *     ... any other of your own custom data properties ...,
+     *
+     *     "springSecurityPermissions": [
+     *         "perm1",
+     *         "perm2",
+     *         ...,
+     *         "permN"j
+     *     ]
+     * }
+     * 
+ * You can change the name by calling {@link #setFieldName(String)}. + * + * @return the name of the {@link com.stormpath.sdk.directory.CustomData} field used to store the {@code Set<String>} + * of granted authorities. + */ + public String getFieldName() { + return this.fieldName; + } + + /** + * Sets the name of the {@link com.stormpath.sdk.directory.CustomData} field used to store the {@code Set<String>} + * of permissions. The default name is {@code springSecurityPermissions}, implying a {@code CustomData} JSON + * representation as follows: + *
+     * {
+     *     ... any other of your own custom data properties ...,
+     *
+     *     "springSecurityPermissions": [
+     *         "perm1",
+     *         "perm2",
+     *         ...,
+     *         "permN"j
+     *     ]
+     * }
+     * 
+ * If you changed this name to be {@code myApplicationPermissions} for example, the CustomData representation + * would look something like this instead: + *
+     * {
+     *     ... any other of your own custom data properties ...,
+     *
+     *     "myApplicationPermissions": [
+     *         "perm1",
+     *         "perm2",
+     *         ...,
+     *         "permN"j
+     *     ]
+     * }
+     * 
+ *

Usage Warning

+ * If you change this value, you will also need to adjust your {@code AuthenticationProvider} instance's configuration + * to reflect this name so it can continue to function - the provider reads the same {@code CustomData} field, so + * they must be identical to ensure both read and write scenarios access the same field. + *

+ * For example, when using Spring xml configuration: + *

+     * 
+     *      
+     * 
+     *
+     * 
+     *      
+     * 
+     *
+     * 
+     *      ...
+     *      
+     *      
+     * 
+     * 
+ * + * @param fieldName the name of the {@link com.stormpath.sdk.directory.CustomData} field used to store the {@code Set<String>} + * of permissions. + * @return this object for method chaining. + */ + public CustomDataPermissionsEditor setFieldName(String fieldName) { + this.fieldName = fieldName; + return this; + } + + @Override + public PermissionsEditor append(String permission) { + Assert.hasText(permission, "permission string argument cannot be null or empty."); + + Collection permissions = lookupPermissionStrings(); + + String fieldName = getFieldName(); + + if (permissions == null) { + permissions = new LinkedHashSet(); + CUSTOM_DATA.put(fieldName, permissions); + } else if (permissions instanceof List) { + //hasn't yet been converted to a set that we maintain: + permissions = asSet(fieldName, (List) permissions); + CUSTOM_DATA.put(fieldName, permissions); + } + //else the Collection should be a Set + permissions.add(permission); + + return this; + } + + @Override + public PermissionsEditor remove(String perm) { + if (StringUtils.hasText(perm)) { + Collection perms = lookupPermissionStrings(); + if (!CollectionUtils.isEmpty(perms)) { + if (perms instanceof List) { + //hasn't yet been converted to a set that we maintain: + String attrName = getFieldName(); + perms = asSet(attrName, (List) perms); + CUSTOM_DATA.put(attrName, perms); + } + perms.remove(perm); + } + } + return this; + } + + @Override + public Set getPermissionStrings() { + + Collection perms = lookupPermissionStrings(); + + if (CollectionUtils.isEmpty(perms)) { + return Collections.emptySet(); + } + + Set set; + + if (perms instanceof List) { + set = asSet(getFieldName(), (List) perms); + } else { + assert perms instanceof Set : "perms instance must be a Set"; + set = (Set) perms; + } + + return Collections.unmodifiableSet(set); + } + + private static Set asSet(String fieldName, List list) { + Set set = new LinkedHashSet(list.size()); + for (Object element : list) { + if (element != null) { + if (!(element instanceof String)) { + String msg = "CustomData field '" + fieldName + "' contains an element that is not a String " + + "as required. Element type: " + element.getClass().getName() + ", element value: " + element; + throw new IllegalArgumentException(msg); + } + String s = (String) element; + + set.add(s); + } + } + return set; + } + + @SuppressWarnings("unchecked") + private Collection lookupPermissionStrings() { + + final String fieldName = getFieldName(); + + Object value = CUSTOM_DATA.get(fieldName); + + if (value == null) { + return null; + } + + if (value instanceof Set) { + return (Set) value; + } + + List permList = null; + + if (value instanceof List) { + permList = (List) value; + } else { + String msg = "Unable to recognize CustomData field '" + fieldName + "' value of type " + + value.getClass().getName() + ". Expected type: Set or List."; + throw new IllegalArgumentException(msg); + } + + return permList; + } + +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/PermissionsEditor.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/PermissionsEditor.java new file mode 100644 index 0000000000..2db6a884db --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/PermissionsEditor.java @@ -0,0 +1,55 @@ +/* + * 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.spring.security.authz; + +import java.util.Set; + +/** + * A {@code PermissionsEditor} allows one to read and manipulate (append or remove) permission on an underlying data + * structure. + *

+ * The primary default implementation of this interface is the {@link CustomDataPermissionsEditor}, which reads and + * modifies a Set of permissions stored in a {@link com.stormpath.sdk.directory.CustomData CustomData} instance. + * + * @see CustomDataPermissionsEditor + * @since 0.2.0 + */ +public interface PermissionsEditor { + + /** + * Adds a permissions String to the associated Set of permission strings. + * + * @param permission the permissions string to add to the associated Set of permission strings. + * @return this object for method chaining. + */ + PermissionsEditor append(String permission); + + /** + * Removes the specified permission String from the associated Set of permission Strings. + * + * @param permission the permission string to remove from the associated Set of permission strings. + * @return this object for method chaining. + */ + PermissionsEditor remove(String permission); + + /** + * Returns a read-only (immutable) view of the stored permission strings. An immutable empty set will be returned + * if there are not any currently stored. + * + * @return an immutable view of the stored permission strings. + */ + Set getPermissionStrings(); +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/DomainPermission.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/DomainPermission.java new file mode 100644 index 0000000000..034307540e --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/DomainPermission.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/* + * Copyright 2014 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.spring.security.authz.permission; + +import com.stormpath.sdk.lang.Strings; +import com.stormpath.spring.security.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.Set; + +/** + * Provides a base Permission class from which type-safe/domain-specific subclasses may extend. Can be used + * as a base class for JPA/Hibernate persisted permissions that wish to store the parts of the permission string + * in separate columns (e.g. 'domain', 'actions' and 'targets' columns), which can be used in querying + * strategies. + * + * @since 0.1.1 + */ +public class DomainPermission extends WildcardPermission { + + private String domain; + private Set actions; + private Set targets; + + private static final long serialVersionUID = 1l; + + /** + * Creates a domain permission with *all* actions for *all* targets; + */ + public DomainPermission() { + this.domain = getDomain(getClass()); + setParts(getDomain(getClass())); + } + + public DomainPermission(String actions) { + domain = getDomain(getClass()); + this.actions = CollectionUtils.asSet(StringUtils.tokenizeToStringArray(actions, SUBPART_DIVIDER_TOKEN)); + encodeParts(domain, actions, null); + } + + public DomainPermission(String actions, String targets) { + this.domain = getDomain(getClass()); + this.actions = CollectionUtils.asSet(StringUtils.tokenizeToStringArray(actions, SUBPART_DIVIDER_TOKEN)); + this.targets = CollectionUtils.asSet(StringUtils.tokenizeToStringArray(targets, SUBPART_DIVIDER_TOKEN)); + encodeParts(this.domain, actions, targets); + } + + protected DomainPermission(Set actions, Set targets) { + this.domain = getDomain(getClass()); + setParts(domain, actions, targets); + } + + private void encodeParts(String domain, String actions, String targets) { + if (!Strings.hasText(domain)) { + throw new IllegalArgumentException("domain argument cannot be null or empty."); + } + StringBuilder sb = new StringBuilder(domain); + + if (!Strings.hasText(actions)) { + if (Strings.hasText(targets)) { + sb.append(PART_DIVIDER_TOKEN).append(WILDCARD_TOKEN); + } + } else { + sb.append(PART_DIVIDER_TOKEN).append(actions); + } + if (Strings.hasText(targets)) { + sb.append(PART_DIVIDER_TOKEN).append(targets); + } + setParts(sb.toString()); + } + + protected void setParts(String domain, Set actions, Set targets) { + String actionsString = StringUtils.collectionToDelimitedString(actions, SUBPART_DIVIDER_TOKEN); + String targetsString = StringUtils.collectionToDelimitedString(targets, SUBPART_DIVIDER_TOKEN); + encodeParts(domain, actionsString, targetsString); + this.domain = domain; + this.actions = actions; + this.targets = targets; + } + + protected String getDomain(Class clazz) { + String domain = clazz.getSimpleName().toLowerCase(); + //strip any trailing 'permission' text from the name (as all subclasses should have been named): + int index = domain.lastIndexOf("permission"); + if (index != -1) { + domain = domain.substring(0, index); + } + return domain; + } + + public String getDomain() { + return domain; + } + + protected void setDomain(String domain) { + if (this.domain != null && this.domain.equals(domain)) { + return; + } + this.domain = domain; + setParts(domain, actions, targets); + } + + public Set getActions() { + return actions; + } + + protected void setActions(Set actions) { + if (this.actions != null && this.actions.equals(actions)) { + return; + } + this.actions = actions; + setParts(domain, actions, targets); + } + + public Set getTargets() { + return targets; + } + + protected void setTargets(Set targets) { + this.targets = targets; + if (this.targets != null && this.targets.equals(targets)) { + return; + } + this.targets = targets; + setParts(domain, actions, targets); + } +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/Permission.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/Permission.java new file mode 100644 index 0000000000..155fb6771d --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/Permission.java @@ -0,0 +1,89 @@ +/* + * Copyright 2014 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.spring.security.authz.permission; + +import org.springframework.security.core.GrantedAuthority; + +/** + * A Permission represents the ability to perform an action or access a resource. A Permission is the most + * granular, or atomic, unit in a system's security policy and is the cornerstone upon which fine-grained security + * models are built. + *

+ * It is important to understand a Permission instance only represents functionality or access - it does not grant it. + * Granting access to an application functionality or a particular resource is done by the application's security + * configuration, typically by assigning Permissions to users, roles and/or groups. + *

+ * Spring Security is role-based in nature, where a role represents common behavior for certain user types. + * For example, a system might have an Aministrator role, a User or Guest roles, etc. + *

+ * But if you have a dynamic security model, where roles can be created and deleted at runtime, you can't hard-code + * role names in your code. In this environment, roles themselves aren't very useful. What matters is what + * permissions are assigned to these roles. + *

+ * Under this paradigm, permissions are immutable and reflect an application's raw functionality + * (opening files, accessing a web URL, creating users, etc). This is what allows a system's security policy + * to be dynamic: because Permissions represent raw functionality and only change when the application's + * source code changes, they are immutable at runtime - they represent 'what' the system can do. Roles, users, and + * groups are the 'who' of the application. Determining 'who' can do 'what' then becomes a simple exercise of + * associating Permissions to roles, users, and groups in some way. + *

+ * Most applications do this by associating a named role with permissions (i.e. a role 'has a' collection of + * Permissions) and then associate users with roles (i.e. a user 'has a' collection of roles) so that by transitive + * association, the user 'has' the permissions in their roles. There are numerous variations on this theme + * (permissions assigned directly to users, or assigned to groups, and users added to groups and these groups in turn + * have roles, etc, etc). When employing a permission-based security model instead of a role-based one, users, roles, + * and groups can all be created, configured and/or deleted at runtime. This enables an extremely powerful security + * model. + *

+ * In this case, we extend the {@link GrantedAuthority} interface with a method that allows us to compare permissions. + * With such operation we can customize the way a comparison is made. For example, instead of comparing if one permission + * equals a required permission, we can check if one permission satisfies it in some other way. + *

+ * A benefit to this is that, although it assumes most systems are based on these types of static role or + * dynamic role w/ permission schemes, it does not require a system to model their security data this way - all + * Permission checks can been relegated to custom implementations, and only those implementations really determine how + * a user 'has' a permission or not. The {@link org.springframework.security.authentication.AuthenticationProvider + * AuthenticationProvider} could use the semantics described here, or it could utilize some other mechanism entirely - it + * is always up to the application developer. + *

+ * Here we provide a very powerful default implementation of this interface in the form of the + * {@link com.stormpath.spring.security.authz.permission.WildcardPermission WildcardPermission}. We highly recommend that you + * investigate this class before trying to implement your own Permissions. + * + * @see com.stormpath.spring.security.authz.permission.WildcardPermission WildcardPermission + * @since 0.1.1 + */ +public interface Permission extends GrantedAuthority { + + /** + * Returns {@code true} if this current instance implies all the functionality and/or resource access + * described by the specified {@code Permission} argument, {@code false} otherwise. + *

+ *

That is, this current instance must be exactly equal to or a superset of the functionalty + * and/or resource access described by the given {@code Permission} argument. Yet another way of saying this + * would be: + *

+ *

If "permission1 implies permission2", i.e. permission1.implies(permission2) , + * then any Subject granted {@code permission1} would have ability greater than or equal to that defined by + * {@code permission2}. + * + * @param p the permission to check for behavior/functionality comparison. + * @return {@code true} if this current instance implies all the functionality and/or resource access + * described by the specified {@code Permission} argument, {@code false} otherwise. + */ + boolean implies(Permission p); + +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/WildcardPermission.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/WildcardPermission.java new file mode 100644 index 0000000000..911cde6273 --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/WildcardPermission.java @@ -0,0 +1,289 @@ +/* + * Copyright 2014 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.spring.security.authz.permission; + +import com.stormpath.sdk.lang.Strings; +import com.stormpath.spring.security.util.CollectionUtils; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * A WildcardPermission is a very flexible permission construct supporting multiple levels of + * permission matching. However, most people will probably follow some standard conventions as explained below. + *

+ *

Simple Usage

+ *

+ * In the simplest form, WildcardPermission can be used as a simple permission string. You could grant a + * user an "editNewsletter" permission and then check to see if the user has the editNewsletter + * permission by calling + *

+ * permission.implies("editNewsletter") + *

+ * The simple permission string may work for simple applications, but it requires you to have permissions like + * "viewNewsletter", "deleteNewsletter", + * "createNewsletter", etc. You can also grant a user "*" permissions + * using the wildcard character (giving this class its name), which means they have all permissions. But + * using this approach there's no way to just say a user has "all newsletter permissions". + *

+ * For this reason, WildcardPermission supports multiple levels of permissioning. + *

+ *

Multiple Levels

+ *

+ * WildcardPermission also supports the concept of multiple levels. For example, you could + * restructure the previous simple example by granting a user the permission "newsletter:edit". + * The colon in this example is a special character used by the WildcardPermission that delimits the + * next token in the permission. + *

+ * In this example, the first token is the domain that is being operated on + * and the second token is the action being performed. Each level can contain multiple values. So you + * could simply grant a user the permission "newsletter:view,edit,create" which gives them + * access to perform view, edit, and create actions in the newsletter + * domain. Then you could check to see if the user has the "newsletter:create" + * permission by calling + *

+ * permission.implies("newsletter:create") + *

+ * (which would return true). + *

+ * In addition to granting multiple permissions via a single string, you can grant all permission for a particular + * level. So if you wanted to grant a user all actions in the newsletter domain, you could simply give + * them "newsletter:*". Now, any permission check for "newsletter:XXX" + * will return true. It is also possible to use the wildcard token at the domain level (or both): so you + * could grant a user the "view" action across all domains "*:view". + *

+ *

Instance-level Access Control

+ *

+ * Another common usage of the WildcardPermission is to model instance-level Access Control Lists. + * In this scenario you use three tokens - the first is the domain, the second is the action, and + * the third is the instance you are acting on. + *

+ * So for example you could grant a user "newsletter:edit:12,13,18". In this example, assume + * that the third token is the system's ID of the newsletter. That would allow the user to edit newsletters + * 12, 13, and 18. This is an extremely powerful way to express permissions, + * since you can now say things like "newsletter:*:13" (grant a user all actions for newsletter + * 13), "newsletter:view,create,edit:*" (allow the user to + * view, create, or edit any newsletter), or + * "newsletter:*:* (allow the user to perform any action on any newsletter). + *

+ * To perform checks against these instance-level permissions, the application should include the instance ID in the + * permission check like so: + *

+ * permission.implies( "newsletter:edit:13" ) + *

+ * There is no limit to the number of tokens that can be used, so it is up to your imagination in terms of ways that + * this could be used in your application. + * + * @since 0.2.0 + */ +public class WildcardPermission implements Permission, Serializable { + + //TODO - JavaDoc methods + + /*-------------------------------------------- + | C O N S T A N T S | + ============================================*/ + public static final String WILDCARD_TOKEN = "*"; + public static final String PART_DIVIDER_TOKEN = ":"; + public static final String SUBPART_DIVIDER_TOKEN = ","; + protected static final boolean DEFAULT_CASE_SENSITIVE = false; + + /*-------------------------------------------- + | I N S T A N C E V A R I A B L E S | + ============================================*/ + private List> parts; + private final boolean caseSensitive; + + /*-------------------------------------------- + | C O N S T R U C T O R S | + ============================================*/ + /** + * Default no-arg constructor for subclasses only - end-user developers instantiating instances must + * provide a wildcard string at a minimum, since these instances are immutable once instantiated. + *

+ * Note that the WildcardPermission class is very robust and typically subclasses are not necessary unless you + * wish to create type-safe Permission objects that would be used in your application, such as perhaps a + * {@code UserPermission}, {@code SystemPermission}, {@code PrinterPermission}, etc. If you want such type-safe + * permission usage, consider subclassing the {@link DomainPermission DomainPermission} class for your needs. + */ + protected WildcardPermission() { + this.caseSensitive = DEFAULT_CASE_SENSITIVE; + } + + public WildcardPermission(String wildcardString) { + this(wildcardString, DEFAULT_CASE_SENSITIVE); + } + + public WildcardPermission(String wildcardString, boolean caseSensitive) { + this.caseSensitive = caseSensitive; + setParts(wildcardString, caseSensitive); + } + + protected void setParts(String wildcardString) { + setParts(wildcardString, DEFAULT_CASE_SENSITIVE); + } + + protected void setParts(String wildcardString, boolean caseSensitive) { + if (wildcardString == null || wildcardString.trim().length() == 0) { + throw new IllegalArgumentException("Wildcard string cannot be null or empty. Make sure permission strings are properly formatted."); + } + + wildcardString = wildcardString.trim(); + + List parts = CollectionUtils.asList(wildcardString.split(PART_DIVIDER_TOKEN)); + + this.parts = new ArrayList>(); + for (String part : parts) { + Set subparts = CollectionUtils.asSet(part.split(SUBPART_DIVIDER_TOKEN)); + if (subparts.isEmpty()) { + throw new IllegalArgumentException("Wildcard string cannot contain parts with only dividers. Make sure permission strings are properly formatted."); + } + this.parts.add(subparts); + } + + if (this.parts.isEmpty()) { + throw new IllegalArgumentException("Wildcard string cannot contain only dividers. Make sure permission strings are properly formatted."); + } + } + + private boolean isCaseSensitive() { + return this.caseSensitive; + } + + /*-------------------------------------------- + | A C C E S S O R S / M O D I F I E R S | + ============================================*/ + protected List> getParts() { + return this.parts; + } + + /*-------------------------------------------- + | M E T H O D S | + ============================================*/ + + @Override + public String getAuthority() { + return toString(); + } + + /** + * Only supports comparisons with other WildcardPermissions. + * + * @param p the WildcardPermission to be compared to + * @return true if the given WildcardPermission matches the permission specified by this permission + */ + @Override + public boolean implies(Permission p) { + // By default only supports comparisons with other WildcardPermissions + if (!(p instanceof WildcardPermission)) { + return false; + } + + WildcardPermission wp = (WildcardPermission) p; + + List> otherParts = wp.getParts(); + + int i = 0; + for (Set otherPart : otherParts) { + // If this permission has less parts than the other permission, everything after the number of parts contained + // in this permission is automatically implied, so return true + if (getParts().size() - 1 < i) { + return true; + } else { + Set part = getParts().get(i); + + if(isCaseSensitive()) { + // Let's do a case sensitive comparison + if (!compareCaseSensitive(part, otherPart)) { + return false; + } + } else { + // Let's do a case insensitive comparison + if(! compareCaseInsensitive(part, otherPart)) { + return false; + } + } + i++; + } + } + + // If this permission has more parts than the other parts, only imply it if all of the other parts are wildcards + for (; i < getParts().size(); i++) { + Set part = getParts().get(i); + if (!part.contains(WILDCARD_TOKEN)) { + return false; + } + } + + return true; + } + + public String toString() { + StringBuilder buffer = new StringBuilder(); + for (Set part : this.getParts()) { + if (buffer.length() > 0) { + buffer.append(PART_DIVIDER_TOKEN); + } + buffer.append(Strings.collectionToDelimitedString(part, SUBPART_DIVIDER_TOKEN)); + } + return buffer.toString(); + } + + public boolean equals(Object o) { + if (o instanceof WildcardPermission) { + WildcardPermission wp = (WildcardPermission) o; + return parts.equals(wp.parts); + } + return false; + + } + + public int hashCode() { + return parts.hashCode(); + } + + private boolean compareCaseSensitive(Set part, Set otherPart) { + if (!part.contains(WILDCARD_TOKEN) && !part.containsAll(otherPart)) { + return false; + } + return true; + } + + private boolean compareCaseInsensitive(Set part, Set otherPart) { + if (!part.contains(WILDCARD_TOKEN)) { + boolean foundSubOtherPart = false; + for(String subOtherPart : otherPart){ + foundSubOtherPart = false; + for(String subPart : part){ + if(subPart.equalsIgnoreCase(subOtherPart)) { + foundSubOtherPart = true; + break; + } + } + if(!foundSubOtherPart) { + return false; + } + } + if(!foundSubOtherPart) { + return false; + } + } + return true; + } + +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/evaluator/WildcardPermissionEvaluator.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/evaluator/WildcardPermissionEvaluator.java new file mode 100644 index 0000000000..b16b7415e0 --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/evaluator/WildcardPermissionEvaluator.java @@ -0,0 +1,90 @@ +/* + * Copyright 2014 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.spring.security.authz.permission.evaluator; + +import com.stormpath.spring.security.authz.permission.Permission; +import com.stormpath.spring.security.authz.permission.WildcardPermission; +import org.springframework.security.access.PermissionEvaluator; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +import java.io.Serializable; +import java.util.Collection; + +/** + * A {@link PermissionEvaluator} that determines if a {@link WildcardPermission} matches a given permission. + *

Usage

+ * In order to use it you need to configure Spring this way: + *
+ *      
+ *      
+ *          
+ *      
+ *      
+ *          
+ *      
+ * 
+ * And then you can simply evaluate permissions this way using Method Security Expressions: + *
+        @PreAuthorize("hasPermission(...)")
+ * 
+ * or using JSP taglibs + *
+ *      
+ * 
+ * + * @since 0.2.0 + */ +public class WildcardPermissionEvaluator implements PermissionEvaluator { + + @Override + public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { + + String domainObjectString = ""; + if(targetDomainObject != null) { + domainObjectString = targetDomainObject + WildcardPermission.PART_DIVIDER_TOKEN; + } + + //Let's construct a WildcardPermission out of the given parameters + Permission toMatch = new WildcardPermission( domainObjectString + permission); + + Collection authorities = authentication.getAuthorities(); + for(GrantedAuthority authority : authorities) { + //This evaluator only compares WildcardPermissions + if (authority instanceof WildcardPermission) { + WildcardPermission wp = (WildcardPermission) authority; + //Let's delegate the actual comparison to the WildcardPermission + if(wp.implies(toMatch)){ + return true; + } + } + } + + return false; + } + + @Override + public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { + String targetIdString = ""; + if(targetIdString != null) { + targetIdString = WildcardPermission.PART_DIVIDER_TOKEN + targetId; + } + + return hasPermission(authentication, targetType + targetIdString, permission); + } + +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/cache/SpringCache.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/cache/SpringCache.java new file mode 100644 index 0000000000..c22f3d4758 --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/cache/SpringCache.java @@ -0,0 +1,68 @@ +/* + * 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.spring.security.cache; + +import com.stormpath.sdk.cache.Cache; +import com.stormpath.sdk.lang.Assert; + +/** + * A Stormpath SDK {@link com.stormpath.sdk.cache.Cache} implementation that wraps a Spring {@link org.springframework.cache.Cache Cache} instance. + * This allows the Stormpath SDK to use your existing Spring caching mechanism so you only need to configure one + * caching implementation. + *

+ * This implementation effectively acts as an adapter or bridge from the Stormpath SDK cache API to the Spring cache API. + * + * @since 0.2.0 + */ +public class SpringCache implements Cache { + + private final org.springframework.cache.Cache SPRING_CACHE; + + /** + * Constructs a new {@code SpringCache} instance that wraps (delegates to) the specified + * Spring {@link org.springframework.cache.Cache Cache} instance. + * + * @param springCache the target Spring cache to wrap. + */ + public SpringCache(final org.springframework.cache.Cache springCache) { + Assert.notNull(springCache, "Spring cache instance cannot be null."); + this.SPRING_CACHE = springCache; + } + + @Override + public V get(K key) { + org.springframework.cache.Cache.ValueWrapper valueWrapper = SPRING_CACHE.get(key); + if(valueWrapper != null) { + return (V) valueWrapper.get(); + } + return null; + } + + @Override + public V put(K key, V value) { + V previousValue = get(key); + SPRING_CACHE.put(key, value); + return previousValue; + } + + @Override + public V remove(K key) { + V value = get(key); + SPRING_CACHE.evict(key); + return value; + } + +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/cache/SpringCacheManager.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/cache/SpringCacheManager.java new file mode 100644 index 0000000000..18e3509c4d --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/cache/SpringCacheManager.java @@ -0,0 +1,62 @@ +/* + * 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.spring.security.cache; + +import com.stormpath.sdk.cache.Cache; +import com.stormpath.sdk.cache.CacheManager; +import com.stormpath.sdk.lang.Assert; + +/** + * A Stormpath SDK {@link com.stormpath.sdk.cache.CacheManager} implementation that wraps a Spring + * {@link org.springframework.cache.CacheManager CacheManager} instance. This allows the Stormpath SDK to use your + * existing Spring caching mechanism so you only need to configure one caching implementation. + *

+ * This implementation effectively acts as an adapter or bridge from the Stormpath SDK cacheManager API to the Spring + * CacheManager API. + * + * @since 0.2.0 + */ +public class SpringCacheManager implements CacheManager { + + private final org.springframework.cache.CacheManager SPRING_CACHE_MANAGER; + + /** + * Constructs a new {@code SpringCacheManager} instance that wraps (delegates to) the specified + * Spring {@link org.springframework.cache.CacheManager CacheManager} instance. + * + * @param springCacheManager the target Spring cache manager to wrap. + */ + public SpringCacheManager(org.springframework.cache.CacheManager springCacheManager) { + Assert.notNull(springCacheManager, "Spring CacheManager instance cannot be null."); + this.SPRING_CACHE_MANAGER = springCacheManager; + } + + /** + * Consults the wrapped Spring {@link org.springframework.cache.CacheManager CacheManager} instance to obtain a + * named Spring {@link org.springframework.cache.Cache Cache} instance. The instance is wrapped and returned as a + * {@link SpringCache} instance, which acts as a bridge/adapter over Spring's existing Cache API. + * + * @param name the name of the cache to acquire. + * @param The cache key type + * @param The cache value type + * @return the Cache with the given name + */ + @Override + public Cache getCache(String name) { + final org.springframework.cache.Cache springCache = SPRING_CACHE_MANAGER.getCache(name); + return new SpringCache(springCache); + } +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/client/ClientFactory.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/client/ClientFactory.java new file mode 100644 index 0000000000..6ce5225b7b --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/client/ClientFactory.java @@ -0,0 +1,190 @@ +/* + * 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.spring.security.client; + +import com.stormpath.sdk.api.ApiKeys; +import com.stormpath.sdk.cache.CacheManager; +import com.stormpath.sdk.client.ApiKey; +import com.stormpath.sdk.client.Client; +import com.stormpath.sdk.client.ClientBuilder; +import com.stormpath.sdk.client.Clients; +import com.stormpath.spring.security.cache.SpringCacheManager; +import org.springframework.beans.factory.config.AbstractFactoryBean; + +import java.io.InputStream; +import java.io.Reader; +import java.util.Properties; + +/** + * A simple factory that allows a Stormpath SDK Client to be created via Spring's + * {@link org.springframework.beans.factory.config.AbstractFactoryBean} concept. + *

+ * As this class is a simple bridge between APIs, it does not do much - all configuration properties are immediately + * passed through to an internal {@link com.stormpath.sdk.client.ClientBuilder ClientBuilder} instance, and the + * {@link #createInstance()} implementation merely calls {@link com.stormpath.sdk.client.ClientBuilder#build()}. + *

Usage

+ * Example {@code spring.xml} configuration: + *

+ *

+ * 
+ * 
+ * 
+ *
+ * ...
+ * 
+ * + * @see ClientBuilder + * @see ClientBuilder#setApiKey(com.stormpath.sdk.api.ApiKey) + * @see com.stormpath.sdk.api.ApiKeyBuilder#setFileLocation(String) + */ +public class ClientFactory extends AbstractFactoryBean { + + private ClientBuilder clientBuilder; + private ApiKey apiKey; + + public ClientFactory() { + super(); + this.clientBuilder = Clients.builder(); + } + + @Override + protected Client createInstance() throws Exception { + return clientBuilder.build(); + } + + @Override + public Class getObjectType() { + return Client.class; + } + + public ClientBuilder getClientBuilder() { + return clientBuilder; + } + + public void setClientBuilder(ClientBuilder clientBuilder) { + this.clientBuilder = clientBuilder; + } + + /** + * Calls {@code clientBuilder.setApiKey(ApiKeys.builder().}{@link com.stormpath.sdk.api.ApiKeyBuilder#setFileLocation(String) setFileLocation(apiKeyFileLocation)}{@code .build())}. + * See that JavaDoc for expected syntax/format. + * + * @param apiKeyFileLocation the file, classpath or url location of the API Key {@code .properties} file to load when + * constructing the API Key to use for communicating with the Stormpath REST API. + * @see ClientBuilder#setApiKey(com.stormpath.sdk.api.ApiKey) + * @see com.stormpath.sdk.api.ApiKeyBuilder#setFileLocation(String) + * @since 0.2 + */ + public void setApiKeyFileLocation(String apiKeyFileLocation) { + this.clientBuilder.setApiKey(ApiKeys.builder().setFileLocation(apiKeyFileLocation).build()); + } + + /** + * Calls {@code clientBuilder.setApiKey(ApiKeys.builder().}{@link com.stormpath.sdk.api.ApiKeyBuilder#setInputStream(java.io.InputStream) setInputStream(apiKeyInputStream)}{@code .build())}. + * + * @param apiKeyInputStream the InputStream to use to construct a configuration Properties instance. + * @see ClientBuilder#setApiKey(com.stormpath.sdk.api.ApiKey) + * @see com.stormpath.sdk.api.ApiKeyBuilder#setInputStream(java.io.InputStream) + * @since 0.2 + */ + public void setApiKeyInputStream(InputStream apiKeyInputStream) { + this.clientBuilder.setApiKey(ApiKeys.builder().setInputStream(apiKeyInputStream).build()); + } + + /** + * Calls {@code clientBuilder.setApiKey(ApiKeys.builder().}{@link com.stormpath.sdk.api.ApiKeyBuilder#setReader(java.io.Reader) setReader(apiKeyReader)}{@code .build())}. + * + * @param apiKeyReader the reader to use to construct a configuration Properties instance. + * @see ClientBuilder#setApiKey(com.stormpath.sdk.api.ApiKey) + * @see com.stormpath.sdk.api.ApiKeyBuilder#setReader(java.io.Reader) + * @since 0.2 + */ + public void setApiKeyReader(Reader apiKeyReader) { + this.clientBuilder.setApiKey(ApiKeys.builder().setReader(apiKeyReader).build()); + } + + /** + * Calls {@code clientBuilder.setApiKey(ApiKeys.builder().}{@link com.stormpath.sdk.api.ApiKeyBuilder#setProperties(java.util.Properties) setProperties(properties)}{@code .build())}. + * + * @param properties the properties instance to use to load the API Key ID and Secret. + * @see ClientBuilder#setApiKey(com.stormpath.sdk.api.ApiKey) + * @see com.stormpath.sdk.api.ApiKeyBuilder#setProperties(java.util.Properties) + * @since 0.2 + */ + public void setApiKeyProperties(Properties properties) { + this.clientBuilder.setApiKey(ApiKeys.builder().setProperties(properties).build()); + } + + /** + * Calls {@code clientBuilder.setApiKey(ApiKeys.builder().}{@link com.stormpath.sdk.api.ApiKeyBuilder#setIdPropertyName(String) setIdPropertyName(apiKeyIdPropertyName)}{@code .build())}. + * + * @param apiKeyIdPropertyName the name used to query for the API Key ID from a Properties instance. + * @see ClientBuilder#setApiKey(com.stormpath.sdk.api.ApiKey) + * @see com.stormpath.sdk.api.ApiKeyBuilder#setIdPropertyName(String) + * @since 0.2 + */ + public void setApiKeyIdPropertyName(String apiKeyIdPropertyName) { + this.clientBuilder.setApiKey(ApiKeys.builder().setIdPropertyName(apiKeyIdPropertyName).build()); + } + + /** + * Calls {@code clientBuilder.setApiKey(ApiKeys.builder().}{@link com.stormpath.sdk.api.ApiKeyBuilder#setSecretPropertyName(String) setSecretPropertyName(apiKeySecretPropertyName)}{@code .build())}. + * + * @param apiKeySecretPropertyName the name used to query for the API Key Secret from a Properties instance. + * @see ClientBuilder#setApiKey(com.stormpath.sdk.api.ApiKey) + * @see com.stormpath.sdk.api.ApiKeyBuilder#setSecretPropertyName(String) + * @since 0.2 + */ + public void setApiKeySecretPropertyName(String apiKeySecretPropertyName) { + this.clientBuilder.setApiKey(ApiKeys.builder().setSecretPropertyName(apiKeySecretPropertyName).build()); + } + + /** + * Uses the specified Spring {@link CacheManager} instance as the Stormpath SDK Client's CacheManager, allowing both + * Spring and Stormpath SDK to share the same cache mechanism. + *

+ * If for some reason you don't want to share the same cache mechanism, you can explicitly set a Stormpath SDK-only + * {@link com.stormpath.sdk.cache.CacheManager CacheManager} instance via the + * {@link #setStormpathCacheManager(com.stormpath.sdk.cache.CacheManager) setStormpathCacheManager} method. + * + * @param cacheManager the Spring CacheManager to use for the Stormpath SDK Client's caching needs. + * @since 0.2.0 + * @see #setStormpathCacheManager(com.stormpath.sdk.cache.CacheManager) + */ + public void setCacheManager(org.springframework.cache.CacheManager cacheManager) { + CacheManager stormpathCacheManager = new SpringCacheManager(cacheManager); + this.clientBuilder.setCacheManager(stormpathCacheManager); + } + + /** + * Calls {@code clientBuilder.}{@link ClientBuilder#setCacheManager(com.stormpath.sdk.cache.CacheManager) setCacheManager} + * using the specified Stormpath {@link com.stormpath.sdk.cache.CacheManager CacheManager} instance, but note: + * This method should only be used if the Stormpath SDK should use a different CacheManager than what + * Spring uses. + *

+ * If you prefer that Spring and the Stormpath SDK use the same cache mechanism to reduce complexity/configuration, + * configure your preferred Spring {@code cacheManager} first and then use that cacheManager by calling the + * {@link #setCacheManager(org.springframework.cache.CacheManager) setCacheManager(springCacheManager)} method + * instead of this one. + * + * @param cacheManager the Storpmath SDK-specific CacheManager to use for the Stormpath SDK Client's caching needs. + * @since 0.2.0 + * @see #setCacheManager(org.springframework.cache.CacheManager) + */ + public void setStormpathCacheManager(com.stormpath.sdk.cache.CacheManager cacheManager) { + this.clientBuilder.setCacheManager(cacheManager); + } +} \ No newline at end of file diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AccountCustomDataPermissionResolver.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AccountCustomDataPermissionResolver.java new file mode 100644 index 0000000000..fb77008641 --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AccountCustomDataPermissionResolver.java @@ -0,0 +1,35 @@ +/* + * 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.spring.security.provider; + +import com.stormpath.sdk.account.Account; +import com.stormpath.spring.security.authz.permission.Permission; + +import java.util.Set; + +/** + * A {@link CustomDataPermissionResolver} implementation that merely delegates lookup logic to the parent + * class, first calling {@link com.stormpath.sdk.account.Account#getCustomData() account.getCustomData()}. + * + * @since 0.2.0 + */ +public class AccountCustomDataPermissionResolver extends CustomDataPermissionResolver implements AccountPermissionResolver { + + @Override + public Set resolvePermissions(Account account) { + return super.getPermissions(account.getCustomData()); + } +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AccountGrantedAuthorityResolver.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AccountGrantedAuthorityResolver.java new file mode 100644 index 0000000000..9b7e1c1cf9 --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AccountGrantedAuthorityResolver.java @@ -0,0 +1,56 @@ +/* + * 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.spring.security.provider; + +import com.stormpath.sdk.account.Account; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Set; + +/** + * An {@code AccountGrantedAuthorityResolver} inspects a Stormpath {@link Account} and returns that {@code Account}'s + * directly assigned {@link org.springframework.security.core.GrantedAuthority}s. + *

+ * Note that this interface is for resolving granted authorities that are directly assigned to an Account. Granted authorities + * that are assigned to an account's groups (and therefore implicitly associated with an Account), would be resolved + * instead by a {@link GroupGrantedAuthorityResolver} instance. + *

+ * Spring Security checks these granted authorities (in addition to any assigned groups' granted authorities) to determine whether or not an + * {@link org.springframework.security.core.Authentication Authentication} representing the {@code Account} is permitted to do something. + * + * @see GroupGrantedAuthorityResolver + */ +public interface AccountGrantedAuthorityResolver { + + /** + * Returns a set of {@link org.springframework.security.core.GrantedAuthority GrantedAuthority}s assigned to a particular Stormpath + * {@link Account}. + *

+ * Note that method is for resolving granted authorities that are directly assigned to an Account. Granted authorities + * that are assigned to an account's groups (and therefore implicitly associated with an Account), would be resolved + * instead by a {@link GroupGrantedAuthorityResolver} instance. + *

+ * Spring Security checks these granted authorities to determine whether or not an {@link org.springframework.security.core.Authentication Authentication} + * representing the {@code Account} is permitted to do something. + * + * @param account the Stormpath {@code Account} to inspect to return its directly assigned Spring Security granted authorities. + * @return a set of Spring Security {@link org.springframework.security.core.GrantedAuthority GrantedAuthority}s assigned to the account, to be + * used by Spring Security for runtime authorization checks. + * @see GroupGrantedAuthorityResolver + */ + Set resolveGrantedAuthorities(Account account); +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AccountPermissionResolver.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AccountPermissionResolver.java new file mode 100644 index 0000000000..acac05ae96 --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AccountPermissionResolver.java @@ -0,0 +1,52 @@ +/* + * 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.spring.security.provider; + +import com.stormpath.sdk.account.Account; +import com.stormpath.spring.security.authz.permission.Permission; + +import java.util.Set; + +/** + * An {@code AccountPermissionResolver} inspects a Stormpath {@link Account} and returns that {@code Account}'s + * directly assigned {@link com.stormpath.spring.security.authz.permission.Permission}s. + *

+ * Note that this interface is for resolving permissions that are directly assigned to an Account. Permissions + * that are assigned to an account's groups (and therefore implicitly associated with an Account), would be resolved + * instead by a {@link GroupPermissionResolver} instance instead. + *

+ * + * @since 0.2.0 + * @see GroupPermissionResolver + */ +public interface AccountPermissionResolver { + + /** + * Returns a set of {@link com.stormpath.spring.security.authz.permission.Permission}s assigned to a particular Stormpath + * {@link Account}. + *

+ * Note that method is for resolving permissions that are directly assigned to an Account. Permissions + * that are assigned to an account's groups (and therefore implicitly associated with an Account), would be resolved + * instead by a {@link GroupPermissionResolver} instance. + *

+ * @param account the Stormpath {@code Account} to inspect to return its directly assigned permissions. + * @return a set of {@link com.stormpath.spring.security.authz.permission.Permission Permission}s assigned to the account, to be + * used by Spring Security for runtime permission checks. + * @see GroupPermissionResolver + */ + Set resolvePermissions(Account account); + +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AuthenticationTokenFactory.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AuthenticationTokenFactory.java new file mode 100644 index 0000000000..1dd2e3333e --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AuthenticationTokenFactory.java @@ -0,0 +1,52 @@ +/* + * 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.spring.security.provider; + +import com.stormpath.sdk.account.Account; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +/** + * Interface to be implemented as a factory for {@code Authentication} tokens. + * + * @see UsernamePasswordAuthenticationTokenFactory + */ + +public interface AuthenticationTokenFactory { + + /** + * + * Creates a token for an authenticated principal once the request has been processed by the + * {@link com.stormpath.spring.security.provider.StormpathAuthenticationProvider#authenticate(Authentication)} method. + *

+ * Once the request has been authenticated, this Authentication will usually be stored in a thread-local + * SecurityContext managed by the {@link org.springframework.security.core.context.SecurityContextHolder}. + *

+ * + * @param principal The identity of the principal. In the case of an authentication with username and + * password, this would be the username. + * @param credentials The credentials that prove the principal is correct. This is usually a password, but could be anything + * relevant to the AuthenticationManager + * @param authorities the authorities that the principal has been granted + * @param account the Stormpath Account corresponding to the principal + * @return the Authentication token representing the authenticated principal + * + */ + public Authentication createAuthenticationToken(Object principal, Object credentials, Collection authorities, Account account); + +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/CustomDataPermissionResolver.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/CustomDataPermissionResolver.java new file mode 100644 index 0000000000..e6d0f3653b --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/CustomDataPermissionResolver.java @@ -0,0 +1,205 @@ +/* + * 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.spring.security.provider; + +import com.stormpath.sdk.directory.CustomData; +import com.stormpath.spring.security.authz.CustomDataPermissionsEditor; +import com.stormpath.spring.security.authz.permission.Permission; +import org.springframework.util.CollectionUtils; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * A {@code CustomDataPermissionResolver} accesses a {@link #getCustomDataFieldName() specific named field} value of + * a {@link com.stormpath.sdk.directory.CustomData} resource that contains a {@code Set<String>} of Spring Security permission Strings. + *

+ * The permissions stored in that field are assumed to be assigned to the CustomData's owning entity (an + * {@link com.stormpath.sdk.account.Account Account} or {@link com.stormpath.sdk.group.Group Group}). + *

Custom Data Field Name

+ * You can configure what named field is used to store the permissions via + * {@link #setCustomDataFieldName(String) fieldName} property. + *

String to Permission Conversion

+ * The Strings stored in the CustomData resource are converted to {@link com.stormpath.spring.security.authz.permission.Permission} instances via the + * {@link #getPermissionResolver() getPermissionResolver} property. Unless overridden, the default instance is a + * {@link WildcardPermissionResolver}. + * + * @see AccountCustomDataPermissionResolver + * @see GroupCustomDataPermissionResolver + */ +public class CustomDataPermissionResolver { + + private String customDataFieldName; + + private PermissionResolver permissionResolver; + + /** + * Creates a new instance, using the default {@link #getCustomDataFieldName() customDataFieldName} of + * {@code springSecurityPermissions} and a default {@link WildcardPermissionResolver}. + */ + public CustomDataPermissionResolver() { + this.customDataFieldName = CustomDataPermissionsEditor.DEFAULT_CUSTOM_DATA_FIELD_NAME; + this.permissionResolver = new WildcardPermissionResolver(); + } + + /** + * Returns the name of the {@link com.stormpath.sdk.directory.CustomData} field used to store the {@code Set<String>} + * of permissions. The default name is + * {@code springSecurityPermissions}, implying a {@code CustomData} JSON representation as follows: + *
+     * {
+     *     ... any other of your own custom data properties ...,
+     *
+     *     "springSecurityPermissions": [
+     *         "perm1",
+     *         "perm2",
+     *         ...,
+     *         "permN"j
+     *     ]
+     * }
+     * 
+ * You can change the name by calling {@link #setCustomDataFieldName(String)}. + * + * @return the name of the {@link com.stormpath.sdk.directory.CustomData} field used to store the {@code Set<String>} + * of permissions. + */ + public String getCustomDataFieldName() { + return customDataFieldName; + } + + /** + * Sets the name of the {@link com.stormpath.sdk.directory.CustomData} field used to store the {@code Set<String>} + * of permissions. The default name is + * {@code springSecurityPermissions}, implying a {@code CustomData} JSON representation as follows: + *
+     * {
+     *     ... any other of your own custom data properties ...,
+     *
+     *     "springSecurityPermissions": [
+     *         "perm1",
+     *         "perm2",
+     *         ...,
+     *         "permN"j
+     *     ]
+     * }
+     * 
+ * If you changed this name to be {@code myApplicationPermissions} for example, the CustomData representation + * would look something like this instead: + *
+     * {
+     *     ... any other of your own custom data properties ...,
+     *
+     *     "myApplicationPermissions": [
+     *         "perm1",
+     *         "perm2",
+     *         ...,
+     *         "permN"j
+     *     ]
+     * }
+     * 
+ * + * @param customDataFieldName the name of the {@link com.stormpath.sdk.directory.CustomData} field used to store the {@code Set<String>} + * of permissions. + */ + public void setCustomDataFieldName(String customDataFieldName) { + this.customDataFieldName = customDataFieldName; + } + + /** + * Returns the {@link AccountPermissionResolver} used to convert {@link #getCustomDataFieldName() stored permission strings} + * to {@link Permission} instances for authorization. + *

+ * The default / pre-configured instance is a {@link WildcardPermissionResolver}. + * + * @return the {@link AccountPermissionResolver} used to convert {@link #getCustomDataFieldName() stored permission strings} + * to {@link Permission} instances for authorization. + */ + public PermissionResolver getPermissionResolver() { + return permissionResolver; + } + + /** + * Sets the {@link AccountPermissionResolver} used to convert {@link #getCustomDataFieldName() stored permission strings} + * to {@link Permission} instances for authorization. + * + * @param permissionResolver the {@link AccountPermissionResolver} used to convert {@link #getCustomDataFieldName() stored permission strings} + * to {@link Permission} instances for authorization. + */ + public void setPermissionResolver(PermissionResolver permissionResolver) { + this.permissionResolver = permissionResolver; + } + + /** + * Returns a {@code Set<String>} of permission strings that are stored in the specified + * {@link com.stormpath.sdk.directory.CustomData} instance (under the {@link #getCustomDataFieldName() customDataFieldName} key), or an + * empty collection if no permissions are stored. + *

+ * This implementation internally delegates field access and Set construction to a + * {@link CustomDataPermissionsEditor} instance, e.g. + *

+     * return new CustomDataPermissionsEditor(customData)
+     *     .setFieldName(getCustomDataFieldName())
+     *     .getPermissionStrings();
+     * 
+ * + * @param customData the custom data instance that might have a {@code Set<String>} of permissions stored in + * a key named {@link #getCustomDataFieldName()}. + * @return a {@code Set<String>} of permission strings that are stored in the specified + * {@link com.stormpath.sdk.directory.CustomData} instance (under the {@link #getCustomDataFieldName() customDataFieldName} key), or an + * empty collection if no permissions are stored. + * @see CustomDataPermissionsEditor + */ + protected Set getPermissionStrings(CustomData customData) { + return new CustomDataPermissionsEditor(customData) + .setFieldName(getCustomDataFieldName()) + .getPermissionStrings(); + } + + /** + * Returns a set of {@link Permission} instances stored in the specified {@link com.stormpath.sdk.directory.CustomData} resource. This + * implementation will: + *
    + *
  1. {@link #getPermissionStrings(com.stormpath.sdk.directory.CustomData) Get all permission strings} stored + * in the CustomData instance
  2. + *
  3. Loop over these strings, and for each one, create a {@link Permission} instance using the + * {@link #getPermissionResolver() permissionResolver} property.
  4. + *
  5. Return the total constructed Set of Permission instances to the caller.
  6. + *
+ * + * @param customData the CustomData instance that may contain permission strings to obtain + * @return a set of {@link Permission} instances stored in the specified {@link com.stormpath.sdk.directory.CustomData} resource. + */ + protected Set getPermissions(CustomData customData) { + + Set permStrings = getPermissionStrings(customData); + + if (CollectionUtils.isEmpty(permStrings)) { + return Collections.emptySet(); + } + + PermissionResolver permissionResolver = getPermissionResolver(); + + Set permissions = new HashSet(permStrings.size()); + + for (String s : permStrings) { + Permission permission = permissionResolver.resolvePermission(s); + permissions.add(permission); + } + + return permissions; + } +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/DefaultGroupGrantedAuthorityResolver.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/DefaultGroupGrantedAuthorityResolver.java new file mode 100644 index 0000000000..62919592da --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/DefaultGroupGrantedAuthorityResolver.java @@ -0,0 +1,173 @@ +/* + * Copyright 2015 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.spring.security.provider; + +import com.stormpath.sdk.group.Group; +import com.stormpath.sdk.lang.Assert; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import java.util.HashSet; +import java.util.Set; + +/** + * Default implementation of the {@code GroupGrantedAuthorityResolver} interface that allows a Stormpath + * {@link com.stormpath.sdk.group.Group} to be translated into Spring Security granted authorities based on + * custom preferences. + *

Overview

+ * This implementation converts a Group into one or more granted authorities based on one or more configured + * {@link com.stormpath.spring.security.provider.DefaultGroupGrantedAuthorityResolver.Mode Mode}s: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Mode (case insensitive)BehaviorExample
HREFReturns the Group's fully qualified HREF as the granted authority{@code https://api.stormpath.com/v1/groups/upXiExAmPlEfA5L1G5ZaSQ}
IDReturns Group's globally unique identifier as the granted authority{@code upXiExAmPlEfA5L1G5ZaSQ}
NAMEReturns Group's name as the granted authority{@code administrators}
+ *

Usage

+ * You can choose one or more modes either by referencing the {@link com.stormpath.spring.security.provider.DefaultGroupGrantedAuthorityResolver.Mode Mode} enum values directly, or by using + * the mode string names. + *

+ * For example, in code: + *

+ * Set<DefaultGroupGrantedAuthorityResolver.Mode> modes = CollectionUtils.asSet(
+ *     DefaultGroupGrantedAuthorityResolver.Mode.HREF, DefaultGroupGrantedAuthorityResolver.Mode.ID
+ * );
+ * groupRoleResolver.setModes(modes);
+ * 
+ *

+ * Or maybe in spring.xml: + *

+ * 
+ *      
+ *          
+ *              href
+ *              id
+ *          
+ *      
+ * 
+ * 
+ * In the above configuration, each Group translates into two granted authorities: one is the raw href itself, + * the other one is the Group ID. You can specify one or more modes to translate into one or more granted authorities + * names respectively. + * modeNames are case-insensitive. + *

+ * WARNING: Group Names, while easier to read in code, can change at any time via a REST API call or by using + * the Stormpath UI Console. It is strongly recommended to use only the HREF or ID modes as these values + * will never change. Acquiring group names might also incur a REST server call, whereas the the HREF is guaranteed + * to be present. + * + */ +public class DefaultGroupGrantedAuthorityResolver implements GroupGrantedAuthorityResolver { + + private Set modes; + + public DefaultGroupGrantedAuthorityResolver() { + this.modes = new HashSet(); + this.modes.add(Mode.HREF); + } + + public Set getModes() { + return modes; + } + + public void setModes(Set modes) { + if (modes == null || modes.isEmpty()) { + throw new IllegalArgumentException("modes property cannot be null or empty."); + } + this.modes = modes; + } + + @Override + public Set resolveGrantedAuthorities(Group group) { + + Set set = new HashSet(); + + Set modes = getModes(); + + String groupHref = group.getHref(); + + //REST resource hrefs should never ever be null: + if (groupHref == null) { + throw new IllegalStateException("Group does not have an href property. This should never happen."); + } + + if (modes.contains(Mode.HREF)) { + set.add(new SimpleGrantedAuthority(groupHref)); + } + if (modes.contains(Mode.ID)) { + String instanceId = getInstanceId(groupHref); + if (instanceId != null) { + set.add(new SimpleGrantedAuthority(instanceId)); + } + } + if (modes.contains(Mode.NAME)) { + String name = group.getName(); + if (name != null) { + set.add(new SimpleGrantedAuthority(name)); + } + } + + return set; + } + + private String getInstanceId(String href) { + int i = href.lastIndexOf('/'); + if (i >= 0) { + return href.substring(i + 1); + } + return null; + } + + public enum Mode { + + HREF, + ID, + NAME; + + public static Mode fromString(final String name) { + Assert.notNull(name); + + String upper = name.toUpperCase(); + for (Mode mode : values()) { + if (mode.name().equals(upper)) { + return mode; + } + } + throw new IllegalArgumentException("There is no Mode name '" + name + "'"); + } + } +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/GroupCustomDataPermissionResolver.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/GroupCustomDataPermissionResolver.java new file mode 100644 index 0000000000..6721e98836 --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/GroupCustomDataPermissionResolver.java @@ -0,0 +1,35 @@ +/* + * 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.spring.security.provider; + +import com.stormpath.sdk.group.Group; +import com.stormpath.spring.security.authz.permission.Permission; + +import java.util.Set; + +/** + * A {@link CustomDataPermissionResolver} implementation that merely delegates lookup logic to the parent + * class, first calling {@link com.stormpath.sdk.group.Group#getCustomData() group.getCustomData()}. + * + * @since 0.2.0 + */ +public class GroupCustomDataPermissionResolver extends CustomDataPermissionResolver implements GroupPermissionResolver { + + @Override + public Set resolvePermissions(Group group) { + return super.getPermissions(group.getCustomData()); + } +} \ No newline at end of file diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/GroupGrantedAuthorityResolver.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/GroupGrantedAuthorityResolver.java new file mode 100644 index 0000000000..cbe4aed430 --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/GroupGrantedAuthorityResolver.java @@ -0,0 +1,47 @@ +/* + * 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.spring.security.provider; + +import com.stormpath.sdk.group.Group; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Set; + +/** + * A {@code GroupGrantedAuthorityResolver} inspects a Stormpath {@link Group} and returns that {@code Group}'s assigned + * {@link org.springframework.security.core.GrantedAuthority}s. + *

+ * Spring Security checks these granted authorities to determine whether or not an {@link org.springframework.security.core.Authentication Authentication} + * associated with the {@code Group} is permitted to do something. + * + * @see com.stormpath.spring.security.provider.AccountGrantedAuthorityResolver + */ +public interface GroupGrantedAuthorityResolver { + + /** + * Returns a set of {@link org.springframework.security.core.GrantedAuthority GrantedAuthority}s assigned to a particular Stormpath {@link Group}. + *

+ * Spring Security checks these granted authorities to determine whether or not an {@link org.springframework.security.core.Authentication Authentication} + * associated with the {@code Group} is permitted to do something. + * + * @param group the Stormpath {@code Group} to inspect to return its assigned Spring Security granted authorities. + * @return a set of Spring Security {@link org.springframework.security.core.GrantedAuthority GrantedAuthority}s assigned to the + * group, to be used by Spring Security for runtime authorization checks. + * @see com.stormpath.spring.security.provider.AccountGrantedAuthorityResolver + */ + Set resolveGrantedAuthorities(Group group); + +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/GroupPermissionResolver.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/GroupPermissionResolver.java new file mode 100644 index 0000000000..98da3d9789 --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/GroupPermissionResolver.java @@ -0,0 +1,47 @@ +/* + * 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.spring.security.provider; + +import com.stormpath.sdk.group.Group; +import com.stormpath.spring.security.authz.permission.Permission; + +import java.util.Set; + +/** + * A {@code GroupPermissionResolver} inspects a Stormpath {@link Group} and returns that {@code Group}'s assigned + * {@link Permission}s. + *

+ * Spring Security will check these permissions to determine whether or not a {@link org.springframework.security.core.Authentication Authentication} + * associated with the {@code Group} is permitted to do something. + * + * @see AccountPermissionResolver + * @since 0.2.0 + */ +public interface GroupPermissionResolver { + + /** + * Returns a set of {@link Permission Permission}s assigned to a particular Stormpath {@link Group}. + *

+ * Spring Security will check these permissions to determine whether or not a {@link org.springframework.security.core.Authentication Authentication} + * associated with the {@code Group} is permitted to do something. + * + * @param group the Stormpath {@code Group} to inspect to return its assigned Permissions. + * @return a set of {@link Permission Permission}s assigned to the group, to be used by Spring Security for runtime + * permission checks. + * @see AccountPermissionResolver + */ + Set resolvePermissions(Group group); +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/InvalidPermissionStringException.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/InvalidPermissionStringException.java new file mode 100644 index 0000000000..2a022c7374 --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/InvalidPermissionStringException.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/* + * Copyright 2014 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.spring.security.provider; + + +/** + * Thrown by {@link PermissionResolver#resolvePermission(String)} when the String being parsed is not + * valid for that resolver. + * + * @since 0.2.0 + */ +public class InvalidPermissionStringException extends RuntimeException +{ + + private String permissionString; + + /** + * Constructs a new exception with the given message and permission string. + * + * @param message the exception message. + * @param permissionString the invalid permission string. + */ + public InvalidPermissionStringException(String message, String permissionString) { + super(message); + this.permissionString = permissionString; + } + + /** + * Returns the permission string that was invalid and caused this exception to + * be thrown. + * + * @return the permission string. + */ + public String getPermissionString() { + return this.permissionString; + } + + +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/PermissionResolver.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/PermissionResolver.java new file mode 100644 index 0000000000..a9ca911d51 --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/PermissionResolver.java @@ -0,0 +1,51 @@ +/* + * 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.spring.security.provider; + +import com.stormpath.spring.security.authz.permission.Permission; + +/** + * A {@code PermisisonResolver} resolves a String value and converts it into a + * {@link com.stormpath.spring.security.authz.permission.Permission Permission} instance. + *

+ * The default {@link WildcardPermissionResolver} should be + * suitable for most purposes, which constructs {@link com.stormpath.spring.security.authz.permission.WildcardPermission} objects. + * However, any resolver may be configured if an application wishes to use different + * {@link com.stormpath.spring.security.authz.permission.Permission} implementations. + *

+ * We suggest to use {@link com.stormpath.spring.security.authz.permission.WildcardPermission WildcardPermission}s. + * One of the nice things about {@code WildcardPermission}s is that it makes it very easy to + * store complex permissions in the database - and also makes it very easy to represent permissions in JSP files, + * annotations, etc., where a simple string representation is useful. + *

+ * You are of course free to provide custom String-to-Permission conversion by providing Spring Security components any instance + * of this interface. + * + * @since 0.2.0 + */ +public interface PermissionResolver { + + /** + * Resolves a Permission based on the given String representation. + * + * @param permissionString the String representation of a permission. + * @return A Permission object that can be used internally to determine a subject's permissions. + * @throws InvalidPermissionStringException + * if the permission string is not valid for this resolver. + */ + Permission resolvePermission(String permissionString); + +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/StormpathAuthenticationProvider.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/StormpathAuthenticationProvider.java new file mode 100644 index 0000000000..b52ab68ff4 --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/StormpathAuthenticationProvider.java @@ -0,0 +1,459 @@ +/* + * 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.spring.security.provider; + +import com.stormpath.sdk.account.Account; +import com.stormpath.sdk.application.Application; +import com.stormpath.sdk.authc.AuthenticationRequest; +import com.stormpath.sdk.authc.UsernamePasswordRequest; +import com.stormpath.sdk.client.Client; +import com.stormpath.sdk.group.Group; +import com.stormpath.sdk.group.GroupList; +import com.stormpath.sdk.resource.ResourceException; +import com.stormpath.spring.security.authz.permission.Permission; +import com.stormpath.spring.security.util.StringUtils; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * A {@code AuthenticationProvider} implementation that uses the Stormpath Cloud Identity + * Management service for authentication and authorization operations for a single Application. + *

+ * The Stormpath-registered + * Application's Stormpath REST URL + * must be configured as the {@code applicationRestUrl} property. + *

Authentication

+ * Once your application's REST URL is configured, this provider implementation automatically executes authentication + * attempts without any need of further configuration by interacting with the Application's + * loginAttempts endpoint. + *

Authorization

+ * Stormpath Accounts and Groups can be translated to Spring Security granted authorities via the following components. You + * can implement implementations of these interfaces and plug them into this provider for custom translation behavior: + *
    + *
  • {@link AccountPermissionResolver AccountPermissionResolver}
  • + *
  • {@link GroupPermissionResolver GroupPermissionResolver}
  • + *
  • {@link AccountGrantedAuthorityResolver AccountGrantedAuthorityResolver}
  • + *
  • {@link GroupGrantedAuthorityResolver GroupGrantedAuthorityResolver}
  • + *
+ *

+ * This provider implementation comes pre-configured with the following default implementations, which should suit most + * Spring Security+Stormpath use cases: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
PropertyPre-configured ImplementationNotes
{@link #getGroupGrantedAuthorityResolver() groupGrantedAuthorityResolver}{@link DefaultGroupGrantedAuthorityResolver}Each Stormpath Group can be represented as up to three possible Spring Security granted authorities (with + * 1-to-1 being the default). See the {@link DefaultGroupGrantedAuthorityResolver} JavaDoc for more info.
{@link #getAccountGrantedAuthorityResolver() accountGrantedAuthorityResolver}NoneMost Spring Security+Stormpath applications should only need the above {@code DefaultGroupGrantedAuthorityResolver} + * when using Stormpath Groups as Spring Security granted authorities. This authentication provider implementation + * already acquires the {@link com.stormpath.sdk.account.Account#getGroups() account's assigned groups} and resolves the group + * granted authorities via the above {@code groupGrantedAuthorityResolver}. You only need to configure this property + * if you need an additional way to represent an account's assigned granted authorities that cannot already be + * represented via Stormpath account <--> group associations.
{@link #getGroupPermissionResolver() groupPermissionResolver}{@link GroupCustomDataPermissionResolver}The {@code GroupCustomDataPermissionResolver} assumes the convention that a Group's assigned permissions + * are stored as a nested {@code Set<String>} field in the + * {@link com.stormpath.sdk.group.Group#getCustomData() group's CustomData resource}. See the + * {@link GroupCustomDataPermissionResolver} JavaDoc for more information.
{@link #getAccountPermissionResolver() accountPermissionResolver}{@link AccountCustomDataPermissionResolver}The {@code AccountCustomDataPermissionResolver} assumes the convention that an Account's directly + * assigned permissions are stored as a nested {@code Set<String>} field in the + * {@link com.stormpath.sdk.account.Account#getCustomData() account's CustomData resource}. See the + * {@link AccountCustomDataPermissionResolver} JavaDoc for more information.
+ *

Transitive Permissions

+ * This implementation represents an Account's granted permissions as all permissions that: + *
    + *
  1. Are assigned directly to the Account itself
  2. + *
  3. Are assigned to any of the Account's assigned Groups
  4. + *
+ *

Assigning Permissions

+ * A Spring Security Authentication Provider is a read-only component - it typically does not support account/group/permission + * updates directly. + * Therefore, you make modifications to these components by interacting with the data store (e.g. Stormpath) directly. + *

+ * The {@link com.stormpath.spring.security.authz.CustomDataPermissionsEditor CustomDataPermissionsEditor} has been provided for + * this purpose. For example, assuming the convention of storing permissions in an account or group's CustomData + * resource: + *

+ * Account account = getAccount();
+ * new CustomDataPermissionsEditor(account.getCustomData())
+ *     .append("someResourceType:anIdentifier:anAction")
+ *     .append("anotherResourceType:anIdentifier:*")
+ *     .remove("oldPermission");
+ * account.save();
+ * 
+ * Again, the default {@link #getGroupPermissionResolver() groupPermissionResolver} and + * {@link #getAccountPermissionResolver() accountPermissionResolver} instances assume this CustomData storage strategy, + * so if you use them, the above {@code CustomDataPermissionsEditor} will work easily. + *

+ * When the given credentials are successfully authenticated an {@link AuthenticationTokenFactory AuthenticationTokenFactory} instance + * is used to create an authenticated token to be returned to the provider's client. By default, the {@link UsernamePasswordAuthenticationTokenFactory} + * is used, constructing {@code UsernamePasswordAuthenticationToken} objects. By default, the principal stored in this token is a {@link StormpathUserDetails} + * containing Stormpath account's properties like href, given name, username, etc. It can be easily modified by creating a new + * AuthenticationTokenFactory and setting it to this provider via {@link #setAuthenticationTokenFactory(AuthenticationTokenFactory)}. + * + * @see AccountGrantedAuthorityResolver + * @see GroupGrantedAuthorityResolver + * @see AuthenticationTokenFactory + * @see StormpathUserDetails + */ +public class StormpathAuthenticationProvider implements AuthenticationProvider { + + private Client client; + private String applicationRestUrl; + private GroupGrantedAuthorityResolver groupGrantedAuthorityResolver; + private GroupPermissionResolver groupPermissionResolver; + private AccountGrantedAuthorityResolver accountGrantedAuthorityResolver; + private AccountPermissionResolver accountPermissionResolver; + private AuthenticationTokenFactory authenticationTokenFactory; + + private Application application; //acquired via the client at runtime, not configurable by the StormpathAuthenticationProvider user + + public StormpathAuthenticationProvider() { + setGroupGrantedAuthorityResolver(new DefaultGroupGrantedAuthorityResolver()); + setGroupPermissionResolver(new GroupCustomDataPermissionResolver()); + setAccountPermissionResolver(new AccountCustomDataPermissionResolver()); + setAuthenticationTokenFactory(new UsernamePasswordAuthenticationTokenFactory()); + } + + /** + * Returns the {@code Client} instance used to communicate with Stormpath's REST API. + * + * @return the {@code Client} instance used to communicate with Stormpath's REST API. + */ + public Client getClient() { + return client; + } + + /** + * Sets the {@code Client} instance used to communicate with Stormpath's REST API. + * + * @param client the {@code Client} instance used to communicate with Stormpath's REST API. + */ + public void setClient(Client client) { + this.client = client; + } + + /** + * Returns the Stormpath REST URL of the specific application communicating with Stormpath. + *

+ * Any application supported by Stormpath will have a + * dedicated unique REST URL. The + * Stormpath REST URL of the Spring Security-enabled application communicating with Stormpath via this Provider must be + * configured by this property. + * + * @return the Stormpath REST URL of the specific application communicating with Stormpath. + */ + public String getApplicationRestUrl() { + return applicationRestUrl; + } + + /** + * Sets the Stormpath REST URL of the specific application communicating with Stormpath. + *

+ * Any application supported by Stormpath will have a + * dedicated unique REST URL. The + * Stormpath REST URL of the Spring Security-enabled application communicating with Stormpath via this Provider must be + * configured by this property. + * + * @param applicationRestUrl the Stormpath REST URL of the specific application communicating with Stormpath. + */ + public void setApplicationRestUrl(String applicationRestUrl) { + this.applicationRestUrl = applicationRestUrl; + } + + /** + * Returns the {@link GroupGrantedAuthorityResolver} used to translate Stormpath Groups into Spring Security granted authorities. + * Unless overridden via {@link #setGroupGrantedAuthorityResolver(GroupGrantedAuthorityResolver) setGroupGrantedAuthorityResolver}, + * the default instance is a {@link DefaultGroupGrantedAuthorityResolver}. + * + * @return the {@link GroupGrantedAuthorityResolver} used to translate Stormpath Groups into Spring Security granted authorities. + */ + public GroupGrantedAuthorityResolver getGroupGrantedAuthorityResolver() { + return groupGrantedAuthorityResolver; + } + + /** + * Sets the {@link GroupGrantedAuthorityResolver} used to translate Stormpath Groups into Spring Security granted authorities. + * Unless overridden, the default instance is a {@link DefaultGroupGrantedAuthorityResolver}. + * + * @param groupGrantedAuthorityResolver the {@link GroupGrantedAuthorityResolver} used to translate Stormpath Groups into + * Spring Security granted authorities. + */ + public void setGroupGrantedAuthorityResolver(GroupGrantedAuthorityResolver groupGrantedAuthorityResolver) { + this.groupGrantedAuthorityResolver = groupGrantedAuthorityResolver; + } + + /** + * Returns the {@link AccountGrantedAuthorityResolver} used to discover a Stormpath Account's assigned permissions. This + * is {@code null} by default and must be configured based on your application's needs. + * + * @return the {@link AccountGrantedAuthorityResolver} used to discover a Stormpath Account's assigned permissions. + */ + public AccountGrantedAuthorityResolver getAccountGrantedAuthorityResolver() { + return accountGrantedAuthorityResolver; + } + + /** + * Sets the {@link AccountGrantedAuthorityResolver} used to discover a Stormpath Account's assigned permissions. This + * is {@code null} by default and must be configured based on your application's needs. + * + * @param accountGrantedAuthorityResolver the {@link AccountGrantedAuthorityResolver} used to discover a Stormpath Account's + * assigned permissions + */ + public void setAccountGrantedAuthorityResolver(AccountGrantedAuthorityResolver accountGrantedAuthorityResolver) { + this.accountGrantedAuthorityResolver = accountGrantedAuthorityResolver; + } + + /** + * Returns the {@link GroupPermissionResolver} used to discover a Stormpath Groups' assigned permissions. Unless + * overridden via {@link #setGroupPermissionResolver(GroupPermissionResolver) setGroupPermissionResolver}, the + * default instance is a {@link GroupCustomDataPermissionResolver}. + * + * @return the {@link GroupPermissionResolver} used to discover a Stormpath Groups' assigned permissions + * @since 0.2.0 + */ + public GroupPermissionResolver getGroupPermissionResolver() { + return groupPermissionResolver; + } + + /** + * Sets the {@link GroupPermissionResolver} used to discover a Stormpath Groups' assigned permissions. Unless + * overridden, the default instance is a {@link GroupCustomDataPermissionResolver}. + * + * @param groupPermissionResolver the {@link GroupPermissionResolver} used to discover a Stormpath Groups' assigned + * permissions + * @since 0.2.0 + */ + public void setGroupPermissionResolver(GroupPermissionResolver groupPermissionResolver) { + this.groupPermissionResolver = groupPermissionResolver; + } + + /** + * Returns the {@link AccountPermissionResolver} used to discover a Stormpath Account's directly-assigned + * permissions. Unless overridden via + * {@link #setAccountPermissionResolver(AccountPermissionResolver) setAccountPermissionResolver}, the default + * instance is a {@link AccountCustomDataPermissionResolver}. + * + * @return the {@link AccountPermissionResolver} used to discover a Stormpath Account's assigned permissions. + * @since 0.2.0 + */ + public AccountPermissionResolver getAccountPermissionResolver() { + return accountPermissionResolver; + } + + /** + * Sets the {@link AccountPermissionResolver} used to discover a Stormpath Account's assigned permissions. Unless + * overridden, the default instance is a {@link AccountCustomDataPermissionResolver}. + * + * @param accountPermissionResolver the {@link AccountPermissionResolver} used to discover a Stormpath Account's + * assigned permissions + * @since 0.2.0 + */ + public void setAccountPermissionResolver(AccountPermissionResolver accountPermissionResolver) { + this.accountPermissionResolver = accountPermissionResolver; + } + + /** + * + * Returns the {@link AccountGrantedAuthorityResolver} used to discover a Stormpath Account's assigned permissions. Unless + * overridden, the default instance is a {@link UsernamePasswordAuthenticationTokenFactory}. + * + * @return the token factory to be used when creating tokens for the successfully authenticated credentials. + */ + public AuthenticationTokenFactory getAuthenticationTokenFactory() { + return authenticationTokenFactory; + } + + /** + * Sets the {@link AuthenticationTokenFactory} used to create authenticated tokens. Unless overridden via + * {@link #setAuthenticationTokenFactory(AuthenticationTokenFactory)} setAuthenticationTokenFactory}, + * the default instance is a {@link UsernamePasswordAuthenticationTokenFactory}. + * + * @param authenticationTokenFactory the token factory to be used when creating tokens for the successfully + * authenticated credentials. + */ + public void setAuthenticationTokenFactory(AuthenticationTokenFactory authenticationTokenFactory) { + if (authenticationTokenFactory == null) { + throw new IllegalArgumentException("authenticationTokenFactory cannot be null."); + } + this.authenticationTokenFactory = authenticationTokenFactory; + } + + private void assertState() { + if (this.client == null) { + throw new IllegalStateException("Stormpath SDK Client instance must be configured."); + } + if (this.applicationRestUrl == null) { + throw new IllegalStateException("\n\nThis application's Stormpath REST URL must be configured.\n\n " + + "You may get your application's Stormpath REST URL as shown here:\n\n " + + "http://www.stormpath.com/docs/application-rest-url\n\n" + + "Copy and paste the 'REST URL' value as the 'applicationRestUrl' property of this class."); + } + } + + /** + * Performs actual authentication for the received authentication credentials using + * Stormpath Cloud Identity Management service for a single application. + * + * @param authentication the authentication request object. + * + * @return a fully authenticated object including credentials. + * + * @throws AuthenticationException if authentication fails. + */ + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + + assertState(); + AuthenticationRequest request = createAuthenticationRequest(authentication); + Application application = ensureApplicationReference(); + + Account account; + + try { + account = application.authenticateAccount(request).getAccount(); + } catch (ResourceException e) { + String msg = StringUtils.clean(e.getMessage()); + if (msg == null) { + msg = StringUtils.clean(e.getDeveloperMessage()); + } + if (msg == null) { + msg = "Invalid login or password."; + } + throw new AuthenticationServiceException(msg, e); + } finally { + //Clear the request data to prevent later memory access + request.clear(); + } + + Authentication authToken = this.authenticationTokenFactory.createAuthenticationToken( + authentication.getPrincipal(), null, getGrantedAuthorities(account), account); + + return authToken; + } + + /** + * Returns true if this AuthenticationProvider supports the indicated + * Authentication object. + * + * @param authentication the class to validate this AuthenticationProvider supports + * + * @return true if the given class is supported + */ + @Override + public boolean supports(Class authentication) { + return Authentication.class.isAssignableFrom(authentication); + } + + //This is not thread safe, but the Client is, and this is only executed during initial Application + //acquisition, so it is negligible if this executes a few times instead of just once. + protected final Application ensureApplicationReference() { + if (this.application == null) { + String href = getApplicationRestUrl(); + this.application = client.getDataStore().getResource(href, Application.class); + } + return this.application; + } + + protected AuthenticationRequest createAuthenticationRequest(Authentication authentication) { + String username = (String) authentication.getPrincipal(); + String password = (String) authentication.getCredentials(); + return new UsernamePasswordRequest(username, password); + } + + protected Collection getGrantedAuthorities(Account account) { + Collection grantedAuthorities = new HashSet(); + + GroupList groups = account.getGroups(); + + for (Group group : groups) { + Set groupGrantedAuthorities = resolveGrantedAuthorities(group); + grantedAuthorities.addAll(groupGrantedAuthorities); + + Set groupPermissions = resolvePermissions(group); + grantedAuthorities.addAll(groupPermissions); + } + + Set accountGrantedAuthorities = resolveGrantedAuthorities(account); + grantedAuthorities.addAll(accountGrantedAuthorities); + + Set accountPermissions = resolvePermissions(account); + for (GrantedAuthority permission : accountPermissions) { + grantedAuthorities.add(permission); + } + + return grantedAuthorities; + } + + private Set resolveGrantedAuthorities(Group group) { + if (groupGrantedAuthorityResolver != null) { + return groupGrantedAuthorityResolver.resolveGrantedAuthorities(group); + } + return Collections.emptySet(); + } + + private Set resolveGrantedAuthorities(Account account) { + if (accountGrantedAuthorityResolver != null) { + return accountGrantedAuthorityResolver.resolveGrantedAuthorities(account); + } + return Collections.emptySet(); + } + + //since 0.1.1 + private Set resolvePermissions(Group group) { + if (groupPermissionResolver != null) { + return groupPermissionResolver.resolvePermissions(group); + } + return Collections.emptySet(); + } + + //since 0.1.1 + private Set resolvePermissions(Account account) { + if (accountPermissionResolver != null) { + return accountPermissionResolver.resolvePermissions(account); + } + return Collections.emptySet(); + } + + +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/StormpathUserDetails.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/StormpathUserDetails.java new file mode 100644 index 0000000000..c7e86f21a1 --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/StormpathUserDetails.java @@ -0,0 +1,120 @@ +/* + * Copyright 2014 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.spring.security.provider; + +import com.stormpath.sdk.account.Account; +import com.stormpath.sdk.account.AccountStatus; +import com.stormpath.spring.security.util.StringUtils; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + + +/** + * Models Stormpath account information retrieved by a {@link StormpathAuthenticationProvider}. + *

+ * Note that this implementation is immutable. + * + * @since 0.2.0 + */ +public class StormpathUserDetails implements UserDetails { + + private final String username; + private final String password; + private final Collection authorities; + private Map stormpathUserDetails = null; + + public StormpathUserDetails(String username, String password, Collection grantedAuthorities, Account account) { + if (username == null) { + throw new IllegalArgumentException("Username cannot be null."); + } + if (grantedAuthorities == null) { + throw new IllegalArgumentException("Granted authorities cannot be null."); + } + if (account == null) { + throw new IllegalArgumentException("Account cannot be null."); + } + this.password = password; + this.stormpathUserDetails = createUnmodifiableMap(account); + this.username = account.getUsername(); + this.authorities = grantedAuthorities; + } + + public Map getProperties() { + return this.stormpathUserDetails; + } + + @Override + public Collection getAuthorities() { + return this.authorities; + } + + @Override + public String getUsername() { + return this.username; + } + + @Override + public String getPassword() { + return this.password; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return AccountStatus.ENABLED.toString().equals(this.stormpathUserDetails.get("status")); + } + + private Map createUnmodifiableMap(Account account) { + Map props = new HashMap(); + props.put("href", account.getHref()); + nullSafePut(props, "username", account.getUsername()); + nullSafePut(props, "email", account.getEmail()); + nullSafePut(props, "givenName", account.getGivenName()); + nullSafePut(props, "middleName", account.getMiddleName()); + nullSafePut(props, "surname", account.getSurname()); + if(account.getStatus() != null) { + nullSafePut(props, "status", account.getStatus().toString()); + } + return Collections.unmodifiableMap(props); + } + + private void nullSafePut(Map props, String propName, String value) { + value = StringUtils.clean(value); + if (value != null) { + props.put(propName, value); + } + } + +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/UsernamePasswordAuthenticationTokenFactory.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/UsernamePasswordAuthenticationTokenFactory.java new file mode 100644 index 0000000000..4a8fd8c98d --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/UsernamePasswordAuthenticationTokenFactory.java @@ -0,0 +1,41 @@ +/* + * 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.spring.security.provider; + +import com.stormpath.sdk.account.Account; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; + +/** + * A {@link com.stormpath.spring.security.provider.AuthenticationTokenFactory} implementation that creates representation + * of a username and password token. The principal stored in the token is an instance of {@link StormpathUserDetails} which + * contains information about the Stormpath Account for the authenticated user, such as href, given name, username, etc. + * + * @since 0.2.0 + */ +public class UsernamePasswordAuthenticationTokenFactory implements AuthenticationTokenFactory { + + @Override + public Authentication createAuthenticationToken(Object principal, Object credentials, Collection authorities, Account account) { + UserDetails userDetails = new StormpathUserDetails(principal.toString(), (String) credentials, authorities, account); + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, credentials, userDetails.getAuthorities()); + return authToken; + } +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/WildcardPermissionResolver.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/WildcardPermissionResolver.java new file mode 100644 index 0000000000..07795ab660 --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/WildcardPermissionResolver.java @@ -0,0 +1,40 @@ +/* + * 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.spring.security.provider; + +import com.stormpath.spring.security.authz.permission.Permission; +import com.stormpath.spring.security.authz.permission.WildcardPermission; + +/** + * GrantedAuthorityResolver implementation that returns a new {@link com.stormpath.spring.security.authz.permission.WildcardPermission WildcardPermission} + * based on the input string. + * + * @since 0.2.0 + */ +public class WildcardPermissionResolver implements PermissionResolver { + + /** + * Returns a new {@link com.stormpath.spring.security.authz.permission.WildcardPermission WildcardPermission} instance constructed based on the specified + * permissionString. + * + * @param permissionString the permission string to convert to a {@link Permission Permission} instance. + * @return a new {@link com.stormpath.spring.security.authz.permission.WildcardPermission WildcardPermission} instance constructed based on the specified + * permissionString + */ + public Permission resolvePermission(String permissionString) { + return new WildcardPermission(permissionString); + } +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/util/CollectionUtils.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/util/CollectionUtils.java new file mode 100644 index 0000000000..8d138b24c9 --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/util/CollectionUtils.java @@ -0,0 +1,52 @@ +/* + * Copyright 2014 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.spring.security.util; + +import java.util.*; + +/** + * Static helper class for use dealing with Collections. + * + * @since 0.2.0 + */ +public class CollectionUtils { + + //TODO - complete JavaDoc + + public static Set asSet(E... elements) { + if (elements == null || elements.length == 0) { + return Collections.emptySet(); + } + LinkedHashSet set = new LinkedHashSet(elements.length * 4 / 3 + 1); + Collections.addAll(set, elements); + return set; + } + + public static List asList(E... elements) { + if (elements == null || elements.length == 0) { + return Collections.emptyList(); + } + // Avoid integer overflow when a large array is passed in + int capacity = computeListCapacity(elements.length); + ArrayList list = new ArrayList(capacity); + Collections.addAll(list, elements); + return list; + } + + static int computeListCapacity(int arraySize) { + return (int) Math.min(5L + arraySize + (arraySize / 10), Integer.MAX_VALUE); + } +} diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/util/StringUtils.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/util/StringUtils.java new file mode 100644 index 0000000000..80a065ce4b --- /dev/null +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/util/StringUtils.java @@ -0,0 +1,60 @@ +/* + * 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.spring.security.util; + +/** + *

Simple utility class for String operations useful across the plugin. + *

+ * + * @since 0.1.0 + */ +public class StringUtils { + + /** + * Constant representing the empty string, equal to "" + */ + public static final String EMPTY_STRING = ""; + + /** + * Returns a 'cleaned' representation of the specified argument. 'Cleaned' is defined as the following: + *

+ *

    + *
  1. If the specified String is null, return null
  2. + *
  3. If not null, {@link String#trim() trim()} it.
  4. + *
  5. If the trimmed string is equal to the empty String (i.e. ""), return null
  6. + *
  7. If the trimmed string is not the empty string, return the trimmed version
  8. . + *
+ *

+ * Therefore this method always ensures that any given string has trimmed text, and if it doesn't, null + * is returned. + * + * @param in the input String to clean. + * @return a populated-but-trimmed String or null otherwise + */ + public static String clean(String in) { + String out = in; + + if (in != null) { + out = in.trim(); + if (out.equals(EMPTY_STRING)) { + out = null; + } + } + + return out; + } + +} diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/CustomDataPermissionsEditorTest.groovy b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/CustomDataPermissionsEditorTest.groovy new file mode 100644 index 0000000000..6ef740cac2 --- /dev/null +++ b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/CustomDataPermissionsEditorTest.groovy @@ -0,0 +1,301 @@ +/* + * Copyright 2015 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.spring.security.authz + +import com.fasterxml.jackson.databind.ObjectMapper +import org.junit.Assert +import org.junit.Test + +class CustomDataPermissionsEditorTest { + + @Test + void testConstantValue() { + //This ensures we don't change the constant value - doing so would not be runtime backwards compatible. + //If the value is changed in code, this test will fail, as expected (DO NOT change the value!) + Assert.assertEquals "springSecurityPermissions", CustomDataPermissionsEditor.DEFAULT_CUSTOM_DATA_FIELD_NAME + } + + @Test(expected = IllegalArgumentException) + void testNewInstanceWithNullArg() { + new CustomDataPermissionsEditor(null) + } + + @Test + void tesNewInstance() { + def customData = new MockCustomData() + def editor = new CustomDataPermissionsEditor(customData) + Assert.assertSame customData, editor.CUSTOM_DATA + Assert.assertEquals CustomDataPermissionsEditor.DEFAULT_CUSTOM_DATA_FIELD_NAME, editor.getFieldName() + } + + @Test + void testSetFieldName() { + def customData = new MockCustomData() + def editor = new CustomDataPermissionsEditor(customData) + def fieldName = 'foo' + editor.setFieldName(fieldName) + Assert.assertEquals fieldName, editor.getFieldName() + + editor.append('bar') + + //assert changed + Assert.assertFalse customData.containsKey(CustomDataPermissionsEditor.DEFAULT_CUSTOM_DATA_FIELD_NAME) + Assert.assertTrue customData.containsKey('foo') + Assert.assertEquals 'bar', editor.getPermissionStrings().iterator().next() + } + + @Test //tests that we can append a value even if there is not yet a field dedicated for storing perms + void testAppendWhenNonExistent() { + def customData = new MockCustomData() + def editor = new CustomDataPermissionsEditor(customData) + + Assert.assertNull customData.springSecurityPermissions + + editor.append('foo:*') + + Assert.assertNotNull customData.springSecurityPermissions + Assert.assertTrue customData.springSecurityPermissions instanceof LinkedHashSet + Assert.assertEquals 1, customData.springSecurityPermissions.size() + Assert.assertEquals 'foo:*', customData.springSecurityPermissions.iterator().next() + } + + @Test //tests that we can append a value when there is already a perm collection present + void testAppendWithExistingList() { + def customData = new MockCustomData() + customData.springSecurityPermissions = ['foo:*'] + + def editor = new CustomDataPermissionsEditor(customData) + + Assert.assertNotNull customData.springSecurityPermissions + Assert.assertTrue customData.springSecurityPermissions instanceof List + + editor.append('bar:*') + + Assert.assertNotNull customData.springSecurityPermissions + Assert.assertTrue customData.springSecurityPermissions instanceof LinkedHashSet + Assert.assertEquals 2, customData.springSecurityPermissions.size() + def i = customData.springSecurityPermissions.iterator() + + Assert.assertEquals 'foo:*', i.next() + Assert.assertEquals 'bar:*', i.next() + } + + @Test + void testRemoveWithNullArg() { + + def customData = new MockCustomData() + def editor = new CustomDataPermissionsEditor(customData) + + Assert.assertNull customData.springSecurityPermissions + + editor.remove(null) + + Assert.assertNull customData.springSecurityPermissions + } + + @Test + void testRemoveWithEmptyArg() { + + def customData = new MockCustomData() + def editor = new CustomDataPermissionsEditor(customData) + + Assert.assertNull customData.springSecurityPermissions + + editor.remove(' ') + + Assert.assertNull customData.springSecurityPermissions + } + + @Test + void testRemoveWhenNonExistent() { + + def customData = new MockCustomData() + def editor = new CustomDataPermissionsEditor(customData) + + Assert.assertNull customData.springSecurityPermissions + + editor.remove('foo') + + Assert.assertNull customData.springSecurityPermissions + } + + @Test + void testRemoveWithExistingList() { + def customData = new MockCustomData() + customData.springSecurityPermissions = ['foo:*', 'bar'] + def editor = new CustomDataPermissionsEditor(customData) + + def result = editor.getPermissionStrings() + + Assert.assertNotNull result + Assert.assertEquals 2, result.size() + def i = result.iterator() + Assert.assertEquals 'foo:*', i.next() + Assert.assertEquals 'bar', i.next() + + editor.remove('foo:*') + + result = editor.getPermissionStrings() + Assert.assertNotNull result + Assert.assertEquals 1, result.size() + Assert.assertEquals 'bar', result.iterator().next() + } + + @Test + void testRemoveWithExistingSet() { + def customData = new MockCustomData() + def editor = new CustomDataPermissionsEditor(customData) + + editor.append('foo:*').append('bar') + + def result = editor.getPermissionStrings() + + Assert.assertNotNull result + Assert.assertEquals 2, result.size() + def i = result.iterator() + Assert.assertEquals 'foo:*', i.next() + Assert.assertEquals 'bar', i.next() + + editor.remove('foo:*') + + result = editor.getPermissionStrings() + Assert.assertNotNull result + Assert.assertEquals 1, result.size() + Assert.assertEquals 'bar', result.iterator().next() + } + + @Test + void testGetPermissionStringsWhenNonExistent() { + + def customData = new MockCustomData() + def editor = new CustomDataPermissionsEditor(customData) + + Assert.assertNull customData.springSecurityPermissions + + def result = editor.getPermissionStrings(); + + Assert.assertNotNull result + Assert.assertTrue result.isEmpty() + } + + @Test(expected=UnsupportedOperationException) + void testGetPermissionStringsReturnsImmutableSet() { + + def customData = new MockCustomData() + def editor = new CustomDataPermissionsEditor(customData) + + Assert.assertNull customData.springSecurityPermissions + + Set result = editor.getPermissionStrings(); + + Assert.assertNotNull result + Assert.assertTrue result.isEmpty() + + result.add('foo') + } + + @Test + void testGetPermissionStringsWithExistingList() { + + def customData = new MockCustomData() + customData.springSecurityPermissions = ['foo:*'] + def editor = new CustomDataPermissionsEditor(customData) + + def result = editor.getPermissionStrings(); + + Assert.assertNotNull result + Assert.assertEquals 1, result.size() + Assert.assertEquals 'foo:*', result.iterator().next() + } + + @Test + void testGetPermissionStringsWithExistingValuesSomeWithNull() { + + def customData = new MockCustomData() + customData.springSecurityPermissions = ['foo:*', null, 'bar'] + def editor = new CustomDataPermissionsEditor(customData) + + def result = editor.getPermissionStrings(); + + Assert.assertNotNull result + Assert.assertEquals 2, result.size() + def i = result.iterator() + Assert.assertEquals 'foo:*', i.next() + Assert.assertEquals 'bar', i.next() + } + + @Test + void testGetPermissionStringsWithExistingValuesSomeNotStrings() { + + def customData = new MockCustomData() + def nonString = 123 + customData.springSecurityPermissions = ['foo:*', nonString, 'bar'] //tests user erroneous population + def editor = new CustomDataPermissionsEditor(customData) + + try { + editor.getPermissionStrings(); + } catch (IllegalArgumentException iae) { + String expectedMsg = "CustomData field 'springSecurityPermissions' contains an element that is not a String as " + + "required. Element type: " + nonString.getClass().getName() + ", element value: " + nonString + Assert.assertEquals expectedMsg, iae.getMessage() + } + } + + @Test + void testGetPermissionStringsWithExistingStringArray() { + + ObjectMapper mapper = new ObjectMapper() + def json = ''' + { + "springSecurityPermissions": [ + "foo:*", + "bar" + ] + } + ''' + def m = mapper.readValue(json, Map.class) + + def customData = new MockCustomData() + customData.springSecurityPermissions = m.springSecurityPermissions + def editor = new CustomDataPermissionsEditor(customData) + + def result = editor.getPermissionStrings(); + + Assert.assertNotNull result + Assert.assertEquals 2, result.size() + def i = result.iterator() + Assert.assertEquals 'foo:*', i.next() + Assert.assertEquals 'bar', i.next() + } + + @Test + void testGetPermissionStringsWithNonListProperty() { + + def value = 42 + def customData = new MockCustomData() + customData.springSecurityPermissions = value + def editor = new CustomDataPermissionsEditor(customData) + + try { + editor.getPermissionStrings(); + } catch (IllegalArgumentException iae) { + String expectedMsg = "Unable to recognize CustomData field 'springSecurityPermissions' value of type " + + value.getClass().getName() + ". Expected type: Set or List." + Assert.assertEquals expectedMsg, iae.getMessage(); + } + } + +} diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/MockCustomData.groovy b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/MockCustomData.groovy new file mode 100644 index 0000000000..5a3d4d4984 --- /dev/null +++ b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/MockCustomData.groovy @@ -0,0 +1,120 @@ +/* + * 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.spring.security.authz + +import com.stormpath.sdk.directory.CustomData + +class MockCustomData implements CustomData { + + private final Date CREATED_AT; + private transient Date modifiedAt; + private final Map FIELDS; + String href; + + public MockCustomData() { + CREATED_AT = new Date() + FIELDS = [:] + } + + @Override + Date getCreatedAt() { + return CREATED_AT + } + + @Override + Date getModifiedAt() { + return modifiedAt + } + + @Override + void delete() { + touch() + } + + @Override + int size() { + return FIELDS.size(); + } + + @Override + boolean isEmpty() { + return FIELDS.isEmpty() + } + + @Override + boolean containsKey(Object o) { + return FIELDS.containsKey(o) + } + + @Override + boolean containsValue(Object o) { + return FIELDS.containsValue(o) + } + + @Override + Object get(Object o) { + return FIELDS.get(o) + } + + @Override + Object put(String k, Object v) { + return FIELDS.put(k,v) + } + + @Override + Object remove(Object o) { + return FIELDS.remove(o) + } + + @Override + void putAll(Map map) { + FIELDS.putAll(map) + } + + @Override + void clear() { + FIELDS.clear() + } + + @Override + Set keySet() { + return FIELDS.keySet() + } + + @Override + Collection values() { + return FIELDS.values() + } + + @Override + Set> entrySet() { + return FIELDS.entrySet() + } + + @Override + String getHref() { + return href; + } + + @Override + void save() { + touch() + } + + private void touch() { + modifiedAt = new Date() + } +} diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/DomainPermissionTest.groovy b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/DomainPermissionTest.groovy new file mode 100644 index 0000000000..4b37bd90d5 --- /dev/null +++ b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/DomainPermissionTest.groovy @@ -0,0 +1,261 @@ +/* + * Copyright 2014 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.spring.security.authz.permission + +import org.junit.Test + +import static org.junit.Assert.* + +/** + * @since 0.2.0 + */ +class DomainPermissionTest { + @Test + public void testDefaultConstructor() { + DomainPermission p; + List> parts; + Set set; + String entry; + + // No arg constructor + p = new DomainPermission(); + + // Verify domain + assertTrue("domain".equals(p.getDomain())); + + // Verify actions + set = p.getActions(); + assertNull(set); + + // Verify targets + set = p.getTargets(); + assertNull(set); + + // Verify parts + parts = p.getParts(); + assertEquals("Number of parts", 1, parts.size()); + set = parts.get(0); + assertEquals(1, set.size()); + entry = set.iterator().next(); + assertEquals("domain", entry); + } + + @Test + public void testActionsConstructorWithSingleAction() { + DomainPermission p; + List> parts; + Set set; + Iterator iterator; + String entry; + + // Actions constructor with a single action + p = new DomainPermission("action1"); + + // Verify domain + assertEquals("domain", p.getDomain()); + + // Verify actions + set = p.getActions(); + assertNotNull(set); + assertEquals(1, set.size()); + iterator = set.iterator(); + entry = iterator.next(); + assertEquals("action1", entry); + + // Verify targets + set = p.getTargets(); + assertNull(set); + + // Verify parts + parts = p.getParts(); + assertEquals(2, parts.size()); + set = parts.get(0); + assertEquals(1, set.size()); + iterator = set.iterator(); + entry = iterator.next(); + assertEquals("domain", entry); + set = parts.get(1); + assertEquals(1, set.size()); + iterator = set.iterator(); + entry = iterator.next(); + assertEquals("action1", entry); + } + + @Test + public void testActionsConstructorWithMultipleActions() { + DomainPermission p; + List> parts; + Set set; + Iterator iterator; + String entry; + + // Actions constructor with three actions + p = new DomainPermission("action1,action2,action3"); + + // Verify domain + assertEquals("domain", p.getDomain()); + + // Verify actions + set = p.getActions(); + assertNotNull(set); + assertEquals(3, set.size()); + iterator = set.iterator(); + entry = iterator.next(); + assertEquals("action1", entry); + entry = iterator.next(); + assertEquals("action2", entry); + entry = iterator.next(); + assertEquals("action3", entry); + + // Verify targets + set = p.getTargets(); + assertNull(set); + + // Verify parts + parts = p.getParts(); + assertEquals(2, parts.size()); + set = parts.get(0); + assertEquals(1, set.size()); + iterator = set.iterator(); + entry = iterator.next(); + assertEquals("domain", entry); + set = parts.get(1); + assertEquals(3, set.size()); + iterator = set.iterator(); + entry = iterator.next(); + assertEquals("action1", entry); + entry = iterator.next(); + assertEquals("action2", entry); + entry = iterator.next(); + assertEquals("action3", entry); + } + + @Test + public void testActionsTargetsConstructorWithSingleActionAndTarget() { + DomainPermission p; + List> parts; + Set set; + Iterator iterator; + String entry; + + // Actions and target constructor with a single action and target + p = new DomainPermission("action1", "target1"); + + // Verify domain + assertEquals("domain", p.getDomain()); + + // Verify actions + set = p.getActions(); + assertNotNull(set); + assertEquals(1, set.size()); + iterator = set.iterator(); + entry = iterator.next(); + assertEquals("action1", entry); + + // Verify targets + set = p.getTargets(); + assertNotNull(set); + assertEquals(1, set.size()); + iterator = set.iterator(); + entry = iterator.next(); + assertEquals("target1", entry); + + // Verify parts + parts = p.getParts(); + assertEquals(3, parts.size()); + set = parts.get(0); + assertEquals(1, set.size()); + iterator = set.iterator(); + entry = iterator.next(); + assertEquals("domain", entry); + set = parts.get(1); + assertEquals(1, set.size()); + iterator = set.iterator(); + entry = iterator.next(); + assertEquals("action1", entry); + set = parts.get(2); + assertEquals(1, set.size()); + iterator = set.iterator(); + entry = iterator.next(); + assertEquals("target1", entry); + } + + @Test + public void testActionsTargetsConstructorWithMultipleActionsAndTargets() { + DomainPermission p; + List> parts; + Set set; + Iterator iterator; + String entry; + + // Actions and target constructor with a single action and target + p = new DomainPermission("action1,action2,action3", "target1,target2,target3"); + + // Verify domain + assertEquals("domain", p.getDomain()); + + // Verify actions + set = p.getActions(); + assertNotNull(set); + assertEquals(3, set.size()); + iterator = set.iterator(); + entry = iterator.next(); + assertEquals("action1", entry); + entry = iterator.next(); + assertEquals("action2", entry); + entry = iterator.next(); + assertEquals("action3", entry); + + // Verify targets + set = p.getTargets(); + assertNotNull(set); + assertEquals(3, set.size()); + iterator = set.iterator(); + entry = iterator.next(); + assertEquals("target1", entry); + entry = iterator.next(); + assertEquals("target2", entry); + entry = iterator.next(); + assertEquals("target3", entry); + + // Verify parts + parts = p.getParts(); + assertEquals(3, parts.size()); + set = parts.get(0); + assertEquals(1, set.size()); + iterator = set.iterator(); + entry = iterator.next(); + assertEquals("domain", entry); + set = parts.get(1); + assertEquals(3, set.size()); + iterator = set.iterator(); + entry = iterator.next(); + assertEquals("action1", entry); + entry = iterator.next(); + assertEquals("action2", entry); + entry = iterator.next(); + assertEquals("action3", entry); + set = parts.get(2); + assertEquals(3, set.size()); + iterator = set.iterator(); + entry = iterator.next(); + assertEquals("target1", entry); + entry = iterator.next(); + assertEquals("target2", entry); + entry = iterator.next(); + assertEquals("target3", entry); + } +} diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/WildcardPermissionTest.groovy b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/WildcardPermissionTest.groovy new file mode 100644 index 0000000000..2a75ea7e89 --- /dev/null +++ b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/WildcardPermissionTest.groovy @@ -0,0 +1,243 @@ +/* + * Copyright 2014 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.spring.security.authz.permission + +import org.junit.Test + +import static org.junit.Assert.* + +/** + * @since 0.2.0 + */ +class WildcardPermissionTest { + + @Test(expected = IllegalArgumentException.class) + public void testNull() { + new WildcardPermission(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testEmpty() { + new WildcardPermission(""); + } + + @Test(expected = IllegalArgumentException.class) + public void testBlank() { + new WildcardPermission(" "); + } + + @Test(expected = IllegalArgumentException.class) + public void testOnlyDelimiters() { + new WildcardPermission("::,,::,:"); + } + + @Test + public void testNamed() { + WildcardPermission p1, p2; + + // Case insensitive, same + p1 = new WildcardPermission("something"); + p2 = new WildcardPermission("something"); + assertTrue(p1.implies(p2)); + assertTrue(p2.implies(p1)); + + // Case insensitive, different case + p1 = new WildcardPermission("something"); + p2 = new WildcardPermission("SOMETHING"); + assertTrue(p1.implies(p2)); + assertTrue(p2.implies(p1)); + + // Case insensitive, different word + p1 = new WildcardPermission("something"); + p2 = new WildcardPermission("else"); + assertFalse(p1.implies(p2)); + assertFalse(p2.implies(p1)); + + // Case sensitive same + p1 = new WildcardPermission("BLAHBLAH", true); + p2 = new WildcardPermission("BLAHBLAH", true); + assertTrue(p1.implies(p2)); + assertTrue(p2.implies(p1)); + + // Case sensitive, different case + p1 = new WildcardPermission("BLAHBLAH", true); + p2 = new WildcardPermission("bLAHBLAH", true); + assertFalse(p1.implies(p2)); + assertFalse(p2.implies(p1)); + + // Case sensitive, different word + p1 = new WildcardPermission("BLAHBLAH", true); + p2 = new WildcardPermission("whatwhat", true); + assertFalse(p1.implies(p2)); + assertFalse(p2.implies(p1)); + + } + + @Test + public void testLists() { + WildcardPermission p1, p2, p3; + + p1 = new WildcardPermission("one,two"); + p2 = new WildcardPermission("one"); + assertTrue(p1.implies(p2)); + assertFalse(p2.implies(p1)); + + p1 = new WildcardPermission("one,two,three"); + p2 = new WildcardPermission("one,three"); + assertTrue(p1.implies(p2)); + assertFalse(p2.implies(p1)); + + p1 = new WildcardPermission("one,two:one,two,three"); + p2 = new WildcardPermission("one:three"); + p3 = new WildcardPermission("one:two,three"); + assertTrue(p1.implies(p2)); + assertFalse(p2.implies(p1)); + assertTrue(p1.implies(p3)); + assertFalse(p2.implies(p3)); + assertTrue(p3.implies(p2)); + + p1 = new WildcardPermission("one,two,three:one,two,three:one,two"); + p2 = new WildcardPermission("one:three:two"); + assertTrue(p1.implies(p2)); + assertFalse(p2.implies(p1)); + + p1 = new WildcardPermission("one"); + p2 = new WildcardPermission("one:two,three,four"); + p3 = new WildcardPermission("one:two,three,four:five:six:seven"); + assertTrue(p1.implies(p2)); + assertTrue(p1.implies(p3)); + assertFalse(p2.implies(p1)); + assertFalse(p3.implies(p1)); + assertTrue(p2.implies(p3)); + + } + + @Test + public void testWildcards() { + WildcardPermission p1, p2, p3, p4, p5, p6, p7, p8; + + p1 = new WildcardPermission("*"); + p2 = new WildcardPermission("one"); + p3 = new WildcardPermission("one:two"); + p4 = new WildcardPermission("one,two:three,four"); + p5 = new WildcardPermission("one,two:three,four,five:six:seven,eight"); + assertTrue(p1.implies(p2)); + assertTrue(p1.implies(p3)); + assertTrue(p1.implies(p4)); + assertTrue(p1.implies(p5)); + + p1 = new WildcardPermission("newsletter:*"); + p2 = new WildcardPermission("newsletter:read"); + p3 = new WildcardPermission("newsletter:read,write"); + p4 = new WildcardPermission("newsletter:*"); + p5 = new WildcardPermission("newsletter:*:*"); + p6 = new WildcardPermission("newsletter:*:read"); + p7 = new WildcardPermission("newsletter:write:*"); + p8 = new WildcardPermission("newsletter:read,write:*"); + assertTrue(p1.implies(p2)); + assertTrue(p1.implies(p3)); + assertTrue(p1.implies(p4)); + assertTrue(p1.implies(p5)); + assertTrue(p1.implies(p6)); + assertTrue(p1.implies(p7)); + assertTrue(p1.implies(p8)); + + + p1 = new WildcardPermission("newsletter:*:*"); + assertTrue(p1.implies(p2)); + assertTrue(p1.implies(p3)); + assertTrue(p1.implies(p4)); + assertTrue(p1.implies(p5)); + assertTrue(p1.implies(p6)); + assertTrue(p1.implies(p7)); + assertTrue(p1.implies(p8)); + + p1 = new WildcardPermission("newsletter:*:*:*"); + assertTrue(p1.implies(p2)); + assertTrue(p1.implies(p3)); + assertTrue(p1.implies(p4)); + assertTrue(p1.implies(p5)); + assertTrue(p1.implies(p6)); + assertTrue(p1.implies(p7)); + assertTrue(p1.implies(p8)); + + p1 = new WildcardPermission("newsletter:*:read"); + p2 = new WildcardPermission("newsletter:123:read"); + p3 = new WildcardPermission("newsletter:123,456:read,write"); + p4 = new WildcardPermission("newsletter:read"); + p5 = new WildcardPermission("newsletter:read,write"); + p6 = new WildcardPermission("newsletter:123:read:write"); + assertTrue(p1.implies(p2)); + assertFalse(p1.implies(p3)); + assertFalse(p1.implies(p4)); + assertFalse(p1.implies(p5)); + assertTrue(p1.implies(p6)); + + p1 = new WildcardPermission("newsletter:*:read:*"); + assertTrue(p1.implies(p2)); + assertTrue(p1.implies(p6)); + + + assertFalse(p1.implies(new Permission() { + boolean implies(Permission p) { + return true; + } + @Override + String getAuthority() { + return "newsletter:*:read:*"; + } + })); + } + + @Test + public void testGetAuthority() { + WildcardPermission p1, p2, p3, p4, p5, p6; + + p1 = new WildcardPermission("*"); + p2 = new WildcardPermission("one"); + p3 = new WildcardPermission("one:two"); + p4 = new WildcardPermission("one,two:three,four"); + p5 = new WildcardPermission("one,two:three,four,five:six:seven,eight"); + p6 = new WildcardPermission("one,two:three,four,five:six:*"); + + assertEquals(p1.getAuthority(), "*"); + assertEquals(p2.getAuthority(), "one"); + assertEquals(p3.getAuthority(), "one:two"); + assertEquals(p4.getAuthority(), "one,two:three,four"); + assertEquals(p5.getAuthority(), "one,two:three,four,five:six:seven,eight"); + assertEquals(p6.getAuthority(), "one,two:three,four,five:six:*"); + } + + @Test + public void testEquals() { + WildcardPermission p1, p2, p3, p4; + + p1 = new WildcardPermission("*"); + p2 = new WildcardPermission("one"); + p3 = new WildcardPermission("one,two:three,four,five:six:seven,eight"); + p4 = new WildcardPermission("one,two:three,four,five:six:*"); + + assertTrue(p1.equals(new WildcardPermission("*"))); + assertTrue(p2.equals(new WildcardPermission("one"))); + assertTrue(p3.equals(new WildcardPermission("one,two:three,four,five:six:seven,eight"))); + assertTrue(p4.equals(new WildcardPermission("one,two:three,four,five:six:*"))); + assertFalse(p4.equals(new WildcardPermission("one,two:three,four,five:six:"))); + + assertFalse(p4.equals(new String("one,two:three,four,five:six:*"))); + + } + +} \ No newline at end of file diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/evaluator/WildcardPermissionEvaluatorTest.groovy b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/evaluator/WildcardPermissionEvaluatorTest.groovy new file mode 100644 index 0000000000..29e899463b --- /dev/null +++ b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/evaluator/WildcardPermissionEvaluatorTest.groovy @@ -0,0 +1,163 @@ +/* + * Copyright 2014 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.spring.security.authz.permission.evaluator + +import com.stormpath.spring.security.authz.permission.Permission +import com.stormpath.spring.security.authz.permission.WildcardPermission +import org.junit.Test +import org.springframework.security.core.Authentication +import org.springframework.security.core.GrantedAuthority + +import static org.easymock.EasyMock.* +import static org.junit.Assert.assertEquals + + +class WildcardPermissionEvaluatorTest { + + @Test + public void testNamed() { + + // Case insensitive, same + doTest(null, "something", constructPermissionSet(["something"] as String[]), true) + + // Case insensitive, different case + doTest(null, "something", constructPermissionSet(["SOMETHING"] as String[]), true) + + // Case insensitive, different case + doTest(null, "SOMETHING", constructPermissionSet(["something"] as String[]), true) + + // Case insensitive, different word + doTest(null, "something", constructPermissionSet(["else"] as String[], false), false) + + // Case sensitive same + doTest(null, "BLAHBLAH", constructPermissionSet(["BLAHBLAH"] as String[], true), true) + + // Case sensitive, different case + doTest(null, "BLAHBLAH", constructPermissionSet(["bLAHBLAH"] as String[], true), false) + + // Case sensitive, different case + doTest(null, "bLAHBLAH", constructPermissionSet(["BLAHBLAH"] as String[], true), false) + + // Case sensitive, different word + doTest(null, "BLAHBLAH", constructPermissionSet(["whatwhat"] as String[], true), false) + } + + @Test + public void testLists() { + + doTest(null, "one,two", constructPermissionSet(["one"] as String[]), false) + doTest(null, "one", constructPermissionSet(["one,two"] as String[]), true) + + doTest(null, "one,two,three", constructPermissionSet(["one,three"] as String[]), false) + doTest(null, "one,three", constructPermissionSet(["one,two,three"] as String[]), true) + + doTest("one,two", "one,two,three", constructPermissionSet(["one:three"] as String[]), false) + doTest(null, "one:three", constructPermissionSet(["one,two:one,two,three"] as String[]), true) + + doTest("one,two", "one,two,three", constructPermissionSet(["one:two,three"] as String[]), false) + doTest(null, "one:two,three", constructPermissionSet(["one,two:one,two,three"] as String[]), true) + + doTest(null, "one:three", constructPermissionSet(["one:two,three"] as String[]), true) + doTest(null, "one:two,three", constructPermissionSet(["one:three"] as String[]), false) + + doTest("one,two,three", "one,two,three:one,two", constructPermissionSet(["one:three:two"] as String[]), false) + doTest("one:three", "two", constructPermissionSet(["one,two,three:one,two,three:one,two"] as String[]), true) + + doTest(null, "one", constructPermissionSet(["one:two,three,four"] as String[]), false) + doTest("one", "two,three,four", constructPermissionSet(["one"] as String[]), true) + + doTest(null, "one", constructPermissionSet(["one:two,three,four:five:six:seven"] as String[]), false) + doTest("one:two,three,four", "five:six:seven", constructPermissionSet(["one"] as String[]), true) + + doTest(null, "one:two,three,four", constructPermissionSet(["one:two,three,four:five:six:seven"] as String[]), false) + doTest(null, "one:two,three,four:five:six:seven", constructPermissionSet(["one:two,three,four"] as String[]), true) + } + + @Test + public void testWildcards() { + + doTest(null, "one", constructPermissionSet(["*"] as String[]), true) + doTest(null, "one:two", constructPermissionSet(["*"] as String[]), true) + doTest("one", "one", constructPermissionSet(["*"] as String[]), true) + doTest(null, "one,two:three,four", constructPermissionSet(["*"] as String[]), true) + doTest("one,two", "three,four", constructPermissionSet(["*"] as String[]), true) + doTest(null, "one,two:three,four,five:six:seven,eight", constructPermissionSet(["*"] as String[]), true) + doTest("one,two:three,four,five", "six:seven,eight", constructPermissionSet(["*"] as String[]), true) + + doTest("newsletter", "read", constructPermissionSet(["newsletter:*"] as String[]), true) + doTest("newsletter", "read,write", constructPermissionSet(["*"] as String[]), true) + doTest("newsletter", "write:*", constructPermissionSet(["*"] as String[]), true) + + } + + @Test + public void testWithFourParameters() { + + doTestFourParameters("123", "newsletter", "read", constructPermissionSet(["newsletter:*:read"] as String[]), true) + doTestFourParameters("123,456", "newsletter", "read,write", constructPermissionSet(["newsletter:*:read"] as String[]), false) + doTestFourParameters("123,456", "newsletter", "read,write", constructPermissionSet(["newsletter:*:read,write"] as String[]), true) + doTestFourParameters("read", "newsletter", "", constructPermissionSet(["newsletter:*:read"] as String[]), false) + doTestFourParameters("", "newsletter", "read", constructPermissionSet(["newsletter:*:read"] as String[]), true) + doTestFourParameters("read,write", "newsletter", "", constructPermissionSet(["newsletter:*:read"] as String[]), false) + doTestFourParameters("", "newsletter", "read,write", constructPermissionSet(["newsletter:*:read"] as String[]), false) + doTestFourParameters("123", "newsletter", "read:write", constructPermissionSet(["newsletter:*:read"] as String[]), true) + } + + private void doTestFourParameters(Serializable targetId, String targetType, Object permission, Collection gaList, Boolean expected) { + + def authentication = createMock(Authentication) + + expect(authentication.getAuthorities()) andReturn gaList + + replay authentication + + WildcardPermissionEvaluator wpe = new WildcardPermissionEvaluator(); + assertEquals(expected, wpe.hasPermission(authentication, targetId, targetType, permission)) + + verify authentication + + } + + + private void doTest(Object targetDomainObject, Object permission, Collection gaList, Boolean expected) { + + def authentication = createMock(Authentication) + + expect(authentication.getAuthorities()) andReturn gaList + + replay authentication + + WildcardPermissionEvaluator wpe = new WildcardPermissionEvaluator(); + assertEquals(expected, wpe.hasPermission(authentication, targetDomainObject, permission)) + + verify authentication + + } + + private Set constructPermissionSet(String[] permissions, Boolean caseSensitive=false) { + Set permissionSet = new HashSet() + for (String permission : permissions) { + if(caseSensitive) { + permissionSet.add(new WildcardPermission(permission, true)) + } else { + permissionSet.add(new WildcardPermission(permission)) + } + } + return permissionSet + } + +} diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/cache/SpringCacheManagerTest.groovy b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/cache/SpringCacheManagerTest.groovy new file mode 100644 index 0000000000..3ead53fd55 --- /dev/null +++ b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/cache/SpringCacheManagerTest.groovy @@ -0,0 +1,52 @@ +/* + * Copyright 2014 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.spring.security.cache + +import org.easymock.EasyMock +import org.junit.Assert +import org.junit.Test + +/** + * @since 0.2.0 + */ +class SpringCacheManagerTest { + + @Test(expected = IllegalArgumentException) + void testNullCacheManager() { + new SpringCacheManager(null) + } + + @Test + void testGetCache() { + + def springCacheManager = EasyMock.createStrictMock(org.springframework.cache.CacheManager) + def springCache = EasyMock.createStrictMock(org.springframework.cache.Cache ) + + def cacheName = 'name' + + EasyMock.expect(springCacheManager.getCache(EasyMock.same(cacheName))).andReturn springCache + + EasyMock.replay springCache, springCacheManager + + SpringCacheManager cacheManager = new SpringCacheManager(springCacheManager) + def cache = cacheManager.getCache(cacheName) + Assert.assertNotNull(cache) + Assert.assertSame springCache, cache.SPRING_CACHE + + EasyMock.verify springCache, springCacheManager + } + +} diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/cache/SpringCacheTest.groovy b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/cache/SpringCacheTest.groovy new file mode 100644 index 0000000000..cc0e7733da --- /dev/null +++ b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/cache/SpringCacheTest.groovy @@ -0,0 +1,124 @@ +/* + * Copyright 2014 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.spring.security.cache + +import org.easymock.EasyMock +import org.junit.Assert +import org.junit.Test + +/** + * @since 0.2.0 + */ +class SpringCacheTest { + + @Test(expected = IllegalArgumentException) + void testNullSpringCache() { + new SpringCache(null) + } + + @Test + void testGet() { + + def springCache = EasyMock.createStrictMock(org.springframework.cache.Cache) + def valueWrapper = EasyMock.createStrictMock(org.springframework.cache.Cache.ValueWrapper) + + def key = 'key' + def value = 'value' + + EasyMock.expect(springCache.get(key)).andReturn valueWrapper + EasyMock.expect(valueWrapper.get()) andReturn value + + EasyMock.replay(springCache, valueWrapper) + + def cache = new SpringCache(springCache) + def retval = cache.get(key) + + Assert.assertSame value, retval + + EasyMock.verify(springCache, valueWrapper) + } + + @Test + void testGetNull() { + + def springCache = EasyMock.createStrictMock(org.springframework.cache.Cache) + + def key = 'key' + def value = 'value' + + EasyMock.expect(springCache.get(key)).andReturn null + + EasyMock.replay springCache + + def cache = new SpringCache(springCache) + def retval = cache.get(key) + + Assert.assertSame null, retval + + EasyMock.verify springCache + } + + + @Test + void testPut() { + + def springCache = EasyMock.createStrictMock(org.springframework.cache.Cache ) + def valueWrapper = EasyMock.createStrictMock(org.springframework.cache.Cache.ValueWrapper) + + def key = 'key' + def value = 'value1' + def prev = 'value0' + + EasyMock.expect(springCache.get(key)) andReturn valueWrapper + EasyMock.expect(springCache.put(EasyMock.same(key), EasyMock.same(value))) + EasyMock.expect(valueWrapper.get()) andReturn prev + + EasyMock.replay(springCache, valueWrapper) + + def cache = new SpringCache(springCache) + + def retval = cache.put(key, value) + + Assert.assertSame prev, retval + + EasyMock.verify(springCache, valueWrapper) + } + + @Test + void testRemove() { + + def springCache = EasyMock.createStrictMock(org.springframework.cache.Cache) + def valueWrapper = EasyMock.createStrictMock(org.springframework.cache.Cache.ValueWrapper) + + def key = 'key' + def prev = 'value0' + + EasyMock.expect(springCache.get(key)) andReturn valueWrapper + EasyMock.expect(springCache.evict(key)) + EasyMock.expect(valueWrapper.get()) andReturn prev + + EasyMock.replay(springCache, valueWrapper) + + def cache = new SpringCache(springCache) + + def retval = cache.remove(key) + + Assert.assertSame prev, retval + + EasyMock.verify(springCache, valueWrapper) + } + +} diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/client/ClientFactoryTest.groovy b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/client/ClientFactoryTest.groovy new file mode 100644 index 0000000000..61c14a11b3 --- /dev/null +++ b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/client/ClientFactoryTest.groovy @@ -0,0 +1,47 @@ +/* + * Copyright 2014 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.spring.security.client + +import com.stormpath.sdk.client.ClientBuilder +import org.junit.Before +import org.junit.Test + +import static org.easymock.EasyMock.createMock +import static org.junit.Assert.assertEquals +import static org.junit.Assert.assertNotNull + +class ClientFactoryTest { + + ClientFactory clientFactory + + @Before + void setUp() { + clientFactory = new ClientFactory() + } + + @Test + public void testGetClientBuilder() { + assertNotNull clientFactory.getClientBuilder() + } + + @Test + public void testSetClientBuilder() { + def builder = createMock(ClientBuilder) + clientFactory.setClientBuilder(builder) + assertEquals builder, clientFactory.getClientBuilder() + } +} + diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/DefaultGroupGrantedAuthorityResolverTest.groovy b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/DefaultGroupGrantedAuthorityResolverTest.groovy new file mode 100644 index 0000000000..6835f81aa2 --- /dev/null +++ b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/DefaultGroupGrantedAuthorityResolverTest.groovy @@ -0,0 +1,193 @@ +/* + * 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.spring.security.provider + +import com.stormpath.sdk.group.Group +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.springframework.security.core.GrantedAuthority + +import static org.easymock.EasyMock.* +import static org.hamcrest.core.IsInstanceOf.instanceOf +import static org.junit.Assert.* + +class DefaultGroupGrantedAuthorityResolverTest { + + DefaultGroupGrantedAuthorityResolver resolver + + @Before + void setUp() { + resolver = new DefaultGroupGrantedAuthorityResolver() + } + + @Test + void testDefaultInstance() { + Assert.assertEquals 1, resolver.modes.size() + Assert.assertSame DefaultGroupGrantedAuthorityResolver.Mode.HREF, resolver.modes.iterator().next() + } + + @Test + void testSetModes() { + resolver.setModes([DefaultGroupGrantedAuthorityResolver.Mode.ID] as Set) + Assert.assertEquals 1, resolver.modes.size() + Assert.assertSame DefaultGroupGrantedAuthorityResolver.Mode.ID, resolver.modes.iterator().next() + } + + @Test(expected=IllegalArgumentException) + void testSetNullModes() { + resolver.setModes(null) + } + + @Test(expected=IllegalArgumentException) + void testSetEmptyModes() { + resolver.setModes(Collections.emptySet()) + } + + @Test + void testResolveGrantedAuthorityWithHref() { + + def group = createStrictMock(Group) + + def href = 'https://api.stormpath.com/groups/foo' + + expect(group.href).andReturn(href) + + replay group + + def roleNames = resolver.resolveGrantedAuthorities(group) + + Assert.assertEquals 1, roleNames.size() + def retrievedRole = roleNames.iterator().next() + assertThat retrievedRole, instanceOf(GrantedAuthority.class) + assertEquals href, retrievedRole.toString() + + verify group + } + + @Test(expected=IllegalStateException) + void testResolveGrantedAuthorityWithMissingHref() { + + def group = createStrictMock(Group) + + expect(group.href).andReturn null + + replay group + + try { + resolver.resolveGrantedAuthorities(group) + } finally { + verify group + } + } + + @Test + void testResolveGrantedAuthorityWithId() { + + def group = createStrictMock(Group) + + def href = 'https://api.stormpath.com/groups/foo' + + expect(group.href).andReturn(href) + + replay group + + resolver.modes = [DefaultGroupGrantedAuthorityResolver.Mode.ID] as Set + def roleNames = resolver.resolveGrantedAuthorities(group) + + Assert.assertEquals 1, roleNames.size() + def retrievedRole = roleNames.iterator().next() + assertThat retrievedRole, instanceOf(GrantedAuthority.class) + assertEquals 'foo', retrievedRole.toString() + + verify group + } + + @Test + void testResolveGrantedAuthorityWithIdAndInvalidHref() { + + def group = createStrictMock(Group) + + def href = 'whatever' + + expect(group.href).andReturn(href) + + replay group + + resolver.modes = [DefaultGroupGrantedAuthorityResolver.Mode.ID] as Set + def roleNames = resolver.resolveGrantedAuthorities(group) + + assertNotNull roleNames + assertTrue roleNames.isEmpty() + + verify group + } + + @Test + void testResolveGrantedAuthorityWithName() { + + def group = createStrictMock(Group) + + def href = 'https://api.stormpath.com/groups/foo' + + expect(group.href).andReturn(href) + expect(group.name).andReturn('bar') + + replay group + + resolver.modes = [DefaultGroupGrantedAuthorityResolver.Mode.NAME] as Set + def roleNames = resolver.resolveGrantedAuthorities(group) + + Assert.assertEquals 1, roleNames.size() + def retrievedRole = roleNames.iterator().next() + assertThat retrievedRole, instanceOf(GrantedAuthority.class) + assertEquals 'bar', retrievedRole.toString() + + verify group + } + + @Test(expected = IllegalArgumentException) + void testResolveGrantedAuthorityFromEmptyString() { + DefaultGroupGrantedAuthorityResolver.Mode.fromString("") + } + + @Test(expected = IllegalArgumentException) + void testResolveGrantedAuthorityFromNull() { + DefaultGroupGrantedAuthorityResolver.Mode.fromString(null) + } + + @Test(expected = IllegalArgumentException) + void testResolveGrantedAuthorityFromUnknownName() { + DefaultGroupGrantedAuthorityResolver.Mode.fromString("foo") + } + + @Test + void testResolveGrantedAuthorityFromString() { + + def mode = DefaultGroupGrantedAuthorityResolver.Mode.fromString("href") + assertEquals(DefaultGroupGrantedAuthorityResolver.Mode.HREF, mode) + + mode = DefaultGroupGrantedAuthorityResolver.Mode.fromString("HREF") + assertEquals(DefaultGroupGrantedAuthorityResolver.Mode.HREF, mode) + + mode = DefaultGroupGrantedAuthorityResolver.Mode.fromString("Name") + assertEquals(DefaultGroupGrantedAuthorityResolver.Mode.NAME, mode) + } + + +} diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/SimpleError.groovy b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/SimpleError.groovy new file mode 100644 index 0000000000..60ddaa9cb8 --- /dev/null +++ b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/SimpleError.groovy @@ -0,0 +1,27 @@ +/* + * 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.spring.security.provider + +class SimpleError implements com.stormpath.sdk.error.Error { + + int status + int code + String message + String developerMessage + String moreInfo +} diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/StormpathAuthenticationProviderTest.groovy b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/StormpathAuthenticationProviderTest.groovy new file mode 100644 index 0000000000..43d92f830e --- /dev/null +++ b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/StormpathAuthenticationProviderTest.groovy @@ -0,0 +1,268 @@ +/* + * 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.spring.security.provider + +import com.stormpath.sdk.account.Account +import com.stormpath.sdk.account.AccountStatus +import com.stormpath.sdk.application.Application +import com.stormpath.sdk.authc.AuthenticationRequest +import com.stormpath.sdk.authc.AuthenticationResult +import com.stormpath.sdk.client.Client +import com.stormpath.sdk.directory.CustomData +import com.stormpath.sdk.ds.DataStore +import com.stormpath.sdk.group.Group +import com.stormpath.sdk.group.GroupList +import org.easymock.IAnswer +import org.junit.Before +import org.junit.Test +import org.springframework.security.authentication.* +import org.springframework.security.core.Authentication +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.core.userdetails.UserDetails + +import static org.easymock.EasyMock.* +import static org.junit.Assert.* + +class StormpathAuthenticationProviderTest { + + StormpathAuthenticationProvider authenticationProvider + + @Before + void setUp() { + authenticationProvider = new StormpathAuthenticationProvider() + } + + @Test(expected = IllegalStateException) + void testInitWithoutClient() { + def token = createStrictMock(UsernamePasswordAuthenticationToken) + + replay token + + try { + authenticationProvider.authenticate(token) //needs client and applicationUrl properties + } finally { + verify token + } + } + + @Test(expected = IllegalStateException) + void testInitWithoutApplicationUrl() { + def client = createStrictMock(Client) + def token = createStrictMock(UsernamePasswordAuthenticationToken) + + replay client, token + + authenticationProvider.client = client + try { + authenticationProvider.authenticate(token) //needs applicationUrl property + } finally { + verify client, token + } + } + + @Test + void testSetClient() { + def client = createStrictMock(Client) + + replay client + + authenticationProvider.client = client + + assertSame client, authenticationProvider.client + + verify client + } + + @Test + void testDoGetAuthenticationInfoSuccess() { + + def appHref = 'https://api.stormpath.com/v1/applications/foo' + def username = 'myUsername' + def password = 'secret' + def acctUsername = 'jsmith' + def acctHref = 'https://api.stormpath.com/v1/accounts/123' + def acctEmail = 'jsmith@foo.com' + def acctGivenName = 'John' + def acctMiddleName = 'A' + def acctSurname = 'Smith' + def acctStatus = AccountStatus.ENABLED + Set groupSpringSecurityGrantedAuthorities = new HashSet<>(); + groupSpringSecurityGrantedAuthorities.add("groupSpringSecurityPermissionsItem"); + Set accountSpringSecurityGrantedAuthorities = new HashSet<>(); + accountSpringSecurityGrantedAuthorities.add("accountSpringSecurityPermissionsItem"); + + def authentication = createStrictMock(UsernamePasswordAuthenticationToken) + def client = createStrictMock(Client) + def dataStore = createStrictMock(DataStore) + def app = createStrictMock(Application) + def authenticationResult = createStrictMock(AuthenticationResult) + def account = createStrictMock(Account) + def groupList = createStrictMock(GroupList) + def iterator = createStrictMock(Iterator) + def group = createStrictMock(Group) + def groupCustomData = createStrictMock(CustomData) + def accountCustomData = createStrictMock(CustomData) + def accountGrantedAuthorityResolver = createStrictMock(AccountGrantedAuthorityResolver) + def groupGrantedAuthorityResolver = createStrictMock(GroupGrantedAuthorityResolver) + def groupGrantedAuthority = createStrictMock(GrantedAuthority) + def accountGrantedAuthority = createStrictMock(GrantedAuthority) + def groupGrantedAuthoritySet = new HashSet() + groupGrantedAuthoritySet.add(groupGrantedAuthority) + def accountGrantedAuthoritySet = new HashSet() + accountGrantedAuthoritySet.add(accountGrantedAuthority) + + expect(authentication.principal).andReturn username + expect(authentication.credentials).andReturn password + expect(client.dataStore).andStubReturn(dataStore) + expect(dataStore.getResource(eq(appHref), same(Application))).andReturn(app) + expect(app.authenticateAccount(anyObject() as AuthenticationRequest)).andAnswer( new IAnswer() { + AuthenticationResult answer() throws Throwable { + def authcRequest = getCurrentArguments()[0] as AuthenticationRequest + + assertEquals username, authcRequest.principals + assertTrue Arrays.equals(password.toCharArray(), authcRequest.credentials as char[]) + + return authenticationResult + } + }) + expect(authenticationResult.account).andReturn account + expect(account.groups).andReturn groupList + expect(group.customData).andReturn groupCustomData + expect(groupCustomData.get("springSecurityPermissions")).andReturn groupSpringSecurityGrantedAuthorities + expect(account.customData).andReturn accountCustomData + expect(accountCustomData.get("springSecurityPermissions")).andReturn accountSpringSecurityGrantedAuthorities + expect(account.href).andReturn acctHref + expect(account.username).andReturn acctUsername + expect(account.email).andReturn acctEmail + expect(account.givenName).andReturn acctGivenName + expect(account.middleName).andReturn acctMiddleName + expect(account.surname).andReturn acctSurname + expect(account.getStatus()).andReturn acctStatus times 2 + expect(account.username).andReturn acctUsername + expect(authentication.principal).andReturn username + expect(groupList.iterator()).andReturn iterator + expect(iterator.hasNext()).andReturn true + expect(iterator.next()).andReturn group + expect(groupGrantedAuthorityResolver.resolveGrantedAuthorities(group)).andReturn groupGrantedAuthoritySet + expect(iterator.hasNext()).andReturn false + expect(accountGrantedAuthorityResolver.resolveGrantedAuthorities(account)).andReturn accountGrantedAuthoritySet + + replay authentication, client, dataStore, app, authenticationResult, account, accountCustomData, groupList, iterator, group, groupCustomData, + accountGrantedAuthorityResolver, groupGrantedAuthorityResolver, groupGrantedAuthority, accountGrantedAuthority + + authenticationProvider.client = client + authenticationProvider.applicationRestUrl = appHref + authenticationProvider.accountGrantedAuthorityResolver = accountGrantedAuthorityResolver + authenticationProvider.groupGrantedAuthorityResolver = groupGrantedAuthorityResolver + + Authentication info = authenticationProvider.authenticate(authentication) + + assertTrue info instanceof UsernamePasswordAuthenticationToken + assertTrue info.authenticated + + assertEquals acctUsername, ((UserDetails)info.principal).username + assertEquals null, ((UserDetails)info.principal).password + assertEquals 4, info.authorities.size() + assertTrue info.authorities.contains(groupGrantedAuthority) + assertTrue info.authorities.contains(accountGrantedAuthority) + assertEquals acctHref, ((StormpathUserDetails)info.principal).properties.get("href") + assertEquals acctUsername, ((StormpathUserDetails)info.principal).properties.get("username") + assertEquals acctEmail, ((StormpathUserDetails)info.principal).properties.get("email") + assertEquals acctGivenName, ((StormpathUserDetails)info.principal).properties.get("givenName") + assertEquals acctMiddleName, ((StormpathUserDetails)info.principal).properties.get("middleName") + assertEquals acctSurname, ((StormpathUserDetails)info.principal).properties.get("surname") + assertTrue(((UserDetails)info.principal).enabled) + assertTrue(((UserDetails)info.principal).accountNonLocked) + assertTrue(((UserDetails)info.principal).accountNonExpired) + assertTrue(((UserDetails)info.principal).credentialsNonExpired) + + verify authentication, client, dataStore, app, authenticationResult, account, accountCustomData, groupList, iterator, group, groupCustomData, + accountGrantedAuthorityResolver, groupGrantedAuthorityResolver, groupGrantedAuthority, accountGrantedAuthority + } + + @Test(expected=AuthenticationServiceException) + void testAuthenticateException() { + + def appHref = 'https://api.stormpath.com/v1/applications/foo' + def client = createStrictMock(Client) + def dataStore = createStrictMock(DataStore) + def app = createStrictMock(Application) + + int status = 400 + int code = 400 + def msg = 'Invalid username or password.' + def devMsg = 'Invalid username or password.' + def moreInfo = 'mailto:support@stormpath.com' + + expect(client.dataStore).andStubReturn(dataStore) + + def error = new SimpleError(status:status, code:code, message: msg, developerMessage: devMsg, moreInfo: moreInfo) + + expect(dataStore.getResource(eq(appHref), same(Application))).andReturn app + expect(app.authenticateAccount(anyObject() as AuthenticationRequest)).andThrow(new com.stormpath.sdk.resource.ResourceException(error)) + + replay client, dataStore, app + + authenticationProvider.client = client + authenticationProvider.applicationRestUrl = appHref + + def token = new UsernamePasswordAuthenticationToken('foo', 'bar') + try { + authenticationProvider.authenticate(token) + } + finally { + verify client, dataStore, app + } + } + + @Test(expected=IllegalArgumentException) + void testInvalidAuthenticationTokenFactory() { + authenticationProvider.authenticationTokenFactory = null + } + + @Test + void testSetAuthenticationTokenFactory() { + def authenticationTokenFactory = new UsernamePasswordAuthenticationTokenFactory() + authenticationProvider.authenticationTokenFactory = authenticationTokenFactory + assertEquals authenticationTokenFactory, authenticationProvider.authenticationTokenFactory + } + + @Test + void testSetAccountGrantedAuthorityResolver() { + def accountGrantedAuthorityResolver = createNiceMock(AccountGrantedAuthorityResolver) + authenticationProvider.accountGrantedAuthorityResolver = accountGrantedAuthorityResolver + assertEquals accountGrantedAuthorityResolver, authenticationProvider.accountGrantedAuthorityResolver + } + + @Test + void testSetGroupGrantedAuthorityResolver() { + def groupGrantedAuthorityResolver = createNiceMock(GroupGrantedAuthorityResolver) + authenticationProvider.groupGrantedAuthorityResolver = groupGrantedAuthorityResolver + assertEquals groupGrantedAuthorityResolver, authenticationProvider.groupGrantedAuthorityResolver + } + + @Test + void testSupports() { + assertTrue authenticationProvider.supports(UsernamePasswordAuthenticationToken) + assertTrue authenticationProvider.supports(RememberMeAuthenticationToken) + assertTrue authenticationProvider.supports(AnonymousAuthenticationToken) + assertTrue authenticationProvider.supports(Authentication) + assertTrue authenticationProvider.supports(AbstractAuthenticationToken) + assertFalse authenticationProvider.supports(String) + assertFalse authenticationProvider.supports(Object) + } + +} diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/StormpathUserDetailsTest.groovy b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/StormpathUserDetailsTest.groovy new file mode 100644 index 0000000000..ed15c82b4e --- /dev/null +++ b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/StormpathUserDetailsTest.groovy @@ -0,0 +1,79 @@ +/* + * Copyright 2014 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.spring.security.provider + +import com.stormpath.sdk.account.Account +import com.stormpath.sdk.account.AccountStatus +import org.junit.Test +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.core.userdetails.UserDetails + +import static org.easymock.EasyMock.* +import static org.junit.Assert.assertTrue + +class StormpathUserDetailsTest { + + @Test(expected = IllegalArgumentException.class) + public void testUserNotNull(){ + def account = createMock(Account) + Collection grantedAuthorities = new HashSet(); + new StormpathUserDetails(null, "psswd", grantedAuthorities, account); + } + + @Test(expected = IllegalArgumentException.class) + public void testGrantedAuthoritiesNotNull(){ + def account = createMock(Account) + new StormpathUserDetails("username", "psswd", null, account); + } + + @Test(expected = IllegalArgumentException.class) + public void testAccountNotNull(){ + Collection grantedAuthorities = new HashSet(); + new StormpathUserDetails("username", "psswd", grantedAuthorities, null); + } + + @Test + public void test(){ + def account = createMock(Account) + + def acctUsername = 'jsmith' + def acctHref = 'https://api.stormpath.com/v1/accounts/123' + def acctEmail = 'jsmith@foo.com' + def acctGivenName = 'John' + def acctMiddleName = 'A' + def acctSurname = 'Smith' + def acctStatus = AccountStatus.ENABLED + + expect(account.href).andReturn acctHref + expect(account.username).andReturn acctUsername times 2 + expect(account.email).andReturn acctEmail + expect(account.givenName).andReturn acctGivenName + expect(account.middleName).andReturn acctMiddleName + expect(account.surname).andReturn acctSurname + expect(account.getStatus()).andReturn acctStatus times 2 + + replay account + + Collection grantedAuthorities = new HashSet(); + UserDetails userDetails = new StormpathUserDetails("username", "psswd", grantedAuthorities, account); + assertTrue(userDetails.accountNonExpired) + assertTrue(userDetails.accountNonLocked) + assertTrue(userDetails.credentialsNonExpired) + + verify account + } + +} diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/UsernamePasswordAuthenticationTokenFactoryTest.groovy b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/UsernamePasswordAuthenticationTokenFactoryTest.groovy new file mode 100644 index 0000000000..bb36afc4bb --- /dev/null +++ b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/UsernamePasswordAuthenticationTokenFactoryTest.groovy @@ -0,0 +1,87 @@ +/* + * 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.spring.security.provider + +import com.stormpath.sdk.account.Account +import com.stormpath.sdk.account.AccountStatus +import org.junit.Test +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.core.authority.SimpleGrantedAuthority + +import static org.easymock.EasyMock.* +import static org.hamcrest.core.IsInstanceOf.instanceOf +import static org.junit.Assert.* + +class UsernamePasswordAuthenticationTokenFactoryTest { + + @Test + void testSetAuthenticationTokenFactory() { + + def acctHref = 'https://api.stormpath.com/v1/accounts/123' + def acctUsername = 'jsmith' + def acctEmail = 'jsmith@foo.com' + def acctGivenName = 'John' + def acctMiddleName = 'A' + def acctSurname = 'Smith' + def acctStatus = AccountStatus.ENABLED + + def authenticationTokenFactory = new UsernamePasswordAuthenticationTokenFactory() + def grantedAuthorityA = new SimpleGrantedAuthority("ROLE_A"); + def grantedAuthorityB = new SimpleGrantedAuthority("ROLE_B"); + def Set gas = new HashSet<>(2); + gas.add(grantedAuthorityA) + gas.add(grantedAuthorityB) + Collection authorities = new ArrayList(); + def account = createStrictMock(Account) + + expect(account.href).andReturn acctHref + expect(account.username).andReturn acctUsername + expect(account.email).andReturn acctEmail + expect(account.givenName).andReturn acctGivenName + expect(account.middleName).andReturn acctMiddleName + expect(account.surname).andReturn acctSurname + expect(account.status).andReturn acctStatus times 2 + expect(account.username).andReturn acctUsername + + replay account + + def token = authenticationTokenFactory.createAuthenticationToken("foo", "bar", gas, account) + assertNotNull token + assertThat token, instanceOf(UsernamePasswordAuthenticationToken.class) + assertEquals acctUsername, ((StormpathUserDetails)token.principal).getUsername() + assertEquals "bar", token.getCredentials() + assertArrayEquals gas.toArray(), token.getAuthorities().toArray() + assertEquals gas.size(), token.getAuthorities().size() + assertEquals acctHref, ((StormpathUserDetails)token.principal).properties.get("href") + assertEquals acctUsername, ((StormpathUserDetails)token.principal).properties.get("username") + assertEquals acctEmail, ((StormpathUserDetails)token.principal).properties.get("email") + assertEquals acctGivenName, ((StormpathUserDetails)token.principal).properties.get("givenName") + assertEquals acctMiddleName, ((StormpathUserDetails)token.principal).properties.get("middleName") + assertEquals acctSurname, ((StormpathUserDetails)token.principal).properties.get("surname") + assertEquals acctStatus.toString(), ((StormpathUserDetails)token.principal).properties.get("status") + assertTrue(((StormpathUserDetails)token.principal).enabled) + assertTrue(((StormpathUserDetails)token.principal).accountNonLocked) + assertTrue(((StormpathUserDetails)token.principal).accountNonExpired) + assertTrue(((StormpathUserDetails)token.principal).credentialsNonExpired) + + verify account + + } + + +} diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/util/CollectionUtilsTest.groovy b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/util/CollectionUtilsTest.groovy new file mode 100644 index 0000000000..8d4006a773 --- /dev/null +++ b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/util/CollectionUtilsTest.groovy @@ -0,0 +1,25 @@ +package com.stormpath.spring.security.util + +import org.junit.Assert +import org.junit.Test + +/** + * @since 0.2.0 + */ +class CollectionUtilsTest { + + @Test + public void testEmptyList() { + + def list1 = CollectionUtils.asList(null) + Assert.assertTrue(list1 instanceof List); + Assert.assertTrue(list1.size() == 0); + + def list2 = CollectionUtils.asList() + Assert.assertTrue(list2 instanceof List); + Assert.assertTrue(list2.size() == 0); + + } + + +} diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/util/StringUtilsTest.groovy b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/util/StringUtilsTest.groovy new file mode 100644 index 0000000000..81ab486116 --- /dev/null +++ b/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/util/StringUtilsTest.groovy @@ -0,0 +1,34 @@ +/* + * 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.spring.security.util + +import org.junit.Test + +import static org.junit.Assert.* + +class StringUtilsTest { + + @Test + public void clean() { + assertNull(StringUtils.clean(null)); + assertNull(StringUtils.clean(" ")); + assertEquals("foo", StringUtils.clean(" foo ")); + assertEquals("foo", StringUtils.clean("foo")); + } + + +} + diff --git a/pom.xml b/pom.xml index 99ed621960..3f8b302f90 100644 --- a/pom.xml +++ b/pom.xml @@ -105,6 +105,7 @@ 3.0.1 4.1.4.RELEASE 1.2.1.RELEASE + 4.0.0.RELEASE 1.8.8 @@ -314,6 +315,17 @@ + + org.springframework.security + spring-security-core + ${spring.security.version} + + + commons-logging + commons-logging + + + org.springframework spring-web @@ -443,6 +455,7 @@ **/*ManualIT.java **/*ManualIT.groovy + com.stormpath.spring.config.MinimalStormpathConfigurationIT From 38d206cce254ad8fbb6a1b8a716a169df73c7083 Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 10 Apr 2015 11:15:26 -0300 Subject: [PATCH 2/7] Removed unnecessary excludedGroups tag --- pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3f8b302f90..b84200fd7b 100644 --- a/pom.xml +++ b/pom.xml @@ -455,7 +455,6 @@ **/*ManualIT.java **/*ManualIT.groovy - com.stormpath.spring.config.MinimalStormpathConfigurationIT From f2e9ca8071be4e00475e376d6e884f5ef126492e Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 10 Apr 2015 12:50:11 -0300 Subject: [PATCH 3/7] Forcing CI re-run --- .../com/stormpath/spring/security/authz/PermissionsEditor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/PermissionsEditor.java b/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/PermissionsEditor.java index 2db6a884db..4d78c3acd7 100644 --- a/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/PermissionsEditor.java +++ b/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/PermissionsEditor.java @@ -52,4 +52,5 @@ public interface PermissionsEditor { * @return an immutable view of the stored permission strings. */ Set getPermissionStrings(); + } From b486c759b3aa4c9387ba8a6a432e2eee22c85e4c Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 10 Apr 2015 16:10:42 -0300 Subject: [PATCH 4/7] Spring Security module moved under extensions/spring dir --- extensions/pom.xml | 1 - extensions/spring/pom.xml | 1 + .../stormpath-spring-security}/pom.xml | 2 +- .../spring/security/authz/CustomDataPermissionsEditor.java | 0 .../com/stormpath/spring/security/authz/PermissionsEditor.java | 0 .../spring/security/authz/permission/DomainPermission.java | 0 .../stormpath/spring/security/authz/permission/Permission.java | 0 .../spring/security/authz/permission/WildcardPermission.java | 0 .../authz/permission/evaluator/WildcardPermissionEvaluator.java | 0 .../java/com/stormpath/spring/security/cache/SpringCache.java | 0 .../com/stormpath/spring/security/cache/SpringCacheManager.java | 0 .../com/stormpath/spring/security/client/ClientFactory.java | 0 .../security/provider/AccountCustomDataPermissionResolver.java | 0 .../security/provider/AccountGrantedAuthorityResolver.java | 0 .../spring/security/provider/AccountPermissionResolver.java | 0 .../spring/security/provider/AuthenticationTokenFactory.java | 0 .../spring/security/provider/CustomDataPermissionResolver.java | 0 .../security/provider/DefaultGroupGrantedAuthorityResolver.java | 0 .../security/provider/GroupCustomDataPermissionResolver.java | 0 .../spring/security/provider/GroupGrantedAuthorityResolver.java | 0 .../spring/security/provider/GroupPermissionResolver.java | 0 .../security/provider/InvalidPermissionStringException.java | 0 .../stormpath/spring/security/provider/PermissionResolver.java | 0 .../security/provider/StormpathAuthenticationProvider.java | 0 .../spring/security/provider/StormpathUserDetails.java | 0 .../provider/UsernamePasswordAuthenticationTokenFactory.java | 0 .../spring/security/provider/WildcardPermissionResolver.java | 0 .../com/stormpath/spring/security/util/CollectionUtils.java | 0 .../java/com/stormpath/spring/security/util/StringUtils.java | 0 .../security/authz/CustomDataPermissionsEditorTest.groovy | 0 .../com/stormpath/spring/security/authz/MockCustomData.groovy | 0 .../security/authz/permission/DomainPermissionTest.groovy | 0 .../security/authz/permission/WildcardPermissionTest.groovy | 0 .../permission/evaluator/WildcardPermissionEvaluatorTest.groovy | 0 .../spring/security/cache/SpringCacheManagerTest.groovy | 0 .../com/stormpath/spring/security/cache/SpringCacheTest.groovy | 0 .../stormpath/spring/security/client/ClientFactoryTest.groovy | 0 .../provider/DefaultGroupGrantedAuthorityResolverTest.groovy | 0 .../com/stormpath/spring/security/provider/SimpleError.groovy | 0 .../provider/StormpathAuthenticationProviderTest.groovy | 0 .../spring/security/provider/StormpathUserDetailsTest.groovy | 0 .../UsernamePasswordAuthenticationTokenFactoryTest.groovy | 0 .../stormpath/spring/security/util/CollectionUtilsTest.groovy | 0 .../com/stormpath/spring/security/util/StringUtilsTest.groovy | 0 44 files changed, 2 insertions(+), 2 deletions(-) rename extensions/{spring-security => spring/stormpath-spring-security}/pom.xml (96%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/authz/CustomDataPermissionsEditor.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/authz/PermissionsEditor.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/authz/permission/DomainPermission.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/authz/permission/Permission.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/authz/permission/WildcardPermission.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/authz/permission/evaluator/WildcardPermissionEvaluator.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/cache/SpringCache.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/cache/SpringCacheManager.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/client/ClientFactory.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/provider/AccountCustomDataPermissionResolver.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/provider/AccountGrantedAuthorityResolver.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/provider/AccountPermissionResolver.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/provider/AuthenticationTokenFactory.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/provider/CustomDataPermissionResolver.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/provider/DefaultGroupGrantedAuthorityResolver.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/provider/GroupCustomDataPermissionResolver.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/provider/GroupGrantedAuthorityResolver.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/provider/GroupPermissionResolver.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/provider/InvalidPermissionStringException.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/provider/PermissionResolver.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/provider/StormpathAuthenticationProvider.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/provider/StormpathUserDetails.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/provider/UsernamePasswordAuthenticationTokenFactory.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/provider/WildcardPermissionResolver.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/util/CollectionUtils.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/main/java/com/stormpath/spring/security/util/StringUtils.java (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/test/groovy/com/stormpath/spring/security/authz/CustomDataPermissionsEditorTest.groovy (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/test/groovy/com/stormpath/spring/security/authz/MockCustomData.groovy (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/test/groovy/com/stormpath/spring/security/authz/permission/DomainPermissionTest.groovy (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/test/groovy/com/stormpath/spring/security/authz/permission/WildcardPermissionTest.groovy (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/test/groovy/com/stormpath/spring/security/authz/permission/evaluator/WildcardPermissionEvaluatorTest.groovy (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/test/groovy/com/stormpath/spring/security/cache/SpringCacheManagerTest.groovy (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/test/groovy/com/stormpath/spring/security/cache/SpringCacheTest.groovy (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/test/groovy/com/stormpath/spring/security/client/ClientFactoryTest.groovy (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/test/groovy/com/stormpath/spring/security/provider/DefaultGroupGrantedAuthorityResolverTest.groovy (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/test/groovy/com/stormpath/spring/security/provider/SimpleError.groovy (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/test/groovy/com/stormpath/spring/security/provider/StormpathAuthenticationProviderTest.groovy (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/test/groovy/com/stormpath/spring/security/provider/StormpathUserDetailsTest.groovy (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/test/groovy/com/stormpath/spring/security/provider/UsernamePasswordAuthenticationTokenFactoryTest.groovy (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/test/groovy/com/stormpath/spring/security/util/CollectionUtilsTest.groovy (100%) rename extensions/{spring-security => spring/stormpath-spring-security}/src/test/groovy/com/stormpath/spring/security/util/StringUtilsTest.groovy (100%) diff --git a/extensions/pom.xml b/extensions/pom.xml index 0547ec097c..2e0a7ebc8d 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -35,7 +35,6 @@ servlet-plugin httpclient spring - spring-security diff --git a/extensions/spring/pom.xml b/extensions/spring/pom.xml index 8504c907d3..5898cd2211 100644 --- a/extensions/spring/pom.xml +++ b/extensions/spring/pom.xml @@ -39,6 +39,7 @@ stormpath-spring stormpath-spring-webmvc + stormpath-spring-security spring-boot-starter-stormpath spring-boot-starter-stormpath-webmvc spring-boot-starter-stormpath-thymeleaf diff --git a/extensions/spring-security/pom.xml b/extensions/spring/stormpath-spring-security/pom.xml similarity index 96% rename from extensions/spring-security/pom.xml rename to extensions/spring/stormpath-spring-security/pom.xml index e72726ddf2..7f013ac73a 100644 --- a/extensions/spring-security/pom.xml +++ b/extensions/spring/stormpath-spring-security/pom.xml @@ -26,7 +26,7 @@ stormpath-spring-security - Stormpath Java SDK :: Extensions :: Stormpath Spring Security Plugin + Stormpath Java SDK :: Spring :: Spring Security Stormpath Spring Security integration allows Spring Security applications to use Stormpath as the backend for all of their security needs. diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/CustomDataPermissionsEditor.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/authz/CustomDataPermissionsEditor.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/CustomDataPermissionsEditor.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/authz/CustomDataPermissionsEditor.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/PermissionsEditor.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/authz/PermissionsEditor.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/PermissionsEditor.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/authz/PermissionsEditor.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/DomainPermission.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/authz/permission/DomainPermission.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/DomainPermission.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/authz/permission/DomainPermission.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/Permission.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/authz/permission/Permission.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/Permission.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/authz/permission/Permission.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/WildcardPermission.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/authz/permission/WildcardPermission.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/WildcardPermission.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/authz/permission/WildcardPermission.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/evaluator/WildcardPermissionEvaluator.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/authz/permission/evaluator/WildcardPermissionEvaluator.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/authz/permission/evaluator/WildcardPermissionEvaluator.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/authz/permission/evaluator/WildcardPermissionEvaluator.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/cache/SpringCache.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/cache/SpringCache.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/cache/SpringCache.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/cache/SpringCache.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/cache/SpringCacheManager.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/cache/SpringCacheManager.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/cache/SpringCacheManager.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/cache/SpringCacheManager.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/client/ClientFactory.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/client/ClientFactory.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/client/ClientFactory.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/client/ClientFactory.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AccountCustomDataPermissionResolver.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/AccountCustomDataPermissionResolver.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AccountCustomDataPermissionResolver.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/AccountCustomDataPermissionResolver.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AccountGrantedAuthorityResolver.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/AccountGrantedAuthorityResolver.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AccountGrantedAuthorityResolver.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/AccountGrantedAuthorityResolver.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AccountPermissionResolver.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/AccountPermissionResolver.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AccountPermissionResolver.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/AccountPermissionResolver.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AuthenticationTokenFactory.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/AuthenticationTokenFactory.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/AuthenticationTokenFactory.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/AuthenticationTokenFactory.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/CustomDataPermissionResolver.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/CustomDataPermissionResolver.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/CustomDataPermissionResolver.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/CustomDataPermissionResolver.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/DefaultGroupGrantedAuthorityResolver.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/DefaultGroupGrantedAuthorityResolver.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/DefaultGroupGrantedAuthorityResolver.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/DefaultGroupGrantedAuthorityResolver.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/GroupCustomDataPermissionResolver.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/GroupCustomDataPermissionResolver.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/GroupCustomDataPermissionResolver.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/GroupCustomDataPermissionResolver.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/GroupGrantedAuthorityResolver.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/GroupGrantedAuthorityResolver.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/GroupGrantedAuthorityResolver.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/GroupGrantedAuthorityResolver.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/GroupPermissionResolver.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/GroupPermissionResolver.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/GroupPermissionResolver.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/GroupPermissionResolver.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/InvalidPermissionStringException.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/InvalidPermissionStringException.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/InvalidPermissionStringException.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/InvalidPermissionStringException.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/PermissionResolver.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/PermissionResolver.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/PermissionResolver.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/PermissionResolver.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/StormpathAuthenticationProvider.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/StormpathAuthenticationProvider.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/StormpathAuthenticationProvider.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/StormpathAuthenticationProvider.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/StormpathUserDetails.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/StormpathUserDetails.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/StormpathUserDetails.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/StormpathUserDetails.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/UsernamePasswordAuthenticationTokenFactory.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/UsernamePasswordAuthenticationTokenFactory.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/UsernamePasswordAuthenticationTokenFactory.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/UsernamePasswordAuthenticationTokenFactory.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/WildcardPermissionResolver.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/WildcardPermissionResolver.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/provider/WildcardPermissionResolver.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/provider/WildcardPermissionResolver.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/util/CollectionUtils.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/util/CollectionUtils.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/util/CollectionUtils.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/util/CollectionUtils.java diff --git a/extensions/spring-security/src/main/java/com/stormpath/spring/security/util/StringUtils.java b/extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/util/StringUtils.java similarity index 100% rename from extensions/spring-security/src/main/java/com/stormpath/spring/security/util/StringUtils.java rename to extensions/spring/stormpath-spring-security/src/main/java/com/stormpath/spring/security/util/StringUtils.java diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/CustomDataPermissionsEditorTest.groovy b/extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/authz/CustomDataPermissionsEditorTest.groovy similarity index 100% rename from extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/CustomDataPermissionsEditorTest.groovy rename to extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/authz/CustomDataPermissionsEditorTest.groovy diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/MockCustomData.groovy b/extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/authz/MockCustomData.groovy similarity index 100% rename from extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/MockCustomData.groovy rename to extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/authz/MockCustomData.groovy diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/DomainPermissionTest.groovy b/extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/DomainPermissionTest.groovy similarity index 100% rename from extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/DomainPermissionTest.groovy rename to extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/DomainPermissionTest.groovy diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/WildcardPermissionTest.groovy b/extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/WildcardPermissionTest.groovy similarity index 100% rename from extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/WildcardPermissionTest.groovy rename to extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/WildcardPermissionTest.groovy diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/evaluator/WildcardPermissionEvaluatorTest.groovy b/extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/evaluator/WildcardPermissionEvaluatorTest.groovy similarity index 100% rename from extensions/spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/evaluator/WildcardPermissionEvaluatorTest.groovy rename to extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/authz/permission/evaluator/WildcardPermissionEvaluatorTest.groovy diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/cache/SpringCacheManagerTest.groovy b/extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/cache/SpringCacheManagerTest.groovy similarity index 100% rename from extensions/spring-security/src/test/groovy/com/stormpath/spring/security/cache/SpringCacheManagerTest.groovy rename to extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/cache/SpringCacheManagerTest.groovy diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/cache/SpringCacheTest.groovy b/extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/cache/SpringCacheTest.groovy similarity index 100% rename from extensions/spring-security/src/test/groovy/com/stormpath/spring/security/cache/SpringCacheTest.groovy rename to extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/cache/SpringCacheTest.groovy diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/client/ClientFactoryTest.groovy b/extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/client/ClientFactoryTest.groovy similarity index 100% rename from extensions/spring-security/src/test/groovy/com/stormpath/spring/security/client/ClientFactoryTest.groovy rename to extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/client/ClientFactoryTest.groovy diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/DefaultGroupGrantedAuthorityResolverTest.groovy b/extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/provider/DefaultGroupGrantedAuthorityResolverTest.groovy similarity index 100% rename from extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/DefaultGroupGrantedAuthorityResolverTest.groovy rename to extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/provider/DefaultGroupGrantedAuthorityResolverTest.groovy diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/SimpleError.groovy b/extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/provider/SimpleError.groovy similarity index 100% rename from extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/SimpleError.groovy rename to extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/provider/SimpleError.groovy diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/StormpathAuthenticationProviderTest.groovy b/extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/provider/StormpathAuthenticationProviderTest.groovy similarity index 100% rename from extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/StormpathAuthenticationProviderTest.groovy rename to extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/provider/StormpathAuthenticationProviderTest.groovy diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/StormpathUserDetailsTest.groovy b/extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/provider/StormpathUserDetailsTest.groovy similarity index 100% rename from extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/StormpathUserDetailsTest.groovy rename to extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/provider/StormpathUserDetailsTest.groovy diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/UsernamePasswordAuthenticationTokenFactoryTest.groovy b/extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/provider/UsernamePasswordAuthenticationTokenFactoryTest.groovy similarity index 100% rename from extensions/spring-security/src/test/groovy/com/stormpath/spring/security/provider/UsernamePasswordAuthenticationTokenFactoryTest.groovy rename to extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/provider/UsernamePasswordAuthenticationTokenFactoryTest.groovy diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/util/CollectionUtilsTest.groovy b/extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/util/CollectionUtilsTest.groovy similarity index 100% rename from extensions/spring-security/src/test/groovy/com/stormpath/spring/security/util/CollectionUtilsTest.groovy rename to extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/util/CollectionUtilsTest.groovy diff --git a/extensions/spring-security/src/test/groovy/com/stormpath/spring/security/util/StringUtilsTest.groovy b/extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/util/StringUtilsTest.groovy similarity index 100% rename from extensions/spring-security/src/test/groovy/com/stormpath/spring/security/util/StringUtilsTest.groovy rename to extensions/spring/stormpath-spring-security/src/test/groovy/com/stormpath/spring/security/util/StringUtilsTest.groovy From d64616db8e1c631791aa0a7a0e91e5b9e1e3369a Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 10 Apr 2015 16:13:01 -0300 Subject: [PATCH 5/7] Minor change --- extensions/spring/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/spring/pom.xml b/extensions/spring/pom.xml index 5898cd2211..e43829b58c 100644 --- a/extensions/spring/pom.xml +++ b/extensions/spring/pom.xml @@ -38,8 +38,8 @@ stormpath-spring - stormpath-spring-webmvc stormpath-spring-security + stormpath-spring-webmvc spring-boot-starter-stormpath spring-boot-starter-stormpath-webmvc spring-boot-starter-stormpath-thymeleaf From 92e8e4d806963b4dfb8926b5e916e69a0157e0e0 Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 10 Apr 2015 16:39:47 -0300 Subject: [PATCH 6/7] Changed parent POM for Spring Security --- extensions/spring/stormpath-spring-security/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/spring/stormpath-spring-security/pom.xml b/extensions/spring/stormpath-spring-security/pom.xml index 7f013ac73a..7e5c16194e 100644 --- a/extensions/spring/stormpath-spring-security/pom.xml +++ b/extensions/spring/stormpath-spring-security/pom.xml @@ -19,8 +19,8 @@ 4.0.0 - com.stormpath.sdk - stormpath-sdk-root + com.stormpath.spring + stormpath-spring-parent 1.0-SNAPSHOT ../../pom.xml From 32770abd82edf89774395e3e58fadf4c5f1a76be Mon Sep 17 00:00:00 2001 From: Les Hazlewood Date: Mon, 11 May 2015 15:17:38 -0700 Subject: [PATCH 7/7] #175: merged from master to reflect most recent release --- extensions/spring/stormpath-spring-security/pom.xml | 4 ++-- .../sdk/impl/application/DefaultApplicationTest.groovy | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/extensions/spring/stormpath-spring-security/pom.xml b/extensions/spring/stormpath-spring-security/pom.xml index 7e5c16194e..0a7a471ce5 100644 --- a/extensions/spring/stormpath-spring-security/pom.xml +++ b/extensions/spring/stormpath-spring-security/pom.xml @@ -21,12 +21,12 @@ com.stormpath.spring stormpath-spring-parent - 1.0-SNAPSHOT + 1.0.RC4.3-SNAPSHOT ../../pom.xml stormpath-spring-security - Stormpath Java SDK :: Spring :: Spring Security + Stormpath :: Spring :: Spring Security Stormpath Spring Security integration allows Spring Security applications to use Stormpath as the backend for all of their security needs. diff --git a/impl/src/test/groovy/com/stormpath/sdk/impl/application/DefaultApplicationTest.groovy b/impl/src/test/groovy/com/stormpath/sdk/impl/application/DefaultApplicationTest.groovy index 14a2f378c1..862717ffe6 100644 --- a/impl/src/test/groovy/com/stormpath/sdk/impl/application/DefaultApplicationTest.groovy +++ b/impl/src/test/groovy/com/stormpath/sdk/impl/application/DefaultApplicationTest.groovy @@ -36,8 +36,8 @@ import com.stormpath.sdk.impl.account.DefaultVerificationEmailRequest import com.stormpath.sdk.impl.authc.BasicLoginAttempt import com.stormpath.sdk.impl.authc.DefaultBasicLoginAttempt import com.stormpath.sdk.impl.directory.DefaultCustomData -import com.stormpath.sdk.impl.ds.DefaultDataStore import com.stormpath.sdk.impl.directory.DefaultDirectory +import com.stormpath.sdk.impl.ds.DefaultDataStore import com.stormpath.sdk.impl.ds.InternalDataStore import com.stormpath.sdk.impl.ds.JacksonMapMarshaller import com.stormpath.sdk.impl.group.DefaultGroupList @@ -48,7 +48,10 @@ import com.stormpath.sdk.impl.http.support.DefaultRequest import com.stormpath.sdk.impl.idsite.DefaultIdSiteUrlBuilder import com.stormpath.sdk.impl.provider.DefaultProviderAccountAccess import com.stormpath.sdk.impl.provider.ProviderAccountAccess -import com.stormpath.sdk.impl.resource.* +import com.stormpath.sdk.impl.resource.CollectionReference +import com.stormpath.sdk.impl.resource.ResourceReference +import com.stormpath.sdk.impl.resource.StatusProperty +import com.stormpath.sdk.impl.resource.StringProperty import com.stormpath.sdk.impl.tenant.DefaultTenant import com.stormpath.sdk.lang.Objects import com.stormpath.sdk.provider.* @@ -57,6 +60,7 @@ import com.stormpath.sdk.tenant.Tenant import org.easymock.EasyMock import org.easymock.IArgumentMatcher import org.testng.annotations.Test + import java.lang.reflect.Field import static org.easymock.EasyMock.*