Skip to content
This repository was archived by the owner on Dec 12, 2018. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
## Change Log ##

### 1.0 ##

Notes:

- The previous [Spring Security support project](https://github.com/stormpath/stormpath-spring-security) now resides in this project codebase and will have the same versions of the Java SDK moving forward.

- Please see the [1.0](https://github.com/stormpath/stormpath-sdk-java/issues?q=milestone%3A1.0+is%3Aclosed) issues list for more information.

### 1.0.RC4.2 ##

This release adds the capability for the ID Site URL to be constructed following the base url obtained from the application href.
Expand Down
1 change: 1 addition & 0 deletions extensions/spring/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

<modules>
<module>stormpath-spring</module>
<module>stormpath-spring-security</module>
<module>stormpath-spring-webmvc</module>
<module>spring-boot-starter-stormpath</module>
<module>spring-boot-starter-stormpath-webmvc</module>
Expand Down
57 changes: 57 additions & 0 deletions extensions/spring/stormpath-spring-security/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.stormpath.spring</groupId>
<artifactId>stormpath-spring-parent</artifactId>
<version>1.0.RC4.3-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

<artifactId>stormpath-spring-security</artifactId>
<name>Stormpath :: Spring :: Spring Security </name>
<description>
Stormpath Spring Security integration allows Spring Security applications to use Stormpath as
the backend for all of their security needs.
</description>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>com.stormpath.sdk</groupId>
<artifactId>stormpath-sdk-api</artifactId>
</dependency>
<dependency>
<groupId>com.stormpath.sdk</groupId>
<artifactId>stormpath-sdk-httpclient</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -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)}.
* <h3>Usage</h3>
* 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.
* <p/>
* For example:
* <pre>
* CustomData data = account.getCustomData();
* new CustomDataPermissionsEditor(customData)
* .append("user:1234:edit")
* .append("document:*")
* .remove("printer:*:print");
* data.save();
* </pre>
* Invoking this code would remove the first two permission and remove the 3rd.
* <p/>
* <b>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.</b>
*
* <h3>Field Name</h3>
* This implementation assumes a CustomData field named {@code springSecurityPermissions} to store the Set of Spring Security
* granted authorities, implying the following CustomData JSON structure:
* <pre>
* {
* ... any other of your own custom data properties ...,
*
* "springSecurityPermissions": [
* "perm1",
* "perm2",
* ...,
* "permN"j
* ]
* }
* </pre>
* 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:
* <pre>
* {
* ... any other of your own custom data properties ...,
*
* "myApplicationPermissions": [
* "perm1",
* "perm2",
* ...,
* "permN"j
* ]
* }
* </pre>
*
* @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&lt;String&gt;}
* of permissions. The default name is
* {@code springSecurityPermissions}, implying a {@code CustomData} JSON representation as follows:
* <pre>
* {
* ... any other of your own custom data properties ...,
*
* "springSecurityPermissions": [
* "perm1",
* "perm2",
* ...,
* "permN"j
* ]
* }
* </pre>
* 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&lt;String&gt;}
* 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&lt;String&gt;}
* of permissions. The default name is {@code springSecurityPermissions}, implying a {@code CustomData} JSON
* representation as follows:
* <pre>
* {
* ... any other of your own custom data properties ...,
*
* "springSecurityPermissions": [
* "perm1",
* "perm2",
* ...,
* "permN"j
* ]
* }
* </pre>
* If you changed this name to be {@code myApplicationPermissions} for example, the CustomData representation
* would look something like this instead:
* <pre>
* {
* ... any other of your own custom data properties ...,
*
* "myApplicationPermissions": [
* "perm1",
* "perm2",
* ...,
* "permN"j
* ]
* }
* </pre>
* <h3>Usage Warning</h3>
* 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.
* <p/>
* For example, when using Spring xml configuration:
* <pre>
* <bean id="groupPermissionResolver" class="com.stormpath.spring.security.provider.GroupCustomDataPermissionResolver">
* <property name="customDataFieldName" value="myApplicationPermissions" />
* </bean>
*
* <bean id="accountPermissionResolver" class="com.stormpath.spring.security.provider.AccountCustomDataPermissionResolver">
* <property name="customDataFieldName" value="myApplicationPermissions" />
* </bean>
*
* <bean id="authenticationProvider" class="com.stormpath.spring.security.provider.StormpathAuthenticationProvider">
* ...
* <property name="groupPermissionResolver" ref="groupPermissionResolver" />
* <property name="accountPermissionResolver" ref="accountPermissionResolver" />
* </bean>
* </pre>
*
* @param fieldName the name of the {@link com.stormpath.sdk.directory.CustomData} field used to store the {@code Set&lt;String&gt;}
* 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<String> permissions = lookupPermissionStrings();

String fieldName = getFieldName();

if (permissions == null) {
permissions = new LinkedHashSet<String>();
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<String> 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<String> getPermissionStrings() {

Collection<String> perms = lookupPermissionStrings();

if (CollectionUtils.isEmpty(perms)) {
return Collections.emptySet();
}

Set<String> set;

if (perms instanceof List) {
set = asSet(getFieldName(), (List) perms);
} else {
assert perms instanceof Set : "perms instance must be a Set<String>";
set = (Set<String>) perms;
}

return Collections.unmodifiableSet(set);
}

private static Set<String> asSet(String fieldName, List list) {
Set<String> set = new LinkedHashSet<String>(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<String> lookupPermissionStrings() {

final String fieldName = getFieldName();

Object value = CUSTOM_DATA.get(fieldName);

if (value == null) {
return null;
}

if (value instanceof Set) {
return (Set<String>) 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<String> or List<String>.";
throw new IllegalArgumentException(msg);
}

return permList;
}

}
Loading