Add ACL Document Management System XML sample
Closes gh-34
This commit is contained in:
parent
4c00a8fb4e
commit
0bf72c4580
|
@ -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()
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
version=5.6.0-SNAPSHOT
|
Binary file not shown.
|
@ -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
|
|
@ -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" "$@"
|
|
@ -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
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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() + "']";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -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() });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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() + "']";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
|
@ -57,3 +57,4 @@ include ":servlet:spring-boot:kotlin:hello-security"
|
||||||
include ":servlet:xml:java:helloworld"
|
include ":servlet:xml:java:helloworld"
|
||||||
include ":servlet:xml:java:preauth"
|
include ":servlet:xml:java:preauth"
|
||||||
include ":servlet:xml:java:contacts"
|
include ":servlet:xml:java:contacts"
|
||||||
|
include ":servlet:xml:java:dms"
|
||||||
|
|
Loading…
Reference in New Issue