Add ACL Document Management System XML sample

Closes gh-34
This commit is contained in:
Marcus Da Coregio 2021-09-03 15:56:11 -03:00
parent 4c00a8fb4e
commit 0bf72c4580
22 changed files with 1665 additions and 0 deletions

View File

@ -0,0 +1,41 @@
plugins {
id "java"
id "war"
}
repositories {
jcenter()
maven { url "https://repo.spring.io/snapshot" }
}
dependencies {
implementation platform("org.springframework.security:spring-security-bom:5.6.0-SNAPSHOT")
implementation platform("org.springframework:spring-framework-bom:5.3.9")
implementation platform("org.junit:junit-bom:5.7.0")
implementation 'org.springframework:spring-beans'
implementation 'org.springframework:spring-jdbc'
implementation 'org.springframework:spring-tx'
implementation "org.springframework.security:spring-security-acl"
implementation "org.springframework.security:spring-security-core"
implementation "org.springframework.security:spring-security-config"
implementation "org.thymeleaf:thymeleaf-spring5:3.0.11.RELEASE"
implementation 'javax.servlet:jstl:1.2'
implementation 'org.slf4j:slf4j-api:1.7.30'
implementation 'org.slf4j:slf4j-simple:1.7.30'
runtime 'net.sf.ehcache:ehcache:2.10.5'
runtime 'org.hsqldb:hsqldb:2.5.0'
runtime 'org.springframework:spring-context-support'
testImplementation "org.springframework:spring-test"
testImplementation "org.springframework.security:spring-security-test"
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation "org.assertj:assertj-core:3.18.0"
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}
tasks.withType(Test).configureEach {
useJUnitPlatform()
}

View File

@ -0,0 +1 @@
version=5.6.0-SNAPSHOT

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
servlet/xml/java/dms/gradlew vendored Executable file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# 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
#
# https://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.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

104
servlet/xml/java/dms/gradlew.bat vendored Normal file
View File

@ -0,0 +1,104 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,103 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* 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
*
* https://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 sample.dms;
import java.util.ArrayList;
import java.util.List;
import org.springframework.util.Assert;
/**
* Base implementation for a element.
*
* @author Ben Alex
*/
public abstract class AbstractElement {
/** The name of this token (a filename or directory segment name). */
private final String name;
/** The parent of this token (a directory, or null if referring to root). */
private final AbstractElement parent;
/** The database identifier for this object (null if not persisted). */
private Long id;
/**
* Constructor to use to represent a root element. A root element has an id of -1.
*/
protected AbstractElement() {
this.name = "/";
this.parent = null;
this.id = -1L;
}
/**
* Constructor to use to represent a non-root element.
* @param name name for this element (required, cannot be "/")
* @param parent for this element (required, cannot be null)
*/
protected AbstractElement(String name, AbstractElement parent) {
Assert.hasText(name, "Name required");
Assert.notNull(parent, "Parent required");
Assert.notNull(parent.getId(), "The parent must have been saved in order to create a child");
this.name = name;
this.parent = parent;
}
public Long getId() {
return this.id;
}
/**
* Gets the name of this element.
* @return the name of this token (never null, although will be "/" if root, otherwise
* it won't include separators)
*/
public String getName() {
return this.name;
}
public AbstractElement getParent() {
return this.parent;
}
/**
* Gets the fully-qualified name of this element, including any parents.
* @return the fully-qualified name of this element, including any parents
*/
public String getFullName() {
List<String> strings = new ArrayList<>();
AbstractElement currentElement = this;
while (currentElement != null) {
strings.add(0, currentElement.getName());
currentElement = currentElement.getParent();
}
StringBuilder sb = new StringBuilder();
String lastCharacter = null;
for (String token : strings) {
if (!"/".equals(lastCharacter) && lastCharacter != null) {
sb.append("/");
}
sb.append(token);
lastCharacter = token.substring(token.length() - 1);
}
return sb.toString();
}
}

View File

@ -0,0 +1,186 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* 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
*
* https://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 sample.dms;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;
/**
* Populates the DMS in-memory database with document and ACL information.
*
* @author Ben Alex
*/
public class DataSourcePopulator implements InitializingBean {
protected static final int LEVEL_NEGATE_READ = 0;
protected static final int LEVEL_GRANT_READ = 1;
protected static final int LEVEL_GRANT_WRITE = 2;
protected static final int LEVEL_GRANT_ADMIN = 3;
protected JdbcTemplate template;
protected DocumentDao documentDao;
public DataSourcePopulator(DataSource dataSource, DocumentDao documentDao) {
Assert.notNull(dataSource, "DataSource required");
Assert.notNull(documentDao, "DocumentDao required");
this.template = new JdbcTemplate(dataSource);
this.documentDao = documentDao;
}
public void afterPropertiesSet() {
// ACL tables
this.template.execute(
"CREATE TABLE ACL_SID(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,PRINCIPAL BOOLEAN NOT NULL,SID VARCHAR_IGNORECASE(100) NOT NULL,CONSTRAINT UNIQUE_UK_1 UNIQUE(SID,PRINCIPAL));");
this.template.execute(
"CREATE TABLE ACL_CLASS(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,CLASS VARCHAR_IGNORECASE(100) NOT NULL,CLASS_ID_TYPE VARCHAR_IGNORECASE(100),CONSTRAINT UNIQUE_UK_2 UNIQUE(CLASS));");
this.template.execute(
"CREATE TABLE ACL_OBJECT_IDENTITY(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,OBJECT_ID_CLASS BIGINT NOT NULL,OBJECT_ID_IDENTITY VARCHAR_IGNORECASE(36) NOT NULL,PARENT_OBJECT BIGINT,OWNER_SID BIGINT,ENTRIES_INHERITING BOOLEAN NOT NULL,CONSTRAINT UNIQUE_UK_3 UNIQUE(OBJECT_ID_CLASS,OBJECT_ID_IDENTITY),CONSTRAINT FOREIGN_FK_1 FOREIGN KEY(PARENT_OBJECT)REFERENCES ACL_OBJECT_IDENTITY(ID),CONSTRAINT FOREIGN_FK_2 FOREIGN KEY(OBJECT_ID_CLASS)REFERENCES ACL_CLASS(ID),CONSTRAINT FOREIGN_FK_3 FOREIGN KEY(OWNER_SID)REFERENCES ACL_SID(ID));");
this.template.execute(
"CREATE TABLE ACL_ENTRY(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY,ACL_OBJECT_IDENTITY BIGINT NOT NULL,ACE_ORDER INT NOT NULL,SID BIGINT NOT NULL,MASK INTEGER NOT NULL,GRANTING BOOLEAN NOT NULL,AUDIT_SUCCESS BOOLEAN NOT NULL,AUDIT_FAILURE BOOLEAN NOT NULL,CONSTRAINT UNIQUE_UK_4 UNIQUE(ACL_OBJECT_IDENTITY,ACE_ORDER),CONSTRAINT FOREIGN_FK_4 FOREIGN KEY(ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY(ID),CONSTRAINT FOREIGN_FK_5 FOREIGN KEY(SID) REFERENCES ACL_SID(ID));");
// Normal authentication tables
this.template.execute(
"CREATE TABLE USERS(USERNAME VARCHAR_IGNORECASE(50) NOT NULL PRIMARY KEY,PASSWORD VARCHAR_IGNORECASE(500) NOT NULL,ENABLED BOOLEAN NOT NULL);");
this.template.execute(
"CREATE TABLE AUTHORITIES(USERNAME VARCHAR_IGNORECASE(50) NOT NULL,AUTHORITY VARCHAR_IGNORECASE(50) NOT NULL,CONSTRAINT FK_AUTHORITIES_USERS FOREIGN KEY(USERNAME) REFERENCES USERS(USERNAME));");
this.template.execute("CREATE UNIQUE INDEX IX_AUTH_USERNAME ON AUTHORITIES(USERNAME,AUTHORITY);");
// Document management system business tables
this.template.execute(
"CREATE TABLE DIRECTORY(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY, DIRECTORY_NAME VARCHAR_IGNORECASE(50) NOT NULL, PARENT_DIRECTORY_ID BIGINT)");
this.template.execute(
"CREATE TABLE FILE(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 100) NOT NULL PRIMARY KEY, FILE_NAME VARCHAR_IGNORECASE(50) NOT NULL, CONTENT VARCHAR_IGNORECASE(1024), PARENT_DIRECTORY_ID BIGINT)");
// Populate the authentication and role tables
this.template.execute(
"INSERT INTO USERS VALUES('rod','$2a$10$75pBjapg4Nl8Pzd.3JRnUe7PDJmk9qBGwNEJDAlA3V.dEJxcDKn5O',TRUE);");
this.template.execute(
"INSERT INTO USERS VALUES('dianne','$2a$04$bCMEyxrdF/7sgfUiUJ6Ose2vh9DAMaVBldS1Bw2fhi1jgutZrr9zm',TRUE);");
this.template.execute(
"INSERT INTO USERS VALUES('scott','$2a$06$eChwvzAu3TSexnC3ynw4LOSw1qiEbtNItNeYv5uI40w1i3paoSfLu',TRUE);");
this.template.execute(
"INSERT INTO USERS VALUES('peter','$2a$04$8.H8bCMROLF4CIgd7IpeQ.tcBXLP5w8iplO0n.kCIkISwrIgX28Ii',FALSE);");
this.template.execute(
"INSERT INTO USERS VALUES('bill','$2a$04$8.H8bCMROLF4CIgd7IpeQ.3khQlPVNWbp8kzSQqidQHGFurim7P8O',TRUE);");
this.template.execute(
"INSERT INTO USERS VALUES('bob','$2a$06$zMgxlMf01SfYNcdx7n4NpeFlAGU8apCETz/i2C7VlYWu6IcNyn4Ay',TRUE);");
this.template.execute(
"INSERT INTO USERS VALUES('jane','$2a$05$ZrdS7yMhCZ1J.AAidXZhCOxdjD8LO/dhlv4FJzkXA6xh9gdEbBT/u',TRUE);");
this.template.execute("INSERT INTO AUTHORITIES VALUES('rod','ROLE_USER');");
this.template.execute("INSERT INTO AUTHORITIES VALUES('rod','ROLE_SUPERVISOR');");
this.template.execute("INSERT INTO AUTHORITIES VALUES('dianne','ROLE_USER');");
this.template.execute("INSERT INTO AUTHORITIES VALUES('scott','ROLE_USER');");
this.template.execute("INSERT INTO AUTHORITIES VALUES('peter','ROLE_USER');");
this.template.execute("INSERT INTO AUTHORITIES VALUES('bill','ROLE_USER');");
this.template.execute("INSERT INTO AUTHORITIES VALUES('bob','ROLE_USER');");
this.template.execute("INSERT INTO AUTHORITIES VALUES('jane','ROLE_USER');");
// Now create an ACL entry for the root directory
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("rod", "ignored",
AuthorityUtils.createAuthorityList(("ROLE_IGNORED"))));
addPermission(this.documentDao, Directory.ROOT_DIRECTORY, "ROLE_USER", LEVEL_GRANT_WRITE);
// Now go off and create some directories and files for our users
createSampleData("rod", "koala");
createSampleData("dianne", "emu");
createSampleData("scott", "wombat");
}
/**
* Creates a directory for the user, and a series of sub-directories. The root
* directory is the parent for the user directory. The sub-directories are
* "confidential" and "shared". The ROLE_USER will be given read and write access to
* "shared".
* @param username the user's username
* @param password the user's password
*/
private void createSampleData(String username, String password) {
Assert.notNull(this.documentDao, "DocumentDao required");
Assert.hasText(username, "Username required");
Authentication auth = new UsernamePasswordAuthenticationToken(username, password);
try {
// Set the SecurityContextHolder ThreadLocal so any subclasses
// automatically know which user is operating
SecurityContextHolder.getContext().setAuthentication(auth);
// Create the home directory first
Directory home = new Directory(username, Directory.ROOT_DIRECTORY);
this.documentDao.create(home);
addPermission(this.documentDao, home, username, LEVEL_GRANT_ADMIN);
addPermission(this.documentDao, home, "ROLE_USER", LEVEL_GRANT_READ);
createFiles(this.documentDao, home);
// Now create the confidential directory
Directory confid = new Directory("confidential", home);
this.documentDao.create(confid);
addPermission(this.documentDao, confid, "ROLE_USER", LEVEL_NEGATE_READ);
createFiles(this.documentDao, confid);
// Now create the shared directory
Directory shared = new Directory("shared", home);
this.documentDao.create(shared);
addPermission(this.documentDao, shared, "ROLE_USER", LEVEL_GRANT_READ);
addPermission(this.documentDao, shared, "ROLE_USER", LEVEL_GRANT_WRITE);
createFiles(this.documentDao, shared);
}
finally {
// Clear the SecurityContextHolder ThreadLocal so future calls are
// guaranteed to be clean
SecurityContextHolder.clearContext();
}
}
private void createFiles(DocumentDao documentDao, Directory parent) {
Assert.notNull(documentDao, "DocumentDao required");
Assert.notNull(parent, "Parent required");
int countBeforeInsert = documentDao.findElements(parent).length;
for (int i = 0; i < 10; i++) {
File file = new File("file_" + i + ".txt", parent);
documentDao.create(file);
}
Assert.isTrue(countBeforeInsert + 10 == documentDao.findElements(parent).length,
"Failed to increase count by 10");
}
/**
* Allows subclass to add permissions.
* @param documentDao that will presumably offer methods to enable the operation to be
* completed
* @param element to the subject of the new permissions
* @param recipient to receive permission (if it starts with ROLE_ it is assumed to be
* a GrantedAuthority, else it is a username)
* @param level based on the static final integer fields on this class
*/
protected void addPermission(DocumentDao documentDao, AbstractElement element, String recipient, int level) {
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* 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
*
* https://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 sample.dms;
/**
* Represents a directory.
*
* @author Ben Alex
*/
public class Directory extends AbstractElement {
/** The root directory. */
public static final Directory ROOT_DIRECTORY = new Directory();
private Directory() {
}
public Directory(String name, Directory parent) {
super(name, parent);
}
@Override
public String toString() {
return "Directory[fullName='" + getFullName() + "'; name='" + getName() + "'; id='" + getId() + "'; parent='"
+ getParent() + "']";
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* 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
*
* https://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 sample.dms;
/**
* Interface to define Document data access operations.
*
* @author Ben Alex
*/
public interface DocumentDao {
/**
* Creates an entry in the database for the element.
* @param element an unsaved element (the "id" will be updated after method is
* invoked)
*/
void create(AbstractElement element);
/**
* Removes a file from the database for the specified element.
* @param file the file to remove (cannot be null)
*/
void delete(File file);
/**
* Modifies a file in the database.
* @param file the file to update (cannot be null)
*/
void update(File file);
/**
* Locates elements in the database which appear under the presented directory.
* @param directory the directory (cannot be null - use
* {@link Directory#ROOT_DIRECTORY} for root)
* @return zero or more elements in the directory (an empty array may be returned -
* never null)
*/
AbstractElement[] findElements(Directory directory);
}

View File

@ -0,0 +1,128 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* 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
*
* https://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 sample.dms;
import java.util.List;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.security.util.FieldUtils;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
/**
* Basic JDBC implementation of {@link DocumentDao}.
*
* @author Ben Alex
*/
public class DocumentDaoImpl extends JdbcDaoSupport implements DocumentDao {
private static final String INSERT_INTO_DIRECTORY = "insert into directory(directory_name, parent_directory_id) values (?,?)";
private static final String INSERT_INTO_FILE = "insert into file(file_name, content, parent_directory_id) values (?,?,?)";
private static final String SELECT_FROM_DIRECTORY = "select id from directory where parent_directory_id = ?";
private static final String SELECT_FROM_DIRECTORY_NULL = "select id from directory where parent_directory_id is null";
private static final String SELECT_FROM_FILE = "select id, file_name, content, parent_directory_id from file where parent_directory_id = ?";
private static final String SELECT_FROM_DIRECTORY_SINGLE = "select id, directory_name, parent_directory_id from directory where id = ?";
private static final String DELETE_FROM_FILE = "delete from file where id = ?";
private static final String UPDATE_FILE = "update file set content = ? where id = ?";
private static final String SELECT_IDENTITY = "call identity()";
private Long obtainPrimaryKey() {
Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(), "Transaction must be running");
return getJdbcTemplate().queryForObject(SELECT_IDENTITY, Long.class);
}
public void create(AbstractElement element) {
Assert.notNull(element, "Element required");
Assert.isNull(element.getId(), "Element has previously been saved");
if (element instanceof Directory) {
Directory directory = (Directory) element;
Long parentId = (directory.getParent() == null) ? null : directory.getParent().getId();
getJdbcTemplate().update(INSERT_INTO_DIRECTORY, new Object[] { directory.getName(), parentId });
FieldUtils.setProtectedFieldValue("id", directory, obtainPrimaryKey());
}
else if (element instanceof File) {
File file = (File) element;
Long parentId = (file.getParent() == null) ? null : file.getParent().getId();
getJdbcTemplate().update(INSERT_INTO_FILE, new Object[] { file.getName(), file.getContent(), parentId });
FieldUtils.setProtectedFieldValue("id", file, obtainPrimaryKey());
}
else {
throw new IllegalArgumentException("Unsupported AbstractElement");
}
}
public void delete(File file) {
Assert.notNull(file, "File required");
Assert.notNull(file.getId(), "File ID required");
getJdbcTemplate().update(DELETE_FROM_FILE, new Object[] { file.getId() });
}
private Directory getDirectoryWithImmediateParentPopulated(final Long id) {
return getJdbcTemplate().queryForObject(SELECT_FROM_DIRECTORY_SINGLE, new Object[] { id }, (rs, rowNumber) -> {
Long parentDirectoryId = rs.getLong("parent_directory_id");
Directory parentDirectory = Directory.ROOT_DIRECTORY;
if (parentDirectoryId != null && !parentDirectoryId.equals(-1L)) {
// Need to go and lookup the parent, so do that first
parentDirectory = getDirectoryWithImmediateParentPopulated(parentDirectoryId);
}
Directory directory = new Directory(rs.getString("directory_name"), parentDirectory);
FieldUtils.setProtectedFieldValue("id", directory, rs.getLong("id"));
return directory;
});
}
public AbstractElement[] findElements(Directory directory) {
Assert.notNull(directory, "Directory required (the ID can be null to refer to root)");
if (directory.getId() == null) {
List<Directory> directories = getJdbcTemplate().query(SELECT_FROM_DIRECTORY_NULL,
(rs, rowNumber) -> getDirectoryWithImmediateParentPopulated(rs.getLong("id")));
return directories.toArray(new AbstractElement[] {});
}
List<AbstractElement> directories = getJdbcTemplate().query(SELECT_FROM_DIRECTORY,
new Object[] { directory.getId() },
(rs, rowNumber) -> getDirectoryWithImmediateParentPopulated(rs.getLong("id")));
List<File> files = getJdbcTemplate().query(SELECT_FROM_FILE, new Object[] { directory.getId() },
(rs, rowNumber) -> {
Long parentDirectoryId = rs.getLong("parent_directory_id");
Directory parentDirectory = null;
if (parentDirectoryId != null) {
parentDirectory = getDirectoryWithImmediateParentPopulated(parentDirectoryId);
}
File file = new File(rs.getString("file_name"), parentDirectory);
FieldUtils.setProtectedFieldValue("id", file, rs.getLong("id"));
return file;
});
// Add the File elements after the Directory elements
directories.addAll(files);
return directories.toArray(new AbstractElement[] {});
}
public void update(File file) {
Assert.notNull(file, "File required");
Assert.notNull(file.getId(), "File ID required");
getJdbcTemplate().update(UPDATE_FILE, new Object[] { file.getContent(), file.getId() });
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* 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
*
* https://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 sample.dms;
import org.springframework.util.Assert;
/**
* Represents a File.
*
* @author Ben Alex
*/
public class File extends AbstractElement {
/** Content of the file, which can be null. */
private String content;
public File(String name, Directory parent) {
super(name, parent);
Assert.isTrue(!parent.equals(Directory.ROOT_DIRECTORY), "Cannot insert File into root directory");
}
public String getContent() {
return this.content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "File[fullName='" + getFullName() + "'; name='" + getName() + "'; id='" + getId() + "'; content="
+ getContent() + "'; parent='" + getParent() + "']";
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* 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
*
* https://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 sample.dms.secured;
import javax.sql.DataSource;
import sample.dms.AbstractElement;
import sample.dms.DataSourcePopulator;
import sample.dms.DocumentDao;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.GrantedAuthoritySid;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.acls.model.NotFoundException;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.acls.model.Sid;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;
public class SecureDataSourcePopulator extends DataSourcePopulator {
private final MutableAclService aclService;
public SecureDataSourcePopulator(DataSource dataSource, SecureDocumentDao documentDao,
MutableAclService aclService) {
super(dataSource, documentDao);
Assert.notNull(aclService, "MutableAclService required");
this.aclService = aclService;
}
protected void addPermission(DocumentDao documentDao, AbstractElement element, String recipient, int level) {
Assert.notNull(documentDao, "DocumentDao required");
Assert.isInstanceOf(SecureDocumentDao.class, documentDao, "DocumentDao should have been a SecureDocumentDao");
Assert.notNull(element, "Element required");
Assert.hasText(recipient, "Recipient required");
Assert.notNull(SecurityContextHolder.getContext().getAuthentication(),
"SecurityContextHolder must contain an Authentication");
// We need SecureDocumentDao to assign different permissions
// SecureDocumentDao dao = (SecureDocumentDao) documentDao;
// We need to construct an ACL-specific Sid. Note the prefix contract is defined
// on the superclass method's JavaDocs
Sid sid = null;
if (recipient.startsWith("ROLE_")) {
sid = new GrantedAuthoritySid(recipient);
}
else {
sid = new PrincipalSid(recipient);
}
// We need to identify the target domain object and create an ObjectIdentity for
// it
// This works because AbstractElement has a "getId()" method
ObjectIdentity identity = new ObjectIdentityImpl(element);
// ObjectIdentity identity = new ObjectIdentityImpl(element.getClass(),
// element.getId()); // equivalent
// Next we need to create a Permission
Permission permission = null;
if (level == LEVEL_NEGATE_READ || level == LEVEL_GRANT_READ) {
permission = BasePermission.READ;
}
else if (level == LEVEL_GRANT_WRITE) {
permission = BasePermission.WRITE;
}
else if (level == LEVEL_GRANT_ADMIN) {
permission = BasePermission.ADMINISTRATION;
}
else {
throw new IllegalArgumentException("Unsupported LEVEL_");
}
// Attempt to retrieve the existing ACL, creating an ACL if it doesn't already
// exist for this ObjectIdentity
MutableAcl acl = null;
try {
acl = (MutableAcl) this.aclService.readAclById(identity);
}
catch (NotFoundException nfe) {
acl = this.aclService.createAcl(identity);
Assert.notNull(acl, "Acl could not be retrieved or created");
}
// Now we have an ACL, add another ACE to it
// granting
// granting
acl.insertAce(acl.getEntries().size(), permission, sid, level != LEVEL_NEGATE_READ); // not
// Finally, persist the modified ACL
this.aclService.updateAcl(acl);
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* 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
*
* https://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 sample.dms.secured;
import sample.dms.DocumentDao;
/**
* Extends the {@link DocumentDao} and introduces ACL-related methods.
*
* @author Ben Alex
*
*/
public interface SecureDocumentDao extends DocumentDao {
/**
* Gets all the users existing in the system.
* @return all the usernames existing in the system.
*/
String[] getUsers();
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* 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
*
* https://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 sample.dms.secured;
import sample.dms.AbstractElement;
import sample.dms.DocumentDaoImpl;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;
/**
* Adds extra {@link SecureDocumentDao} methods.
*
* @author Ben Alex
*
*/
public class SecureDocumentDaoImpl extends DocumentDaoImpl implements SecureDocumentDao {
private static final String SELECT_FROM_USERS = "SELECT USERNAME FROM USERS ORDER BY USERNAME";
private final MutableAclService mutableAclService;
public SecureDocumentDaoImpl(MutableAclService mutableAclService) {
Assert.notNull(mutableAclService, "MutableAclService required");
this.mutableAclService = mutableAclService;
}
public String[] getUsers() {
return getJdbcTemplate().query(SELECT_FROM_USERS, (rs, rowNumber) -> rs.getString("USERNAME"))
.toArray(new String[] {});
}
public void create(AbstractElement element) {
super.create(element);
// Create an ACL identity for this element
ObjectIdentity identity = new ObjectIdentityImpl(element);
MutableAcl acl = this.mutableAclService.createAcl(identity);
// If the AbstractElement has a parent, go and retrieve its identity (it should
// already exist)
if (element.getParent() != null) {
ObjectIdentity parentIdentity = new ObjectIdentityImpl(element.getParent());
MutableAcl aclParent = (MutableAcl) this.mutableAclService.readAclById(parentIdentity);
acl.setParent(aclParent);
}
acl.insertAce(acl.getEntries().size(), BasePermission.ADMINISTRATION,
new PrincipalSid(SecurityContextHolder.getContext().getAuthentication()), true);
this.mutableAclService.updateAcl(acl);
}
}

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Application context representing the application without any security services.
-
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:mem:insecuredms"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionAttributeSource">
<value>
sample.dms.DocumentDao.*=PROPAGATION_REQUIRED
</value>
</property>
<property name="transactionManager" ref="transactionManager" />
</bean>
<bean id="documentDao" class="sample.dms.DocumentDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSourcePopulator" class="sample.dms.DataSourcePopulator">
<constructor-arg ref="dataSource"/>
<constructor-arg ref="documentDao"/>
</bean>
</beans>

View File

@ -0,0 +1,245 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Application context representing the application WITH security services.
-
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:s="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd">
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:mem:securedms"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionAttributeSource">
<value>
sample.dms.secured.SecureDocumentDao.*=PROPAGATION_REQUIRED
sample.dms.DocumentDao.*=PROPAGATION_REQUIRED
org.springframework.security.acls.model.AclService.*=PROPAGATION_REQUIRED
org.springframework.security.acls.model.MutableAclService.*=PROPAGATION_REQUIRED
org.springframework.security.acls.jdbc.JdbcMutableAclService.*=PROPAGATION_REQUIRED
org.springframework.security.acls.jdbc.JdbcAclService.*=PROPAGATION_REQUIRED
</value>
</property>
<property name="transactionManager" ref="transactionManager" />
</bean>
<bean id="documentDao" class="sample.dms.secured.SecureDocumentDaoImpl">
<constructor-arg ref="aclService"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSourcePopulator" class="sample.dms.secured.SecureDataSourcePopulator">
<constructor-arg ref="dataSource"/>
<constructor-arg ref="documentDao"/>
<constructor-arg ref="aclService"/>
</bean>
<!-- =================================== SECURITY DEFINITION BEANS ======================================== -->
<!-- ======================== AUTHENTICATION (note there is no UI and this is for integration tests only) ======================= -->
<s:authentication-manager alias="authenticationManager">
<s:authentication-provider ref="daoAuthenticationProvider"/>
</s:authentication-manager>
<bean id="jdbcDaoImpl" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="jdbcDaoImpl"/>
<property name="userCache" ref="userCache"/>
<property name="passwordEncoder">
<bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
</property>
</bean>
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
<bean id="userCacheBackend" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager" ref="cacheManager"/>
<property name="cacheName" value="userCache"/>
</bean>
<bean id="userCache" class="org.springframework.security.core.userdetails.cache.EhCacheBasedUserCache">
<property name="cache" ref="userCacheBackend"/>
</bean>
<!-- Automatically receives AuthenticationEvent messages -->
<bean id="loggerListener" class="org.springframework.security.authentication.event.LoggerListener"/>
<!-- ========================= "BEFORE INVOCATION" AUTHORIZATION DEFINITIONS ============================== -->
<!-- ACL permission masks used by this application -->
<bean id="org.springframework.security.acls.domain.BasePermission.ADMINISTRATION" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<property name="staticField" value="org.springframework.security.acls.domain.BasePermission.ADMINISTRATION"/>
</bean>
<bean id="org.springframework.security.acls.domain.BasePermission.READ" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<property name="staticField" value="org.springframework.security.acls.domain.BasePermission.READ"/>
</bean>
<bean id="org.springframework.security.acls.domain.BasePermission.WRITE" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<property name="staticField" value="org.springframework.security.acls.domain.BasePermission.WRITE"/>
</bean>
<!-- An access decision voter that reads ROLE_* configuration settings -->
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter"/>
<!-- An access decision voter that reads ACL_ABSTRACT_ELEMENT_WRITE_PARENT configuration settings -->
<bean id="aclAbstractElementWriteParentVoter" class="org.springframework.security.acls.AclEntryVoter">
<constructor-arg ref="aclService"/>
<constructor-arg value="ACL_ABSTRACT_ELEMENT_WRITE_PARENT"/>
<constructor-arg>
<list>
<ref bean="org.springframework.security.acls.domain.BasePermission.ADMINISTRATION"/>
<ref bean="org.springframework.security.acls.domain.BasePermission.WRITE"/>
</list>
</constructor-arg>
<property name="processDomainObjectClass" value="sample.dms.AbstractElement"/>
<property name="internalMethod" value="getParent"/>
</bean>
<!-- An access decision voter that reads ACL_ABSTRACT_ELEMENT_WRITE configuration settings -->
<bean id="aclAbstractElementWriteVoter" class="org.springframework.security.acls.AclEntryVoter">
<constructor-arg ref="aclService"/>
<constructor-arg value="ACL_ABSTRACT_ELEMENT_WRITE"/>
<constructor-arg>
<list>
<ref bean="org.springframework.security.acls.domain.BasePermission.ADMINISTRATION"/>
<ref bean="org.springframework.security.acls.domain.BasePermission.WRITE"/>
</list>
</constructor-arg>
<property name="processDomainObjectClass" value="sample.dms.AbstractElement"/>
</bean>
<!-- An access decision manager used by the business objects -->
<bean id="businessAccessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<constructor-arg>
<list>
<ref bean="roleVoter"/>
<ref bean="aclAbstractElementWriteParentVoter"/>
<ref bean="aclAbstractElementWriteVoter"/>
</list>
</constructor-arg>
<property name="allowIfAllAbstainDecisions" value="true"/>
</bean>
<!-- ========= ACCESS CONTROL LIST LOOKUP MANAGER DEFINITIONS ========= -->
<bean id="aclCache" class="org.springframework.security.acls.domain.EhCacheBasedAclCache">
<constructor-arg>
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager" ref="cacheManager"/>
<property name="cacheName" value="aclCache"/>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy">
<constructor-arg>
<bean class="org.springframework.security.acls.domain.ConsoleAuditLogger"/>
</constructor-arg>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
<constructor-arg>
<list>
<bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="ROLE_ACL_ADMIN"/>
</bean>
</list>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
<bean id="lookupStrategy" class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
<constructor-arg ref="dataSource"/>
<constructor-arg ref="aclCache"/>
<constructor-arg ref="aclAuthorizationStrategy"/>
<constructor-arg>
<bean class="org.springframework.security.acls.domain.ConsoleAuditLogger"/>
</constructor-arg>
</bean>
<bean id="aclAuthorizationStrategy" class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
<constructor-arg>
<list>
<bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="ROLE_ADMINISTRATOR"/>
</bean>
<bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="ROLE_ADMINISTRATOR"/>
</bean>
<bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="ROLE_ADMINISTRATOR"/>
</bean>
</list>
</constructor-arg>
</bean>
<bean id="aclService" class="org.springframework.security.acls.jdbc.JdbcMutableAclService">
<constructor-arg ref="dataSource"/>
<constructor-arg ref="lookupStrategy"/>
<constructor-arg ref="aclCache"/>
</bean>
<!-- ============== "AFTER INTERCEPTION" AUTHORIZATION DEFINITIONS =========== -->
<bean id="afterInvocationManager" class="org.springframework.security.access.intercept.AfterInvocationProviderManager">
<property name="providers">
<list>
<ref bean="afterAclCollectionRead"/>
</list>
</property>
</bean>
<!-- Processes AFTER_ACL_COLLECTION_READ configuration settings -->
<bean id="afterAclCollectionRead" class="org.springframework.security.acls.afterinvocation.AclEntryAfterInvocationCollectionFilteringProvider">
<constructor-arg ref="aclService"/>
<constructor-arg>
<list>
<ref bean="org.springframework.security.acls.domain.BasePermission.ADMINISTRATION"/>
<ref bean="org.springframework.security.acls.domain.BasePermission.READ"/>
</list>
</constructor-arg>
</bean>
<!-- ================= METHOD INVOCATION AUTHORIZATION ==================== -->
<bean id="methodSecurityAdvisor" class="org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor">
<constructor-arg value="methodSecurityInterceptor" />
<constructor-arg ref="msmds" />
<constructor-arg value="msmds" />
</bean>
<bean id="methodSecurityInterceptor" class="org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="businessAccessDecisionManager"/>
<property name="afterInvocationManager" ref="afterInvocationManager"/>
<property name="securityMetadataSource" ref="msmds" />
</bean>
<s:method-security-metadata-source id="msmds">
<s:protect method="sample.dms.DocumentDao.create" access="ACL_ABSTRACT_ELEMENT_WRITE_PARENT" />
<s:protect method="sample.dms.DocumentDao.delete" access="ACL_ABSTRACT_ELEMENT_WRITE" />
<s:protect method="sample.dms.DocumentDao.update" access="ACL_ABSTRACT_ELEMENT_WRITE" />
<s:protect method="sample.dms.DocumentDao.findElements" access="AFTER_ACL_COLLECTION_READ" />
<s:protect method="sample.dms.secured.SecureDocumentDao.getUsers" access="ROLE_USER" />
</s:method-security-metadata-source>
</beans>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Application context representing the transaction, auto proxy and data source beans.
-
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="autoproxy" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
<bean id="transactionAdvisor" class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor" autowire="constructor" />
</beans>

View File

@ -0,0 +1,156 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* 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
*
* https://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 sample;
/*
* Copyright 2002-2016 the original author or authors.
*
* 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
*
* https://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.
*/
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.dms.AbstractElement;
import sample.dms.Directory;
import sample.dms.DocumentDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.annotation.Transactional;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Basic integration test for DMS sample.
*
* @author Ben Alex
*
*/
@ContextConfiguration(
locations = { "classpath:applicationContext-dms-shared.xml", "classpath:applicationContext-dms-insecure.xml" })
@ExtendWith(SpringExtension.class)
@Transactional
public class DmsIntegrationTests {
@Autowired
protected JdbcTemplate jdbcTemplate;
@Autowired
protected DocumentDao documentDao;
@AfterEach
void clearContext() {
SecurityContextHolder.clearContext();
}
public void setDocumentDao(DocumentDao documentDao) {
this.documentDao = documentDao;
}
@Test
void testBasePopulation() {
assertThat(this.jdbcTemplate.queryForObject("select count(id) from DIRECTORY", Integer.class)).isEqualTo(9);
assertThat((int) this.jdbcTemplate.queryForObject("select count(id) from FILE", Integer.class)).isEqualTo(90);
assertThat(this.documentDao.findElements(Directory.ROOT_DIRECTORY).length).isEqualTo(3);
}
@Test
void testMarissaRetrieval() {
process("rod", "koala", false);
}
@Test
void testScottRetrieval() {
process("scott", "wombat", false);
}
@Test
void testDianneRetrieval() {
process("dianne", "emu", false);
}
protected void process(String username, String password, boolean shouldBeFiltered) {
SecurityContextHolder.getContext()
.setAuthentication(new UsernamePasswordAuthenticationToken(username, password));
System.out.println("------ Test for username: " + username + " ------");
AbstractElement[] rootElements = this.documentDao.findElements(Directory.ROOT_DIRECTORY);
assertThat(rootElements).hasSize(3);
Directory homeDir = null;
Directory nonHomeDir = null;
for (AbstractElement rootElement : rootElements) {
if (rootElement.getName().equals(username)) {
homeDir = (Directory) rootElement;
}
else {
nonHomeDir = (Directory) rootElement;
}
}
System.out.println("Home directory......: " + homeDir.getFullName());
System.out.println("Non-home directory..: " + nonHomeDir.getFullName());
AbstractElement[] homeElements = this.documentDao.findElements(homeDir);
assertThat(homeElements).hasSize(12); // confidential and shared
// directories,
// plus 10 files
AbstractElement[] nonHomeElements = this.documentDao.findElements(nonHomeDir);
assertThat(nonHomeElements).hasSize(shouldBeFiltered ? 11 : 12);
// cannot see the user's "confidential" sub-directory when filtering
// Attempt to read the other user's confidential directory from the returned
// results
// Of course, we shouldn't find a "confidential" directory in the results if we're
// filtering
Directory nonHomeConfidentialDir = null;
for (AbstractElement nonHomeElement : nonHomeElements) {
if (nonHomeElement.getName().equals("confidential")) {
nonHomeConfidentialDir = (Directory) nonHomeElement;
}
}
if (shouldBeFiltered) {
assertThat(nonHomeConfidentialDir).withFailMessage("Found confidential directory when we should not have")
.isNull();
}
else {
System.out.println("Inaccessible dir....: " + nonHomeConfidentialDir.getFullName());
assertThat(this.documentDao.findElements(nonHomeConfidentialDir).length).isEqualTo(10); // 10
// files
// (no
// sub-directories)
}
SecurityContextHolder.clearContext();
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* 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
*
* https://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 sample;
import org.junit.jupiter.api.Test;
import org.springframework.test.context.ContextConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Basic integration test for DMS sample when security has been added.
*
* @author Ben Alex
*
*/
@ContextConfiguration(
locations = { "classpath:applicationContext-dms-shared.xml", "classpath:applicationContext-dms-secure.xml" })
public class SecureDmsIntegrationTests extends DmsIntegrationTests {
@Override
@Test
void testBasePopulation() {
assertThat(this.jdbcTemplate.queryForObject("select count(id) from DIRECTORY", Integer.class)).isEqualTo(9);
assertThat(this.jdbcTemplate.queryForObject("select count(id) from FILE", Integer.class)).isEqualTo(90);
assertThat(this.jdbcTemplate.queryForObject("select count(id) from ACL_SID", Integer.class)).isEqualTo(4); // 3
// users
// +
// 1
// role
assertThat(this.jdbcTemplate.queryForObject("select count(id) from ACL_CLASS", Integer.class)).isEqualTo(2); // Directory
// and
// File
assertThat(this.jdbcTemplate.queryForObject("select count(id) from ACL_OBJECT_IDENTITY", Integer.class))
.isEqualTo(100);
assertThat(this.jdbcTemplate.queryForObject("select count(id) from ACL_ENTRY", Integer.class)).isEqualTo(115);
}
@Override
@Test
void testMarissaRetrieval() {
process("rod", "koala", true);
}
@Override
@Test
void testScottRetrieval() {
process("scott", "wombat", true);
}
@Override
@Test
void testDianneRetrieval() {
process("dianne", "emu", true);
}
}

View File

@ -0,0 +1,15 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.springframework.security" level="${sec.log.level:-WARN}"/>
<root level="${root.level:-WARN}">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -57,3 +57,4 @@ include ":servlet:spring-boot:kotlin:hello-security"
include ":servlet:xml:java:helloworld"
include ":servlet:xml:java:preauth"
include ":servlet:xml:java:contacts"
include ":servlet:xml:java:dms"