├── .gitignore ├── .travis.yml ├── README.adoc ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── rest ├── build.gradle └── src │ ├── main │ ├── java │ │ └── sample │ │ │ ├── RestApplication.java │ │ │ ├── config │ │ │ └── SecurityConfig.java │ │ │ ├── data │ │ │ ├── Message.java │ │ │ ├── MessageRepository.java │ │ │ ├── User.java │ │ │ └── UserRepository.java │ │ │ ├── mvc │ │ │ ├── AdminController.java │ │ │ ├── ExploitDemoController.java │ │ │ ├── MessageController.java │ │ │ ├── SecurityController.java │ │ │ └── UserController.java │ │ │ └── security │ │ │ ├── Authz.java │ │ │ ├── CurrentUser.java │ │ │ ├── ReadableMessage.java │ │ │ └── UserRepositoryUserDetailsService.java │ ├── resources │ │ ├── application.yml │ │ ├── data.sql │ │ ├── password-encode.sql │ │ ├── static │ │ │ ├── assets │ │ │ │ ├── css │ │ │ │ │ └── custom.css │ │ │ │ ├── img │ │ │ │ │ ├── favicon.ico │ │ │ │ │ └── logo.png │ │ │ │ └── js │ │ │ │ │ ├── app │ │ │ │ │ ├── app.js │ │ │ │ │ ├── message │ │ │ │ │ │ ├── message-compose.tpl.html │ │ │ │ │ │ ├── message-list.tpl.html │ │ │ │ │ │ ├── message-view.tpl.html │ │ │ │ │ │ └── message.js │ │ │ │ │ ├── router.js │ │ │ │ │ ├── util.js │ │ │ │ │ └── xss-app.js │ │ │ │ │ └── common │ │ │ │ │ ├── directives │ │ │ │ │ └── header │ │ │ │ │ │ └── header.tpl.html │ │ │ │ │ ├── partials │ │ │ │ │ ├── alertModal.tpl.html │ │ │ │ │ └── login.tpl.html │ │ │ │ │ └── services │ │ │ │ │ ├── message-service.js │ │ │ │ │ ├── security-service.js │ │ │ │ │ ├── underscore.js │ │ │ │ │ └── user-service.js │ │ │ └── index.html │ │ └── templates │ │ │ └── xss.html │ └── webapp │ │ └── WEB-INF │ │ └── jsp │ │ └── xss │ │ ├── fix.jsp │ │ └── jsp.jsp │ └── test │ └── java │ └── sample │ ├── JsonUtil.java │ └── SpringSecurityApplicationTests.java ├── settings.gradle └── ui ├── build.gradle └── src └── main ├── java └── sample │ └── UiApplication.java ├── resources ├── application.yml ├── static │ ├── assets │ │ ├── css │ │ │ └── custom.css │ │ ├── img │ │ │ ├── favicon.ico │ │ │ └── logo.png │ │ └── js │ │ │ ├── app │ │ │ ├── app.js │ │ │ ├── message │ │ │ │ ├── message-compose.tpl.html │ │ │ │ ├── message-list.tpl.html │ │ │ │ ├── message-view.tpl.html │ │ │ │ └── message.js │ │ │ ├── router.js │ │ │ ├── util.js │ │ │ └── xss-app.js │ │ │ └── common │ │ │ ├── directives │ │ │ └── header │ │ │ │ └── header.tpl.html │ │ │ ├── partials │ │ │ └── alertModal.tpl.html │ │ │ └── services │ │ │ ├── message-service.js │ │ │ ├── security-service.js │ │ │ ├── underscore.js │ │ │ └── user-service.js │ └── index.html └── templates │ ├── custom-login.html │ └── xss.html └── webapp └── WEB-INF └── jsp └── xss ├── fix.jsp └── jsp.jsp /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | */src/*/java/META-INF 3 | */src/META-INF/ 4 | */src/*/java/META-INF/ 5 | .classpath 6 | .springBeans 7 | .project 8 | .DS_Store 9 | .settings/ 10 | .idea/ 11 | out/ 12 | bin/ 13 | intellij/ 14 | build/ 15 | *.log 16 | *.log.* 17 | *.iml 18 | *.ipr 19 | *.iws 20 | .gradle/ 21 | atlassian-ide-plugin.xml 22 | *.rdb -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | services: 4 | - redis-server 5 | 6 | jdk: 7 | - oraclejdk8 8 | 9 | os: 10 | - linux 11 | 12 | branches: 13 | only: 14 | - master 15 | 16 | before_cache: 17 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 18 | cache: 19 | directories: 20 | - $HOME/.gradle/caches/ 21 | - $HOME/.gradle/wrapper/ 22 | 23 | script: ./gradlew build 24 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | image:https://travis-ci.org/rwinch/spring-security-4.1-and-beyond.svg?branch=master["Build Status", link="https://travis-ci.org/rwinch/spring-security-4.1-and-beyond"] 2 | 3 | 4 | NOTE: All JavaScript REST calls should be accept application/json 5 | 6 | Authenticate with rob@example.com / password 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwinch/spring-security-4.1-and-beyond/2142178824477ac97e3353366d7cff5cc633f187/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jul 06 08:42:36 CDT 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /rest/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url 'https://repo.spring.io/libs-snapshot' } 4 | mavenCentral() 5 | } 6 | dependencies { 7 | classpath("org.springframework.boot:spring-boot-gradle-plugin:1.4.0.RELEASE") 8 | } 9 | } 10 | 11 | apply plugin: 'java' 12 | apply plugin: 'spring-boot' 13 | apply plugin: 'eclipse' 14 | apply plugin: 'idea' 15 | 16 | group = 'samples' 17 | 18 | sourceCompatibility = 1.8 19 | targetCompatibility = 1.8 20 | 21 | ext['spring-security.version'] = '4.1.2.BUILD-SNAPSHOT' 22 | ext['assertj.version'] = '3.5.2' 23 | 24 | repositories { 25 | maven { url 'https://repo.spring.io/libs-snapshot' } 26 | } 27 | 28 | dependencies { 29 | compile "org.springframework.boot:spring-boot-starter-web", 30 | "org.springframework.boot:spring-boot-starter-data-jpa", 31 | "org.springframework.boot:spring-boot-starter-security", 32 | "org.springframework.security:spring-security-data", 33 | "org.springframework.boot:spring-boot-devtools", 34 | "org.springframework.boot:spring-boot-starter-thymeleaf", 35 | "org.webjars:webjars-locator", 36 | "org.webjars:angularjs:1.4.9", 37 | "org.webjars:bootstrap:3.3.6", 38 | "org.webjars:jquery:1.11.3", 39 | "com.fasterxml.jackson.core:jackson-annotations:2.8.0", 40 | "com.h2database:h2", 41 | "org.springframework.data:spring-data-jpa", 42 | "javax.servlet:jstl", 43 | "com.maxmind.geoip2:geoip2:2.7.0" 44 | 45 | testCompile "org.springframework.boot:spring-boot-starter-test", 46 | "org.springframework.security:spring-security-test", 47 | "com.jayway.jsonpath:json-path" 48 | 49 | } -------------------------------------------------------------------------------- /rest/src/main/java/sample/RestApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package sample; 17 | 18 | import org.springframework.boot.SpringApplication; 19 | import org.springframework.boot.autoconfigure.SpringBootApplication; 20 | 21 | /** 22 | * @author Rob Winch 23 | */ 24 | @SpringBootApplication 25 | public class RestApplication { 26 | 27 | public static void main(String[] args) { 28 | SpringApplication.run(RestApplication.class, args); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /rest/src/main/java/sample/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package sample.config; 17 | 18 | import org.springframework.context.annotation.Bean; 19 | import org.springframework.context.annotation.Configuration; 20 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 21 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 22 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 23 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 24 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 25 | import org.springframework.security.web.csrf.CookieCsrfTokenRepository; 26 | 27 | /** 28 | * @author Rob Winch 29 | * @author Joe Grandja 30 | */ 31 | @Configuration 32 | @EnableWebSecurity 33 | @EnableGlobalMethodSecurity(prePostEnabled = true) 34 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 35 | 36 | // @formatter:off 37 | @Override 38 | protected void configure(HttpSecurity http) throws Exception { 39 | http 40 | .csrf() 41 | .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) 42 | .and() 43 | .authorizeRequests() 44 | .antMatchers("/", "/assets/**", "/webjars/**").permitAll() 45 | .antMatchers("/users/{userId}").access("@authz.check(#userId,principal)") 46 | .mvcMatchers("/admin").denyAll() 47 | .anyRequest().authenticated() 48 | .and() 49 | .httpBasic() 50 | .and() 51 | .formLogin() 52 | .loginPage("/") 53 | .permitAll() 54 | .and() 55 | .headers() 56 | .contentSecurityPolicy("default-src 'self' " + 57 | "https://ajax.googleapis.com " + 58 | "https://cdnjs.cloudflare.com; " + 59 | "style-src 'self' 'unsafe-inline'"); 60 | } 61 | // @formatter:on 62 | 63 | @Bean 64 | public BCryptPasswordEncoder passwordEncoder() { 65 | return new BCryptPasswordEncoder(); 66 | } 67 | } -------------------------------------------------------------------------------- /rest/src/main/java/sample/data/Message.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2013 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package sample.data; 17 | 18 | import org.hibernate.validator.constraints.NotEmpty; 19 | 20 | import javax.persistence.*; 21 | import javax.validation.constraints.NotNull; 22 | import java.util.Calendar; 23 | 24 | /** 25 | * A Message is an entity that can be sent to a {@link User}. 26 | * 27 | * @author Rob Winch 28 | */ 29 | @Entity 30 | public class Message { 31 | @Id 32 | @GeneratedValue(strategy = GenerationType.AUTO) 33 | private Long id; 34 | 35 | @NotEmpty(message = "Message is required.") 36 | private String text; 37 | 38 | @NotEmpty(message = "Summary is required.") 39 | private String summary; 40 | 41 | private Calendar created = Calendar.getInstance(); 42 | 43 | @OneToOne 44 | @NotNull 45 | private User to; 46 | 47 | @OneToOne 48 | @NotNull 49 | private User from; 50 | 51 | public Message() { 52 | to = new User(); 53 | from = new User(); 54 | } 55 | 56 | public User getFrom() { 57 | return from; 58 | } 59 | 60 | public void setFrom(User from) { 61 | this.from = from; 62 | } 63 | 64 | public User getTo() { 65 | return to; 66 | } 67 | 68 | public void setTo(User to) { 69 | this.to = to; 70 | } 71 | 72 | public Long getId() { 73 | return id; 74 | } 75 | 76 | public void setId(Long id) { 77 | this.id = id; 78 | } 79 | 80 | public Calendar getCreated() { 81 | return created; 82 | } 83 | 84 | public void setCreated(Calendar created) { 85 | this.created = created; 86 | } 87 | 88 | public String getText() { 89 | return text; 90 | } 91 | 92 | public void setText(String text) { 93 | this.text = text; 94 | } 95 | 96 | public String getSummary() { 97 | return summary; 98 | } 99 | 100 | public void setSummary(String summary) { 101 | this.summary = summary; 102 | } 103 | } -------------------------------------------------------------------------------- /rest/src/main/java/sample/data/MessageRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package sample.data; 17 | 18 | import org.springframework.data.jpa.repository.Query; 19 | import org.springframework.data.repository.CrudRepository; 20 | import org.springframework.data.repository.query.Param; 21 | import org.springframework.security.access.prepost.PostAuthorize; 22 | 23 | import sample.security.ReadableMessage; 24 | 25 | /** 26 | * Manages {@link Message} instances 27 | * 28 | * @author Rob Winch 29 | * 30 | */ 31 | public interface MessageRepository extends CrudRepository { 32 | 33 | @Query("select m from Message m where m.to.id = ?#{principal.id}") 34 | Iterable inbox(); 35 | 36 | @Query("select m from Message m where m.from.id = ?#{principal.id}") 37 | Iterable sent(); 38 | 39 | @ReadableMessage 40 | Message findOne(@Param("id") Long id); 41 | 42 | @ReadableMessage 43 | Message findBySummary(@Param("summary") String summary); 44 | 45 | S save(S message); 46 | 47 | void delete(Long id); 48 | } 49 | -------------------------------------------------------------------------------- /rest/src/main/java/sample/data/User.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package sample.data; 17 | 18 | import java.io.Serializable; 19 | 20 | import javax.persistence.Column; 21 | import javax.persistence.Entity; 22 | import javax.persistence.GeneratedValue; 23 | import javax.persistence.GenerationType; 24 | import javax.persistence.Id; 25 | 26 | import org.hibernate.validator.constraints.Email; 27 | import org.hibernate.validator.constraints.NotEmpty; 28 | 29 | import com.fasterxml.jackson.annotation.JsonIgnore; 30 | 31 | /** 32 | * Represents a user in our system. 33 | * 34 | *

35 | * In a real system use {@link PasswordEncoder} to ensure the password is 36 | * secured properly. This demonstration does not address this due to time 37 | * restrictions. 38 | *

39 | * 40 | * @author Rob Winch 41 | */ 42 | @Entity 43 | public class User implements Serializable { 44 | 45 | @Id 46 | @GeneratedValue(strategy = GenerationType.AUTO) 47 | private Long id; 48 | 49 | @NotEmpty(message = "First name is required.") 50 | private String firstName; 51 | 52 | @NotEmpty(message = "Last name is required.") 53 | private String lastName; 54 | 55 | @Email(message = "Please provide a valid email address.") 56 | @NotEmpty(message = "Email is required.") 57 | @Column(unique = true, nullable = false) 58 | private String email; 59 | 60 | @NotEmpty(message = "Password is required.") 61 | private String password; 62 | 63 | public User() { 64 | } 65 | 66 | public User(User user) { 67 | this.id = user.id; 68 | this.firstName = user.firstName; 69 | this.lastName = user.lastName; 70 | this.email = user.email; 71 | this.password = user.password; 72 | } 73 | 74 | @JsonIgnore 75 | public String getPassword() { 76 | return password; 77 | } 78 | 79 | public void setPassword(String password) { 80 | this.password = password; 81 | } 82 | 83 | public Long getId() { 84 | return id; 85 | } 86 | 87 | public void setId(Long id) { 88 | this.id = id; 89 | } 90 | 91 | public String getFirstName() { 92 | return firstName; 93 | } 94 | 95 | public void setFirstName(String firstName) { 96 | this.firstName = firstName; 97 | } 98 | 99 | public String getLastName() { 100 | return lastName; 101 | } 102 | 103 | public void setLastName(String lastName) { 104 | this.lastName = lastName; 105 | } 106 | 107 | public String getEmail() { 108 | return email; 109 | } 110 | 111 | public void setEmail(String email) { 112 | this.email = email; 113 | } 114 | 115 | private static final long serialVersionUID = 2738859149330833739L; 116 | } -------------------------------------------------------------------------------- /rest/src/main/java/sample/data/UserRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package sample.data; 17 | 18 | import org.springframework.data.repository.CrudRepository; 19 | import org.springframework.data.repository.query.Param; 20 | 21 | /** 22 | * Allows managing {@link User} instances. 23 | * 24 | * @author Rob Winch 25 | * 26 | */ 27 | public interface UserRepository extends CrudRepository { 28 | 29 | User findByEmail(@Param("email") String email); 30 | } 31 | -------------------------------------------------------------------------------- /rest/src/main/java/sample/mvc/AdminController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package sample.mvc; 17 | 18 | import java.util.Collections; 19 | import java.util.Map; 20 | 21 | import org.springframework.web.bind.annotation.RequestMapping; 22 | import org.springframework.web.bind.annotation.RestController; 23 | 24 | @RestController 25 | public class AdminController { 26 | 27 | @RequestMapping("/admin") 28 | public Map admin() { 29 | return Collections.singletonMap("password", "#s1pGo #gottacatchemall"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /rest/src/main/java/sample/mvc/ExploitDemoController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package sample.mvc; 17 | 18 | import java.util.Map; 19 | 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.stereotype.Controller; 22 | import org.springframework.web.bind.annotation.RequestMapping; 23 | import org.springframework.web.bind.annotation.RequestParam; 24 | 25 | import sample.data.Message; 26 | import sample.data.MessageRepository; 27 | 28 | @Controller 29 | public class ExploitDemoController { 30 | private final MessageRepository messageRepository; 31 | 32 | @Autowired 33 | public ExploitDemoController(MessageRepository messageRepository) { 34 | this.messageRepository = messageRepository; 35 | } 36 | 37 | @RequestMapping("/xss") 38 | public String xss(@RequestParam(defaultValue = "120") Long id, Map model) { 39 | Message message = messageRepository.findOne(id); 40 | model.put("message", message); 41 | return "xss"; 42 | } 43 | 44 | @RequestMapping("/fixxss") 45 | public String fixxss(@RequestParam(defaultValue = "120") Long id, Map model) { 46 | Message message = messageRepository.findOne(id); 47 | model.put("message", message); 48 | return "xss/fix"; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /rest/src/main/java/sample/mvc/MessageController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package sample.mvc; 17 | 18 | import javax.validation.Valid; 19 | 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.web.bind.annotation.CrossOrigin; 22 | import org.springframework.web.bind.annotation.PathVariable; 23 | import org.springframework.web.bind.annotation.RequestBody; 24 | import org.springframework.web.bind.annotation.RequestMapping; 25 | import org.springframework.web.bind.annotation.RequestMethod; 26 | import org.springframework.web.bind.annotation.RestController; 27 | 28 | import sample.data.Message; 29 | import sample.data.MessageRepository; 30 | import sample.data.User; 31 | import sample.data.UserRepository; 32 | import sample.security.CurrentUser; 33 | 34 | /** 35 | * Controller for managing {@link Message} instances. 36 | * 37 | * @author Rob Winch 38 | * @author Joe Grandja 39 | * 40 | */ 41 | @CrossOrigin 42 | @RestController 43 | @RequestMapping(value = "/messages") 44 | public class MessageController { 45 | 46 | private final MessageRepository messageRepository; 47 | private final UserRepository userRepository; 48 | 49 | @Autowired 50 | public MessageController(MessageRepository messageRepository, UserRepository userRepository) { 51 | this.messageRepository = messageRepository; 52 | this.userRepository = userRepository; 53 | } 54 | 55 | @RequestMapping(value = "/inbox") 56 | public Iterable inbox() { 57 | return messageRepository.inbox(); 58 | } 59 | 60 | @RequestMapping(value = "/sent") 61 | public Iterable sent() { 62 | return messageRepository.sent(); 63 | } 64 | 65 | @RequestMapping(value = "/{id}") 66 | public Message get(@PathVariable Long id) { 67 | return messageRepository.findOne(id); 68 | } 69 | 70 | @RequestMapping(method = RequestMethod.POST) 71 | public Message save(@Valid @RequestBody Message message, @CurrentUser User currentUser) { 72 | message.setTo(userRepository.findByEmail(message.getTo().getEmail())); 73 | message.setFrom(userRepository.findByEmail(currentUser.getEmail())); 74 | message = messageRepository.save(message); 75 | 76 | return message; 77 | } 78 | 79 | @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) 80 | public void delete(@PathVariable Long id) { 81 | messageRepository.delete(id); 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /rest/src/main/java/sample/mvc/SecurityController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package sample.mvc; 17 | 18 | import org.springframework.http.HttpStatus; 19 | import org.springframework.http.ResponseEntity; 20 | import org.springframework.web.bind.annotation.CrossOrigin; 21 | import org.springframework.web.bind.annotation.RequestMapping; 22 | import org.springframework.web.bind.annotation.RestController; 23 | 24 | import sample.data.User; 25 | import sample.security.CurrentUser; 26 | 27 | /** 28 | * @author Rob Winch 29 | */ 30 | @CrossOrigin 31 | @RestController 32 | public class SecurityController { 33 | 34 | @RequestMapping(value = "/principal") 35 | public ResponseEntity currentPrincipal(@CurrentUser User currentUser) { 36 | return new ResponseEntity(currentUser, HttpStatus.OK); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /rest/src/main/java/sample/mvc/UserController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package sample.mvc; 17 | 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.web.bind.annotation.CrossOrigin; 20 | import org.springframework.web.bind.annotation.PathVariable; 21 | import org.springframework.web.bind.annotation.RequestMapping; 22 | import org.springframework.web.bind.annotation.RequestMethod; 23 | import org.springframework.web.bind.annotation.RestController; 24 | import sample.data.User; 25 | import sample.data.UserRepository; 26 | 27 | /** 28 | * Controller for managing {@link User} instances. 29 | * 30 | * @author Joe Grandja 31 | */ 32 | @CrossOrigin 33 | @RestController 34 | @RequestMapping(value = "/users") 35 | public class UserController { 36 | private final UserRepository userRepository; 37 | 38 | @Autowired 39 | public UserController(UserRepository userRepository) { 40 | this.userRepository = userRepository; 41 | } 42 | 43 | @RequestMapping(method = RequestMethod.GET) 44 | public Iterable get() { 45 | return userRepository.findAll(); 46 | } 47 | 48 | @RequestMapping("/{id}") 49 | public User user(@PathVariable Long id) { 50 | return userRepository.findOne(id); 51 | } 52 | } -------------------------------------------------------------------------------- /rest/src/main/java/sample/security/Authz.java: -------------------------------------------------------------------------------- 1 | package sample.security; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import sample.data.Message; 6 | import sample.data.User; 7 | 8 | @Component 9 | public class Authz { 10 | 11 | public boolean check(Long userId, User user) { 12 | return userId.equals(user.getId()); 13 | } 14 | 15 | public boolean check(Message message, User user) { 16 | if(message == null) { 17 | return true; 18 | } 19 | if(user == null) { 20 | return false; 21 | } 22 | return check(message.getTo().getId(), user) || check(message.getFrom().getId(), user); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /rest/src/main/java/sample/security/CurrentUser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package sample.security; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | import org.springframework.security.core.Authentication; 25 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 26 | import org.springframework.security.core.context.SecurityContextHolder; 27 | 28 | /** 29 | * Annotate Spring MVC method arguments with this annotation to indicate you 30 | * wish to specify the argument with the value of the current 31 | * {@link Authentication#getPrincipal()} found on the 32 | * {@link SecurityContextHolder}. 33 | * 34 | *

35 | * Creating your own annotation that uses {@link AuthenticationPrincipal} as a 36 | * meta annotation creates a layer of indirection between your code and Spring 37 | * Security's. For simplicity, you could instead use the 38 | * {@link AuthenticationPrincipal} directly. 39 | *

40 | * 41 | * @author Rob Winch 42 | * 43 | */ 44 | @Target(ElementType.PARAMETER) 45 | @Retention(RetentionPolicy.RUNTIME) 46 | @Documented 47 | @AuthenticationPrincipal 48 | public @interface CurrentUser { 49 | 50 | } 51 | -------------------------------------------------------------------------------- /rest/src/main/java/sample/security/ReadableMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package sample.security; 17 | 18 | import java.lang.annotation.Retention; 19 | import java.lang.annotation.RetentionPolicy; 20 | 21 | import org.springframework.security.access.prepost.PostAuthorize; 22 | 23 | @Retention(RetentionPolicy.RUNTIME) 24 | @PostAuthorize("@authz.check(returnObject, principal)") 25 | public @interface ReadableMessage { 26 | 27 | } 28 | -------------------------------------------------------------------------------- /rest/src/main/java/sample/security/UserRepositoryUserDetailsService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package sample.security; 17 | 18 | import java.util.Collection; 19 | 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.security.core.GrantedAuthority; 22 | import org.springframework.security.core.authority.AuthorityUtils; 23 | import org.springframework.security.core.userdetails.UserDetails; 24 | import org.springframework.security.core.userdetails.UserDetailsService; 25 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 26 | import sample.data.User; 27 | import sample.data.UserRepository; 28 | import org.springframework.stereotype.Service; 29 | 30 | /** 31 | * @author Rob Winch 32 | * 33 | */ 34 | @Service 35 | public class UserRepositoryUserDetailsService implements UserDetailsService { 36 | private final UserRepository userRepository; 37 | 38 | @Autowired 39 | public UserRepositoryUserDetailsService(UserRepository userRepository) { 40 | this.userRepository = userRepository; 41 | } 42 | 43 | /* 44 | * (non-Javadoc) 45 | * 46 | * @see org.springframework.security.core.userdetails.UserDetailsService# 47 | * loadUserByUsername(java.lang.String) 48 | */ 49 | @Override 50 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 51 | User user = userRepository.findByEmail(username); 52 | if (user == null) { 53 | throw new UsernameNotFoundException("Could not find user " + username); 54 | } 55 | return new CustomUserDetails(user); 56 | } 57 | 58 | private final static class CustomUserDetails extends User implements UserDetails { 59 | 60 | private CustomUserDetails(User user) { 61 | super(user); 62 | } 63 | 64 | @Override 65 | public Collection getAuthorities() { 66 | return AuthorityUtils.createAuthorityList("ROLE_USER"); 67 | } 68 | 69 | @Override 70 | public String getUsername() { 71 | return getEmail(); 72 | } 73 | 74 | @Override 75 | public boolean isAccountNonExpired() { 76 | return true; 77 | } 78 | 79 | @Override 80 | public boolean isAccountNonLocked() { 81 | return true; 82 | } 83 | 84 | @Override 85 | public boolean isCredentialsNonExpired() { 86 | return true; 87 | } 88 | 89 | @Override 90 | public boolean isEnabled() { 91 | return true; 92 | } 93 | 94 | private static final long serialVersionUID = 5639683223516504866L; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /rest/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 3 | 4 | security: 5 | user: 6 | name: joe@example.com 7 | password: password 8 | 9 | spring: 10 | jackson: 11 | date-format: com.fasterxml.jackson.databind.util.ISO8601DateFormat 12 | datasource: 13 | data: classpath:data.sql,classpath:password-encode.sql 14 | devtools: 15 | restart: 16 | exclude: META-INF/maven/**,META-INF/resources/**,resources/**,public/**,**/*Test.class,**/*Tests.class 17 | thymeleaf: 18 | cache: false 19 | 20 | logging: 21 | level: 22 | root: INFO 23 | org.springframework.web: INFO 24 | org.springframework.security: DEBUG 25 | -------------------------------------------------------------------------------- /rest/src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | insert into user(id,email,password,first_name,last_name) values (0,'rob@example.com','password','Rob','Winch'); 2 | insert into user(id,email,password,first_name,last_name) values (1,'joe@example.com','password','Joe','Grandja'); 3 | insert into user(id,email,password,first_name,last_name) values (2,'eve@example.com','password','Eve','sdropper'); 4 | 5 | insert into message(id,created,to_id,from_id,summary,text) values (100,'2014-07-10 10:00:00',0,1,'Hello Rob','This message is for Rob'); 6 | insert into message(id,created,to_id,from_id,summary,text) values (101,'2014-07-10 14:00:00',0,1,'How are you Rob?','This message is for Rob'); 7 | insert into message(id,created,to_id,from_id,summary,text) values (102,'2014-07-11 22:00:00',0,1,'Is this secure?','This message is for Rob'); 8 | 9 | insert into message(id,created,to_id,from_id,summary,text) values (110,'2014-07-12 10:00:00',1,0,'Hello Joe','This message is for Joe'); 10 | insert into message(id,created,to_id,from_id,summary,text) values (111,'2014-07-12 10:00:00',1,0,'Greetings Joe','This message is for Joe'); 11 | insert into message(id,created,to_id,from_id,summary,text) values (112,'2014-07-12 10:00:00',1,0,'Is this secure?','This message is for Joe'); 12 | 13 | insert into message(id,created,to_id,from_id,summary,text) values (120,'2014-07-12 10:00:00',2,2,'Hello Self','Ready to Hack!'); 14 | insert into message(id,created,to_id,from_id,summary,text) values (121,'2014-07-12 10:00:00',0,2,'XSS in Script',''); 15 | insert into message(id,created,to_id,from_id,summary,text) values (122,'2014-07-12 10:00:00',0,2,'XSS in HTML',''');document.write(''thank you come again'');//'); 16 | 17 | -- XSS Test with Angular 18 | -- {{ 'a'.constructor.prototype.charAt=[].join; $eval("x=alert('Got you!')") }} 19 | -- {{ 'a'.constructor.prototype.charAt=[].join; $eval("x=alert(document.cookie)") }} 20 | 21 | 22 | -------------------------------------------------------------------------------- /rest/src/main/resources/password-encode.sql: -------------------------------------------------------------------------------- 1 | update user set password = '$2a$10$FBAKClV1zBIOOC9XMXf3AO8RoGXYVYsfvUdoLxGkd/BnXEn4tqT3u'; -------------------------------------------------------------------------------- /rest/src/main/resources/static/assets/css/custom.css: -------------------------------------------------------------------------------- 1 | html { 2 | position: relative; 3 | min-height: 100%; 4 | } 5 | 6 | body { 7 | /* Margin bottom by footer height + 50px of padding*/ 8 | margin-bottom: 110px; 9 | } 10 | 11 | footer { 12 | position: absolute; 13 | bottom: 0; 14 | width: 100%; 15 | height: 60px; 16 | padding: 20px 0; 17 | } 18 | 19 | footer a { 20 | text-decoration: underline; 21 | } 22 | 23 | footer > .container { 24 | padding-right: 15px; 25 | padding-left: 15px; 26 | } 27 | 28 | .form-message-compose .form-group { 29 | padding: 7px; 30 | } 31 | 32 | .message-view > .row { 33 | padding: 7px; 34 | } 35 | 36 | .message-view .message-label { 37 | font-weight: bold; 38 | } 39 | 40 | .modal-dialog { 41 | width: 450px; 42 | } 43 | 44 | .modal-header, .modal-footer { 45 | border: none; 46 | } 47 | 48 | .modal-title { 49 | color: #B0B0AF; 50 | font-size: 24px; 51 | padding-left: 20px; 52 | } 53 | 54 | .modal-body { 55 | font-size: 14px; 56 | font-weight: normal; 57 | } 58 | 59 | .modal-footer { 60 | text-align: right; 61 | } 62 | 63 | .modal-footer .modal-button-ok, .modal-button-cancel { 64 | width: 100px; 65 | border: none; 66 | border-radius: 0; 67 | } -------------------------------------------------------------------------------- /rest/src/main/resources/static/assets/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwinch/spring-security-4.1-and-beyond/2142178824477ac97e3353366d7cff5cc633f187/rest/src/main/resources/static/assets/img/favicon.ico -------------------------------------------------------------------------------- /rest/src/main/resources/static/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwinch/spring-security-4.1-and-beyond/2142178824477ac97e3353366d7cff5cc633f187/rest/src/main/resources/static/assets/img/logo.png -------------------------------------------------------------------------------- /rest/src/main/resources/static/assets/js/app/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('secure-messaging-app', [ 4 | 'secure-messaging-app.router', 5 | 'secure-messaging-app.security-service', 6 | 'secure-messaging-app.util', 7 | 'ui.bootstrap', 8 | 'underscore' 9 | ]) 10 | 11 | .factory('authInterceptor', ['$q', '$rootScope', '$location', function($q, $rootScope, $location) { 12 | var responseError = function(response) { 13 | if (response.status === 401) { 14 | $location.path('/login'); 15 | return $q.reject(response); 16 | } 17 | return response; 18 | }; 19 | 20 | return { 21 | responseError: responseError 22 | } 23 | }]) 24 | 25 | .config(['$httpProvider', function($httpProvider) { 26 | $httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 27 | $httpProvider.interceptors.push('authInterceptor'); 28 | }]) 29 | 30 | .constant('BASE_API_ENDPOINT', '') 31 | 32 | .controller('appController', ['$scope', '$location', 'commonService', 'securityService', function ($scope, $location, commonService, securityService) { 33 | $scope.updateCurrentActiveLink = function (clickedLink) { 34 | $scope.currentActiveLink = clickedLink; 35 | }; 36 | 37 | $scope.isCurrentActiveLink = function (link) { 38 | return $scope.currentActiveLink === link; 39 | }; 40 | 41 | $scope.$on(commonService.EVENT_TYPES.CURRENT_PRINCIPAL_CHANGE_EVENT, function() { 42 | console.log("***** " + commonService.EVENT_TYPES.CURRENT_PRINCIPAL_CHANGE_EVENT); 43 | $scope.currentPrincipal = commonService.getProperty(commonService.CURRENT_PRINCIPAL_KEY); 44 | }); 45 | 46 | $scope.getCurrentPrincipal = function() { 47 | securityService.currentPrincipal(); 48 | }; 49 | 50 | $scope.logout = function() { 51 | securityService.logout(); 52 | }; 53 | 54 | $scope.goTo = function(view) { 55 | $location.path(view); 56 | }; 57 | 58 | var init = function() { 59 | if($location.path() != '' && $location.path() != '/login') { 60 | $scope.getCurrentPrincipal(); 61 | } 62 | }; 63 | 64 | init(); 65 | 66 | }]) 67 | 68 | .controller('loginController', ['$scope', '$location', 'commonService', 'securityService', function ($scope, $location, commonService, securityService) { 69 | $scope.login = function() { 70 | securityService.login($scope.auth, function(response, success) { 71 | if (success) { 72 | $location.path('/inbox'); 73 | } 74 | }); 75 | }; 76 | 77 | var init = function() { 78 | $scope.auth = {}; 79 | }; 80 | 81 | init(); 82 | }]) 83 | 84 | .directive('headerDirective', function() { 85 | return { 86 | templateUrl : 'assets/js/common/directives/header/header.tpl.html' 87 | }; 88 | }) 89 | ; -------------------------------------------------------------------------------- /rest/src/main/resources/static/assets/js/app/message/message-compose.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Compose

4 |
5 | 6 |
7 | 11 |
12 |
13 |
14 | 15 |
16 | 17 |
18 |
19 |
20 | 21 |
22 | 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 |
-------------------------------------------------------------------------------- /rest/src/main/resources/static/assets/js/app/message/message-list.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |

{{messageListType}}

3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
CreatedSummaryDelete
{{ message.created | date:'MM/dd/yyyy' }}{{message.summary}}Delete
20 |
21 |
-------------------------------------------------------------------------------- /rest/src/main/resources/static/assets/js/app/message/message-view.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |

Message

3 |
4 |
5 |
To:
6 |
{{currentMessage.to.firstName + ' ' + currentMessage.to.lastName}}
7 |
8 |
9 |
Subject:
10 |
{{currentMessage.summary}}
11 |
12 |
13 |
Message:
14 |
{{currentMessage.text}}
15 |
16 |
17 |
Created:
18 |
{{currentMessage.created | date:'MM/dd/yyyy'}}
19 |
20 |
21 |
22 | Done 23 |
24 |
25 |
26 |
-------------------------------------------------------------------------------- /rest/src/main/resources/static/assets/js/app/message/message.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | angular.module('secure-messaging-app.message-controllers', [ 4 | 'secure-messaging-app.message-service', 5 | 'secure-messaging-app.user-service', 6 | 'secure-messaging-app.router', 7 | 'secure-messaging-app.util', 8 | 'ngAnimate', 9 | 'underscore', 10 | 'ui.bootstrap' 11 | ]) 12 | 13 | .controller('inboxMessagesController', ['$scope', '$rootScope', '$location', 'messageEndpoints', 'messageService', 'commonService', 'alertService', function ($scope, $rootScope, $location, messageEndpoints, messageService, commonService, alertService) { 14 | 15 | $scope.messageListType = "Inbox"; 16 | 17 | $scope.viewMessage = function(message) { 18 | messageService.viewMessage(message); 19 | }; 20 | 21 | $scope.deleteMessage = function(message) { 22 | messageService.deleteMessage(message, function(message, deleted) { 23 | if (deleted) { 24 | $scope.messages = _.reject($scope.messages, function(msg) { 25 | return msg.id == message.id 26 | }); 27 | } 28 | }); 29 | }; 30 | 31 | $scope.getMessages = function() { 32 | messageEndpoints.inbox.query() 33 | .$promise 34 | .then(function(result) { 35 | $scope.messages = result; 36 | }) 37 | .catch(function(error) { 38 | // console.log("***** Error retrieving inbox messages: " + JSON.stringify(error.data)); 39 | // alertService.openModal({title : "Error", message : "An error occurred while retrieving inbox messages."}); 40 | }); 41 | }; 42 | 43 | var init = function() { 44 | $scope.getMessages(); 45 | }; 46 | 47 | init(); 48 | }]) 49 | 50 | .controller('sentMessagesController', ['$scope', '$rootScope', '$location', 'messageEndpoints', 'messageService', 'commonService', 'alertService', function ($scope, $rootScope, $location, messageEndpoints, messageService, commonService, alertService) { 51 | 52 | $scope.messageListType = "Sent"; 53 | 54 | $scope.viewMessage = function(message) { 55 | messageService.viewMessage(message); 56 | }; 57 | 58 | $scope.deleteMessage = function(message) { 59 | messageService.deleteMessage(message, function(message, deleted) { 60 | if (deleted) { 61 | $scope.messages = _.reject($scope.messages, function(msg) { 62 | return msg.id == message.id 63 | }); 64 | } 65 | }); 66 | }; 67 | 68 | $scope.getMessages = function() { 69 | messageEndpoints.sent.query() 70 | .$promise 71 | .then(function(result) { 72 | $scope.messages = result; 73 | }) 74 | .catch(function(error) { 75 | // console.log("***** Error retrieving sent messages: " + JSON.stringify(error.data)); 76 | // alertService.openModal({title : "Error", message : "An error occurred while retrieving sent messages."}); 77 | }); 78 | }; 79 | 80 | var init = function() { 81 | $scope.getMessages(); 82 | }; 83 | 84 | init(); 85 | }]) 86 | 87 | .controller('viewMessageController', ['$scope', 'commonService', function ($scope, commonService) { 88 | 89 | $scope.$on(commonService.EVENT_TYPES.CURRENT_MESSAGE_CHANGE_EVENT, function() { 90 | console.log("***** " + commonService.EVENT_TYPES.CURRENT_MESSAGE_CHANGE_EVENT); 91 | init(); 92 | }); 93 | 94 | var init = function() { 95 | $scope.currentMessage = commonService.getProperty(commonService.CURRENT_MESSAGE_KEY); 96 | $scope.lastView = $scope.currentActiveLink 97 | }; 98 | 99 | init(); 100 | }]) 101 | 102 | .controller('composeMessageController', ['$scope', '$rootScope', '$location', 'userEndpoint', 'messageEndpoints', 'messageService', 'commonService', 'alertService', function ($scope, $rootScope, $location, userEndpoint, messageEndpoints, messageService, commonService, alertService) { 103 | 104 | $scope.getAllUsers = function() { 105 | userEndpoint.query() 106 | .$promise 107 | .then(function(result) { 108 | $scope.users = result; 109 | }) 110 | .catch(function(error) { 111 | // console.log("***** Error retrieving the list of users: " + JSON.stringify(error.data)); 112 | // alertService.openModal({title : "Error", message : "An error occurred while retrieving the list of users."}); 113 | }); 114 | }; 115 | 116 | $scope.sendMessage = function() { 117 | console.log("***** Send Message: " + JSON.stringify($scope.newMessage)); 118 | messageEndpoints.resource.save({}, $scope.newMessage) 119 | .$promise 120 | .then(function(result) { 121 | console.log("***** Message Sent"); 122 | alertService.openModal({title : "Success", message : "The message was successfully sent."}); 123 | $location.path('/sent'); 124 | }) 125 | .catch(function(error) { 126 | console.log("***** Error Sending Message: " + JSON.stringify(error.data)); 127 | alertService.openModal({title : "Error", message : "An error occurred while attempting to send the message."}); 128 | }); 129 | 130 | }; 131 | 132 | var init = function() { 133 | $scope.newMessage = {}; 134 | $scope.getAllUsers(); 135 | }; 136 | 137 | init(); 138 | }]); 139 | -------------------------------------------------------------------------------- /rest/src/main/resources/static/assets/js/app/router.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | angular.module('secure-messaging-app.router', ['ngRoute', 4 | 'secure-messaging-app.message-controllers' 5 | ]) 6 | 7 | .config(['$routeProvider', function($routeProvider) { 8 | $routeProvider 9 | 10 | .when('/login', { 11 | templateUrl : 'assets/js/common/partials/login.tpl.html', 12 | controller : 'loginController' 13 | }) 14 | 15 | .when('/inbox', { 16 | templateUrl : 'assets/js/app/message/message-list.tpl.html', 17 | controller : 'inboxMessagesController' 18 | }) 19 | 20 | .when('/sent', { 21 | templateUrl : 'assets/js/app/message/message-list.tpl.html', 22 | controller : 'sentMessagesController' 23 | }) 24 | 25 | .when('/compose', { 26 | templateUrl : 'assets/js/app/message/message-compose.tpl.html', 27 | controller : 'composeMessageController' 28 | }) 29 | 30 | .when('/view', { 31 | templateUrl : 'assets/js/app/message/message-view.tpl.html', 32 | controller : 'viewMessageController' 33 | }) 34 | 35 | .otherwise({redirectTo:'/inbox'}); 36 | }]) 37 | ; -------------------------------------------------------------------------------- /rest/src/main/resources/static/assets/js/app/util.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | angular.module('secure-messaging-app.util', [ 4 | 'ngAnimate', 5 | 'ui.bootstrap' 6 | ]) 7 | 8 | .factory('commonService', function() { 9 | 10 | var _properties = {}; 11 | 12 | var _eventTypes = { 13 | CURRENT_MESSAGE_CHANGE_EVENT : 'CURRENT_MESSAGE_CHANGE_EVENT', 14 | CURRENT_PRINCIPAL_CHANGE_EVENT : 'CURRENT_PRINCIPAL_CHANGE_EVENT' 15 | }; 16 | 17 | return { 18 | CURRENT_MESSAGE_KEY: 'CURRENT_MESSAGE', 19 | 20 | CURRENT_PRINCIPAL_KEY : 'CURRENT_PRINCIPAL', 21 | 22 | EVENT_TYPES: _eventTypes, 23 | 24 | setProperty: function (key, value) { 25 | _properties[key] = value; 26 | }, 27 | 28 | getProperty: function (key) { 29 | return _properties[key]; 30 | } 31 | } 32 | }) 33 | 34 | .controller('alertModalController', ['$scope', '$uibModalInstance', 'modalData', function($scope, $uibModalInstance, modalData) { 35 | $scope.alertTitle = modalData.title; 36 | $scope.alertMessage = modalData.message; 37 | 38 | $scope.ok = function() { 39 | $uibModalInstance.close(); 40 | } 41 | }]) 42 | 43 | .factory('alertService', ['$uibModal', function($uibModal) { 44 | function openModal(args) { 45 | $uibModal.open({ 46 | animation: true, 47 | templateUrl: 'assets/js/common/partials/alertModal.tpl.html', 48 | controller: 'alertModalController', 49 | resolve: { 50 | modalData: function () { 51 | var modalData = { 52 | title : args.title, 53 | message : args.message 54 | }; 55 | return modalData; 56 | } 57 | } 58 | }); 59 | } 60 | 61 | return { 62 | openModal : openModal 63 | } 64 | }]) 65 | ; -------------------------------------------------------------------------------- /rest/src/main/resources/static/assets/js/app/xss-app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('xss-demo-app', []) 4 | 5 | .controller('xssDemoController', ['$scope', function ($scope) { 6 | $scope.updateCurrentActiveLink = function (clickedLink) { 7 | $scope.currentActiveLink = clickedLink; 8 | }; 9 | 10 | $scope.isCurrentActiveLink = function (link) { 11 | return $scope.currentActiveLink === link; 12 | }; 13 | 14 | $scope.logout = function() { 15 | }; 16 | 17 | var init = function() { 18 | $scope.currentPrincipal = {}; 19 | $scope.currentPrincipal.firstName = ''; 20 | $scope.currentPrincipal.lastName = ''; 21 | }; 22 | 23 | init(); 24 | }]) 25 | 26 | .directive('headerDirective', function() { 27 | return { 28 | templateUrl : 'assets/js/common/directives/header/header.tpl.html' 29 | }; 30 | }) 31 | ; -------------------------------------------------------------------------------- /rest/src/main/resources/static/assets/js/common/directives/header/header.tpl.html: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /rest/src/main/resources/static/assets/js/common/partials/alertModal.tpl.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rest/src/main/resources/static/assets/js/common/partials/login.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

SecureMail Login

4 |
5 |
6 |
7 | 8 |
9 |
10 |
11 |
12 | 13 |
14 |
15 |
16 |
17 | 18 |
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /rest/src/main/resources/static/assets/js/common/services/message-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('secure-messaging-app.message-service', [ 4 | 'secure-messaging-app.util', 5 | 'ngResource' 6 | ]) 7 | 8 | .factory('messageEndpoints', ['$resource', 'BASE_API_ENDPOINT', function($resource, BASE_API_ENDPOINT) { 9 | var messageEndpoints = { 10 | resource : $resource(BASE_API_ENDPOINT + 'messages/:id', { id : '@id' }), 11 | 12 | inbox : $resource(BASE_API_ENDPOINT + 'messages/inbox', {}, { 13 | query: { 14 | method : 'GET', 15 | isArray: true 16 | } 17 | }), 18 | 19 | sent : $resource(BASE_API_ENDPOINT + 'messages/sent', {}, { 20 | query: { 21 | method : 'GET', 22 | isArray: true 23 | } 24 | }) 25 | 26 | }; 27 | 28 | return messageEndpoints; 29 | 30 | }]) 31 | 32 | .factory('messageService', ['$rootScope', '$location', 'messageEndpoints', 'commonService', 'alertService', function ($rootScope, $location, messageEndpoints, commonService, alertService) { 33 | var viewMessage = function(message) { 34 | console.log("***** View Message: " + JSON.stringify(message)); 35 | commonService.setProperty(commonService.CURRENT_MESSAGE_KEY, message); 36 | $location.path('/view'); 37 | $rootScope.$broadcast(commonService.EVENT_TYPES.CURRENT_MESSAGE_CHANGE_EVENT); 38 | }; 39 | 40 | var deleteMessage = function(message, callback) { 41 | console.log("***** Delete Message: " + JSON.stringify(message)); 42 | messageEndpoints.resource.delete({ id: message.id }) 43 | .$promise 44 | .then(function(result) { 45 | console.log("***** Message Deleted"); 46 | commonService.setProperty(commonService.CURRENT_MESSAGE_KEY, null); 47 | $rootScope.$broadcast(commonService.EVENT_TYPES.CURRENT_MESSAGE_CHANGE_EVENT); 48 | callback(message, true); 49 | alertService.openModal({title : "Success", message : "The message was successfully deleted."}); 50 | }) 51 | .catch(function(error) { 52 | console.log("***** Error Deleting Message: " + JSON.stringify(error.data)); 53 | callback(message, false); 54 | alertService.openModal({title : "Error", message : "An error occurred while attempting to delete the message."}); 55 | }); 56 | }; 57 | 58 | return { 59 | viewMessage: viewMessage, 60 | deleteMessage: deleteMessage 61 | } 62 | }]) 63 | ; 64 | -------------------------------------------------------------------------------- /rest/src/main/resources/static/assets/js/common/services/security-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('secure-messaging-app.security-service', [ 4 | 'secure-messaging-app.util', 5 | 'ngResource' 6 | ]) 7 | 8 | .factory('securityEndpoints', ['$resource', 'BASE_API_ENDPOINT', function($resource, BASE_API_ENDPOINT) { 9 | var securityEndpoints = { 10 | currentPrincipal : $resource(BASE_API_ENDPOINT + 'principal', {}, { 11 | query: { 12 | method : 'GET', 13 | isArray: false 14 | } 15 | }), 16 | 17 | logout : $resource(BASE_API_ENDPOINT + 'logout'), 18 | 19 | login : $resource(BASE_API_ENDPOINT + 'login?username=:username&password=:password', { username : '@username', password : '@password' }) 20 | }; 21 | 22 | return securityEndpoints; 23 | 24 | }]) 25 | 26 | .factory('basicAuthEndpoint', function ($resource) { 27 | return function(authHeader) { 28 | return $resource('/principal', {}, { 29 | get: { 30 | method: 'GET', 31 | params: {}, 32 | isArray: false, 33 | headers: authHeader || {} 34 | } 35 | }); 36 | }; 37 | }) 38 | 39 | .factory('securityService', ['$rootScope', '$location', 'securityEndpoints', 'commonService', 'alertService', 'basicAuthEndpoint', 'base64', function ($rootScope, $location, securityEndpoints, commonService, alertService, basicAuthEndpoint, base64) { 40 | var currentPrincipal = function() { 41 | console.log("***** Current Principal"); 42 | securityEndpoints.currentPrincipal.query() 43 | .$promise 44 | .then(function(result) { 45 | commonService.setProperty(commonService.CURRENT_PRINCIPAL_KEY, result); 46 | $rootScope.$broadcast(commonService.EVENT_TYPES.CURRENT_PRINCIPAL_CHANGE_EVENT); 47 | }) 48 | .catch(function(error) { 49 | console.log("***** Error Current Principal: " + JSON.stringify(error.data)); 50 | alertService.openModal({title : "Error", message : "An error occurred while attempting to retrieve the current principal."}); 51 | }); 52 | }; 53 | 54 | var logout = function() { 55 | console.log("***** Logout"); 56 | securityEndpoints.logout.save() 57 | .$promise 58 | .then(function(result) { 59 | console.log("***** Logout Success"); 60 | commonService.setProperty(commonService.CURRENT_PRINCIPAL_KEY, null); 61 | $rootScope.$broadcast(commonService.EVENT_TYPES.CURRENT_PRINCIPAL_CHANGE_EVENT); 62 | window.location.href = "/"; 63 | }) 64 | .catch(function(error) { 65 | console.log("***** Logout Failed: " + JSON.stringify(error.data)); 66 | alertService.openModal({title : "Error", message : "An error occurred while attempting to logout."}); 67 | }); 68 | }; 69 | 70 | var login = function(auth, callback) { 71 | console.log("***** Login"); 72 | var basicAuthHeader = {"Authorization" : "Basic " + base64.encode(auth.username + ":" + auth.password)}; 73 | basicAuthEndpoint(basicAuthHeader).get( 74 | function(result) { 75 | console.log("***** Login Success"); 76 | callback(result, true); 77 | commonService.setProperty(commonService.CURRENT_PRINCIPAL_KEY, result); 78 | $rootScope.$broadcast(commonService.EVENT_TYPES.CURRENT_PRINCIPAL_CHANGE_EVENT); 79 | }, 80 | function() { 81 | console.log("***** Login Failed"); 82 | callback(null, false); 83 | alertService.openModal({title: "Error", message: "An error occurred while attempting to login."}); 84 | }); 85 | }; 86 | 87 | return { 88 | currentPrincipal: currentPrincipal, 89 | logout: logout, 90 | login : login 91 | } 92 | }]) 93 | 94 | .factory('base64', function() { 95 | var keyStr = 'ABCDEFGHIJKLMNOP' + 96 | 'QRSTUVWXYZabcdef' + 97 | 'ghijklmnopqrstuv' + 98 | 'wxyz0123456789+/' + 99 | '='; 100 | return { 101 | encode: function (input) { 102 | var output = ""; 103 | var chr1, chr2, chr3 = ""; 104 | var enc1, enc2, enc3, enc4 = ""; 105 | var i = 0; 106 | 107 | do { 108 | chr1 = input.charCodeAt(i++); 109 | chr2 = input.charCodeAt(i++); 110 | chr3 = input.charCodeAt(i++); 111 | 112 | enc1 = chr1 >> 2; 113 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 114 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 115 | enc4 = chr3 & 63; 116 | 117 | if (isNaN(chr2)) { 118 | enc3 = enc4 = 64; 119 | } else if (isNaN(chr3)) { 120 | enc4 = 64; 121 | } 122 | 123 | output = output + 124 | keyStr.charAt(enc1) + 125 | keyStr.charAt(enc2) + 126 | keyStr.charAt(enc3) + 127 | keyStr.charAt(enc4); 128 | chr1 = chr2 = chr3 = ""; 129 | enc1 = enc2 = enc3 = enc4 = ""; 130 | } while (i < input.length); 131 | 132 | return output; 133 | }, 134 | 135 | decode: function (input) { 136 | var output = ""; 137 | var chr1, chr2, chr3 = ""; 138 | var enc1, enc2, enc3, enc4 = ""; 139 | var i = 0; 140 | 141 | // remove all characters that are not A-Z, a-z, 0-9, +, /, or = 142 | var base64test = /[^A-Za-z0-9\+\/\=]/g; 143 | if (base64test.exec(input)) { 144 | alert("There were invalid base64 characters in the input text.\n" + 145 | "Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\n" + 146 | "Expect errors in decoding."); 147 | } 148 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 149 | 150 | do { 151 | enc1 = keyStr.indexOf(input.charAt(i++)); 152 | enc2 = keyStr.indexOf(input.charAt(i++)); 153 | enc3 = keyStr.indexOf(input.charAt(i++)); 154 | enc4 = keyStr.indexOf(input.charAt(i++)); 155 | 156 | chr1 = (enc1 << 2) | (enc2 >> 4); 157 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 158 | chr3 = ((enc3 & 3) << 6) | enc4; 159 | 160 | output = output + String.fromCharCode(chr1); 161 | 162 | if (enc3 != 64) { 163 | output = output + String.fromCharCode(chr2); 164 | } 165 | if (enc4 != 64) { 166 | output = output + String.fromCharCode(chr3); 167 | } 168 | 169 | chr1 = chr2 = chr3 = ""; 170 | enc1 = enc2 = enc3 = enc4 = ""; 171 | 172 | } while (i < input.length); 173 | 174 | return output; 175 | } 176 | }; 177 | }) 178 | ; -------------------------------------------------------------------------------- /rest/src/main/resources/static/assets/js/common/services/underscore.js: -------------------------------------------------------------------------------- 1 | angular.module('underscore', []) 2 | 3 | .factory('_', ['$window', function() { 4 | return $window._; 5 | }]) 6 | ; -------------------------------------------------------------------------------- /rest/src/main/resources/static/assets/js/common/services/user-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('secure-messaging-app.user-service', ['ngResource']) 4 | 5 | .factory('userEndpoint', ['$resource', 'BASE_API_ENDPOINT', function($resource, BASE_API_ENDPOINT) { 6 | var userEndpoint = $resource(BASE_API_ENDPOINT + 'users/:id', { id : '@id' }); 7 | 8 | return userEndpoint; 9 | 10 | }]) 11 | ; -------------------------------------------------------------------------------- /rest/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SecureMail: Spring Session 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 |
18 |
19 |
20 | 21 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /rest/src/main/resources/templates/xss.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SecureMail: Spring Session XSS Demo 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 |
19 |

Message

20 |
21 |
22 |
To:
23 |
24 |
25 |
26 |
Subject:
27 |
28 |
29 |
30 |
Message:
31 |
32 |
33 |
34 |
Created:
35 |
36 |
37 |
38 |
39 | Done 40 |
41 |
42 |
43 |
44 |
45 | 46 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /rest/src/main/webapp/WEB-INF/jsp/xss/fix.jsp: -------------------------------------------------------------------------------- 1 | 2 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 3 | 4 | 5 | 6 | 7 |

8 |

9 | 10 |

11 |

12 | ">Process Text 13 |

14 |

15 | Safe | 16 | HTML | 17 | Contextual 18 |

19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /rest/src/main/webapp/WEB-INF/jsp/xss/jsp.jsp: -------------------------------------------------------------------------------- 1 | 2 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 3 | 4 | 5 | 6 | 7 | 12 |

13 |

14 | ${message.text} 15 |

16 |

17 | ')">Process Text 18 |

19 |

20 | Safe | 21 | HTML | 22 | Contextual 23 |

24 | 25 | 26 | -------------------------------------------------------------------------------- /rest/src/test/java/sample/JsonUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package sample; 17 | 18 | import com.fasterxml.jackson.core.type.TypeReference; 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | 21 | import java.io.IOException; 22 | 23 | /** 24 | * @author Joe Grandja 25 | */ 26 | public final class JsonUtil { 27 | private static ObjectMapper objectMapper = new ObjectMapper(); 28 | 29 | private JsonUtil() { 30 | } 31 | 32 | public static T readValue(String content, TypeReference typeReference) throws IOException { 33 | T value = objectMapper.readValue(content, typeReference); 34 | return value; 35 | } 36 | 37 | public static T readValue(String content, Class valueType) throws IOException { 38 | T value = objectMapper.readValue(content, valueType); 39 | return value; 40 | } 41 | 42 | public static String writeValue(Object value) throws IOException { 43 | String valueStr = objectMapper.writeValueAsString(value); 44 | return valueStr; 45 | } 46 | } -------------------------------------------------------------------------------- /rest/src/test/java/sample/SpringSecurityApplicationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package sample; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 20 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; 21 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 22 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 23 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 24 | 25 | import java.util.Calendar; 26 | import java.util.List; 27 | 28 | import org.junit.Test; 29 | import org.junit.runner.RunWith; 30 | import org.springframework.beans.factory.annotation.Autowired; 31 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 32 | import org.springframework.boot.test.context.SpringBootTest; 33 | import org.springframework.http.MediaType; 34 | import org.springframework.test.annotation.DirtiesContext; 35 | import org.springframework.security.test.context.support.WithAnonymousUser; 36 | import org.springframework.security.test.context.support.WithMockUser; 37 | import org.springframework.security.test.context.support.WithUserDetails; 38 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 39 | import org.springframework.test.web.servlet.MockMvc; 40 | import org.springframework.test.web.servlet.MvcResult; 41 | import org.springframework.transaction.annotation.Transactional; 42 | 43 | import com.fasterxml.jackson.core.type.TypeReference; 44 | 45 | import sample.data.Message; 46 | import sample.data.User; 47 | 48 | /** 49 | * 50 | * @author Rob Winch 51 | * @author Joe Grandja 52 | * 53 | */ 54 | @DirtiesContext 55 | @RunWith(SpringJUnit4ClassRunner.class) 56 | @SpringBootTest(classes = RestApplication.class) 57 | @AutoConfigureMockMvc 58 | @Transactional 59 | @WithUserDetails("joe@example.com") 60 | public class SpringSecurityApplicationTests { 61 | @Autowired 62 | MockMvc mockMvc; 63 | 64 | @Test 65 | @WithAnonymousUser 66 | public void securityEnabled() throws Exception { 67 | mockMvc 68 | .perform(get("messages/inbox") 69 | .header("X-Requested-With", "XMLHttpRequest")) 70 | .andExpect(status().isUnauthorized()); 71 | } 72 | 73 | @Test 74 | public void deleteJoesMessage() throws Exception { 75 | mockMvc.perform(delete("/messages/{id}", 110L) 76 | .header("X-Requested-With", "XMLHttpRequest") 77 | .with(csrf())) 78 | .andExpect(status().isOk()); 79 | } 80 | 81 | @Test 82 | public void getJoesInbox() throws Exception { 83 | MvcResult result = mockMvc.perform(get("/messages/inbox") 84 | .header("X-Requested-With", "XMLHttpRequest")) 85 | .andExpect(status().isOk()) 86 | .andReturn(); 87 | 88 | String json = result.getResponse().getContentAsString(); 89 | 90 | List messages = JsonUtil.readValue(json, new TypeReference>(){}); 91 | assertThat(messages.size()).isEqualTo(3); 92 | 93 | assertThat(messages).extracting(m-> m.getSummary()).containsOnly("Hello Joe","Greetings Joe", "Is this secure?"); 94 | } 95 | 96 | @Test 97 | public void getJoesSent() throws Exception { 98 | MvcResult result = mockMvc.perform(get("/messages/sent") 99 | .header("X-Requested-With", "XMLHttpRequest")) 100 | .andExpect(status().isOk()) 101 | .andReturn(); 102 | 103 | String json = result.getResponse().getContentAsString(); 104 | 105 | List messages = JsonUtil.readValue(json, new TypeReference>(){}); 106 | assertThat(messages).isNotEmpty(); 107 | 108 | assertThat(messages).extracting(m-> m.getSummary()).containsOnly("Hello Rob","How are you Rob?", "Is this secure?"); 109 | } 110 | 111 | @Test 112 | public void getMessage() throws Exception { 113 | MvcResult result = mockMvc.perform(get("/messages/{id}", 111L) 114 | .header("X-Requested-With", "XMLHttpRequest")) 115 | .andExpect(status().isOk()) 116 | .andReturn(); 117 | 118 | String json = result.getResponse().getContentAsString(); 119 | 120 | Message message = JsonUtil.readValue(json, new TypeReference(){}); 121 | 122 | assertThat(message.getSummary()).isEqualTo("Greetings Joe"); 123 | } 124 | 125 | @Test 126 | public void getUsers() throws Exception { 127 | MvcResult result = mockMvc.perform(get("/users") 128 | .header("X-Requested-With", "XMLHttpRequest")) 129 | .andExpect(status().isOk()) 130 | .andReturn(); 131 | 132 | String json = result.getResponse().getContentAsString(); 133 | 134 | List messages = JsonUtil.readValue(json, new TypeReference>(){}); 135 | assertThat(messages.size()).isEqualTo(3); 136 | 137 | assertThat(messages).extracting(m-> m.getEmail()).containsOnly("rob@example.com","joe@example.com", "eve@example.com"); 138 | } 139 | 140 | @Test 141 | public void save() throws Exception { 142 | Message toCreate = toCreate(); 143 | 144 | String body = JsonUtil.writeValue(toCreate); 145 | 146 | mockMvc.perform(post("/messages") 147 | .header("Content-Type", MediaType.APPLICATION_JSON_UTF8_VALUE) 148 | .with(csrf()) 149 | .content(body)) 150 | .andExpect(status().is2xxSuccessful()); 151 | } 152 | 153 | @Test 154 | public void saveValidationFails() throws Exception { 155 | Message toCreate = toCreate(); 156 | toCreate.setSummary(null); 157 | 158 | String body = JsonUtil.writeValue(toCreate); 159 | 160 | mockMvc.perform(post("/messages") 161 | .header("Content-Type", MediaType.APPLICATION_JSON_UTF8_VALUE) 162 | .with(csrf()) 163 | .content(body)) 164 | .andExpect(status().isBadRequest()); 165 | } 166 | 167 | private Message toCreate() { 168 | Message toCreate = new Message(); 169 | toCreate.setCreated(Calendar.getInstance()); 170 | toCreate.setSummary("This is a test.."); 171 | toCreate.setText("of the emergency broadcast system"); 172 | toCreate.setTo(rob()); 173 | return toCreate; 174 | } 175 | 176 | public static User rob() { 177 | User user = new User(); 178 | user.setEmail("rob@example.com"); 179 | user.setFirstName("Rob"); 180 | user.setLastName("Winch"); 181 | user.setId(0L); 182 | return user; 183 | } 184 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':rest' -------------------------------------------------------------------------------- /ui/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url 'https://repo.spring.io/libs-snapshot' } 4 | mavenCentral() 5 | } 6 | dependencies { 7 | classpath("org.springframework.boot:spring-boot-gradle-plugin:1.4.0.RELEASE") 8 | } 9 | } 10 | 11 | apply plugin: 'java' 12 | apply plugin: 'spring-boot' 13 | apply plugin: 'eclipse' 14 | apply plugin: 'idea' 15 | 16 | group = 'samples' 17 | 18 | sourceCompatibility = 1.8 19 | targetCompatibility = 1.8 20 | 21 | repositories { 22 | maven { url 'https://repo.spring.io/libs-snapshot' } 23 | } 24 | 25 | dependencies { 26 | compile "org.springframework.boot:spring-boot-starter-web", 27 | "org.springframework.boot:spring-boot-devtools", 28 | "org.springframework.boot:spring-boot-starter-thymeleaf", 29 | "org.webjars:webjars-locator", 30 | "org.webjars:angularjs:1.4.9", 31 | "org.webjars:bootstrap:3.3.6", 32 | "org.webjars:jquery:1.11.3", 33 | "javax.servlet:jstl" 34 | 35 | testCompile "org.springframework.boot:spring-boot-starter-test" 36 | 37 | } -------------------------------------------------------------------------------- /ui/src/main/java/sample/UiApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package sample; 17 | 18 | import org.springframework.boot.SpringApplication; 19 | import org.springframework.boot.autoconfigure.SpringBootApplication; 20 | 21 | /** 22 | * @author Rob Winch 23 | */ 24 | @SpringBootApplication 25 | public class UiApplication { 26 | 27 | public static void main(String[] args) { 28 | SpringApplication.run(UiApplication.class, args); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ui/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 -------------------------------------------------------------------------------- /ui/src/main/resources/static/assets/css/custom.css: -------------------------------------------------------------------------------- 1 | html { 2 | position: relative; 3 | min-height: 100%; 4 | } 5 | 6 | body { 7 | /* Margin bottom by footer height + 50px of padding*/ 8 | margin-bottom: 110px; 9 | } 10 | 11 | footer { 12 | position: absolute; 13 | bottom: 0; 14 | width: 100%; 15 | height: 60px; 16 | padding: 20px 0; 17 | } 18 | 19 | footer a { 20 | text-decoration: underline; 21 | } 22 | 23 | footer > .container { 24 | padding-right: 15px; 25 | padding-left: 15px; 26 | } 27 | 28 | .form-message-compose .form-group { 29 | padding: 7px; 30 | } 31 | 32 | .message-view > .row { 33 | padding: 7px; 34 | } 35 | 36 | .message-view .message-label { 37 | font-weight: bold; 38 | } 39 | 40 | .modal-dialog { 41 | width: 450px; 42 | } 43 | 44 | .modal-header, .modal-footer { 45 | border: none; 46 | } 47 | 48 | .modal-title { 49 | color: #B0B0AF; 50 | font-size: 24px; 51 | padding-left: 20px; 52 | } 53 | 54 | .modal-body { 55 | font-size: 14px; 56 | font-weight: normal; 57 | } 58 | 59 | .modal-footer { 60 | text-align: right; 61 | } 62 | 63 | .modal-footer .modal-button-ok, .modal-button-cancel { 64 | width: 100px; 65 | border: none; 66 | border-radius: 0; 67 | } -------------------------------------------------------------------------------- /ui/src/main/resources/static/assets/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwinch/spring-security-4.1-and-beyond/2142178824477ac97e3353366d7cff5cc633f187/ui/src/main/resources/static/assets/img/favicon.ico -------------------------------------------------------------------------------- /ui/src/main/resources/static/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwinch/spring-security-4.1-and-beyond/2142178824477ac97e3353366d7cff5cc633f187/ui/src/main/resources/static/assets/img/logo.png -------------------------------------------------------------------------------- /ui/src/main/resources/static/assets/js/app/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('secure-messaging-app', [ 4 | 'secure-messaging-app.router', 5 | 'secure-messaging-app.security-service', 6 | 'secure-messaging-app.util', 7 | 'ui.bootstrap', 8 | 'underscore' 9 | ]) 10 | 11 | .config(['$httpProvider', function($httpProvider) { 12 | $httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 13 | }]) 14 | 15 | .constant('BASE_API_ENDPOINT', 'http://localhost:8081/') 16 | 17 | .controller('appController', ['$scope', '$location', 'commonService', 'securityService', function ($scope, $location, commonService, securityService) { 18 | $scope.updateCurrentActiveLink = function (clickedLink) { 19 | $scope.currentActiveLink = clickedLink; 20 | }; 21 | 22 | $scope.isCurrentActiveLink = function (link) { 23 | return $scope.currentActiveLink === link; 24 | }; 25 | 26 | $scope.$on(commonService.EVENT_TYPES.CURRENT_PRINCIPAL_CHANGE_EVENT, function() { 27 | console.log("***** " + commonService.EVENT_TYPES.CURRENT_PRINCIPAL_CHANGE_EVENT); 28 | $scope.currentPrincipal = commonService.getProperty(commonService.CURRENT_PRINCIPAL_KEY); 29 | }); 30 | 31 | $scope.getCurrentPrincipal = function() { 32 | securityService.currentPrincipal(); 33 | }; 34 | 35 | $scope.logout = function() { 36 | securityService.logout(); 37 | }; 38 | 39 | $scope.goTo = function(view) { 40 | $location.path(view); 41 | }; 42 | 43 | var init = function() { 44 | $scope.getCurrentPrincipal(); 45 | }; 46 | 47 | init(); 48 | 49 | }]) 50 | 51 | .directive('headerDirective', function() { 52 | return { 53 | templateUrl : 'assets/js/common/directives/header/header.tpl.html' 54 | }; 55 | }) 56 | ; -------------------------------------------------------------------------------- /ui/src/main/resources/static/assets/js/app/message/message-compose.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Compose

4 |
5 | 6 |
7 | 11 |
12 |
13 |
14 | 15 |
16 | 17 |
18 |
19 |
20 | 21 |
22 | 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 |
-------------------------------------------------------------------------------- /ui/src/main/resources/static/assets/js/app/message/message-list.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |

{{messageListType}}

3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
CreatedSummaryDelete
{{ message.created | date:'MM/dd/yyyy' }}{{message.summary}}Delete
20 |
21 |
-------------------------------------------------------------------------------- /ui/src/main/resources/static/assets/js/app/message/message-view.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |

Message

3 |
4 |
5 |
To:
6 |
{{currentMessage.toUser.firstName + ' ' + currentMessage.toUser.lastName}}
7 |
8 |
9 |
Subject:
10 |
{{currentMessage.summary}}
11 |
12 |
13 |
Message:
14 |
{{currentMessage.text}}
15 |
16 |
17 |
Created:
18 |
{{currentMessage.created | date:'MM/dd/yyyy'}}
19 |
20 |
21 |
22 | Done 23 |
24 |
25 |
26 |
-------------------------------------------------------------------------------- /ui/src/main/resources/static/assets/js/app/message/message.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | angular.module('secure-messaging-app.message-controllers', [ 4 | 'secure-messaging-app.message-service', 5 | 'secure-messaging-app.user-service', 6 | 'secure-messaging-app.router', 7 | 'secure-messaging-app.util', 8 | 'ngAnimate', 9 | 'underscore', 10 | 'ui.bootstrap' 11 | ]) 12 | 13 | .controller('inboxMessagesController', ['$scope', '$rootScope', '$location', 'messageEndpoints', 'messageService', 'commonService', 'alertService', function ($scope, $rootScope, $location, messageEndpoints, messageService, commonService, alertService) { 14 | 15 | $scope.messageListType = "Inbox"; 16 | 17 | $scope.viewMessage = function(message) { 18 | messageService.viewMessage(message); 19 | }; 20 | 21 | $scope.deleteMessage = function(message) { 22 | messageService.deleteMessage(message, function(message, deleted) { 23 | if (deleted) { 24 | $scope.messages = _.reject($scope.messages, function(msg) { 25 | return msg.id == message.id 26 | }); 27 | } 28 | }); 29 | }; 30 | 31 | $scope.getMessages = function() { 32 | messageEndpoints.inbox.query() 33 | .$promise 34 | .then(function(result) { 35 | $scope.messages = result; 36 | }) 37 | .catch(function(error) { 38 | console.log("***** Error retrieving inbox messages: " + JSON.stringify(error.data)); 39 | alertService.openModal({title : "Error", message : "An error occurred while retrieving inbox messages."}); 40 | }); 41 | }; 42 | 43 | var init = function() { 44 | $scope.getMessages(); 45 | }; 46 | 47 | init(); 48 | }]) 49 | 50 | .controller('sentMessagesController', ['$scope', '$rootScope', '$location', 'messageEndpoints', 'messageService', 'commonService', 'alertService', function ($scope, $rootScope, $location, messageEndpoints, messageService, commonService, alertService) { 51 | 52 | $scope.messageListType = "Sent"; 53 | 54 | $scope.viewMessage = function(message) { 55 | messageService.viewMessage(message); 56 | }; 57 | 58 | $scope.deleteMessage = function(message) { 59 | messageService.deleteMessage(message, function(message, deleted) { 60 | if (deleted) { 61 | $scope.messages = _.reject($scope.messages, function(msg) { 62 | return msg.id == message.id 63 | }); 64 | } 65 | }); 66 | }; 67 | 68 | $scope.getMessages = function() { 69 | messageEndpoints.sent.query() 70 | .$promise 71 | .then(function(result) { 72 | $scope.messages = result; 73 | }) 74 | .catch(function(error) { 75 | console.log("***** Error retrieving sent messages: " + JSON.stringify(error.data)); 76 | alertService.openModal({title : "Error", message : "An error occurred while retrieving sent messages."}); 77 | }); 78 | }; 79 | 80 | var init = function() { 81 | $scope.getMessages(); 82 | }; 83 | 84 | init(); 85 | }]) 86 | 87 | .controller('viewMessageController', ['$scope', 'commonService', function ($scope, commonService) { 88 | 89 | $scope.$on(commonService.EVENT_TYPES.CURRENT_MESSAGE_CHANGE_EVENT, function() { 90 | console.log("***** " + commonService.EVENT_TYPES.CURRENT_MESSAGE_CHANGE_EVENT); 91 | init(); 92 | }); 93 | 94 | var init = function() { 95 | $scope.currentMessage = commonService.getProperty(commonService.CURRENT_MESSAGE_KEY); 96 | $scope.lastView = $scope.currentActiveLink 97 | }; 98 | 99 | init(); 100 | }]) 101 | 102 | .controller('composeMessageController', ['$scope', '$rootScope', '$location', 'userEndpoint', 'messageEndpoints', 'messageService', 'commonService', 'alertService', function ($scope, $rootScope, $location, userEndpoint, messageEndpoints, messageService, commonService, alertService) { 103 | 104 | $scope.getAllUsers = function() { 105 | userEndpoint.query() 106 | .$promise 107 | .then(function(result) { 108 | $scope.users = result; 109 | }) 110 | .catch(function(error) { 111 | console.log("***** Error retrieving the list of users: " + JSON.stringify(error.data)); 112 | alertService.openModal({title : "Error", message : "An error occurred while retrieving the list of users."}); 113 | }); 114 | }; 115 | 116 | $scope.sendMessage = function() { 117 | console.log("***** Send Message: " + JSON.stringify($scope.newMessage)); 118 | messageEndpoints.resource.save({}, $scope.newMessage) 119 | .$promise 120 | .then(function(result) { 121 | console.log("***** Message Sent"); 122 | alertService.openModal({title : "Success", message : "The message was successfully sent."}); 123 | $location.path('/sent'); 124 | }) 125 | .catch(function(error) { 126 | console.log("***** Error Sending Message: " + JSON.stringify(error.data)); 127 | alertService.openModal({title : "Error", message : "An error occurred while attempting to send the message."}); 128 | }); 129 | 130 | }; 131 | 132 | var init = function() { 133 | $scope.newMessage = {}; 134 | $scope.getAllUsers(); 135 | }; 136 | 137 | init(); 138 | }]); 139 | -------------------------------------------------------------------------------- /ui/src/main/resources/static/assets/js/app/router.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | angular.module('secure-messaging-app.router', ['ngRoute', 4 | 'secure-messaging-app.message-controllers' 5 | ]) 6 | 7 | .config(['$routeProvider', function($routeProvider) { 8 | $routeProvider 9 | 10 | .when('/inbox', { 11 | templateUrl : 'assets/js/app/message/message-list.tpl.html', 12 | controller : 'inboxMessagesController' 13 | }) 14 | 15 | .when('/sent', { 16 | templateUrl : 'assets/js/app/message/message-list.tpl.html', 17 | controller : 'sentMessagesController' 18 | }) 19 | 20 | .when('/compose', { 21 | templateUrl : 'assets/js/app/message/message-compose.tpl.html', 22 | controller : 'composeMessageController' 23 | }) 24 | 25 | .when('/view', { 26 | templateUrl : 'assets/js/app/message/message-view.tpl.html', 27 | controller : 'viewMessageController' 28 | }) 29 | 30 | .otherwise({redirectTo:'/'}); 31 | }]) 32 | ; -------------------------------------------------------------------------------- /ui/src/main/resources/static/assets/js/app/util.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | angular.module('secure-messaging-app.util', [ 4 | 'ngAnimate', 5 | 'ui.bootstrap' 6 | ]) 7 | 8 | .factory('commonService', function() { 9 | 10 | var _properties = {}; 11 | 12 | var _eventTypes = { 13 | CURRENT_MESSAGE_CHANGE_EVENT : 'CURRENT_MESSAGE_CHANGE_EVENT', 14 | CURRENT_PRINCIPAL_CHANGE_EVENT : 'CURRENT_PRINCIPAL_CHANGE_EVENT' 15 | }; 16 | 17 | return { 18 | CURRENT_MESSAGE_KEY: 'CURRENT_MESSAGE', 19 | 20 | CURRENT_PRINCIPAL_KEY : 'CURRENT_PRINCIPAL', 21 | 22 | EVENT_TYPES: _eventTypes, 23 | 24 | setProperty: function (key, value) { 25 | _properties[key] = value; 26 | }, 27 | 28 | getProperty: function (key) { 29 | return _properties[key]; 30 | } 31 | } 32 | }) 33 | 34 | .controller('alertModalController', ['$scope', '$uibModalInstance', 'modalData', function($scope, $uibModalInstance, modalData) { 35 | $scope.alertTitle = modalData.title; 36 | $scope.alertMessage = modalData.message; 37 | 38 | $scope.ok = function() { 39 | $uibModalInstance.close(); 40 | } 41 | }]) 42 | 43 | .factory('alertService', ['$uibModal', function($uibModal) { 44 | function openModal(args) { 45 | $uibModal.open({ 46 | animation: true, 47 | templateUrl: 'assets/js/common/partials/alertModal.tpl.html', 48 | controller: 'alertModalController', 49 | resolve: { 50 | modalData: function () { 51 | var modalData = { 52 | title : args.title, 53 | message : args.message 54 | }; 55 | return modalData; 56 | } 57 | } 58 | }); 59 | } 60 | 61 | return { 62 | openModal : openModal 63 | } 64 | }]) 65 | ; -------------------------------------------------------------------------------- /ui/src/main/resources/static/assets/js/app/xss-app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('xss-demo-app', []) 4 | 5 | .controller('xssDemoController', ['$scope', function ($scope) { 6 | $scope.updateCurrentActiveLink = function (clickedLink) { 7 | $scope.currentActiveLink = clickedLink; 8 | }; 9 | 10 | $scope.isCurrentActiveLink = function (link) { 11 | return $scope.currentActiveLink === link; 12 | }; 13 | 14 | $scope.logout = function() { 15 | }; 16 | 17 | var init = function() { 18 | $scope.currentPrincipal = {}; 19 | $scope.currentPrincipal.firstName = ''; 20 | $scope.currentPrincipal.lastName = ''; 21 | }; 22 | 23 | init(); 24 | }]) 25 | 26 | .directive('headerDirective', function() { 27 | return { 28 | templateUrl : 'assets/js/common/directives/header/header.tpl.html' 29 | }; 30 | }) 31 | ; -------------------------------------------------------------------------------- /ui/src/main/resources/static/assets/js/common/directives/header/header.tpl.html: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /ui/src/main/resources/static/assets/js/common/partials/alertModal.tpl.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/src/main/resources/static/assets/js/common/services/message-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('secure-messaging-app.message-service', [ 4 | 'secure-messaging-app.util', 5 | 'ngResource' 6 | ]) 7 | 8 | .factory('messageEndpoints', ['$resource', 'BASE_API_ENDPOINT', function($resource, BASE_API_ENDPOINT) { 9 | var messageEndpoints = { 10 | resource : $resource(BASE_API_ENDPOINT + 'messages/:id', { id : '@id' }), 11 | 12 | inbox : $resource(BASE_API_ENDPOINT + 'messages/inbox', {}, { 13 | query: { 14 | method : 'GET', 15 | isArray: true 16 | } 17 | }), 18 | 19 | sent : $resource(BASE_API_ENDPOINT + 'messages/sent', {}, { 20 | query: { 21 | method : 'GET', 22 | isArray: true 23 | } 24 | }) 25 | 26 | }; 27 | 28 | return messageEndpoints; 29 | 30 | }]) 31 | 32 | .factory('messageService', ['$rootScope', '$location', 'messageEndpoints', 'commonService', 'alertService', function ($rootScope, $location, messageEndpoints, commonService, alertService) { 33 | var viewMessage = function(message) { 34 | console.log("***** View Message: " + JSON.stringify(message)); 35 | commonService.setProperty(commonService.CURRENT_MESSAGE_KEY, message); 36 | $location.path('/view'); 37 | $rootScope.$broadcast(commonService.EVENT_TYPES.CURRENT_MESSAGE_CHANGE_EVENT); 38 | }; 39 | 40 | var deleteMessage = function(message, callback) { 41 | console.log("***** Delete Message: " + JSON.stringify(message)); 42 | messageEndpoints.resource.delete({ id: message.id }) 43 | .$promise 44 | .then(function(result) { 45 | console.log("***** Message Deleted"); 46 | commonService.setProperty(commonService.CURRENT_MESSAGE_KEY, null); 47 | $rootScope.$broadcast(commonService.EVENT_TYPES.CURRENT_MESSAGE_CHANGE_EVENT); 48 | callback(message, true); 49 | alertService.openModal({title : "Success", message : "The message was successfully deleted."}); 50 | }) 51 | .catch(function(error) { 52 | console.log("***** Error Deleting Message: " + JSON.stringify(error.data)); 53 | callback(message, false); 54 | alertService.openModal({title : "Error", message : "An error occurred while attempting to delete the message."}); 55 | }); 56 | }; 57 | 58 | return { 59 | viewMessage: viewMessage, 60 | deleteMessage: deleteMessage 61 | } 62 | }]) 63 | ; 64 | -------------------------------------------------------------------------------- /ui/src/main/resources/static/assets/js/common/services/security-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('secure-messaging-app.security-service', [ 4 | 'secure-messaging-app.util', 5 | 'ngResource' 6 | ]) 7 | 8 | .factory('securityEndpoints', ['$resource', 'BASE_API_ENDPOINT', function($resource, BASE_API_ENDPOINT) { 9 | console.log("oops " + BASE_API_ENDPOINT); 10 | var securityEndpoints = { 11 | currentPrincipal : $resource(BASE_API_ENDPOINT + 'principal', {}, { 12 | query: { 13 | method : 'GET', 14 | isArray: false 15 | } 16 | }), 17 | 18 | logout : $resource(BASE_API_ENDPOINT + 'logout') 19 | }; 20 | 21 | return securityEndpoints; 22 | 23 | }]) 24 | 25 | .factory('securityService', ['$rootScope', '$location', 'securityEndpoints', 'commonService', 'alertService', function ($rootScope, $location, securityEndpoints, commonService, alertService) { 26 | var currentPrincipal = function() { 27 | console.log("***** Current Principal"); 28 | securityEndpoints.currentPrincipal.query() 29 | .$promise 30 | .then(function(result) { 31 | commonService.setProperty(commonService.CURRENT_PRINCIPAL_KEY, result); 32 | $rootScope.$broadcast(commonService.EVENT_TYPES.CURRENT_PRINCIPAL_CHANGE_EVENT); 33 | }) 34 | .catch(function(error) { 35 | console.log("***** Error Current Principal: " + JSON.stringify(error.data)); 36 | alertService.openModal({title : "Error", message : "An error occurred while attempting to retrieve the current principal."}); 37 | }); 38 | }; 39 | 40 | var logout = function() { 41 | console.log("***** Logout"); 42 | securityEndpoints.logout.save() 43 | .$promise 44 | .then(function(result) { 45 | console.log("***** Logout Success"); 46 | commonService.setProperty(commonService.CURRENT_PRINCIPAL_KEY, null); 47 | $rootScope.$broadcast(commonService.EVENT_TYPES.CURRENT_PRINCIPAL_CHANGE_EVENT); 48 | window.location.href = "/"; 49 | }) 50 | .catch(function(error) { 51 | console.log("***** Logout Failed: " + JSON.stringify(error.data)); 52 | alertService.openModal({title : "Error", message : "An error occurred while attempting to logout."}); 53 | }); 54 | }; 55 | 56 | return { 57 | currentPrincipal: currentPrincipal, 58 | logout: logout 59 | } 60 | }]) 61 | ; -------------------------------------------------------------------------------- /ui/src/main/resources/static/assets/js/common/services/underscore.js: -------------------------------------------------------------------------------- 1 | angular.module('underscore', []) 2 | 3 | .factory('_', ['$window', function() { 4 | return $window._; 5 | }]) 6 | ; -------------------------------------------------------------------------------- /ui/src/main/resources/static/assets/js/common/services/user-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('secure-messaging-app.user-service', ['ngResource']) 4 | 5 | .factory('userEndpoint', ['$resource', 'BASE_API_ENDPOINT', function($resource, BASE_API_ENDPOINT) { 6 | var userEndpoint = $resource(BASE_API_ENDPOINT + 'users/:id', { id : '@id' }); 7 | 8 | return userEndpoint; 9 | 10 | }]) 11 | ; -------------------------------------------------------------------------------- /ui/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SecureMail: Spring Session 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 |
18 |
19 |
20 | 21 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ui/src/main/resources/templates/custom-login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SecureMail: Spring Session 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |

SecureMail Login

18 |
19 |
20 | 23 |
24 |
25 |
26 | 27 |
28 |
29 |
30 |
31 | 32 |
33 |
34 |
35 |
36 | 37 |
38 |
39 |
40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /ui/src/main/resources/templates/xss.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SecureMail: Spring Session XSS Demo 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 |
19 |

Message

20 |
21 |
22 |
To:
23 |
24 |
25 |
26 |
Subject:
27 |
28 |
29 |
30 |
Message:
31 |
32 |
33 |
34 |
Created:
35 |
36 |
37 |
38 |
39 | Done 40 |
41 |
42 |
43 |
44 |
45 | 46 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /ui/src/main/webapp/WEB-INF/jsp/xss/fix.jsp: -------------------------------------------------------------------------------- 1 | 2 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 3 | 4 | 5 | 6 | 7 |

8 |

9 | 10 |

11 |

12 | ">Process Text 13 |

14 |

15 | Safe | 16 | HTML | 17 | Contextual 18 |

19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ui/src/main/webapp/WEB-INF/jsp/xss/jsp.jsp: -------------------------------------------------------------------------------- 1 | 2 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 3 | 4 | 5 | 6 | 7 | 12 |

13 |

14 | ${message.text} 15 |

16 |

17 | ')">Process Text 18 |

19 |

20 | Safe | 21 | HTML | 22 | Contextual 23 |

24 | 25 | 26 | --------------------------------------------------------------------------------