├── .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 | #macro>
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 | #if>
28 | #escape>
29 | @compress>
30 | #macro>
31 |
32 | <#macro page_head>
33 | <@page_meta/>
34 | some silly title to have on this page>
35 | <@page_css/>
36 |
37 |
38 | #macro>
39 |
40 | <#macro page_meta>
41 |
42 |
43 |
44 |
45 | #macro>
46 |
47 | <#macro page_css>
48 | <#if cssResources?? && cssResources["all"]?? >
49 | <#list cssResources["all"] as cssFile>
50 |
51 | #list>
52 | #if>
53 | #macro>
54 |
55 | <#macro page_body>
56 |
57 | #macro>
58 |
59 | <#macro page_js>
60 | <#if jsResources?? && jsResources["all"]?? >
61 |
74 | #if>
75 | #macro>
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 | #if>
11 |
12 |
33 | #macro>
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 | #list>
15 |
16 | #if>
17 | #macro>
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 | #list>
31 |
32 | #if>
33 | #macro>
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 | #if>
49 | #function>
50 |
51 | <#macro message code>
52 | <#local message = getMessage(code)>
53 | ${message}
54 | #macro>
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 | #if>
70 | #function>
71 |
72 | <#macro messageWithArgs code args>
73 | <#local message = getMessageWithArgs(code, args)>
74 | ${message}
75 | #macro>
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" #if>>${message!""}
9 | #macro>
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 |
46 | #macro>
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 |
47 | #macro>
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 |
--------------------------------------------------------------------------------