├── .gitignore ├── README.md ├── h2-create.sql ├── h2-drop.sql ├── keystore ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── jamesdbloom │ │ ├── configuration │ │ └── RootConfiguration.java │ │ ├── dao │ │ └── UserDAO.java │ │ ├── domain │ │ ├── EqualsHashCodeToString.java │ │ └── User.java │ │ ├── email │ │ ├── EmailConfiguration.java │ │ └── EmailService.java │ │ ├── security │ │ ├── CredentialValidation.java │ │ ├── SecurityConfig.java │ │ ├── SpringSecurityAuthenticationProvider.java │ │ └── SpringSecurityUserContext.java │ │ ├── uuid │ │ └── UUIDFactory.java │ │ └── web │ │ ├── configuration │ │ └── WebMvcConfiguration.java │ │ ├── controller │ │ ├── LandingPageController.java │ │ ├── LoginPageController.java │ │ ├── RegistrationController.java │ │ └── UpdatePasswordController.java │ │ └── interceptor │ │ └── bundling │ │ ├── AddBundlingModelToViewModelInterceptor.java │ │ └── WroModelHolder.java ├── resources │ ├── ebean.properties │ ├── email.properties │ ├── freemarker_implicit.ftl │ ├── log4j-config.xml │ ├── logback.xml │ ├── validation.properties │ └── web.properties └── webapp │ ├── WEB-INF │ ├── view │ │ ├── landing.ftl │ │ ├── layout │ │ │ ├── default.ftl │ │ │ └── settings.ftl │ │ ├── login.ftl │ │ ├── macro │ │ │ └── messages.ftl │ │ ├── message.ftl │ │ ├── register.ftl │ │ └── updatePassword.ftl │ ├── web.xml │ ├── wro.properties │ └── wro.xml │ ├── favicon.ico │ └── resources │ ├── css │ └── example.css │ ├── icon │ ├── apple-touch-icon.png │ └── favicon.ico │ └── js │ └── example.js └── test └── java └── org └── jamesdbloom ├── acceptance ├── BaseIntegrationTest.java ├── BasePage.java ├── PropertyMockingApplicationContextInitializer.java ├── landing │ └── LandingPageControllerIntegrationTest.java ├── login │ ├── LoginPage.java │ └── LoginPageControllerIntegrationTest.java ├── registration │ ├── RegistrationIntegrationTest.java │ └── RegistrationPage.java ├── security │ └── SecurityIntegrationTest.java └── updatepassword │ ├── UpdatePasswordIntegrationTest.java │ └── UpdatePasswordPage.java ├── domain └── UserTest.java ├── email ├── EmailServiceTest.java └── NewRegistrationEmail.java ├── integration └── SystemTest.java └── web ├── controller ├── LandingPageControllerTest.java └── LoginPageControllerTest.java └── interceptor └── bundling └── AddBundlingModelToViewModelInterceptorTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | *.sqlite 27 | *.trace.db 28 | 29 | # OS generated files # 30 | ###################### 31 | .DS_Store 32 | .DS_Store? 33 | ._* 34 | .Spotlight-V100 35 | .Trashes 36 | Icon? 37 | ehthumbs.db 38 | Thumbs.db 39 | 40 | # JetBrains config files # 41 | ########################## 42 | .idea 43 | *.iml 44 | 45 | # Eclipse config files # 46 | ######################## 47 | .project 48 | 49 | # Maven build artifacts files # 50 | ############################### 51 | target 52 | 53 | # Gradle build artifacts files # 54 | ################################ 55 | .gradle 56 | build 57 | 58 | # Tomcat artifacts # 59 | #################### 60 | tomcat 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Base Spring MVC Web Application 2 | =============================== 3 | 4 | This project is a very simple base Spring MVC web application that can be used as a starting point to build any Spring MVC application. It was created to help the initial phase of starting a new project and wiring together the basic elements. In particular it contains the best practices in a number of areas as follows: 5 | 6 | - Servlet Best Practices 7 | - No web.xml all config using annotation (Servlet 3.0) - _(not yet implemented)_ 8 | - Spring Configuration Best Practices 9 | - JavaConfig configuration (where possible - i.e. everything except Spring Security) 10 | - Spring Security (with custom login page) 11 | - View / Client Best Practices 12 | - Hierachical freemarker (to split up / modularise views) 13 | - CSS at top 14 | - JS at bottum (loaded asynchronously) 15 | - Specifying favicon, apple-touch-icon 16 | - Minified & Bundled JS / CSS - that can be controlled from query string (bundled / unminified / disbaled) 17 | - Minified HTML 18 | - Long expiry time for resources (using fingerprint) - _(not fully working yet)_ 19 | - Testing Best Practices 20 | - BDD style approach 21 | - In-process automated acceptance tests (using new features in Spring 3.2 combined with JSoup) 22 | - Page Object (encapsulates page interaction) 23 | - Logging Best Practices 24 | - Logback 25 | - Symantic logging - _(not yet implemented)_ 26 | 27 | The intention is that this application is: 28 | - a simple example 29 | - a quick to start any Spring MVC web application 30 | - an easy way to follow best practice 31 | 32 | To achieve these aims this project will be kept up to date with the latest features on a regular basis. 33 | 34 | More information about this application can be found at: 35 | - JavaScript and CSS Minification With WRO4J - http://blog.jamesdbloom.com/JSAndCSSMinificationWithWRO4J.html 36 | - Testing Web Pages In Process - http://blog.jamesdbloom.com/TestingWebPagesInProcess.html 37 | - Using PropertySource & Environment - http://blog.jamesdbloom.com/UsingPropertySourceAndEnvironment.html 38 | 39 | **Upgraded for Spring 4.0.2** 40 | 41 | Previous version based on Spring 3.2.1 is still available under branch _spring_3_2_1_version_. 42 | 43 | **New Features Added** 44 | 45 | - Spring Security (via Java Config) 46 | - login / logout 47 | - user registration 48 | - update password 49 | - JPA Persistence via EBeans 50 | - Email sending (including integration testing via Wiser) 51 | - In-process Multi-Page Top-to-Bottom Integration Testing 52 | - All libraries upgrade (except WRO4J) 53 | 54 | **Remaining TODO Items** 55 | 56 | - Thymeleaf Templates (for rendering email HTML) 57 | - Replace web.xml with WebApplicationInitializer & WebMvcConfigurerAdapter (therefore removal of last xml files) 58 | - Upgrade WRO4J 59 | - Long expiry time for resources (using fingerprints) 60 | - Symantic logging using MDC (Mapped Diagnostic Context) 61 | -------------------------------------------------------------------------------- /h2-create.sql: -------------------------------------------------------------------------------- 1 | create table user ( 2 | id varchar(255) not null, 3 | name varchar(255), 4 | email varchar(255), 5 | password varchar(255), 6 | one_time_token varchar(255), 7 | version integer not null, 8 | constraint uq_user_email unique (email), 9 | constraint pk_user primary key (id)) 10 | ; 11 | 12 | create sequence user_seq; 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /h2-drop.sql: -------------------------------------------------------------------------------- 1 | SET REFERENTIAL_INTEGRITY FALSE; 2 | 3 | drop table if exists user; 4 | 5 | SET REFERENTIAL_INTEGRITY TRUE; 6 | 7 | drop sequence if exists user_seq; 8 | 9 | -------------------------------------------------------------------------------- /keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesdbloom/base_spring_mvc_web_application/9fe0210f9e63e376929ea0f120c90f47f68ee242/keystore -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | base_spring_mvc_web_application 8 | base_spring_mvc_web_application 9 | 1.0-SNAPSHOT 10 | war 11 | 12 | 13 | 1.7.4 14 | 1.7 15 | UTF-8 16 | 17 | 4.0.2.RELEASE 18 | 3.2.2.RELEASE 19 | 1.6.2 20 | 2.3.2 21 | 1.7.6 22 | 8.0.3 23 | 4.3.4.Final 24 | 2.1.2.RELEASE 25 | 26 | 27 | 28 | 29 | 30 | org.slf4j 31 | slf4j-api 32 | ${slf4j.version} 33 | 34 | 35 | org.slf4j 36 | slf4j-jcl 37 | ${slf4j.version} 38 | 39 | 40 | org.slf4j 41 | slf4j-log4j12 42 | ${slf4j.version} 43 | 44 | 45 | org.slf4j 46 | slf4j-jdk14 47 | ${slf4j.version} 48 | 49 | 50 | ch.qos.logback 51 | logback-classic 52 | 1.0.9 53 | 54 | 55 | 56 | 57 | org.springframework 58 | spring-aop 59 | ${spring.version} 60 | 61 | 62 | org.springframework 63 | spring-aspects 64 | ${spring.version} 65 | 66 | 67 | org.aspectj 68 | aspectjrt 69 | ${aspectj.version} 70 | 71 | 72 | org.aspectj 73 | aspectjweaver 74 | ${aspectj.version} 75 | 76 | 77 | cglib 78 | cglib-nodep 79 | 3.1 80 | 81 | 82 | 83 | 84 | joda-time 85 | joda-time 86 | 2.3 87 | 88 | 89 | 90 | 91 | org.freemarker 92 | freemarker 93 | 2.3.20 94 | 95 | 96 | org.thymeleaf 97 | thymeleaf 98 | ${thymeleaf.version} 99 | 100 | 101 | org.thymeleaf 102 | thymeleaf-spring3 103 | ${thymeleaf.version} 104 | 105 | 106 | 107 | 108 | javax.servlet 109 | javax.servlet-api 110 | 3.1.0 111 | provided 112 | 113 | 114 | 115 | 116 | javax.mail 117 | mail 118 | 1.4.7 119 | 120 | 121 | org.subethamail 122 | subethasmtp 123 | 3.1.7 124 | 125 | 126 | 127 | 128 | org.jsoup 129 | jsoup 130 | 1.7.3 131 | 132 | 133 | 134 | 135 | com.fasterxml.jackson.core 136 | jackson-core 137 | ${jackson-version} 138 | 139 | 140 | com.fasterxml.jackson.core 141 | jackson-annotations 142 | ${jackson-version} 143 | 144 | 145 | com.fasterxml.jackson.core 146 | jackson-databind 147 | ${jackson-version} 148 | 149 | 150 | 151 | 152 | ro.isdc.wro4j 153 | wro4j-core 154 | ${wro4j.version} 155 | 156 | 157 | ro.isdc.wro4j 158 | wro4j-extensions 159 | ${wro4j.version} 160 | 161 | 162 | 163 | 164 | com.google.guava 165 | guava 166 | 16.0.1 167 | 168 | 169 | 170 | 171 | org.apache.commons 172 | commons-lang3 173 | 3.3 174 | 175 | 176 | commons-collections 177 | commons-collections 178 | 3.2.1 179 | 180 | 181 | 182 | 183 | org.springframework 184 | spring-core 185 | ${spring.version} 186 | 187 | 188 | commons-logging 189 | commons-logging 190 | 191 | 192 | 193 | 194 | org.springframework 195 | spring-context 196 | ${spring.version} 197 | 198 | 199 | org.springframework 200 | spring-context-support 201 | ${spring.version} 202 | 203 | 204 | org.springframework 205 | spring-webmvc 206 | ${spring.version} 207 | 208 | 209 | org.springframework.security 210 | spring-security-core 211 | ${spring-security.version} 212 | 213 | 214 | commons-logging 215 | commons-logging 216 | 217 | 218 | 219 | 220 | org.springframework.security 221 | spring-security-config 222 | ${spring-security.version} 223 | 224 | 225 | commons-logging 226 | commons-logging 227 | 228 | 229 | 230 | 231 | org.springframework.security 232 | spring-security-web 233 | ${spring-security.version} 234 | 235 | 236 | org.springframework.security 237 | spring-security-taglibs 238 | ${spring-security.version} 239 | 240 | 241 | org.springframework 242 | spring-tx 243 | ${spring.version} 244 | 245 | 246 | 247 | 248 | javax.validation 249 | validation-api 250 | 1.0.0.GA 251 | 252 | 253 | org.hibernate 254 | hibernate-validator 255 | 4.3.1.Final 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | javax.persistence 328 | persistence-api 329 | 1.0 330 | 331 | 332 | org.avaje 333 | ebean 334 | 2.8.1 335 | 336 | 337 | com.h2database 338 | h2 339 | 1.2.128 340 | 341 | 342 | 343 | 344 | org.apache.tomcat.embed 345 | tomcat-embed-core 346 | ${tomcat.version} 347 | test 348 | 349 | 350 | org.apache.tomcat.embed 351 | tomcat-embed-logging-juli 352 | ${tomcat.version} 353 | test 354 | 355 | 356 | 357 | 358 | org.apache.httpcomponents 359 | httpclient 360 | 4.3.3 361 | test 362 | 363 | 364 | 365 | 366 | junit 367 | junit 368 | 4.11 369 | test 370 | 371 | 372 | org.hamcrest 373 | hamcrest-library 374 | 1.3 375 | 376 | 377 | org.springframework 378 | spring-test 379 | ${spring.version} 380 | test 381 | 382 | 383 | org.mockito 384 | mockito-all 385 | 1.9.5 386 | 387 | 388 | org.jboss.byteman 389 | byteman-bmunit 390 | 2.1.4.1 391 | test 392 | 393 | 394 | 395 | 396 | 397 | 398 | org.apache.maven.plugins 399 | maven-dependency-plugin 400 | 2.8 401 | 402 | 403 | install 404 | install 405 | 406 | sources 407 | 408 | 409 | 410 | break-on-dependency-warnings 411 | 412 | analyze-only 413 | 414 | 415 | false 416 | true 417 | 418 | 419 | 420 | 421 | 422 | org.apache.maven.plugins 423 | maven-war-plugin 424 | 2.4 425 | 426 | 427 | org.apache.tomcat.maven 428 | tomcat7-maven-plugin 429 | 2.2 430 | 431 | 432 | org.apache.maven.plugins 433 | maven-compiler-plugin 434 | 3.1 435 | 436 | ${java.version} 437 | ${java.version} 438 | ${project.build.sourceEncoding} 439 | 440 | 441 | 442 | org.apache.maven.plugins 443 | maven-resources-plugin 444 | 2.6 445 | 446 | ${project.build.sourceEncoding} 447 | 448 | 449 | 450 | 451 | org.apache.maven.plugins 452 | maven-surefire-plugin 453 | 2.16 454 | 455 | false 456 | true 457 | 458 | 459 | 460 | org.apache.maven.plugins 461 | maven-idea-plugin 462 | 2.2.1 463 | 464 | true 465 | true 466 | 467 | 468 | 469 | 470 | 471 | -------------------------------------------------------------------------------- /src/main/java/org/jamesdbloom/configuration/RootConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.configuration; 2 | 3 | import org.jamesdbloom.dao.UserDAO; 4 | import org.jamesdbloom.email.EmailConfiguration; 5 | import org.jamesdbloom.security.SecurityConfig; 6 | import org.jamesdbloom.uuid.UUIDFactory; 7 | import org.springframework.context.annotation.*; 8 | import org.springframework.core.env.Environment; 9 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 10 | import ro.isdc.wro.manager.factory.ConfigurableWroManagerFactory; 11 | import ro.isdc.wro.manager.factory.WroManagerFactory; 12 | 13 | import javax.annotation.Resource; 14 | import java.util.Properties; 15 | import java.util.concurrent.ThreadPoolExecutor; 16 | 17 | /** 18 | * This configuration contains top level beans and any configuration required by filters (as WebMvcConfiguration only loaded within Dispatcher Servlet) 19 | * 20 | * @author jamesdbloom 21 | */ 22 | @Configuration 23 | @EnableAspectJAutoProxy(proxyTargetClass = true) 24 | @ComponentScan(basePackages = {"org.jamesdbloom.dao"}) 25 | @Import(value = {SecurityConfig.class, EmailConfiguration.class}) 26 | @PropertySource({"classpath:web.properties", "classpath:validation.properties"}) 27 | public class RootConfiguration { 28 | @Resource 29 | private Environment environment; 30 | 31 | @Bean 32 | protected UUIDFactory uuidFactory() { 33 | return new UUIDFactory(); 34 | } 35 | 36 | @Bean 37 | protected UserDAO userDAO(){ 38 | return new UserDAO(); 39 | } 40 | 41 | // this bean is in this ApplicationContext so that it can be used in DelegatingFilterProxy 42 | @Bean 43 | public WroManagerFactory wroManagerFactory() { 44 | ConfigurableWroManagerFactory wroManagerFactory = new ConfigurableWroManagerFactory(); 45 | 46 | wroManagerFactory.setConfigProperties(new Properties() {{ 47 | setProperty("debug", environment.getProperty("bundling.enabled")); 48 | setProperty("preProcessors", "cssImport,semicolonAppender,conformColors"); 49 | setProperty("postProcessors", "yuiCssMin,googleClosureAdvanced"); 50 | setProperty("cacheGzippedContent", "true"); 51 | setProperty("hashStrategy", "MD5"); // should drive the naming strategy to fingerprint resource urls - NOT YET WORKING / CONFIGURED CORRECTLY 52 | setProperty("namingStrategy", "hashEncoder-CRC32"); // should drive the naming strategy to fingerprint resource urls - NOT YET WORKING / CONFIGURED CORRECTLY 53 | }}); 54 | 55 | return wroManagerFactory; 56 | } 57 | 58 | 59 | @Bean 60 | public ThreadPoolTaskExecutor taskExecutor() { 61 | return new ThreadPoolTaskExecutor() {{ 62 | setCorePoolSize(15); 63 | setMaxPoolSize(25); 64 | setQueueCapacity(50); 65 | setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 66 | }}; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/jamesdbloom/dao/UserDAO.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.dao; 2 | 3 | import com.avaje.ebean.Ebean; 4 | import org.jamesdbloom.domain.User; 5 | import org.jamesdbloom.uuid.UUIDFactory; 6 | 7 | import javax.annotation.Resource; 8 | import java.util.Map; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | 11 | /** 12 | * @author jamesdbloom 13 | */ 14 | public class UserDAO { 15 | 16 | public User findByEmail(String email) { 17 | return Ebean.find(User.class) 18 | .where().eq("email", email) 19 | .findUnique(); 20 | } 21 | 22 | public void save(User user) { 23 | Ebean.save(user); 24 | } 25 | 26 | public void delete(String id) { 27 | Ebean.delete(User.class, id); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/jamesdbloom/domain/EqualsHashCodeToString.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.domain; 2 | 3 | import org.apache.commons.lang3.builder.EqualsBuilder; 4 | import org.apache.commons.lang3.builder.HashCodeBuilder; 5 | import org.apache.commons.lang3.builder.ReflectionToStringBuilder; 6 | import org.apache.commons.lang3.builder.ToStringStyle; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.Serializable; 11 | 12 | /** 13 | * @author jamesdbloom 14 | */ 15 | public abstract class EqualsHashCodeToString implements Serializable { 16 | 17 | protected transient Logger logger = LoggerFactory.getLogger(this.getClass()); 18 | 19 | static { 20 | ReflectionToStringBuilder.setDefaultStyle(ToStringStyle.MULTI_LINE_STYLE); 21 | } 22 | 23 | protected String[] fieldsExcludedFromEqualsAndHashCode() { 24 | return new String[]{"logger"}; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return ReflectionToStringBuilder.toStringExclude(this, fieldsExcludedFromEqualsAndHashCode()); 30 | } 31 | 32 | @Override 33 | public boolean equals(Object other) { 34 | return EqualsBuilder.reflectionEquals(this, other, fieldsExcludedFromEqualsAndHashCode()); 35 | } 36 | 37 | @Override 38 | public int hashCode() { 39 | return HashCodeBuilder.reflectionHashCode(this, fieldsExcludedFromEqualsAndHashCode()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/jamesdbloom/domain/User.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | 5 | import javax.persistence.*; 6 | import javax.validation.constraints.NotNull; 7 | import javax.validation.constraints.Pattern; 8 | import javax.validation.constraints.Size; 9 | 10 | /** 11 | * @author jamesdbloom 12 | */ 13 | @Entity 14 | public class User extends EqualsHashCodeToString { 15 | 16 | public static final String PASSWORD_PATTERN = "^.*(?=.{8,})(?=.*\\d)(?=.*[a-zA-Z]).*$"; 17 | public static final String EMAIL_PATTERN = "^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"; 18 | @Id 19 | private String id; 20 | @Version 21 | private Integer version; 22 | @NotNull(message = "validation.user.name") 23 | @Size(min = 3, max = 50, message = "validation.user.name") 24 | private String name; 25 | @NotNull(message = "validation.user.email") 26 | @Pattern(regexp = EMAIL_PATTERN, message = "validation.user.email") 27 | @Column(unique = true) 28 | private String email; 29 | @JsonIgnore 30 | @Pattern(regexp = PASSWORD_PATTERN, message = "validation.user.password") 31 | private String password; 32 | @JsonIgnore 33 | private String oneTimeToken; 34 | 35 | public User() { 36 | } 37 | 38 | public User(String id, String name, String email, String password) { 39 | this.id = id; 40 | this.name = name; 41 | this.email = email; 42 | this.password = password; 43 | } 44 | 45 | public String getId() { 46 | return id; 47 | } 48 | 49 | public void setId(String id) { 50 | this.id = id; 51 | } 52 | 53 | public Integer getVersion() { 54 | return version; 55 | } 56 | 57 | public void setVersion(Integer version) { 58 | this.version = version; 59 | } 60 | 61 | public String getName() { 62 | return name; 63 | } 64 | 65 | public void setName(String name) { 66 | this.name = name; 67 | } 68 | 69 | public User withName(String name) { 70 | withName(name); 71 | return this; 72 | } 73 | 74 | public String getEmail() { 75 | return email; 76 | } 77 | 78 | public void setEmail(String email) { 79 | this.email = email; 80 | } 81 | 82 | public User withEmail(String email) { 83 | setEmail(email); 84 | return this; 85 | } 86 | 87 | public String getPassword() { 88 | return password; 89 | } 90 | 91 | public void setPassword(String password) { 92 | this.password = password; 93 | } 94 | 95 | public User withPassword(String password) { 96 | setPassword(password); 97 | return this; 98 | } 99 | 100 | public String getOneTimeToken() { 101 | return oneTimeToken; 102 | } 103 | 104 | public void setOneTimeToken(String oneTimeToken) { 105 | this.oneTimeToken = oneTimeToken; 106 | } 107 | 108 | public User withOneTimeToken(String oneTimeToken) { 109 | setOneTimeToken(oneTimeToken); 110 | return this; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/org/jamesdbloom/email/EmailConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.email; 2 | 3 | import org.springframework.context.annotation.*; 4 | import org.springframework.core.env.Environment; 5 | import org.springframework.mail.MailSender; 6 | import org.springframework.mail.javamail.JavaMailSenderImpl; 7 | import org.thymeleaf.spring3.SpringTemplateEngine; 8 | import org.thymeleaf.spring3.view.ThymeleafViewResolver; 9 | import org.thymeleaf.templateresolver.ServletContextTemplateResolver; 10 | 11 | import javax.annotation.Resource; 12 | import java.util.Properties; 13 | 14 | /** 15 | * @author jamesdbloom 16 | */ 17 | @Configuration 18 | @PropertySource("classpath:email.properties") 19 | @ComponentScan(basePackages = {"org.jamesdbloom.email"}) 20 | @EnableAspectJAutoProxy 21 | public class EmailConfiguration { 22 | 23 | 24 | @Resource 25 | private Environment environment; 26 | 27 | @Bean 28 | public MailSender mailSender() { 29 | return new JavaMailSenderImpl() {{ 30 | setHost(environment.getProperty("email.host")); 31 | setPort(environment.getProperty("email.port", Integer.class)); 32 | setProtocol(environment.getProperty("email.protocol")); 33 | setUsername(environment.getProperty("email.username")); 34 | setPassword(environment.getProperty("email.password")); 35 | setJavaMailProperties(new Properties() {{ // https://javamail.java.net/nonav/docs/api/com/sun/mail/smtp/package-summary.html 36 | setProperty("mail.smtp.auth", "true"); 37 | setProperty("mail.smtp.starttls.enable", environment.getProperty("email.starttls")); 38 | setProperty("mail.smtp.quitwait", "false"); 39 | }}); 40 | }}; 41 | } 42 | 43 | // @Bean 44 | // public ServletContextTemplateResolver templateResolver() { 45 | // ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(); 46 | // templateResolver.setPrefix("/WEB-INF/pages/"); 47 | // templateResolver.setSuffix(".leaf"); 48 | // templateResolver.setTemplateMode("HTML5"); 49 | // templateResolver.setCharacterEncoding("UTF-8"); 50 | // templateResolver.setCacheable(false); 51 | // return templateResolver; 52 | // } 53 | // 54 | // @Bean 55 | // public SpringTemplateEngine templateEngine(ServletContextTemplateResolver templateResolver) { 56 | // SpringTemplateEngine templateEngine = new SpringTemplateEngine(); 57 | // templateEngine.setTemplateResolver(templateResolver); 58 | // return templateEngine; 59 | // } 60 | // 61 | // @Bean 62 | // public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine templateEngine) { 63 | // ThymeleafViewResolver thymeleafViewResolver = new ThymeleafViewResolver(); 64 | // thymeleafViewResolver.setTemplateEngine(templateEngine); 65 | // thymeleafViewResolver.setOrder(1); 66 | // thymeleafViewResolver.setCharacterEncoding("UTF-8"); 67 | // return thymeleafViewResolver; 68 | // } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/jamesdbloom/email/EmailService.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.email; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | import com.google.common.base.Strings; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.jamesdbloom.domain.User; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.core.env.Environment; 10 | import org.springframework.mail.javamail.JavaMailSender; 11 | import org.springframework.mail.javamail.MimeMessageHelper; 12 | import org.springframework.mail.javamail.MimeMessagePreparator; 13 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 14 | import org.springframework.security.access.prepost.PreAuthorize; 15 | import org.springframework.stereotype.Component; 16 | 17 | import javax.annotation.Resource; 18 | import javax.mail.MessagingException; 19 | import javax.mail.internet.MimeMessage; 20 | import javax.servlet.http.HttpServletRequest; 21 | import java.io.UnsupportedEncodingException; 22 | import java.net.MalformedURLException; 23 | import java.net.URL; 24 | import java.net.URLEncoder; 25 | import java.util.Arrays; 26 | 27 | /** 28 | * @author jamesdbloom 29 | */ 30 | @Component 31 | public class EmailService { 32 | 33 | public static final String NAME_PREFIX = "MyApplication - "; 34 | protected Logger logger = LoggerFactory.getLogger(getClass()); 35 | @Resource 36 | private JavaMailSender mailSender; 37 | @Resource 38 | private Environment environment; 39 | @Resource 40 | private ThreadPoolTaskExecutor taskExecutor; 41 | 42 | @VisibleForTesting 43 | @PreAuthorize("isAuthenticated()") 44 | void sendMessage(final String from, final String[] to, final String subject, final String msg) { 45 | taskExecutor.execute(new SendMessageTask(from, to, subject, msg, mailSender)); 46 | } 47 | 48 | public void sendRegistrationMessage(User user, HttpServletRequest request) throws MalformedURLException, UnsupportedEncodingException { 49 | URL link = createUrl(user, request); 50 | String subject = NAME_PREFIX + "New Registration"; 51 | String formattedMessage = "" + subject + "\n" + 52 | "

" + subject + "

\n" + 53 | "

A new user has just been registered for " + user.getEmail() + "

\n" + 54 | "

To validate this email address please click on the following link " + link + "

\n" + 55 | ""; 56 | sendMessage(environment.getProperty("email.contact.address"), new String[]{user.getEmail()}, subject, formattedMessage); 57 | } 58 | 59 | public void sendUpdatePasswordMessage(User user, HttpServletRequest request) throws MalformedURLException, UnsupportedEncodingException { 60 | URL link = createUrl(user, request); 61 | String subject = NAME_PREFIX + "Update Password"; 62 | String formattedMessage = "" + subject + "\n" + 63 | "

" + subject + "

\n" + 64 | "

To update your password please click on the following link " + link + "

\n" + 65 | ""; 66 | sendMessage(environment.getProperty("email.contact.address"), new String[]{user.getEmail()}, subject, formattedMessage); 67 | } 68 | 69 | @VisibleForTesting 70 | URL createUrl(User user, HttpServletRequest request) throws MalformedURLException, UnsupportedEncodingException { 71 | String hostHeader = request.getHeader("Host"); 72 | String host = environment.getProperty("server.host"); 73 | int port = request.getLocalPort(); 74 | if (!Strings.isNullOrEmpty(hostHeader)) { 75 | if (hostHeader.contains(":")) { 76 | host = StringUtils.substringBefore(hostHeader, ":"); 77 | try { 78 | port = Integer.parseInt(StringUtils.substringAfterLast(hostHeader, ":")); 79 | } catch (NumberFormatException nfe) { 80 | logger.warn("NumberFormatException parsing port from Host header [" + hostHeader + "]", nfe); 81 | } 82 | } else { 83 | host = hostHeader; 84 | } 85 | } 86 | return new URL( 87 | "https", 88 | host, 89 | port, 90 | "/updatePassword?email=" + URLEncoder.encode(user.getEmail(), "UTF-8") + "&oneTimeToken=" + URLEncoder.encode(user.getOneTimeToken(), "UTF-8") 91 | ); 92 | } 93 | 94 | public class SendMessageTask implements Runnable { 95 | private final String from; 96 | private final String[] to; 97 | private final String subject; 98 | private final String msg; 99 | private final JavaMailSender mailSender; 100 | 101 | public SendMessageTask(String from, String[] to, String subject, String msg, JavaMailSender mailSender) { 102 | this.from = from; 103 | this.to = to; 104 | this.subject = subject; 105 | this.msg = msg; 106 | this.mailSender = mailSender; 107 | } 108 | 109 | public void run() { 110 | try { 111 | mailSender.send(new MimeMessagePreparator() { 112 | public void prepare(MimeMessage mimeMessage) throws MessagingException { 113 | MimeMessageHelper message = new MimeMessageHelper(mimeMessage, false, "UTF-8"); 114 | message.setFrom(from); 115 | message.setTo(to); 116 | message.setSubject(subject); 117 | message.setText(msg, true); 118 | } 119 | }); 120 | } catch (Exception e) { 121 | logger.warn("Failed to send " + subject + "email to " + Arrays.asList(to), e); 122 | } 123 | } 124 | 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/org/jamesdbloom/security/CredentialValidation.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.security; 2 | 3 | import org.jamesdbloom.dao.UserDAO; 4 | import org.jamesdbloom.domain.User; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.core.env.Environment; 8 | import org.springframework.security.authentication.BadCredentialsException; 9 | import org.springframework.security.crypto.password.PasswordEncoder; 10 | import org.springframework.stereotype.Component; 11 | 12 | import javax.annotation.Resource; 13 | import java.io.UnsupportedEncodingException; 14 | import java.net.URLDecoder; 15 | 16 | /** 17 | * @author jamesdbloom 18 | */ 19 | @Component 20 | public class CredentialValidation { 21 | 22 | protected Logger logger = LoggerFactory.getLogger(this.getClass()); 23 | 24 | @Resource 25 | private PasswordEncoder passwordEncoder; 26 | @Resource 27 | private UserDAO userDAO; 28 | @Resource 29 | private Environment environment; 30 | 31 | public User validateUsernameAndPassword(String username, CharSequence password) { 32 | User user = userDAO.findByEmail(decodeURL(username)); 33 | if (!credentialsMatch(password, user)) { 34 | logger.info(environment.getProperty("validation.user.invalidCredentials")); 35 | throw new BadCredentialsException(environment.getProperty("validation.user.invalidCredentials")); 36 | } 37 | return user; 38 | } 39 | 40 | private boolean credentialsMatch(CharSequence password, User user) { 41 | return user != null && password != null && user.getPassword() != null && passwordEncoder.matches(password, user.getPassword()); 42 | } 43 | 44 | private String decodeURL(String urlEncoded) { 45 | try { 46 | return URLDecoder.decode(urlEncoded, "UTF-8"); 47 | } catch (UnsupportedEncodingException e) { 48 | throw new RuntimeException("Exception decoding URL value [" + urlEncoded + "]", e); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/jamesdbloom/security/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.security; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.ComponentScan; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.core.env.Environment; 7 | import org.springframework.security.authentication.AuthenticationProvider; 8 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.builders.WebSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 12 | import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; 13 | import org.springframework.security.crypto.password.PasswordEncoder; 14 | import org.springframework.security.crypto.password.StandardPasswordEncoder; 15 | 16 | import javax.annotation.Resource; 17 | 18 | @Configuration 19 | //@EnableGlobalMethodSecurity(prePostEnabled = true) 20 | @EnableWebMvcSecurity 21 | @ComponentScan("org.jamesdbloom.security") 22 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 23 | 24 | @Resource 25 | private AuthenticationProvider springSecurityAuthenticationProvider; 26 | 27 | @Resource 28 | private Environment environment; 29 | 30 | @Bean 31 | protected PasswordEncoder passwordEncoder() { 32 | return new StandardPasswordEncoder("GMbO8etVKRFDEC8mZ1nCLxodpEd3BrrTn4Ju62R5"); 33 | } 34 | 35 | @Override 36 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 37 | auth.authenticationProvider(springSecurityAuthenticationProvider); 38 | } 39 | 40 | @Override 41 | public void configure(WebSecurity web) throws Exception { 42 | web.ignoring().antMatchers("/favicon.ico", "/resources/**", "/bundle/**"); 43 | } 44 | 45 | @Override 46 | protected void configure(HttpSecurity http) throws Exception { 47 | http 48 | .requiresChannel().anyRequest().requiresSecure() 49 | .and() 50 | .portMapper().http(Integer.parseInt(environment.getProperty("http.port", "8080"))).mapsTo(Integer.parseInt(environment.getProperty("https.port", "8443"))) 51 | .and() 52 | .sessionManagement().sessionFixation().newSession() 53 | .and() 54 | .authorizeRequests().antMatchers("/register", "/updatePassword").permitAll() 55 | .and() 56 | .formLogin().loginPage("/login").defaultSuccessUrl("/").permitAll() 57 | .and() 58 | .logout().logoutUrl("/logout").logoutSuccessUrl("/login").invalidateHttpSession(true).deleteCookies("JSESSIONID") 59 | .and() 60 | .authorizeRequests().antMatchers("/**").authenticated(); 61 | } 62 | } -------------------------------------------------------------------------------- /src/main/java/org/jamesdbloom/security/SpringSecurityAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.security; 2 | 3 | import org.jamesdbloom.domain.User; 4 | import org.springframework.security.authentication.AuthenticationProvider; 5 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 6 | import org.springframework.security.core.Authentication; 7 | import org.springframework.security.core.AuthenticationException; 8 | import org.springframework.security.core.authority.AuthorityUtils; 9 | import org.springframework.stereotype.Component; 10 | 11 | import javax.annotation.Resource; 12 | 13 | /** 14 | * @author jamesdbloom 15 | */ 16 | @Component 17 | public class SpringSecurityAuthenticationProvider implements AuthenticationProvider { 18 | 19 | @Resource 20 | private CredentialValidation credentialValidation; 21 | 22 | @Resource 23 | private SpringSecurityUserContext springSecurityUserContext; 24 | 25 | @Override 26 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 27 | UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication; 28 | User user; 29 | if (token.getPrincipal() instanceof User) { 30 | user = (User) token.getPrincipal(); 31 | } else { 32 | user = credentialValidation.validateUsernameAndPassword(token.getName(), (CharSequence) token.getCredentials()); 33 | } 34 | springSecurityUserContext.setCurrentUser(user); 35 | return new UsernamePasswordAuthenticationToken(user, user.getPassword(), AuthorityUtils.createAuthorityList("USER")); 36 | } 37 | 38 | @Override 39 | public boolean supports(Class authentication) { 40 | return UsernamePasswordAuthenticationToken.class.equals(authentication); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/jamesdbloom/security/SpringSecurityUserContext.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.security; 2 | 3 | import org.jamesdbloom.domain.User; 4 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 5 | import org.springframework.security.core.Authentication; 6 | import org.springframework.security.core.context.SecurityContext; 7 | import org.springframework.security.core.context.SecurityContextHolder; 8 | import org.springframework.stereotype.Component; 9 | 10 | /** 11 | * @author jamesdbloom 12 | */ 13 | @Component 14 | public class SpringSecurityUserContext { 15 | 16 | public User getCurrentUser() { 17 | SecurityContext securityContext = SecurityContextHolder.getContext(); 18 | Authentication authentication = securityContext.getAuthentication(); 19 | if (authentication == null || authentication.getPrincipal() == null || authentication.getPrincipal().equals("anonymousUser")) { 20 | return null; 21 | } 22 | return (User) authentication.getPrincipal(); 23 | } 24 | 25 | public void setCurrentUser(User user) { 26 | Authentication authentication = new UsernamePasswordAuthenticationToken(user, user.getPassword()); 27 | SecurityContextHolder.getContext().setAuthentication(authentication); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/jamesdbloom/uuid/UUIDFactory.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.uuid; 2 | 3 | import org.jamesdbloom.domain.User; 4 | 5 | import java.util.UUID; 6 | import java.util.regex.Pattern; 7 | 8 | /** 9 | * @author jamesdbloom 10 | */ 11 | public class UUIDFactory { 12 | 13 | private static final Pattern UUID_PATTERN = Pattern.compile("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"); 14 | 15 | public String generateUUID() { 16 | return UUID.randomUUID().toString(); 17 | } 18 | 19 | public boolean hasMatchingUUID(User user, String oneTimeToken) { 20 | boolean userTokenValid = user != null && user.getOneTimeToken() != null && UUID_PATTERN.matcher(user.getOneTimeToken()).matches(); 21 | boolean matchingTokenValid = oneTimeToken != null && UUID_PATTERN.matcher(oneTimeToken).matches(); 22 | return userTokenValid && matchingTokenValid && user.getOneTimeToken().equals(oneTimeToken); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/jamesdbloom/web/configuration/WebMvcConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.web.configuration; 2 | 3 | import freemarker.cache.ClassTemplateLoader; 4 | import freemarker.cache.MultiTemplateLoader; 5 | import freemarker.cache.TemplateLoader; 6 | import freemarker.cache.WebappTemplateLoader; 7 | import freemarker.template.TemplateException; 8 | import freemarker.template.TemplateExceptionHandler; 9 | import org.jamesdbloom.web.interceptor.bundling.AddBundlingModelToViewModelInterceptor; 10 | import org.jamesdbloom.web.interceptor.bundling.WroModelHolder; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.beans.factory.config.CustomScopeConfigurer; 13 | import org.springframework.context.ApplicationContext; 14 | import org.springframework.context.annotation.*; 15 | import org.springframework.core.env.Environment; 16 | import org.springframework.validation.Validator; 17 | import org.springframework.web.bind.WebDataBinder; 18 | import org.springframework.web.bind.annotation.InitBinder; 19 | import org.springframework.web.context.request.RequestScope; 20 | import org.springframework.web.servlet.LocaleResolver; 21 | import org.springframework.web.servlet.config.annotation.*; 22 | import org.springframework.web.servlet.i18n.SessionLocaleResolver; 23 | import org.springframework.web.servlet.view.freemarker.FreeMarkerConfig; 24 | import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer; 25 | import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver; 26 | import ro.isdc.wro.manager.factory.ConfigurableWroManagerFactory; 27 | import ro.isdc.wro.manager.factory.WroManagerFactory; 28 | 29 | import javax.annotation.Resource; 30 | import javax.servlet.ServletContext; 31 | import java.io.IOException; 32 | import java.util.Collections; 33 | import java.util.Locale; 34 | import java.util.Properties; 35 | 36 | /** 37 | * @author jamesdbloom 38 | */ 39 | @EnableWebMvc 40 | @Configuration 41 | @ComponentScan(basePackages = {"org.jamesdbloom.web"}) 42 | public class WebMvcConfiguration extends WebMvcConfigurationSupport { 43 | 44 | @Resource 45 | private Environment environment; 46 | 47 | @Resource 48 | private WroModelHolder wroModelHolder; 49 | 50 | @Resource 51 | private ServletContext servletContext; 52 | 53 | @Override 54 | public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { 55 | configurer.enable("default"); 56 | } 57 | 58 | @Override 59 | public void addInterceptors(InterceptorRegistry registry) { 60 | registry.addInterceptor(new AddBundlingModelToViewModelInterceptor(wroModelHolder, environment.getProperty("bundling.enabled"))); 61 | } 62 | 63 | @Override 64 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 65 | registry.addResourceHandler("/resources/**").addResourceLocations("/resources/"); 66 | } 67 | 68 | @Bean 69 | public WroManagerFactory wroManagerFactory() { 70 | ConfigurableWroManagerFactory wroManagerFactory = new ConfigurableWroManagerFactory(); 71 | 72 | wroManagerFactory.setConfigProperties(new Properties() {{ 73 | setProperty("debug", environment.getProperty("bundling.enabled")); 74 | setProperty("preProcessors", "cssImport,semicolonAppender,conformColors"); 75 | setProperty("postProcessors", "yuiCssMin,googleClosureAdvanced"); 76 | setProperty("cacheGzippedContent", "true"); 77 | setProperty("hashStrategy", "MD5"); // should drive the naming strategy to fingerprint resource urls - NOT YET WORKING / CONFIGURED CORRECTLY 78 | setProperty("namingStrategy", "hashEncoder-CRC32"); // should drive the naming strategy to fingerprint resource urls - NOT YET WORKING / CONFIGURED CORRECTLY 79 | }}); 80 | 81 | return wroManagerFactory; 82 | } 83 | 84 | @Bean 85 | public FreeMarkerConfigurer freemarkerConfig() throws IOException, TemplateException { 86 | FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer(); 87 | freeMarkerConfigurer.setConfiguration(new freemarker.template.Configuration() {{ 88 | setTemplateLoader(new MultiTemplateLoader( 89 | new TemplateLoader[]{ 90 | new ClassTemplateLoader(FreeMarkerConfig.class, "/"), 91 | new WebappTemplateLoader(servletContext, "/") 92 | } 93 | )); 94 | setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER); 95 | setStrictSyntaxMode(true); 96 | setWhitespaceStripping(true); 97 | }}); 98 | return freeMarkerConfigurer; 99 | } 100 | 101 | @Bean 102 | public FreeMarkerViewResolver freeMarkerViewResolver() { 103 | FreeMarkerViewResolver freeMarkerViewResolver = new FreeMarkerViewResolver(); 104 | freeMarkerViewResolver.setOrder(1); 105 | freeMarkerViewResolver.setPrefix("/WEB-INF/view/"); 106 | freeMarkerViewResolver.setSuffix(".ftl"); 107 | freeMarkerViewResolver.setContentType("text/html;charset=UTF-8"); 108 | return freeMarkerViewResolver; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/org/jamesdbloom/web/controller/LandingPageController.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.web.controller; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RequestMethod; 6 | 7 | /** 8 | * @author jamesdbloom 9 | */ 10 | @Controller 11 | public class LandingPageController { 12 | 13 | @RequestMapping(value = "/", method = RequestMethod.GET) 14 | public String getPage() { 15 | return "landing"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/jamesdbloom/web/controller/LoginPageController.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.web.controller; 2 | 3 | import org.springframework.security.web.csrf.CsrfToken; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | 11 | /** 12 | * @author jamesdbloom 13 | */ 14 | @Controller 15 | public class LoginPageController { 16 | 17 | @RequestMapping(value = "/login", method = RequestMethod.GET) 18 | public String getPage(HttpServletRequest request, Model model) { 19 | CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); 20 | if (csrfToken != null) { 21 | model.addAttribute("csrfParameterName", csrfToken.getParameterName()); 22 | model.addAttribute("csrfToken", csrfToken.getToken()); 23 | } 24 | return "login"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/jamesdbloom/web/controller/RegistrationController.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.web.controller; 2 | 3 | import org.jamesdbloom.dao.UserDAO; 4 | import org.jamesdbloom.domain.User; 5 | import org.jamesdbloom.email.EmailService; 6 | import org.jamesdbloom.uuid.UUIDFactory; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.core.env.Environment; 10 | import org.springframework.security.web.csrf.CsrfToken; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.ui.Model; 13 | import org.springframework.validation.BindingResult; 14 | import org.springframework.validation.ObjectError; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestMethod; 17 | import org.springframework.web.servlet.mvc.support.RedirectAttributes; 18 | 19 | import javax.annotation.Resource; 20 | import javax.servlet.http.HttpServletRequest; 21 | import javax.validation.Valid; 22 | import java.io.UnsupportedEncodingException; 23 | import java.net.MalformedURLException; 24 | 25 | /** 26 | * @author jamesdbloom 27 | */ 28 | @Controller 29 | public class RegistrationController { 30 | 31 | protected Logger logger = LoggerFactory.getLogger(getClass()); 32 | @Resource 33 | private UserDAO userDAO; 34 | @Resource 35 | private Environment environment; 36 | @Resource 37 | private EmailService emailService; 38 | @Resource 39 | private UUIDFactory uuidFactory; 40 | 41 | private void setupModel(Model model) { 42 | model.addAttribute("passwordPattern", User.PASSWORD_PATTERN); 43 | model.addAttribute("emailPattern", User.EMAIL_PATTERN); 44 | model.addAttribute("environment", environment); 45 | } 46 | 47 | @RequestMapping(value = "/register", method = RequestMethod.GET) 48 | public String registerForm(HttpServletRequest request, Model model) { 49 | setupModel(model); 50 | model.addAttribute("user", new User()); 51 | CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); 52 | if (csrfToken != null) { 53 | model.addAttribute("csrfParameterName", csrfToken.getParameterName()); 54 | model.addAttribute("csrfToken", csrfToken.getToken()); 55 | } 56 | return "register"; 57 | } 58 | 59 | @RequestMapping(value = "/register", method = RequestMethod.POST) 60 | public String register(@Valid User user, BindingResult bindingResult, HttpServletRequest request, Model model, RedirectAttributes redirectAttributes) throws MalformedURLException, UnsupportedEncodingException { 61 | 62 | boolean userAlreadyExists = user.getEmail() != null && (userDAO.findByEmail(user.getEmail()) != null); 63 | if (bindingResult.hasErrors() || userAlreadyExists) { 64 | setupModel(model); 65 | if (userAlreadyExists) { 66 | bindingResult.addError(new ObjectError("user", "validation.user.alreadyExists")); 67 | } 68 | model.addAttribute("bindingResult", bindingResult); 69 | model.addAttribute("user", user); 70 | return "register"; 71 | } 72 | user.setOneTimeToken(uuidFactory.generateUUID()); 73 | userDAO.save(user); 74 | emailService.sendRegistrationMessage(user, request); 75 | redirectAttributes.addFlashAttribute("message", "Your account has been created and an email has been sent to " + user.getEmail() + " with a link to create your password and login, please check your spam folder if you don't see the email within 5 minutes"); 76 | redirectAttributes.addFlashAttribute("title", "Account Created"); 77 | return "redirect:/message"; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/org/jamesdbloom/web/controller/UpdatePasswordController.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.web.controller; 2 | 3 | import org.jamesdbloom.dao.UserDAO; 4 | import org.jamesdbloom.domain.User; 5 | import org.jamesdbloom.email.EmailService; 6 | import org.jamesdbloom.security.SpringSecurityUserContext; 7 | import org.jamesdbloom.uuid.UUIDFactory; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.core.env.Environment; 11 | import org.springframework.security.crypto.password.PasswordEncoder; 12 | import org.springframework.security.web.csrf.CsrfToken; 13 | import org.springframework.stereotype.Controller; 14 | import org.springframework.transaction.annotation.Transactional; 15 | import org.springframework.ui.Model; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RequestMethod; 18 | import org.springframework.web.servlet.mvc.support.RedirectAttributes; 19 | 20 | import javax.annotation.Resource; 21 | import javax.servlet.http.HttpServletRequest; 22 | import java.io.UnsupportedEncodingException; 23 | import java.net.MalformedURLException; 24 | import java.net.URLEncoder; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | import java.util.regex.Pattern; 28 | 29 | /** 30 | * @author jamesdbloom 31 | */ 32 | @Controller 33 | public class UpdatePasswordController { 34 | protected Logger logger = LoggerFactory.getLogger(this.getClass()); 35 | private static final Pattern PASSWORD_MATCHER = Pattern.compile(User.PASSWORD_PATTERN); 36 | @Resource 37 | private Environment environment; 38 | @Resource 39 | private UserDAO userDAO; 40 | @Resource 41 | private UUIDFactory uuidFactory; 42 | @Resource 43 | private PasswordEncoder passwordEncoder; 44 | @Resource 45 | private SpringSecurityUserContext securityUserContext; 46 | @Resource 47 | private EmailService emailService; 48 | 49 | @RequestMapping(value = "/sendUpdatePasswordEmail", method = {RequestMethod.GET, RequestMethod.POST}, produces = "application/json; charset=UTF-8") 50 | public String sendUpdatePasswordEmail(String email, HttpServletRequest request, RedirectAttributes redirectAttributes) throws MalformedURLException, UnsupportedEncodingException { 51 | User user = userDAO.findByEmail(email); 52 | if (user != null) { 53 | user.setOneTimeToken(uuidFactory.generateUUID()); 54 | userDAO.save(user); 55 | emailService.sendUpdatePasswordMessage(user, request); 56 | } 57 | redirectAttributes.addFlashAttribute("message", "An email has been sent to " + email + " with a link to create your password and login"); 58 | redirectAttributes.addFlashAttribute("title", "Message Sent"); 59 | return "redirect:/message"; 60 | } 61 | 62 | private boolean hasInvalidToken(User user, String oneTimeToken, HttpServletRequest request, RedirectAttributes redirectAttributes) throws UnsupportedEncodingException { 63 | if (!uuidFactory.hasMatchingUUID(user, oneTimeToken)) { 64 | redirectAttributes.addFlashAttribute("message", "Invalid email or one-time-token" + (user != null ? " - click resend email to receive a new email" : "")); 65 | redirectAttributes.addFlashAttribute("title", "Invalid Request"); 66 | redirectAttributes.addFlashAttribute("error", true); 67 | redirectAttributes.addFlashAttribute("csrfParameterName", ((CsrfToken)request.getAttribute(CsrfToken.class.getName())).getParameterName()); 68 | redirectAttributes.addFlashAttribute("csrfToken", ((CsrfToken)request.getAttribute(CsrfToken.class.getName())).getToken()); 69 | return true; 70 | } 71 | return false; 72 | } 73 | 74 | @RequestMapping(value = "/updatePassword", method = RequestMethod.GET) 75 | public String updatePasswordForm(String email, String oneTimeToken, HttpServletRequest request, Model model, RedirectAttributes redirectAttributes) throws UnsupportedEncodingException { 76 | if (hasInvalidToken(userDAO.findByEmail(email), oneTimeToken, request, redirectAttributes)) { 77 | return "redirect:/message"; 78 | } 79 | model.addAttribute("passwordPattern", User.PASSWORD_PATTERN); 80 | model.addAttribute("environment", environment); 81 | model.addAttribute("email", email); 82 | model.addAttribute("oneTimeToken", oneTimeToken); 83 | CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); 84 | if (csrfToken != null) { 85 | model.addAttribute("csrfParameterName", csrfToken.getParameterName()); 86 | model.addAttribute("csrfToken", csrfToken.getToken()); 87 | } 88 | return "updatePassword"; 89 | } 90 | 91 | @Transactional 92 | @RequestMapping(value = "/updatePassword", method = RequestMethod.POST) 93 | public String updatePassword(String email, String password, String passwordConfirm, String oneTimeToken, HttpServletRequest request, Model model, RedirectAttributes redirectAttributes) throws UnsupportedEncodingException { 94 | User user = userDAO.findByEmail(email); 95 | if (hasInvalidToken(user, oneTimeToken, request, redirectAttributes)) { 96 | return "redirect:/message"; 97 | } 98 | boolean passwordFormatError = !PASSWORD_MATCHER.matcher(String.valueOf(password)).matches(); 99 | boolean passwordsMatchError = !String.valueOf(password).equals(passwordConfirm); 100 | if (passwordFormatError || passwordsMatchError) { 101 | model.addAttribute("passwordPattern", User.PASSWORD_PATTERN); 102 | model.addAttribute("environment", environment); 103 | model.addAttribute("email", email); 104 | model.addAttribute("oneTimeToken", oneTimeToken); 105 | List errors = new ArrayList<>(); 106 | if (passwordFormatError) { 107 | errors.add("validation.user.password"); 108 | } 109 | if (passwordsMatchError) { 110 | errors.add("validation.user.passwordNonMatching"); 111 | } 112 | model.addAttribute("validationErrors", errors); 113 | logger.info("Validation error while trying to update password for " + email + "\n" + errors); 114 | return "updatePassword"; 115 | } 116 | user.setPassword(passwordEncoder.encode(password)); 117 | userDAO.save(user); 118 | 119 | redirectAttributes.addFlashAttribute("message", "Your password has been updated"); 120 | redirectAttributes.addFlashAttribute("title", "Password Updated"); 121 | logger.info("Password updated for " + email); 122 | return "redirect:/message"; 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/org/jamesdbloom/web/interceptor/bundling/AddBundlingModelToViewModelInterceptor.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.web.interceptor.bundling; 2 | 3 | import com.google.common.base.Function; 4 | import com.google.common.base.Strings; 5 | import com.google.common.collect.Lists; 6 | import org.springframework.web.servlet.ModelAndView; 7 | import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 8 | import ro.isdc.wro.model.WroModel; 9 | import ro.isdc.wro.model.group.Group; 10 | import ro.isdc.wro.model.resource.Resource; 11 | import ro.isdc.wro.model.resource.ResourceType; 12 | 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpServletResponse; 15 | import java.util.*; 16 | 17 | /** 18 | * @author jamesdbloom 19 | */ 20 | public class AddBundlingModelToViewModelInterceptor extends HandlerInterceptorAdapter { 21 | 22 | public static final String JS_RESOURCES = "jsResources"; 23 | public static final String CSS_RESOURCES = "cssResources"; 24 | private static final Function RESOURCE_TO_URI = new Function() { 25 | @Override 26 | public String apply(Resource resource) { 27 | return resource.getUri(); 28 | } 29 | }; 30 | private final WroModelHolder wroModelHolder; 31 | private final String bundlingEnabled; 32 | 33 | public AddBundlingModelToViewModelInterceptor(WroModelHolder wroModelHolder, String bundlingEnabled) { 34 | this.wroModelHolder = wroModelHolder; 35 | this.bundlingEnabled = bundlingEnabled; 36 | } 37 | 38 | @Override 39 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 40 | modelAndView.addObject(JS_RESOURCES, getListOfUnbundledResources(ResourceType.JS, wroModelHolder.getWroModel(), request)); 41 | modelAndView.addObject(CSS_RESOURCES, getListOfUnbundledResources(ResourceType.CSS, wroModelHolder.getWroModel(), request)); 42 | } 43 | 44 | private Map> getListOfUnbundledResources(ResourceType resourceType, WroModel wroModel, HttpServletRequest request) { 45 | Map> resources = new HashMap<>(); 46 | if (wroModel != null) { 47 | for (Group group : wroModel.getGroups()) { 48 | if (Strings.isNullOrEmpty(request.getParameter("bundle")) ? Boolean.parseBoolean(bundlingEnabled) : Boolean.parseBoolean(request.getParameter("bundle"))) { 49 | resources.put(group.getName(), Arrays.asList("/bundle/" + group.getName() + "." + resourceType.name().toLowerCase() + (request.getQueryString().contains("minimize=false") ? "?minimize=false" : ""))); 50 | } else { 51 | resources.put(group.getName(), Lists.transform(wroModel.getGroupByName(group.getName()).collectResourcesOfType(resourceType).getResources(), RESOURCE_TO_URI)); 52 | } 53 | } 54 | } else { 55 | resources.put("all", new ArrayList()); 56 | } 57 | return resources; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/jamesdbloom/web/interceptor/bundling/WroModelHolder.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.web.interceptor.bundling; 2 | 3 | import org.springframework.context.annotation.Scope; 4 | import org.springframework.context.annotation.ScopedProxyMode; 5 | import org.springframework.stereotype.Component; 6 | import org.springframework.web.context.WebApplicationContext; 7 | import ro.isdc.wro.http.support.ServletContextAttributeHelper; 8 | import ro.isdc.wro.model.WroModel; 9 | 10 | import javax.annotation.Resource; 11 | import javax.servlet.ServletContext; 12 | 13 | /** 14 | * @author jamesdbloom 15 | */ 16 | @Component 17 | @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) 18 | public class WroModelHolder { 19 | 20 | @Resource 21 | private ServletContext servletContext; 22 | private WroModel wroModel; 23 | 24 | public WroModel getWroModel() { 25 | if (wroModel == null) { 26 | ServletContextAttributeHelper helper = new ServletContextAttributeHelper(servletContext); 27 | if (helper.getManagerFactory() != null) { 28 | wroModel = helper.getManagerFactory().create().getModelFactory().create(); 29 | } 30 | } 31 | return wroModel; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/ebean.properties: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## ------------------------------------------------------------- 4 | ## Load (Dev/Test/Prod) server specific properties 5 | ## ------------------------------------------------------------- 6 | ## This is a possible alternative to using JNDI to set environment 7 | ## properties externally (to the WAR file). This is another way 8 | ## your Dev, Test and Prod servers can have different properties. 9 | 10 | #load.properties.override=${CATALINA_HOME}/conf/myapp.ebean.properties 11 | 12 | ebean.ddl.generate=true 13 | ebean.ddl.run=true 14 | 15 | 16 | ebean.debug.sql=true 17 | ebean.debug.lazyload=false 18 | 19 | 20 | ## ------------------------------------------------------------- 21 | ## Transaction Logging 22 | ## ------------------------------------------------------------- 23 | 24 | ## Use java util logging to log transaction details 25 | #ebean.loggingToJavaLogger=true 26 | 27 | ## General logging level: (none, explicit, all) 28 | ebean.logging=all 29 | 30 | ## Sharing log files: (none, explicit, all) 31 | ebean.logging.logfilesharing=all 32 | 33 | ## location of transaction logs 34 | ebean.logging.directory=logs 35 | #ebean.logging.directory=${catalina.base}/logs/trans 36 | 37 | ## Specific Log levels (none, summary, binding, sql) 38 | ebean.logging.iud=sql 39 | ebean.logging.query=sql 40 | ebean.logging.sqlquery=sql 41 | 42 | ## Log level for txn begin, commit and rollback (none, debug, verbose) 43 | ebean.logging.txnCommit=none 44 | 45 | 46 | 47 | ## ------------------------------------------------------------- 48 | ## DataSources (If using default Ebean DataSourceFactory) 49 | ## ------------------------------------------------------------- 50 | 51 | datasource.default=h2 52 | 53 | datasource.h2.username=sa 54 | datasource.h2.password= 55 | datasource.h2.databaseUrl=jdbc:h2:mem:test 56 | datasource.h2.databaseDriver=org.h2.Driver 57 | datasource.h2.minConnections=1 58 | datasource.h2.maxConnections=25 59 | datasource.h2.heartbeatsql=select 1 60 | datasource.h2.isolationlevel=read_committed 61 | 62 | #datasource.mysql.username=test 63 | #datasource.mysql.password=test 64 | #datasource.mysql.databaseUrl=jdbc:mysql://127.0.0.1:3306/test 65 | #datasource.mysql.databaseDriver=com.mysql.jdbc.Driver 66 | #datasource.mysql.minConnections=1 67 | #datasource.mysql.maxConnections=25 68 | #datasource.mysql.heartbeatsql=select count(*) from dual 69 | #datasource.mysql.isolationlevel=read_committed 70 | 71 | #datasource.ora.username=junk 72 | #datasource.ora.password=junk 73 | #datasource.ora.databaseUrl=jdbc:oracle:thin:@127.0.0.1:1521:XE 74 | #datasource.ora.databaseDriver=oracle.jdbc.driver.OracleDriver 75 | #datasource.ora.minConnections=1 76 | #datasource.ora.maxConnections=25 77 | #datasource.ora.heartbeatsql=select count(*) from dual 78 | #datasource.ora.isolationlevel=read_committed 79 | 80 | #datasource.pg.username=test 81 | #datasource.pg.password=test 82 | #datasource.pg.databaseUrl=jdbc:postgresql://127.0.0.1:5433/test 83 | #datasource.pg.databaseDriver=org.postgresql.Driver 84 | #datasource.pg.heartbeatsql=select 1 85 | 86 | -------------------------------------------------------------------------------- /src/main/resources/email.properties: -------------------------------------------------------------------------------- 1 | # email settings 2 | email.contact.address=fake@email.com 3 | email.host=smtp.gmail.com 4 | email.port=587 5 | email.protocol=smtp 6 | email.username=fake@gmail.com 7 | email.password=password 8 | email.starttls=true -------------------------------------------------------------------------------- /src/main/resources/freemarker_implicit.ftl: -------------------------------------------------------------------------------- 1 | [#ftl] 2 | [#-- @implicitly included --] 3 | 4 | [#macro compress single_line][/#macro] -------------------------------------------------------------------------------- /src/main/resources/log4j-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | server.log 4 | 5 | 6 | %date %level [%thread] %logger{35} [%file:%line] %msg%n 7 | 8 | 9 | 10 | 11 | 12 | %date %level [%thread] %logger{35} %msg%n 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/resources/validation.properties: -------------------------------------------------------------------------------- 1 | # registration 2 | validation.user.name = Please provide a name between 3 and 50 characters 3 | validation.user.email = Please provide a valid email 4 | # update password 5 | validation.user.password = Please provide a password of 8 or more characters with at least 1 digit and 1 letter 6 | validation.user.passwordNonMatching = The second password field does not match the first password field 7 | validation.user.register = Once you have completed your details you will receive an email to validate your account 8 | validation.user.alreadyExists = That email address has already been taken 9 | validation.user.invalidCredentials = Invalid username & password combination 10 | -------------------------------------------------------------------------------- /src/main/resources/web.properties: -------------------------------------------------------------------------------- 1 | # control bundling 2 | bundling.enabled=true -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/view/landing.ftl: -------------------------------------------------------------------------------- 1 | <#include "layout/default.ftl" /> 2 | 3 | <#macro page_body> 4 |
this is a test
5 | 6 | 7 | <@page_html/> -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/view/layout/default.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="cssResources" type="java.util.Map>" --> 2 | <#-- @ftlvariable name="jsResources" type="java.util.Map>" --> 3 | <#-- @ftlvariable name="isAjax" type="java.lang.Boolean" --> 4 | 5 | <#include "settings.ftl" /> 6 | <#import "/org/springframework/web/servlet/view/freemarker/spring.ftl" as spring /> 7 | <#import "/WEB-INF/view/macro/messages.ftl" as messages /> 8 | 9 | <#macro page_html> 10 | <@compress single_line=true> 11 | <#escape x as x?html> 12 | <#if isAjax?? && isAjax> 13 | <@page_body/> 14 | <#else> 15 | 16 | 17 | 18 | <@page_head/> 19 | 20 | <#flush> 21 | 22 | <@page_body/> 23 | <@page_js/> 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | <#macro page_head> 33 | <@page_meta/> 34 | some silly title to have on this page> 35 | <@page_css/> 36 | 37 | 38 | 39 | 40 | <#macro page_meta> 41 | 42 | 43 | 44 | 45 | 46 | 47 | <#macro page_css> 48 | <#if cssResources?? && cssResources["all"]?? > 49 | <#list cssResources["all"] as cssFile> 50 | 51 | 52 | 53 | 54 | 55 | <#macro page_body> 56 | 57 | 58 | 59 | <#macro page_js> 60 | <#if jsResources?? && jsResources["all"]?? > 61 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/view/layout/settings.ftl: -------------------------------------------------------------------------------- 1 | <#ftl strip_whitespace=true strict_syntax=true strip_text=true> 2 | <#setting url_escaping_charset="UTF-8"> -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/view/login.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="csrfParameterName" type="java.lang.String" --> 2 | <#-- @ftlvariable name="csrfToken" type="java.lang.String" --> 3 | <#include "layout/default.ftl" /> 4 | 5 | <#macro page_body> 6 |

Login with Username and Password

7 | 8 | <#if SPRING_SECURITY_LAST_EXCEPTION??> 9 |

${SPRING_SECURITY_LAST_EXCEPTION.message}

10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
User:
Password:
29 | <#if csrfParameterName?? && csrfToken?? > 30 | 31 | 32 |
33 | 34 | 35 | <@page_html/> -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/view/macro/messages.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="environment" type="org.springframework.core.env.Environment" --> 2 | 3 | <#-- 4 | * print_binding_errors 5 | * 6 | * Macro to print out binding errors 7 | --> 8 | <#macro print_binding_errors objectName=""> 9 | <#if bindingResult?? && (bindingResult.getAllErrors()?size > 0) && (bindingResult.objectName = objectName)> 10 |
11 |

There were problems with the data you entered:

12 | <#list bindingResult.getAllErrors() as error> 13 |

– ${environment.getProperty(error.defaultMessage)}

14 | 15 |
16 | 17 | 18 | 19 | <#-- 20 | * print_errors_list 21 | * 22 | * Macro to print out form errors 23 | --> 24 | <#macro print_errors_list> 25 | <#if validationErrors?? && (validationErrors?size > 0)> 26 |
27 |

There were problems with the data you entered:

28 | <#list validationErrors as error> 29 |

– ${environment.getProperty(error)}

30 | 31 |
32 | 33 | 34 | 35 | <#-- 36 | * message 37 | * 38 | * Macro to translate a message code into a message, 39 | * where [[code]] is used if it does not exist, 40 | * where {{code}} is used if it does exist and is blank 41 | --> 42 | <#function getMessage code> 43 | <#local message = springMacroRequestContext.getMessage(code, "[["+code+"]]")> 44 | <#if message == "" > 45 | <#return "{{"+code+"}}" /> 46 | <#else> 47 | <#return message /> 48 | 49 | 50 | 51 | <#macro message code> 52 | <#local message = getMessage(code)> 53 | ${message} 54 | 55 | 56 | <#-- 57 | * message with arguments 58 | * 59 | * Macro to translate a message code into a message, 60 | * where [[code]] is used if it does not exist, 61 | * where {{code}} is used if it does exist and is blank 62 | --> 63 | <#function getMessageWithArgs code args> 64 | <#local message = springMacroRequestContext.getMessage(code, "[["+code+"]]", args)> 65 | <#if message == "" > 66 | <#return "{{"+code+"}}" /> 67 | <#else> 68 | <#return message /> 69 | 70 | 71 | 72 | <#macro messageWithArgs code args> 73 | <#local message = getMessageWithArgs(code, args)> 74 | ${message} 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/view/message.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="title" type="java.lang.String" --> 2 | <#-- @ftlvariable name="message" type="java.lang.String" --> 3 | <#include "layout/default.ftl" /> 4 | 5 | <#macro page_body> 6 |

${title!"Message"}

7 | 8 |

class="error_message" <#else> class="message" >${message!""}

9 | 10 | 11 | <@page_html/> -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/view/register.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="environment" type="org.springframework.core.env.Environment" --> 2 | <#-- @ftlvariable name="name" type="java.lang.String" --> 3 | <#-- @ftlvariable name="email" type="java.lang.String" --> 4 | <#-- @ftlvariable name="emailPattern" type="java.lang.String" --> 5 | <#-- @ftlvariable name="csrfParameterName" type="java.lang.String" --> 6 | <#-- @ftlvariable name="csrfToken" type="java.lang.String" --> 7 | <#include "/WEB-INF/view/layout/default.ftl" /> 8 | 9 | <#macro page_body> 10 |

Update Password

11 | 20 |
21 | 22 |

${environment.getProperty("validation.user.register")}

23 | 24 | <@messages.print_binding_errors "user"/> 25 |
26 |

27 | <@spring.formInput path="user.name" attributes='required="required" pattern=".{3,50}" maxlength="50" class="show_validation" autocorrect="off" autocapitalize="off" autocomplete="off"' /> 28 | 29 |

30 | 31 |

32 | <@spring.formInput path="user.email" fieldType="email" attributes='required="required" pattern="${emailPattern}" class="show_validation" autocorrect="off" autocapitalize="off" autocomplete="off"' /> 33 | 34 |

35 | 36 |
37 | 38 |

39 | 40 |

41 |
42 | <#if csrfParameterName?? && csrfToken?? > 43 | 44 | 45 |
46 | 47 | 48 | <@page_html/> -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/view/updatePassword.ftl: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="environment" type="org.springframework.core.env.Environment" --> 2 | <#-- @ftlvariable name="email" type="java.lang.String" --> 3 | <#-- @ftlvariable name="oneTimeToken" type="java.lang.String" --> 4 | <#-- @ftlvariable name="passwordPattern" type="java.lang.String" --> 5 | <#-- @ftlvariable name="csrfParameterName" type="java.lang.String" --> 6 | <#-- @ftlvariable name="csrfToken" type="java.lang.String" --> 7 | <#include "layout/default.ftl" /> 8 | 9 | <#macro page_body> 10 |

Update Password

11 | 20 |
21 | 22 |

${environment.getProperty("validation.user.password")}

23 | 24 | <@messages.print_errors_list/> 25 |
26 | 27 | 28 | 29 |

30 | 31 |

32 | 33 |

34 | 35 |

36 | 37 |
38 | 39 |

40 | 41 |

42 |
43 | <#if csrfParameterName?? && csrfToken?? > 44 | 45 | 46 |
47 | 48 | 49 | <@page_html/> -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | contextClass 9 | org.springframework.web.context.support.AnnotationConfigWebApplicationContext 10 | 11 | 12 | 13 | 14 | contextConfigLocation 15 | org.jamesdbloom.configuration.RootConfiguration 16 | 17 | 18 | 19 | 20 | org.springframework.web.context.ContextLoaderListener 21 | 22 | 23 | 24 | 25 | org.springframework.web.context.request.RequestContextListener 26 | 27 | 28 | 29 | 30 | ro.isdc.wro.http.WroServletContextListener 31 | 32 | 33 | WroContextFilter 34 | ro.isdc.wro.http.WroContextFilter 35 | 36 | 37 | WroContextFilter 38 | /* 39 | 40 | 41 | 42 | 43 | webResourceOptimizer 44 | ro.isdc.wro.extensions.http.SpringWroFilter 45 | 46 | targetBeanName 47 | wroManagerFactory 48 | 49 | 50 | 51 | webResourceOptimizer 52 | /bundle/* 53 | 54 | 55 | 56 | 57 | springSecurityFilterChain 58 | org.springframework.web.filter.DelegatingFilterProxy 59 | 60 | 61 | springSecurityFilterChain 62 | /* 63 | 64 | 65 | 66 | 67 | dispatcherServlet 68 | org.springframework.web.servlet.DispatcherServlet 69 | 70 | contextClass 71 | org.springframework.web.context.support.AnnotationConfigWebApplicationContext 72 | 73 | 74 | contextConfigLocation 75 | org.jamesdbloom.web.configuration.WebMvcConfiguration 76 | 77 | 1 78 | 79 | 80 | dispatcherServlet 81 | /* 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/wro.properties: -------------------------------------------------------------------------------- 1 | # enable configuration (i.e. the values below) 2 | managerFactoryClassName=ro.isdc.wro.manager.factory.ConfigurableWroManagerFactory 3 | #managerFactoryClassName=ro.isdc.wro.extensions.manager.standalone.FingerprintAwareStandaloneManagerFactory 4 | 5 | # debug - in debug mode: caching (both internal and headers) is disable and minimisation can be disabled using ?minimize=false 6 | debug=false 7 | 8 | # processors - see http://code.google.com/p/wro4j/wiki/AvailableProcessors 9 | preProcessors=cssImport,semicolonAppender,conformColors 10 | postProcessors=yuiCssMin,googleClosureAdvanced 11 | 12 | # gzip - caching gzipped content uses more memory but avoid re-gzipping for every request 13 | cacheGzippedContent=true 14 | 15 | # naming - this should drive the naming strategy to fingerprint resource urls - NOT YET WORKING / CONFIGURED CORRECTLY 16 | hashStrategy=MD5 17 | namingStrategy=hashEncoder-CRC32 -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/wro.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | /resources/css/**.css 7 | /resources/js/**.js 8 | 9 | -------------------------------------------------------------------------------- /src/main/webapp/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesdbloom/base_spring_mvc_web_application/9fe0210f9e63e376929ea0f120c90f47f68ee242/src/main/webapp/favicon.ico -------------------------------------------------------------------------------- /src/main/webapp/resources/css/example.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #f0f8ff; 3 | color: #000; 4 | font-family: Helvetica, arial, freesans, clean, sans-serif; 5 | font-size: 0.85em; 6 | line-height: 1.125em; 7 | } 8 | 9 | .page_message { 10 | padding-bottom: 25px; 11 | } 12 | 13 | .error_box { 14 | padding: 2.5%; 15 | margin: 2.5% auto; 16 | border: 2px solid #ed3f3f; 17 | width: 90%; 18 | overflow: hidden; 19 | } 20 | 21 | .error_message { 22 | padding: 2.5%; 23 | margin: 2.5%; 24 | border-style: solid; 25 | border-width: 2px; 26 | border-color: #7b2020; 27 | width: 90%; 28 | } 29 | 30 | .message { 31 | padding: 2.5%; 32 | margin: 2.5% auto; 33 | border-style: solid; 34 | border-width: 1px; 35 | border-color: #206f92; 36 | width: 90%; 37 | } -------------------------------------------------------------------------------- /src/main/webapp/resources/icon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesdbloom/base_spring_mvc_web_application/9fe0210f9e63e376929ea0f120c90f47f68ee242/src/main/webapp/resources/icon/apple-touch-icon.png -------------------------------------------------------------------------------- /src/main/webapp/resources/icon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesdbloom/base_spring_mvc_web_application/9fe0210f9e63e376929ea0f120c90f47f68ee242/src/main/webapp/resources/icon/favicon.ico -------------------------------------------------------------------------------- /src/main/webapp/resources/js/example.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesdbloom/base_spring_mvc_web_application/9fe0210f9e63e376929ea0f120c90f47f68ee242/src/main/webapp/resources/js/example.js -------------------------------------------------------------------------------- /src/test/java/org/jamesdbloom/acceptance/BaseIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.acceptance; 2 | 3 | import org.jamesdbloom.configuration.RootConfiguration; 4 | import org.jamesdbloom.dao.UserDAO; 5 | import org.jamesdbloom.domain.User; 6 | import org.jamesdbloom.email.EmailService; 7 | import org.jamesdbloom.uuid.UUIDFactory; 8 | import org.jamesdbloom.web.configuration.WebMvcConfiguration; 9 | import org.junit.After; 10 | import org.junit.Before; 11 | import org.junit.BeforeClass; 12 | import org.junit.runner.RunWith; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.mock.web.MockHttpSession; 16 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 17 | import org.springframework.security.core.Authentication; 18 | import org.springframework.security.core.context.SecurityContext; 19 | import org.springframework.security.core.context.SecurityContextHolder; 20 | import org.springframework.security.crypto.password.PasswordEncoder; 21 | import org.springframework.security.web.FilterChainProxy; 22 | import org.springframework.security.web.context.HttpSessionSecurityContextRepository; 23 | import org.springframework.security.web.csrf.CsrfToken; 24 | import org.springframework.test.context.ContextConfiguration; 25 | import org.springframework.test.context.ContextHierarchy; 26 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 27 | import org.springframework.test.context.web.WebAppConfiguration; 28 | import org.springframework.test.web.servlet.MockMvc; 29 | import org.springframework.web.context.WebApplicationContext; 30 | 31 | import javax.annotation.Resource; 32 | 33 | import java.util.UUID; 34 | 35 | import static org.mockito.Mockito.mock; 36 | import static org.mockito.Mockito.spy; 37 | import static org.mockito.Mockito.when; 38 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 39 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 40 | import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; 41 | 42 | /** 43 | * @author jamesdbloom 44 | */ 45 | @RunWith(SpringJUnit4ClassRunner.class) 46 | @WebAppConfiguration 47 | @ContextHierarchy({ 48 | @ContextConfiguration( 49 | name = "root", 50 | classes = { 51 | RootConfiguration.class, 52 | BaseIntegrationTest.MockConfiguration.class 53 | } 54 | ), 55 | @ContextConfiguration( 56 | name = "dispatcher", 57 | classes = WebMvcConfiguration.class, 58 | initializers = PropertyMockingApplicationContextInitializer.class 59 | ) 60 | }) 61 | public abstract class BaseIntegrationTest { 62 | 63 | protected static final String uuid = UUID.randomUUID().toString(); 64 | 65 | @Resource 66 | protected WebApplicationContext webApplicationContext; 67 | protected MockMvc mockMvc; 68 | 69 | @Resource 70 | @SuppressWarnings("SpringJavaAutowiringInspection") 71 | protected FilterChainProxy springSecurityFilter; 72 | 73 | protected static MockHttpSession session; 74 | 75 | @Resource 76 | protected UserDAO userDAO; 77 | @Resource 78 | protected PasswordEncoder passwordEncoder; 79 | protected static CsrfToken csrfToken; 80 | protected User user; 81 | 82 | @BeforeClass 83 | public static void createSession() { 84 | Authentication authentication = new UsernamePasswordAuthenticationToken("user@email.com", "password"); 85 | SecurityContext securityContext = SecurityContextHolder.getContext(); 86 | securityContext.setAuthentication(authentication); 87 | 88 | session = new MockHttpSession(); 89 | session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext); 90 | } 91 | 92 | @Before 93 | public void setupFixture() throws Exception { 94 | mockMvc = webAppContextSetup(webApplicationContext) 95 | .addFilters(springSecurityFilter) 96 | .alwaysDo(print()).build(); 97 | 98 | user = new User(uuid, "user", "user@email.com", passwordEncoder.encode("password")); 99 | userDAO.save(user); 100 | csrfToken = (CsrfToken) mockMvc.perform(get("/").secure(true).session(session)).andReturn().getRequest().getAttribute("_csrf"); 101 | } 102 | 103 | @After 104 | public void cleanFixture() { 105 | userDAO.delete(user.getId()); 106 | } 107 | 108 | @Configuration 109 | static class MockConfiguration { 110 | 111 | @Bean 112 | public UUIDFactory uuidFactory() { 113 | UUIDFactory mockUUIDFactory = spy(new UUIDFactory()); 114 | when(mockUUIDFactory.generateUUID()).thenReturn(uuid); 115 | return mockUUIDFactory; 116 | } 117 | 118 | @Bean 119 | public EmailService emailService() { 120 | return mock(EmailService.class); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/test/java/org/jamesdbloom/acceptance/BasePage.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.acceptance; 2 | 3 | import org.jsoup.Jsoup; 4 | import org.jsoup.nodes.Document; 5 | import org.jsoup.nodes.Element; 6 | 7 | import java.io.UnsupportedEncodingException; 8 | 9 | /** 10 | * @author jamesdbloom 11 | */ 12 | public class BasePage { 13 | protected final Document html; 14 | 15 | public BasePage(String body) throws UnsupportedEncodingException { 16 | html = Jsoup.parse(body); 17 | } 18 | 19 | public String csrfValue() { 20 | Element csrfElement = html.select("#csrf").first(); 21 | if (csrfElement != null) { 22 | return csrfElement.val(); 23 | } else { 24 | return ""; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/org/jamesdbloom/acceptance/PropertyMockingApplicationContextInitializer.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.acceptance; 2 | 3 | import org.springframework.context.ApplicationContextInitializer; 4 | import org.springframework.context.ConfigurableApplicationContext; 5 | import org.springframework.core.env.MutablePropertySources; 6 | import org.springframework.core.env.StandardEnvironment; 7 | import org.springframework.mock.env.MockPropertySource; 8 | 9 | public class PropertyMockingApplicationContextInitializer implements ApplicationContextInitializer { 10 | 11 | @Override 12 | public void initialize(ConfigurableApplicationContext applicationContext) { 13 | MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources(); 14 | MockPropertySource mockEnvVars = new MockPropertySource().withProperty("bundling.enabled", false); 15 | propertySources.replace(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, mockEnvVars); 16 | } 17 | } -------------------------------------------------------------------------------- /src/test/java/org/jamesdbloom/acceptance/landing/LandingPageControllerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.acceptance.landing; 2 | 3 | import org.jamesdbloom.acceptance.BaseIntegrationTest; 4 | import org.junit.Test; 5 | import org.springframework.http.MediaType; 6 | 7 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 8 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 9 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 10 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 11 | 12 | /** 13 | * @author jamesdbloom 14 | */ 15 | public class LandingPageControllerIntegrationTest extends BaseIntegrationTest { 16 | 17 | @Test 18 | public void getPage() throws Exception { 19 | mockMvc.perform( 20 | get("/").secure(true) 21 | .session(session) 22 | .accept(MediaType.TEXT_HTML) 23 | ) 24 | .andExpect(status().isOk()) 25 | .andExpect(content().contentType("text/html;charset=UTF-8")) 26 | .andReturn(); 27 | } 28 | 29 | @Test 30 | public void testShouldNotReturnLoginPageForPostRequest() throws Exception { 31 | mockMvc 32 | .perform( 33 | post("/") 34 | .secure(true) 35 | .session(session) 36 | .param((csrfToken != null ? csrfToken.getParameterName() : "_csrf"), (csrfToken != null ? csrfToken.getToken() : "")) 37 | ) 38 | .andExpect(status().isMethodNotAllowed()) 39 | .andReturn(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/org/jamesdbloom/acceptance/login/LoginPage.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.acceptance.login; 2 | 3 | import org.jamesdbloom.acceptance.BasePage; 4 | import org.jsoup.nodes.Element; 5 | 6 | import java.io.UnsupportedEncodingException; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | import static org.junit.Assert.assertNotNull; 10 | 11 | /** 12 | * @author jamesdbloom 13 | */ 14 | public class LoginPage extends BasePage { 15 | 16 | public LoginPage(String body) throws UnsupportedEncodingException { 17 | super(body); 18 | } 19 | 20 | public void shouldHaveCorrectFields() { 21 | hasCorrectUserNameField(); 22 | hasCorrectPasswordField(); 23 | } 24 | 25 | public void hasCorrectUserNameField() { 26 | Element usernameElement = html.select("input[name=username]").first(); 27 | assertNotNull(usernameElement); 28 | assertEquals("1", usernameElement.attr("tabindex")); 29 | assertEquals("text", usernameElement.attr("type")); 30 | } 31 | 32 | public void hasCorrectPasswordField() { 33 | Element passwordElement = html.select("input[name=password]").first(); 34 | assertNotNull(passwordElement); 35 | assertEquals("2", passwordElement.attr("tabindex")); 36 | assertEquals("password", passwordElement.attr("type")); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/org/jamesdbloom/acceptance/login/LoginPageControllerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.acceptance.login; 2 | 3 | import org.jamesdbloom.acceptance.BaseIntegrationTest; 4 | import org.jamesdbloom.acceptance.PropertyMockingApplicationContextInitializer; 5 | import org.jamesdbloom.configuration.RootConfiguration; 6 | import org.jamesdbloom.dao.UserDAO; 7 | import org.jamesdbloom.domain.User; 8 | import org.jamesdbloom.uuid.UUIDFactory; 9 | import org.jamesdbloom.web.configuration.WebMvcConfiguration; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.http.MediaType; 16 | import org.springframework.mock.web.MockHttpSession; 17 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 18 | import org.springframework.security.core.Authentication; 19 | import org.springframework.security.core.context.SecurityContext; 20 | import org.springframework.security.core.context.SecurityContextHolder; 21 | import org.springframework.security.crypto.password.PasswordEncoder; 22 | import org.springframework.security.web.FilterChainProxy; 23 | import org.springframework.security.web.context.HttpSessionSecurityContextRepository; 24 | import org.springframework.security.web.csrf.CsrfToken; 25 | import org.springframework.test.context.ContextConfiguration; 26 | import org.springframework.test.context.ContextHierarchy; 27 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 28 | import org.springframework.test.context.web.WebAppConfiguration; 29 | import org.springframework.test.web.servlet.MockMvc; 30 | import org.springframework.test.web.servlet.MvcResult; 31 | import org.springframework.web.context.WebApplicationContext; 32 | 33 | import javax.annotation.Resource; 34 | 35 | import static org.mockito.Mockito.mock; 36 | import static org.mockito.Mockito.when; 37 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 38 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 39 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 40 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 41 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 42 | import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; 43 | 44 | /** 45 | * @author jamesdbloom 46 | */ 47 | public class LoginPageControllerIntegrationTest extends BaseIntegrationTest { 48 | 49 | @Test 50 | public void testShouldReturnLoginPageForGetRequest() throws Exception { 51 | // when 52 | MvcResult mvcResult = mockMvc.perform( 53 | get("/login") 54 | .secure(true) 55 | .session(session) 56 | .accept(MediaType.TEXT_HTML) 57 | .param((csrfToken != null ? csrfToken.getParameterName() : "_csrf"), (csrfToken != null ? csrfToken.getToken() : "")) 58 | ) 59 | // then 60 | .andExpect(status().isOk()) 61 | .andExpect(content().contentType("text/html;charset=UTF-8")) 62 | .andReturn(); 63 | 64 | LoginPage loginPage = new LoginPage(mvcResult.getResponse().getContentAsString()); 65 | loginPage.shouldHaveCorrectFields(); 66 | } 67 | 68 | @Test 69 | public void testShouldNotReturnLoginPageForPostRequest() throws Exception { 70 | // when 71 | mockMvc.perform( 72 | post("/login") 73 | .secure(true) 74 | .session(session) 75 | ) 76 | // then 77 | .andExpect(status().isForbidden()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/org/jamesdbloom/acceptance/registration/RegistrationIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.acceptance.registration; 2 | 3 | import org.jamesdbloom.acceptance.BaseIntegrationTest; 4 | import org.jamesdbloom.domain.User; 5 | import org.junit.Test; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.test.web.servlet.MvcResult; 8 | 9 | import java.util.UUID; 10 | 11 | import static org.hamcrest.core.Is.is; 12 | import static org.junit.Assert.assertThat; 13 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 14 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 16 | 17 | /** 18 | * @author jamesdbloom 19 | */ 20 | public class RegistrationIntegrationTest extends BaseIntegrationTest { 21 | 22 | private User expectedUser = new User(UUID.randomUUID().toString(), "register user", "register@email.com", "Password123"); 23 | 24 | @Test 25 | public void shouldGetPage() throws Exception { 26 | // when 27 | mockMvc.perform( 28 | get("/register") 29 | .secure(true) 30 | .session(session) 31 | .accept(MediaType.TEXT_HTML) 32 | ) 33 | // then 34 | .andExpect(status().isOk()) 35 | .andExpect(content().contentType("text/html;charset=UTF-8")); 36 | } 37 | 38 | @Test 39 | public void shouldRegisterUser() throws Exception { 40 | // when 41 | mockMvc.perform( 42 | post("/register") 43 | .secure(true) 44 | .session(session) 45 | .contentType(MediaType.APPLICATION_FORM_URLENCODED) 46 | .param("name", expectedUser.getName()) 47 | .param("email", expectedUser.getEmail()) 48 | .param((csrfToken != null ? csrfToken.getParameterName() : "_csrf"), (csrfToken != null ? csrfToken.getToken() : "")) 49 | ) 50 | 51 | // then 52 | .andExpect(redirectedUrl("/message")) 53 | .andExpect(flash().attributeExists("message")) 54 | .andExpect(flash().attribute("title", "Account Created")); 55 | 56 | User actualUser = userDAO.findByEmail(expectedUser.getEmail()); 57 | 58 | try { 59 | assertThat(actualUser.getName(), is(expectedUser.getName())); 60 | assertThat(actualUser.getEmail(), is(expectedUser.getEmail())); 61 | } finally { 62 | userDAO.delete(actualUser.getId()); 63 | } 64 | } 65 | 66 | @Test 67 | public void shouldGetPageWithNameError() throws Exception { 68 | // when 69 | MvcResult mvcResult = mockMvc.perform( 70 | post("/register") 71 | .secure(true) 72 | .session(session) 73 | .contentType(MediaType.APPLICATION_FORM_URLENCODED) 74 | .param("name", "xx") 75 | .param("email", expectedUser.getEmail()) 76 | .param((csrfToken != null ? csrfToken.getParameterName() : "_csrf"), (csrfToken != null ? csrfToken.getToken() : "")) 77 | ) 78 | 79 | // then 80 | .andExpect(status().isOk()) 81 | .andExpect(content().contentType("text/html;charset=UTF-8")) 82 | .andReturn(); 83 | 84 | RegistrationPage registrationPage = new RegistrationPage(mvcResult.getResponse().getContentAsString()); 85 | registrationPage.hasErrors("user", "Please provide a name between 3 and 50 characters"); 86 | registrationPage.hasRegistrationFields("xx", expectedUser.getEmail()); 87 | } 88 | 89 | @Test 90 | public void shouldGetPageWithEmailError() throws Exception { 91 | // when 92 | MvcResult mvcResult = mockMvc.perform( 93 | post("/register") 94 | .secure(true) 95 | .session(session) 96 | .contentType(MediaType.APPLICATION_FORM_URLENCODED) 97 | .param("name", expectedUser.getName()) 98 | .param("email", "incorrect_email") 99 | .param((csrfToken != null ? csrfToken.getParameterName() : "_csrf"), (csrfToken != null ? csrfToken.getToken() : "")) 100 | ) 101 | 102 | // then 103 | .andExpect(status().isOk()) 104 | .andExpect(content().contentType("text/html;charset=UTF-8")) 105 | .andReturn(); 106 | 107 | RegistrationPage registrationPage = new RegistrationPage(mvcResult.getResponse().getContentAsString()); 108 | registrationPage.hasErrors("user", "Please provide a valid email"); 109 | registrationPage.hasRegistrationFields(expectedUser.getName(), "incorrect_email"); 110 | } 111 | 112 | @Test 113 | public void shouldGetPageWithAllErrors() throws Exception { 114 | // when 115 | MvcResult mvcResult = mockMvc.perform( 116 | post("/register") 117 | .secure(true) 118 | .session(session) 119 | .contentType(MediaType.APPLICATION_FORM_URLENCODED) 120 | .param("name", "xx") 121 | .param("email", "incorrect_email") 122 | .param((csrfToken != null ? csrfToken.getParameterName() : "_csrf"), (csrfToken != null ? csrfToken.getToken() : "")) 123 | ) 124 | // then 125 | .andExpect(status().isOk()) 126 | .andExpect(content().contentType("text/html;charset=UTF-8")) 127 | .andReturn(); 128 | 129 | RegistrationPage registrationPage = new RegistrationPage(mvcResult.getResponse().getContentAsString()); 130 | registrationPage.hasErrors("user", "Please provide a name between 3 and 50 characters", "Please provide a valid email"); 131 | registrationPage.hasRegistrationFields("xx", "incorrect_email"); 132 | } 133 | 134 | @Test 135 | public void shouldGetPageWithEmailAlreadyTakenError() throws Exception { 136 | // given 137 | userDAO.save(new User("already_exists_id", "test name", "already_taken@email.com", "Password123")); 138 | 139 | // when 140 | MvcResult mvcResult = mockMvc.perform( 141 | post("/register") 142 | .secure(true) 143 | .session(session) 144 | .contentType(MediaType.APPLICATION_FORM_URLENCODED) 145 | .param("name", expectedUser.getName()) 146 | .param("email", "already_taken@email.com") 147 | .param((csrfToken != null ? csrfToken.getParameterName() : "_csrf"), (csrfToken != null ? csrfToken.getToken() : "")) 148 | ) 149 | // then 150 | .andExpect(status().isOk()) 151 | .andExpect(content().contentType("text/html;charset=UTF-8")) 152 | .andReturn(); 153 | 154 | RegistrationPage registrationPage = new RegistrationPage(mvcResult.getResponse().getContentAsString()); 155 | registrationPage.hasErrors("user", "That email address has already been taken"); 156 | registrationPage.hasRegistrationFields(expectedUser.getName(), "already_taken@email.com"); 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /src/test/java/org/jamesdbloom/acceptance/registration/RegistrationPage.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.acceptance.registration; 2 | 3 | import com.google.common.base.Function; 4 | import com.google.common.collect.Lists; 5 | import org.jamesdbloom.acceptance.BasePage; 6 | import org.jsoup.Jsoup; 7 | import org.jsoup.nodes.Document; 8 | import org.jsoup.nodes.Element; 9 | 10 | import java.io.UnsupportedEncodingException; 11 | import java.util.List; 12 | 13 | import static org.hamcrest.Matchers.containsInAnyOrder; 14 | import static org.junit.Assert.*; 15 | 16 | /** 17 | * @author jamesdbloom 18 | */ 19 | public class RegistrationPage extends BasePage { 20 | 21 | public RegistrationPage(String body) throws UnsupportedEncodingException { 22 | super(body); 23 | } 24 | 25 | public void hasErrors(String objectName, String... expectedErrorMessages) { 26 | List errorMessages = Lists.transform(html.select("#validation_error_" + objectName + " .validation_error"), new Function() { 27 | public String apply(Element input) { 28 | return input.text().replace("– ", ""); 29 | } 30 | }); 31 | assertThat(errorMessages, containsInAnyOrder(expectedErrorMessages)); 32 | } 33 | 34 | public void shouldHaveCorrectFields() { 35 | hasRegistrationFields("", ""); 36 | } 37 | 38 | public void hasRegistrationFields(String name, String email) { 39 | Element nameInputElement = html.select("#name").first(); 40 | assertNotNull(nameInputElement); 41 | assertEquals(name, nameInputElement.val()); 42 | 43 | Element emailInputElement = html.select("#email").first(); 44 | assertNotNull(emailInputElement); 45 | assertEquals(email, emailInputElement.val()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/org/jamesdbloom/acceptance/security/SecurityIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.acceptance.security; 2 | 3 | import org.jamesdbloom.acceptance.BaseIntegrationTest; 4 | import org.junit.Test; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.mock.web.MockHttpSession; 7 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 8 | import org.springframework.security.core.Authentication; 9 | import org.springframework.security.core.context.SecurityContext; 10 | import org.springframework.security.core.context.SecurityContextHolder; 11 | import org.springframework.security.web.context.HttpSessionSecurityContextRepository; 12 | 13 | import static org.hamcrest.CoreMatchers.is; 14 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 16 | 17 | /** 18 | * @author jamesdbloom 19 | */ 20 | public class SecurityIntegrationTest extends BaseIntegrationTest { 21 | 22 | @Test 23 | public void testShouldRedirectIfNotSecure() throws Exception { 24 | mockMvc.perform( 25 | get("/").secure(false) 26 | .accept(MediaType.TEXT_HTML) 27 | ) 28 | .andExpect(status().is3xxRedirection()) 29 | .andExpect(header().string("Location", is("/"))); 30 | } 31 | 32 | @Test 33 | public void testShouldReturnDirectToLoginPageInNotLoggedIn() throws Exception { 34 | mockMvc.perform( 35 | get("/").secure(true) 36 | .accept(MediaType.TEXT_HTML) 37 | ) 38 | .andExpect(status().is3xxRedirection()) 39 | .andExpect(header().string("Location", is("http://localhost/login"))); 40 | } 41 | 42 | 43 | @Test 44 | public void testShouldNotReturnLoginPageIfLoggedIn() throws Exception { 45 | mockMvc.perform( 46 | get("/").secure(true) 47 | .session(session) 48 | .accept(MediaType.TEXT_HTML) 49 | ) 50 | .andExpect(status().isOk()) 51 | .andExpect(content().contentType("text/html;charset=UTF-8")) 52 | .andExpect(header().doesNotExist("Location")); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/org/jamesdbloom/acceptance/updatepassword/UpdatePasswordIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.acceptance.updatepassword; 2 | 3 | import org.jamesdbloom.acceptance.BaseIntegrationTest; 4 | import org.jamesdbloom.domain.User; 5 | import org.jamesdbloom.uuid.UUIDFactory; 6 | import org.junit.After; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.test.web.servlet.MvcResult; 11 | 12 | import java.util.UUID; 13 | 14 | import static org.junit.Assert.assertTrue; 15 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 16 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 17 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 18 | 19 | /** 20 | * @author jamesdbloom 21 | */ 22 | public class UpdatePasswordIntegrationTest extends BaseIntegrationTest { 23 | 24 | private User expectedUser = new User(UUID.randomUUID().toString(), "password user", "password@email.com", "Password123"); 25 | 26 | @Before 27 | public void saveUser() { 28 | expectedUser.setOneTimeToken(new UUIDFactory().generateUUID()); 29 | userDAO.save(expectedUser); 30 | } 31 | 32 | @After 33 | public void removeUser() { 34 | userDAO.delete(expectedUser.getId()); 35 | } 36 | 37 | @Test 38 | public void shouldGetPage() throws Exception { 39 | // when 40 | mockMvc.perform( 41 | get("/updatePassword") 42 | .secure(true) 43 | .session(session) 44 | .accept(MediaType.TEXT_HTML) 45 | .param("email", expectedUser.getEmail()) 46 | .param("oneTimeToken", expectedUser.getOneTimeToken()) 47 | ) 48 | // then 49 | .andExpect(status().isOk()) 50 | .andExpect(content().contentType("text/html;charset=UTF-8")); 51 | } 52 | 53 | 54 | @Test 55 | public void shouldValidateEmailAndOneTimeTokenWhenDisplayingForm() throws Exception { 56 | // when 57 | mockMvc.perform( 58 | get("/updatePassword") 59 | .secure(true) 60 | .session(session) 61 | .accept(MediaType.TEXT_HTML) 62 | .param("email", "incorrect_email") 63 | .param("oneTimeToken", "incorrect_token") 64 | ) 65 | // then 66 | .andExpect(redirectedUrl("/message")) 67 | .andExpect(flash().attributeExists("message")) 68 | .andExpect(flash().attribute("title", "Invalid Request")) 69 | .andExpect(flash().attribute("error", true)); 70 | } 71 | 72 | @Test 73 | public void shouldUpdatePassword() throws Exception { 74 | // when 75 | mockMvc.perform( 76 | post("/updatePassword") 77 | .secure(true) 78 | .session(session) 79 | .contentType(MediaType.APPLICATION_FORM_URLENCODED) 80 | .param("email", expectedUser.getEmail()) 81 | .param("oneTimeToken", expectedUser.getOneTimeToken()) 82 | .param("password", "NewPassword123") 83 | .param("passwordConfirm", "NewPassword123") 84 | .param((csrfToken != null ? csrfToken.getParameterName() : "_csrf"), (csrfToken != null ? csrfToken.getToken() : "")) 85 | ) 86 | 87 | // then 88 | .andExpect(redirectedUrl("/message")) 89 | .andExpect(flash().attributeExists("message")) 90 | .andExpect(flash().attribute("title", "Password Updated")); 91 | 92 | User actualUser = userDAO.findByEmail(expectedUser.getEmail()); 93 | assertTrue(passwordEncoder.matches("NewPassword123", actualUser.getPassword())); 94 | } 95 | 96 | @Test 97 | public void shouldGetPageWithPasswordFormatError() throws Exception { 98 | // when 99 | MvcResult mvcResult = mockMvc.perform( 100 | post("/updatePassword") 101 | .secure(true) 102 | .session(session) 103 | .contentType(MediaType.APPLICATION_FORM_URLENCODED) 104 | .param("email", expectedUser.getEmail()) 105 | .param("oneTimeToken", expectedUser.getOneTimeToken()) 106 | .param("password", "password") 107 | .param("passwordConfirm", "password") 108 | .param((csrfToken != null ? csrfToken.getParameterName() : "_csrf"), (csrfToken != null ? csrfToken.getToken() : "")) 109 | ) 110 | 111 | // then 112 | .andExpect(status().isOk()) 113 | .andExpect(content().contentType("text/html;charset=UTF-8")) 114 | .andReturn(); 115 | 116 | UpdatePasswordPage updatePasswordPage = new UpdatePasswordPage(mvcResult.getResponse().getContentAsString()); 117 | updatePasswordPage.hasErrors("Please provide a password of 8 or more characters with at least 1 digit and 1 letter"); 118 | } 119 | 120 | @Test 121 | public void shouldGetPageWithPasswordMatchingError() throws Exception { 122 | // when 123 | MvcResult mvcResult = mockMvc.perform( 124 | post("/updatePassword") 125 | .secure(true) 126 | .session(session) 127 | .contentType(MediaType.APPLICATION_FORM_URLENCODED) 128 | .param("email", expectedUser.getEmail()) 129 | .param("oneTimeToken", expectedUser.getOneTimeToken()) 130 | .param("password", "NewPassword123") 131 | .param("passwordConfirm", "Password123") 132 | .param((csrfToken != null ? csrfToken.getParameterName() : "_csrf"), (csrfToken != null ? csrfToken.getToken() : "")) 133 | ) 134 | 135 | // then 136 | .andExpect(status().isOk()) 137 | .andExpect(content().contentType("text/html;charset=UTF-8")) 138 | .andReturn(); 139 | 140 | UpdatePasswordPage updatePasswordPage = new UpdatePasswordPage(mvcResult.getResponse().getContentAsString()); 141 | updatePasswordPage.hasErrors("The second password field does not match the first password field"); 142 | } 143 | 144 | @Test 145 | public void shouldValidateEmailAndOneTimeTokenWhenSubmittingForm() throws Exception { 146 | // when 147 | mockMvc.perform( 148 | post("/updatePassword") 149 | .secure(true) 150 | .session(session) 151 | .accept(MediaType.TEXT_HTML) 152 | .param("email", "incorrect_email") 153 | .param("oneTimeToken", "incorrect_token") 154 | .param("password", "NewPassword123") 155 | .param("passwordConfirm", "NewPassword123") 156 | .param((csrfToken != null ? csrfToken.getParameterName() : "_csrf"), (csrfToken != null ? csrfToken.getToken() : "")) 157 | ) 158 | // then 159 | .andExpect(redirectedUrl("/message")) 160 | .andExpect(flash().attributeExists("message")) 161 | .andExpect(flash().attribute("title", "Invalid Request")) 162 | .andExpect(flash().attribute("error", true)); 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /src/test/java/org/jamesdbloom/acceptance/updatepassword/UpdatePasswordPage.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.acceptance.updatepassword; 2 | 3 | import com.google.common.base.Function; 4 | import com.google.common.collect.Lists; 5 | import org.jamesdbloom.acceptance.BasePage; 6 | import org.jsoup.nodes.Element; 7 | 8 | import java.io.UnsupportedEncodingException; 9 | import java.util.List; 10 | 11 | import static org.hamcrest.Matchers.containsInAnyOrder; 12 | import static org.junit.Assert.assertEquals; 13 | import static org.junit.Assert.assertNotNull; 14 | import static org.junit.Assert.assertThat; 15 | 16 | /** 17 | * @author jamesdbloom 18 | */ 19 | public class UpdatePasswordPage extends BasePage { 20 | 21 | public UpdatePasswordPage(String body) throws UnsupportedEncodingException { 22 | super(body); 23 | } 24 | 25 | public void hasErrors(String... expectedErrorMessages) { 26 | List errorMessages = Lists.transform(html.select("#validation_error .validation_error"), new Function() { 27 | public String apply(Element input) { 28 | return input.text().replace("– ", ""); 29 | } 30 | }); 31 | assertThat(errorMessages, containsInAnyOrder(expectedErrorMessages)); 32 | } 33 | 34 | public void shouldHaveCorrectFields() { 35 | Element passwordInputElement = html.select("#password").first(); 36 | assertNotNull(passwordInputElement); 37 | 38 | Element passwordConfirmInputElement = html.select("#passwordConfirm").first(); 39 | assertNotNull(passwordConfirmInputElement); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/org/jamesdbloom/domain/UserTest.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.domain; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.core.Is.is; 6 | import static org.junit.Assert.assertThat; 7 | 8 | /** 9 | * @author jamesdbloom 10 | */ 11 | public class UserTest { 12 | 13 | @Test 14 | public void shouldReturnValuesSetInConstructor() { 15 | // given 16 | String id = "id"; 17 | String name = "name"; 18 | String email = "email"; 19 | String password = "password"; 20 | 21 | // when 22 | User user = new User(id, name, email, password); 23 | 24 | // then 25 | assertThat(user.getId(), is(id)); 26 | assertThat(user.getName(), is(name)); 27 | assertThat(user.getEmail(), is(email)); 28 | assertThat(user.getPassword(), is(password)); 29 | } 30 | 31 | @Test 32 | public void shouldReturnValuesSetInSetters() { 33 | // given 34 | String id = "id"; 35 | String name = "name"; 36 | String email = "email"; 37 | String password = "password"; 38 | 39 | // when 40 | User user = new User(); 41 | user.setId(id); 42 | user.setName(name); 43 | user.setEmail(email); 44 | user.setPassword(password); 45 | 46 | // then 47 | assertThat(user.getId(), is(id)); 48 | assertThat(user.getName(), is(name)); 49 | assertThat(user.getEmail(), is(email)); 50 | assertThat(user.getPassword(), is(password)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/org/jamesdbloom/email/EmailServiceTest.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.email; 2 | 3 | import org.jamesdbloom.domain.User; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.mockito.ArgumentCaptor; 8 | import org.mockito.InjectMocks; 9 | import org.mockito.Mock; 10 | import org.mockito.runners.MockitoJUnitRunner; 11 | import org.springframework.core.env.Environment; 12 | import org.springframework.mail.javamail.JavaMailSender; 13 | import org.springframework.mail.javamail.JavaMailSenderImpl; 14 | import org.springframework.mail.javamail.MimeMessagePreparator; 15 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 16 | 17 | import javax.mail.internet.MimeMessage; 18 | import javax.servlet.http.HttpServletRequest; 19 | import java.io.UnsupportedEncodingException; 20 | import java.net.MalformedURLException; 21 | import java.net.URL; 22 | 23 | import static org.junit.Assert.assertEquals; 24 | import static org.mockito.Mockito.*; 25 | 26 | /** 27 | * @author jamesdbloom 28 | */ 29 | @RunWith(MockitoJUnitRunner.class) 30 | public class EmailServiceTest { 31 | 32 | @Mock 33 | private JavaMailSender mailSender; 34 | @Mock 35 | private Environment environment; 36 | @Mock 37 | private ThreadPoolTaskExecutor taskExecutor; 38 | @InjectMocks 39 | private EmailService emailService = new EmailService(); 40 | private ArgumentCaptor runnableArgumentCaptor; 41 | private ArgumentCaptor preparatorArgumentCaptor; 42 | private MimeMessage mimeMessage; 43 | 44 | @Before 45 | public void setupMocks() { 46 | runnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class); 47 | doNothing().when(taskExecutor).execute(runnableArgumentCaptor.capture()); 48 | 49 | preparatorArgumentCaptor = ArgumentCaptor.forClass(MimeMessagePreparator.class); 50 | doNothing().when(mailSender).send(preparatorArgumentCaptor.capture()); 51 | 52 | mimeMessage = new JavaMailSenderImpl().createMimeMessage(); 53 | } 54 | 55 | @Test 56 | public void shouldSendEmail() throws Exception { 57 | // given 58 | String from = "from@email.com"; 59 | String[] to = {"to@first-email.com", "to@second-email.com"}; 60 | String subject = "subject"; 61 | String msg = "msg"; 62 | 63 | // when 64 | emailService.sendMessage(from, to, subject, msg); 65 | runnableArgumentCaptor.getValue().run(); 66 | preparatorArgumentCaptor.getValue().prepare(mimeMessage); 67 | 68 | // then 69 | assertEquals(from, mimeMessage.getFrom()[0].toString()); 70 | assertEquals(to[0], mimeMessage.getAllRecipients()[0].toString()); 71 | assertEquals(to[1], mimeMessage.getAllRecipients()[1].toString()); 72 | assertEquals(subject, mimeMessage.getSubject()); 73 | assertEquals(msg, mimeMessage.getContent().toString()); 74 | } 75 | 76 | @Test 77 | public void shouldSendRegistrationEmail() throws Exception { 78 | // given 79 | String token = "token"; 80 | String email = "to@email.com"; 81 | User user = new User() 82 | .withEmail(email) 83 | .withOneTimeToken(token); 84 | 85 | String hostName = "hostName"; 86 | int port = 666; 87 | HttpServletRequest request = mock(HttpServletRequest.class); 88 | when(request.getHeader("Host")).thenReturn(hostName); 89 | when(request.getLocalPort()).thenReturn(port); 90 | 91 | String leagueEmail = "info@squash-league.com"; 92 | when(environment.getProperty("email.contact.address")).thenReturn(leagueEmail); 93 | 94 | // when 95 | emailService.sendRegistrationMessage(user, request); 96 | runnableArgumentCaptor.getValue().run(); 97 | preparatorArgumentCaptor.getValue().prepare(mimeMessage); 98 | 99 | // then 100 | String subject = EmailService.NAME_PREFIX + "New Registration"; 101 | assertEquals(leagueEmail, mimeMessage.getFrom()[0].toString()); 102 | assertEquals(email, mimeMessage.getAllRecipients()[0].toString()); 103 | assertEquals(subject, mimeMessage.getSubject()); 104 | 105 | assertEquals("" + subject + "\n" + 106 | "

" + subject + "

\n" + 107 | "

A new user has just been registered for " + email + "

\n" + 108 | "

To validate this email address please click on the following link https://" + hostName + ":" + port + "/updatePassword?email=to%40email.com&oneTimeToken=" + token + "

\n" + 109 | "", mimeMessage.getContent().toString()); 110 | } 111 | 112 | @Test 113 | public void shouldSendUpdatePasswordEmail() throws Exception { 114 | // given 115 | String token = "token"; 116 | String email = "to@email.com"; 117 | User user = new User() 118 | .withEmail(email) 119 | .withOneTimeToken(token); 120 | 121 | String hostName = "hostName"; 122 | int port = 666; 123 | HttpServletRequest request = mock(HttpServletRequest.class); 124 | when(request.getHeader("Host")).thenReturn(hostName); 125 | when(request.getLocalPort()).thenReturn(port); 126 | 127 | String leagueEmail = "info@squash-league.com"; 128 | when(environment.getProperty("email.contact.address")).thenReturn(leagueEmail); 129 | 130 | // when 131 | emailService.sendUpdatePasswordMessage(user, request); 132 | runnableArgumentCaptor.getValue().run(); 133 | preparatorArgumentCaptor.getValue().prepare(mimeMessage); 134 | 135 | // then 136 | String subject = EmailService.NAME_PREFIX + "Update Password"; 137 | assertEquals(leagueEmail, mimeMessage.getFrom()[0].toString()); 138 | assertEquals(email, mimeMessage.getAllRecipients()[0].toString()); 139 | assertEquals(subject, mimeMessage.getSubject()); 140 | 141 | assertEquals("" + subject + "\n" + 142 | "

" + subject + "

\n" + 143 | "

To update your password please click on the following link https://" + hostName + ":" + port + "/updatePassword?email=to%40email.com&oneTimeToken=" + token + "

\n" + 144 | "", mimeMessage.getContent().toString()); 145 | } 146 | 147 | @Test 148 | public void shouldBuildCorrectURL() throws MalformedURLException, UnsupportedEncodingException { 149 | // given 150 | String hostName = "hostName"; 151 | int port = 666; 152 | HttpServletRequest request = mock(HttpServletRequest.class); 153 | when(request.getHeader("Host")).thenReturn(hostName); 154 | when(request.getLocalPort()).thenReturn(port); 155 | 156 | // when 157 | URL actual = emailService.createUrl(new User().withEmail("to@email.com").withOneTimeToken("token"), request); 158 | 159 | // then 160 | assertEquals("https://" + hostName + ":" + port + "/updatePassword?email=to%40email.com&oneTimeToken=token", actual.toString()); 161 | } 162 | 163 | @Test 164 | public void shouldBuildCorrectURLHostHeaderWithPort() throws MalformedURLException, UnsupportedEncodingException { 165 | // given 166 | String hostName = "hostName:12345"; 167 | HttpServletRequest request = mock(HttpServletRequest.class); 168 | when(request.getHeader("Host")).thenReturn(hostName); 169 | when(request.getLocalPort()).thenReturn(666); 170 | 171 | // when 172 | URL actual = emailService.createUrl(new User().withEmail("to@email.com").withOneTimeToken("token"), request); 173 | 174 | // then 175 | assertEquals("https://" + hostName + "/updatePassword?email=to%40email.com&oneTimeToken=token", actual.toString()); 176 | } 177 | } 178 | 179 | -------------------------------------------------------------------------------- /src/test/java/org/jamesdbloom/email/NewRegistrationEmail.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.email; 2 | 3 | import org.jamesdbloom.acceptance.BasePage; 4 | import org.jsoup.nodes.Element; 5 | 6 | import java.io.UnsupportedEncodingException; 7 | import java.net.URLEncoder; 8 | import java.nio.charset.StandardCharsets; 9 | 10 | import static org.hamcrest.Matchers.containsString; 11 | import static org.junit.Assert.assertEquals; 12 | import static org.junit.Assert.assertNotNull; 13 | import static org.junit.Assert.assertThat; 14 | 15 | /** 16 | * @author jamesdbloom 17 | */ 18 | public class NewRegistrationEmail extends BasePage { 19 | 20 | public NewRegistrationEmail(String body) throws UnsupportedEncodingException { 21 | super(body); 22 | } 23 | 24 | public String shouldHaveCorrectFields(String email) throws UnsupportedEncodingException { 25 | hasCorrectTitle(); 26 | return hasCorrectUpdatePasswordLink(email); 27 | } 28 | 29 | private void hasCorrectTitle() { 30 | Element title = html.select("h1").first(); 31 | assertNotNull(title); 32 | assertEquals("MyApplication - New Registration", title.text()); 33 | } 34 | 35 | private String hasCorrectUpdatePasswordLink(String email) throws UnsupportedEncodingException { 36 | Element updatePasswordLink = html.select("a").first(); 37 | assertNotNull(updatePasswordLink); 38 | assertThat(updatePasswordLink.attr("href"), containsString("updatePassword")); 39 | assertThat(updatePasswordLink.attr("href"), containsString("email=" + URLEncoder.encode(email, StandardCharsets.UTF_8.name()))); 40 | assertThat(updatePasswordLink.attr("href"), containsString("oneTimeToken=")); 41 | return updatePasswordLink.attr("href"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/org/jamesdbloom/integration/SystemTest.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.integration; 2 | 3 | import org.apache.catalina.Context; 4 | import org.apache.catalina.LifecycleException; 5 | import org.apache.catalina.Service; 6 | import org.apache.catalina.connector.Connector; 7 | import org.apache.catalina.startup.ContextConfig; 8 | import org.apache.catalina.startup.Tomcat; 9 | import org.apache.http.HttpResponse; 10 | import org.apache.http.HttpStatus; 11 | import org.apache.http.client.HttpClient; 12 | import org.apache.http.client.entity.UrlEncodedFormEntity; 13 | import org.apache.http.client.methods.HttpGet; 14 | import org.apache.http.client.methods.HttpPost; 15 | import org.apache.http.config.SocketConfig; 16 | import org.apache.http.conn.ssl.AllowAllHostnameVerifier; 17 | import org.apache.http.conn.ssl.SSLContexts; 18 | import org.apache.http.conn.ssl.TrustStrategy; 19 | import org.apache.http.impl.client.CloseableHttpClient; 20 | import org.apache.http.impl.client.HttpClients; 21 | import org.apache.http.message.BasicNameValuePair; 22 | import org.apache.http.util.EntityUtils; 23 | import org.jamesdbloom.acceptance.login.LoginPage; 24 | import org.jamesdbloom.acceptance.registration.RegistrationPage; 25 | import org.jamesdbloom.acceptance.updatepassword.UpdatePasswordPage; 26 | import org.jamesdbloom.email.NewRegistrationEmail; 27 | import org.junit.AfterClass; 28 | import org.junit.BeforeClass; 29 | import org.junit.Test; 30 | import org.subethamail.wiser.Wiser; 31 | import org.subethamail.wiser.WiserMessage; 32 | 33 | import javax.mail.internet.MimeMessage; 34 | import java.io.File; 35 | import java.io.FileInputStream; 36 | import java.io.IOException; 37 | import java.security.KeyStore; 38 | import java.security.cert.CertificateException; 39 | import java.security.cert.X509Certificate; 40 | import java.util.Arrays; 41 | import java.util.concurrent.TimeUnit; 42 | import java.util.logging.Level; 43 | 44 | import static org.hamcrest.Matchers.empty; 45 | import static org.hamcrest.core.Is.is; 46 | import static org.hamcrest.core.IsNot.not; 47 | import static org.junit.Assert.assertThat; 48 | 49 | /** 50 | * @author jamesdbloom 51 | */ 52 | public class SystemTest { 53 | 54 | private static Tomcat tomcat; 55 | private static Wiser wiser; 56 | private static KeyStore trustStore; 57 | 58 | @BeforeClass 59 | public static void setupFixture() throws Exception { 60 | // setup mock smtp 61 | System.setProperty("email.host", "127.0.0.1"); 62 | System.setProperty("email.port", "2500"); 63 | System.setProperty("email.starttls", "false"); 64 | wiser = new Wiser(); 65 | wiser.setPort(2500); 66 | wiser.start(); 67 | 68 | // determine current filesystem location 69 | String classLocation = SystemTest.class.getCanonicalName().replace(".", "/") + ".class"; 70 | String projectBase = SystemTest.class.getClassLoader().getResource(classLocation).toString().replace(classLocation, "../../").replace("file:", ""); 71 | 72 | // start proxy (in tomcat) 73 | tomcat = new Tomcat(); 74 | tomcat.setBaseDir(new File(".").getCanonicalPath() + File.separatorChar + "tomcat"); 75 | 76 | // add http port 77 | tomcat.setPort(8080); 78 | 79 | // add https port 80 | Connector httpsConnector = new Connector(); 81 | httpsConnector.setPort(8443); 82 | httpsConnector.setSecure(true); 83 | httpsConnector.setScheme("https"); 84 | httpsConnector.setAttribute("keystorePass", "changeit"); 85 | httpsConnector.setAttribute("keystoreFile", projectBase + "keystore"); 86 | httpsConnector.setAttribute("clientAuth", "false"); 87 | httpsConnector.setAttribute("sslProtocol", "TLS"); 88 | httpsConnector.setAttribute("SSLEnabled", true); 89 | Service service = tomcat.getService(); 90 | service.addConnector(httpsConnector); 91 | 92 | // add servlet 93 | Context ctx = tomcat.addContext("/", projectBase + "src/main/webapp"); 94 | ContextConfig contextConfig = new ContextConfig(); 95 | ctx.addLifecycleListener(contextConfig); 96 | contextConfig.setDefaultWebXml(projectBase + "src/main/webapp/WEB-INF/web.xml"); 97 | 98 | // control logging level 99 | java.util.logging.Logger.getLogger("").setLevel(Level.FINER); 100 | 101 | // start server 102 | tomcat.start(); 103 | 104 | // load key store for certificates 105 | trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); 106 | try (FileInputStream fileInputStream = new FileInputStream(new File(projectBase + "keystore"))) { 107 | trustStore.load(fileInputStream, "changeit".toCharArray()); 108 | } 109 | } 110 | 111 | @AfterClass 112 | public static void shutdownFixture() throws LifecycleException { 113 | // stop server 114 | tomcat.stop(); 115 | 116 | // stop smtp mock 117 | wiser.stop(); 118 | } 119 | 120 | @Test 121 | public void registerUpdatePasswordLoginAndViewPage() throws Exception { 122 | // given 123 | HttpClient httpClient = createApacheClient(); 124 | 125 | // when - register 126 | HttpResponse registerPageResponse = httpClient.execute(new HttpGet("https://127.0.0.1:8443/register")); 127 | RegistrationPage registrationPage = new RegistrationPage(getBodyAndClose(registerPageResponse)); 128 | registrationPage.shouldHaveCorrectFields(); 129 | String csrf = registrationPage.csrfValue(); 130 | 131 | HttpPost register = new HttpPost("https://127.0.0.1:8443/register"); 132 | register.setEntity(new UrlEncodedFormEntity(Arrays.asList( 133 | new BasicNameValuePair("name", "test_user"), 134 | new BasicNameValuePair("email", "fake@email.com"), 135 | new BasicNameValuePair("_csrf", csrf) 136 | ))); 137 | HttpResponse registerResponse = httpClient.execute(register); 138 | 139 | // then - should respond with success message 140 | assertThat(registerResponse.getStatusLine().getStatusCode(), is(HttpStatus.SC_MOVED_TEMPORARILY)); 141 | assertThat(registerResponse.getFirstHeader("Location").getValue(), is("https://127.0.0.1:8443/message")); 142 | getBodyAndClose(registerResponse); 143 | 144 | // then - should have sent correct email 145 | TimeUnit.SECONDS.sleep(2); 146 | assertThat(wiser.getMessages(), is(not(empty()))); 147 | WiserMessage registrationWiserMessage = wiser.getMessages().get(0); 148 | MimeMessage mimeMessage = registrationWiserMessage.getMimeMessage(); 149 | NewRegistrationEmail registrationEmail = new NewRegistrationEmail(mimeMessage.getContent().toString()); 150 | String updatePasswordURL = registrationEmail.shouldHaveCorrectFields("fake@email.com"); 151 | 152 | // when - updating password 153 | HttpResponse updatePasswordPageResponse = httpClient.execute(new HttpGet(updatePasswordURL)); 154 | UpdatePasswordPage updatePasswordPage = new UpdatePasswordPage(getBodyAndClose(updatePasswordPageResponse)); 155 | updatePasswordPage.shouldHaveCorrectFields(); 156 | csrf = updatePasswordPage.csrfValue(); 157 | 158 | HttpPost updatePasswordRequest = new HttpPost(updatePasswordURL); 159 | updatePasswordRequest.setEntity(new UrlEncodedFormEntity(Arrays.asList( 160 | new BasicNameValuePair("password", "NewPassword123"), 161 | new BasicNameValuePair("passwordConfirm", "NewPassword123"), 162 | new BasicNameValuePair("_csrf", csrf) 163 | ))); 164 | HttpResponse updatePasswordResponse = httpClient.execute(updatePasswordRequest); 165 | 166 | // then - should respond with success message 167 | assertThat(updatePasswordResponse.getStatusLine().getStatusCode(), is(HttpStatus.SC_MOVED_TEMPORARILY)); 168 | assertThat(updatePasswordResponse.getFirstHeader("Location").getValue(), is("https://127.0.0.1:8443/message")); 169 | getBodyAndClose(updatePasswordResponse); 170 | 171 | // when - hit secured page 172 | HttpResponse landingPageResponse = httpClient.execute(new HttpGet("http://127.0.0.1:8080/")); 173 | 174 | // then - login page is displayed 175 | LoginPage securePageResponse = new LoginPage(getBodyAndClose(landingPageResponse)); 176 | securePageResponse.shouldHaveCorrectFields(); 177 | csrf = securePageResponse.csrfValue(); 178 | 179 | // when - login is performed 180 | HttpPost login = new HttpPost("https://127.0.0.1:8443/login"); 181 | login.setEntity(new UrlEncodedFormEntity(Arrays.asList( 182 | new BasicNameValuePair("username", "fake@email.com"), 183 | new BasicNameValuePair("password", "NewPassword123"), 184 | new BasicNameValuePair("_csrf", csrf) 185 | ))); 186 | HttpResponse loginResponse = httpClient.execute(login); 187 | 188 | // then - secured page is displayed 189 | assertThat(loginResponse.getStatusLine().getStatusCode(), is(HttpStatus.SC_MOVED_TEMPORARILY)); 190 | assertThat(loginResponse.getFirstHeader("Location").getValue(), is("https://127.0.0.1:8443/")); 191 | getBodyAndClose(loginResponse); 192 | 193 | // when - logout 194 | httpClient = createApacheClient(); 195 | HttpResponse logoutResponse = httpClient.execute(new HttpGet("https://127.0.0.1:8443/logout")); 196 | 197 | // then - should get redirected to login page 198 | LoginPage logoutRequestResponse = new LoginPage(getBodyAndClose(logoutResponse)); 199 | logoutRequestResponse.shouldHaveCorrectFields(); 200 | } 201 | 202 | private String getBodyAndClose(HttpResponse httpResponse) throws IOException { 203 | String body = EntityUtils.toString(httpResponse.getEntity()); 204 | EntityUtils.consumeQuietly(httpResponse.getEntity()); 205 | return body; 206 | } 207 | 208 | private CloseableHttpClient createApacheClient() throws Exception { 209 | return HttpClients.custom() 210 | // make sure the tests don't block when server fails to start up 211 | .setDefaultSocketConfig(SocketConfig.custom() 212 | .setSoTimeout((int) TimeUnit.SECONDS.toMillis(4)) 213 | .build()) 214 | .setSslcontext( 215 | SSLContexts 216 | .custom() 217 | .loadTrustMaterial(trustStore, new TrustStrategy() { 218 | public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { 219 | return true; 220 | } 221 | }) 222 | .build() 223 | ) 224 | .setHostnameVerifier(new AllowAllHostnameVerifier()) 225 | .build(); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/test/java/org/jamesdbloom/web/controller/LandingPageControllerTest.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.web.controller; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | 7 | /** 8 | * @author jamesdbloom 9 | */ 10 | public class LandingPageControllerTest { 11 | 12 | @Test 13 | public void testShouldReturnCorrectLogicalViewName() { 14 | assertEquals("landing", new LandingPageController().getPage()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/org/jamesdbloom/web/controller/LoginPageControllerTest.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.web.controller; 2 | 3 | import org.junit.Test; 4 | import org.springframework.security.web.csrf.CsrfToken; 5 | import org.springframework.ui.ExtendedModelMap; 6 | import org.springframework.ui.Model; 7 | 8 | import javax.servlet.http.HttpServletRequest; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | import static org.mockito.Mockito.mock; 12 | import static org.mockito.Mockito.when; 13 | 14 | /** 15 | * @author jamesdbloom 16 | */ 17 | public class LoginPageControllerTest { 18 | 19 | @Test 20 | public void testShouldReturnCorrectLogicalViewName() { 21 | // given 22 | HttpServletRequest request = mock(HttpServletRequest.class); 23 | CsrfToken csrfToken = mock(CsrfToken.class); 24 | when(request.getAttribute(CsrfToken.class.getName())).thenReturn(csrfToken); 25 | when(csrfToken.getParameterName()).thenReturn("parameterName"); 26 | when(csrfToken.getToken()).thenReturn("token"); 27 | Model model = new ExtendedModelMap(); 28 | 29 | // when 30 | String page = new LoginPageController().getPage(request, model); 31 | 32 | // then 33 | assertEquals("login", page); 34 | assertEquals("parameterName", model.asMap().get("csrfParameterName")); 35 | assertEquals("token", model.asMap().get("csrfToken")); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/org/jamesdbloom/web/interceptor/bundling/AddBundlingModelToViewModelInterceptorTest.java: -------------------------------------------------------------------------------- 1 | package org.jamesdbloom.web.interceptor.bundling; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.mockito.Mock; 7 | import org.mockito.runners.MockitoJUnitRunner; 8 | import org.springframework.mock.web.MockHttpServletRequest; 9 | import org.springframework.web.servlet.ModelAndView; 10 | import ro.isdc.wro.model.WroModel; 11 | import ro.isdc.wro.model.group.Group; 12 | import ro.isdc.wro.model.resource.Resource; 13 | import ro.isdc.wro.model.resource.ResourceType; 14 | 15 | import java.util.Arrays; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.mockito.Mockito.when; 21 | 22 | /** 23 | * @author jamesdbloom 24 | */ 25 | @RunWith(MockitoJUnitRunner.class) 26 | public class AddBundlingModelToViewModelInterceptorTest { 27 | 28 | @Mock 29 | private WroModelHolder wroModelHolder; 30 | private MockHttpServletRequest mockHttpServletRequest; 31 | 32 | @Before 33 | public void setupFixture() { 34 | WroModel wroModel = new WroModel(); 35 | 36 | Group group_one = new Group("group_one"); 37 | Group group_two = new Group("group_two"); 38 | wroModel.setGroups(Arrays.asList(group_one, group_two)); 39 | 40 | group_one.addResource(Resource.create("group_one_css_uri_one", ResourceType.CSS)); 41 | group_one.addResource(Resource.create("group_one_css_uri_two", ResourceType.CSS)); 42 | group_one.addResource(Resource.create("group_one_js_uri_one", ResourceType.JS)); 43 | group_one.addResource(Resource.create("group_one_js_uri_two", ResourceType.JS)); 44 | 45 | group_two.addResource(Resource.create("group_two_css_uri_one", ResourceType.CSS)); 46 | group_two.addResource(Resource.create("group_two_css_uri_two", ResourceType.CSS)); 47 | group_two.addResource(Resource.create("group_two_js_uri_one", ResourceType.JS)); 48 | group_two.addResource(Resource.create("group_two_js_uri_two", ResourceType.JS)); 49 | 50 | when(wroModelHolder.getWroModel()).thenReturn(wroModel); 51 | 52 | mockHttpServletRequest = new MockHttpServletRequest(); 53 | mockHttpServletRequest.setQueryString(""); 54 | } 55 | 56 | @Test 57 | public void testShouldReturnUnbundledResourcesWhenBundlingDisabled() throws Exception { 58 | // given - setupFixture and 59 | ModelAndView modelAndView = new ModelAndView(); 60 | 61 | // when 62 | new AddBundlingModelToViewModelInterceptor(wroModelHolder, "false").postHandle(mockHttpServletRequest, null, null, modelAndView); 63 | 64 | // then 65 | checkViewModelContainsCorrectUnbundledResources(modelAndView); 66 | } 67 | 68 | private void checkViewModelContainsCorrectUnbundledResources(ModelAndView modelAndView) { 69 | assertEquals(Arrays.asList("group_one_js_uri_one", "group_one_js_uri_two"), ((Map>) modelAndView.getModel().get(AddBundlingModelToViewModelInterceptor.JS_RESOURCES)).get("group_one")); 70 | assertEquals(Arrays.asList("group_two_js_uri_one", "group_two_js_uri_two"), ((Map>) modelAndView.getModel().get(AddBundlingModelToViewModelInterceptor.JS_RESOURCES)).get("group_two")); 71 | assertEquals(Arrays.asList("group_one_css_uri_one", "group_one_css_uri_two"), ((Map>) modelAndView.getModel().get(AddBundlingModelToViewModelInterceptor.CSS_RESOURCES)).get("group_one")); 72 | assertEquals(Arrays.asList("group_two_css_uri_one", "group_two_css_uri_two"), ((Map>) modelAndView.getModel().get(AddBundlingModelToViewModelInterceptor.CSS_RESOURCES)).get("group_two")); 73 | } 74 | 75 | private void checkViewModelContainsCorrectBundledResources(ModelAndView modelAndView, String extraQueryString) { 76 | assertEquals(Arrays.asList("/bundle/group_one.js" + extraQueryString), ((Map>) modelAndView.getModel().get(AddBundlingModelToViewModelInterceptor.JS_RESOURCES)).get("group_one")); 77 | assertEquals(Arrays.asList("/bundle/group_two.js" + extraQueryString), ((Map>) modelAndView.getModel().get(AddBundlingModelToViewModelInterceptor.JS_RESOURCES)).get("group_two")); 78 | assertEquals(Arrays.asList("/bundle/group_one.css" + extraQueryString), ((Map>) modelAndView.getModel().get(AddBundlingModelToViewModelInterceptor.CSS_RESOURCES)).get("group_one")); 79 | assertEquals(Arrays.asList("/bundle/group_two.css" + extraQueryString), ((Map>) modelAndView.getModel().get(AddBundlingModelToViewModelInterceptor.CSS_RESOURCES)).get("group_two")); 80 | } 81 | 82 | @Test 83 | public void testShouldReturnBundledResourcesWhenBundlingEnabled() throws Exception { 84 | // given - setupFixture and 85 | ModelAndView modelAndView = new ModelAndView(); 86 | 87 | // then 88 | new AddBundlingModelToViewModelInterceptor(wroModelHolder, "true").postHandle(mockHttpServletRequest, null, null, modelAndView); 89 | 90 | // then 91 | checkViewModelContainsCorrectBundledResources(modelAndView, ""); 92 | } 93 | 94 | @Test 95 | public void testShouldReturnUnbundledResourcesWhenBundlingDisabledByQueryParameter() throws Exception { 96 | // given - setupFixture and 97 | ModelAndView modelAndView = new ModelAndView(); 98 | mockHttpServletRequest.addParameter("bundling", "false"); 99 | 100 | // then 101 | new AddBundlingModelToViewModelInterceptor(wroModelHolder, "true").postHandle(mockHttpServletRequest, null, null, modelAndView); 102 | 103 | // then 104 | checkViewModelContainsCorrectBundledResources(modelAndView, ""); 105 | } 106 | 107 | @Test 108 | public void testShouldReturnUnbundledResourcesWhenBundlingEnabledByQueryParameter() throws Exception { 109 | // given - setupFixture and 110 | ModelAndView modelAndView = new ModelAndView(); 111 | mockHttpServletRequest.addParameter("bundling", "true"); 112 | 113 | // then 114 | new AddBundlingModelToViewModelInterceptor(wroModelHolder, "false").postHandle(mockHttpServletRequest, null, null, modelAndView); 115 | 116 | // then 117 | checkViewModelContainsCorrectUnbundledResources(modelAndView); 118 | } 119 | 120 | @Test 121 | public void testShouldReturnUnminifiedBundledResourcesWhenMinificationDisabledByQueryParameter() throws Exception { 122 | // given - setupFixture and 123 | ModelAndView modelAndView = new ModelAndView(); 124 | mockHttpServletRequest.setQueryString("minimize=false"); 125 | 126 | // then 127 | new AddBundlingModelToViewModelInterceptor(wroModelHolder, "true").postHandle(mockHttpServletRequest, null, null, modelAndView); 128 | 129 | // then 130 | checkViewModelContainsCorrectBundledResources(modelAndView, "?minimize=false"); 131 | } 132 | } 133 | --------------------------------------------------------------------------------