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:preauth"
|
||||
include ":servlet:xml:java:contacts"
|
||||
include ":servlet:xml:java:dms"
|
||||
|
|
Loading…
Reference in New Issue