├── .gitignore ├── LICENSE.md ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src ├── main ├── config │ └── ci │ │ └── gradle.properties ├── java │ └── com │ │ └── porterhead │ │ └── rest │ │ ├── api │ │ ├── ErrorResponse.java │ │ ├── PagedQueryRequest.java │ │ ├── PagedResponse.java │ │ └── ValidationError.java │ │ ├── authorization │ │ ├── AuthorizationRequestContext.java │ │ ├── AuthorizationService.java │ │ ├── exception │ │ │ └── InvalidAuthorizationHeaderException.java │ │ └── impl │ │ │ ├── RequestSigningAuthorizationService.java │ │ │ ├── SecurityContextImpl.java │ │ │ └── SessionTokenAuthorizationService.java │ │ ├── config │ │ ├── ApplicationConfig.java │ │ ├── ApplicationDevConfig.java │ │ ├── ApplicationProductionConfig.java │ │ └── ApplicationStagingConfig.java │ │ ├── exception │ │ ├── ApplicationRuntimeException.java │ │ ├── BaseWebApplicationException.java │ │ ├── NotFoundException.java │ │ └── ValidationException.java │ │ ├── filter │ │ ├── ResourceFilterFactory.java │ │ └── SecurityContextFilter.java │ │ ├── gateway │ │ └── EmailServicesGateway.java │ │ ├── model │ │ └── BaseEntity.java │ │ ├── resource │ │ ├── GenericExceptionMapper.java │ │ └── HealthCheckResource.java │ │ ├── service │ │ └── BaseService.java │ │ ├── user │ │ ├── EmailServiceTokenModel.java │ │ ├── SocialUserRepository.java │ │ ├── UserRepository.java │ │ ├── UserService.java │ │ ├── UserServiceImpl.java │ │ ├── VerificationTokenRepository.java │ │ ├── VerificationTokenService.java │ │ ├── VerificationTokenServiceImpl.java │ │ ├── api │ │ │ ├── AuthenticatedUserToken.java │ │ │ ├── CreateUserRequest.java │ │ │ ├── EmailVerificationRequest.java │ │ │ ├── ExternalUser.java │ │ │ ├── LoginRequest.java │ │ │ ├── LostPasswordRequest.java │ │ │ ├── OAuth2Request.java │ │ │ ├── PasswordRequest.java │ │ │ ├── SocialProfile.java │ │ │ └── UpdateUserRequest.java │ │ ├── domain │ │ │ ├── AuthorizationToken.java │ │ │ ├── Role.java │ │ │ ├── SocialUser.java │ │ │ ├── SocialUserBuilder.java │ │ │ ├── User.java │ │ │ └── VerificationToken.java │ │ ├── exception │ │ │ ├── AlreadyVerifiedException.java │ │ │ ├── AuthenticationException.java │ │ │ ├── AuthorizationException.java │ │ │ ├── DuplicateUserException.java │ │ │ ├── TokenHasExpiredException.java │ │ │ ├── TokenNotFoundException.java │ │ │ └── UserNotFoundException.java │ │ ├── mail │ │ │ ├── MailSenderService.java │ │ │ ├── MockJavaMailSender.java │ │ │ └── impl │ │ │ │ └── MailSenderServiceImpl.java │ │ ├── resource │ │ │ ├── PasswordResource.java │ │ │ ├── UserResource.java │ │ │ └── VerificationResource.java │ │ └── social │ │ │ ├── JpaConnectionRepository.java │ │ │ ├── JpaUsersConnectionRepository.java │ │ │ └── SocialConfig.java │ │ └── util │ │ ├── DateUtil.java │ │ ├── HashUtil.java │ │ └── StringUtil.java ├── resources │ ├── META-INF │ │ ├── persistence.xml │ │ ├── spring │ │ │ ├── component-scan-context.xml │ │ │ ├── data-context.xml │ │ │ ├── email-services-context.xml │ │ │ ├── email-template-context.xml │ │ │ ├── root-context.xml │ │ │ └── social-configuration-context.xml │ │ └── velocity │ │ │ ├── LostPasswordEmail.vm │ │ │ ├── RegistrationEmail.vm │ │ │ └── VerifyEmail.vm │ ├── logback.xml │ ├── properties │ │ ├── app.properties │ │ ├── dev-app.properties │ │ ├── production-app.properties │ │ └── staging-app.properties │ └── schema │ │ ├── indexes.sql │ │ ├── message_store.sql │ │ └── truncate_data.sql └── webapp │ ├── META-INF │ └── MANIFEST.MF │ ├── WEB-INF │ ├── spring │ │ └── appservlet │ │ │ └── servlet-context.xml │ └── web.xml │ ├── css │ └── styles.css │ ├── dashboard.html │ ├── forgot_password.html │ ├── img │ ├── fb_image.png │ └── fbline.png │ ├── index.html │ ├── js │ ├── bootstrap.js │ ├── bootstrap.min.js │ ├── cookie.js │ ├── enc-base64-min.js │ ├── grid.locale-en.js │ ├── javarest.js │ ├── jquery-1.7.1.min.js │ ├── jquery-1.8.2.min.js │ ├── jquery-full-house.js │ ├── jquery.jqGrid.min.js │ ├── sha256.js │ ├── store.js │ ├── user.js │ └── verify.js │ ├── request_email.html │ ├── reset_password.html │ ├── signup.html │ └── validate.html └── test ├── groovy ├── BaseIntegrationTst.groovy └── UserIntegrationTest.groovy ├── java └── com │ └── porterhead │ └── rest │ ├── authorization │ ├── BaseAuthorizationTst.java │ ├── RequestSigningAuthorizationServiceTest.java │ ├── SecurityContextTest.java │ └── SessionTokenAuthorizationServiceTest.java │ ├── filter │ └── SecurityContextFilterTest.java │ ├── mock │ └── AppMockConfiguration.java │ ├── resource │ ├── BaseResourceTst.java │ ├── ConsumerSimpleSecurityFilter.java │ ├── HealthCheckResourceTest.java │ └── SimpleSecurityFilter.java │ └── user │ ├── BaseServiceTest.java │ ├── MailSenderServiceTest.java │ ├── UserServiceTest.java │ ├── VerificationServiceTest.java │ ├── api │ ├── CreateUserRequestTest.java │ ├── LoginRequestTest.java │ ├── PasswordRequestTest.java │ └── ValidationTst.java │ ├── builder │ └── ExternalUserBuilder.java │ ├── resource │ ├── PasswordResourceTest.java │ ├── UserResourceTest.java │ └── VerificationResourceTest.java │ └── social │ ├── AbstractSocialTst.java │ ├── JpaConnectionRepositoryTest.java │ └── JpaUsersConnectionRepositoryTest.java └── resources ├── integration-test-context.xml └── social-test-context.xml /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | 4 | .gradle/* 5 | 6 | build/* 7 | 8 | out/* 9 | 10 | *.iml 11 | 12 | *.ipr 13 | 14 | *.iws 15 | /build/ 16 | /bin/ 17 | 18 | # eclipse 19 | .settings/ 20 | .project 21 | .classpath 22 | 23 | # Intellij 24 | .idea/ 25 | *.iml 26 | *.iws 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JAVA REST Application 2 | ==================== 3 | 4 | Simple and easily understandable web project that demonstrates the use of: 5 | 6 | * Jersey + JAX-RS 7 | * Spring Integration 8 | * Spring Data + Hibernate 9 | * Groovy Integration tests 10 | * OAuth 11 | * Velocity + Java Mail 12 | * Facebook Login 13 | * Password Reset 14 | * Login/Sign Up + Email Verification 15 | * JSR 303 Validation 16 | 17 | NOTE. For a similar project that uses most of the same components but is built around OAuth2 see 18 | Securing Rest Services with OAuth2 and Spring Security 19 | 20 | to build: 21 | 22 | gradle clean build integrationTest 23 | 24 | or use the gradle wrapper: 25 | 26 | ./gradlew clean build integrationTest 27 | 28 | go to /build/reports/emma for test coverage reports 29 | 30 | to run: 31 | 32 | gradle tomcatRun 33 | 34 | navigate to http://localhost:8080/java-rest/ 35 | 36 | see blog posts: 37 | THANK YOU 38 | 39 | 49 | 50 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | systemProp.spring.profiles.active=dev 2 | 3 | please accept pull request 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iainporter/rest-java/30a904561b856584830ac47b2a348d7de933bd1e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jul 29 21:04:58 BST 2014 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-2.0-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /src/main/config/ci/gradle.properties: -------------------------------------------------------------------------------- 1 | 2 | tomcatContainerId=tomcat6x 3 | tomcatPort=8080 4 | tomcatHostname=localhost 5 | tomcatUsername=admin 6 | tomcatPassword=admin 7 | tomcatContext=rest-java 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/api/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.api; 2 | 3 | import javax.xml.bind.annotation.XmlRootElement; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | /** 8 | * 9 | * @version 1.0 10 | * @author: Iain Porter iain.porter@porterhead.com 11 | * @since 19/10/2012 12 | */ 13 | @XmlRootElement 14 | public class ErrorResponse { 15 | 16 | private String errorCode; 17 | private String consumerMessage; 18 | private String applicationMessage; 19 | private List validationErrors = new ArrayList(); 20 | 21 | 22 | public String getErrorCode() { 23 | return errorCode; 24 | } 25 | 26 | public void setErrorCode(String errorCode) { 27 | this.errorCode = errorCode; 28 | } 29 | 30 | public String getConsumerMessage() { 31 | return consumerMessage; 32 | } 33 | 34 | public void setConsumerMessage(String consumerMessage) { 35 | this.consumerMessage = consumerMessage; 36 | } 37 | 38 | public String getApplicationMessage() { 39 | return applicationMessage; 40 | } 41 | 42 | public void setApplicationMessage(String applicationMessage) { 43 | this.applicationMessage = applicationMessage; 44 | } 45 | 46 | public List getValidationErrors() { 47 | return validationErrors; 48 | } 49 | 50 | public void setValidationErrors(List validationErrors) { 51 | this.validationErrors = validationErrors; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/api/PagedQueryRequest.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.api; 2 | 3 | /** 4 | * @version 1.0 5 | * @author: Iain Porter 6 | * @since 28/02/2013 7 | */ 8 | public class PagedQueryRequest { 9 | 10 | public final static int DEFAULT_PAGE_SIZE = 50; 11 | public final static int MAX_PAGE_SIZE = 100; 12 | public final static String SORT_ORDER_ASCENDING = "asc"; 13 | public final static String SORT_ORDER_DESCENDING = "desc"; 14 | 15 | private int page; 16 | private int pageSize; 17 | private String sortProperty; 18 | private String sortDirection; 19 | private String searchToken; 20 | 21 | public PagedQueryRequest(){} 22 | 23 | public PagedQueryRequest(int page, int pageSize) { 24 | this.page = page; 25 | this.pageSize = pageSize; 26 | } 27 | 28 | public int getPage() { 29 | return page; 30 | } 31 | 32 | public void setPage(int page) { 33 | this.page = page; 34 | } 35 | 36 | public int getPageSize() { 37 | return pageSize == 0 ? DEFAULT_PAGE_SIZE : pageSize > MAX_PAGE_SIZE ? MAX_PAGE_SIZE : pageSize; 38 | } 39 | 40 | public void setPageSize(int pageSize) { 41 | this.pageSize = pageSize; 42 | } 43 | 44 | public String getSortProperty() { 45 | return sortProperty; 46 | } 47 | 48 | public void setSortProperty(String sortProperty) { 49 | this.sortProperty = sortProperty; 50 | } 51 | 52 | public String getSortDirection() { 53 | return sortDirection; 54 | } 55 | 56 | public void setSortDirection(String sortDirection) { 57 | this.sortDirection = sortDirection; 58 | } 59 | 60 | public String getSearchToken() { 61 | return searchToken; 62 | } 63 | 64 | public void setSearchToken(String searchToken) { 65 | this.searchToken = searchToken; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/api/PagedResponse.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.api; 2 | 3 | import javax.xml.bind.annotation.XmlRootElement; 4 | import java.util.List; 5 | 6 | /** 7 | * @version 1.0 8 | * @author: Iain Porter 9 | * @since 28/02/2013 10 | */ 11 | @XmlRootElement 12 | public class PagedResponse { 13 | 14 | private int total; 15 | private int page; 16 | private long records; 17 | private List rows; 18 | 19 | public PagedResponse() {} 20 | 21 | public int getTotal() { 22 | return total; 23 | } 24 | 25 | public void setTotal(int total) { 26 | this.total = total; 27 | } 28 | 29 | public int getPage() { 30 | return page; 31 | } 32 | 33 | public void setPage(int page) { 34 | this.page = page; 35 | } 36 | 37 | public long getRecords() { 38 | return records; 39 | } 40 | 41 | public void setRecords(long records) { 42 | this.records = records; 43 | } 44 | 45 | public List getRows() { 46 | return rows; 47 | } 48 | 49 | public void setRows(List rows) { 50 | this.rows = rows; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/api/ValidationError.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.api; 2 | 3 | import javax.xml.bind.annotation.XmlRootElement; 4 | 5 | /** 6 | * @version 1.0 7 | * @author: Iain Porter 8 | * @since 08/05/2013 9 | */ 10 | @XmlRootElement 11 | public class ValidationError { 12 | 13 | private String propertyName; 14 | private String propertyValue; 15 | private String message; 16 | 17 | public String getPropertyName() { 18 | return propertyName; 19 | } 20 | 21 | public void setPropertyName(String propertyName) { 22 | this.propertyName = propertyName; 23 | } 24 | 25 | public String getPropertyValue() { 26 | return propertyValue; 27 | } 28 | 29 | public void setPropertyValue(String propertyValue) { 30 | this.propertyValue = propertyValue; 31 | } 32 | 33 | public String getMessage() { 34 | return message; 35 | } 36 | 37 | public void setMessage(String message) { 38 | this.message = message; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/authorization/AuthorizationRequestContext.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.authorization; 2 | 3 | /** 4 | * 5 | * @version 1.0 6 | * @author: Iain Porter 7 | * @since 28/01/2013 8 | */ 9 | public class AuthorizationRequestContext { 10 | 11 | /** 12 | * The relative url of the request which starts at the root of the requested resource 13 | */ 14 | private final String requestUrl; 15 | 16 | /** 17 | * The Http method (POST, GET, DELETE, PUT) 18 | */ 19 | private final String httpMethod; 20 | 21 | /** 22 | * An Iso8061 formatted date timestamp 23 | */ 24 | private final String requestDateString; 25 | 26 | /** 27 | * Client generated unique nonce value 28 | */ 29 | private final String nonceToken; 30 | 31 | /** 32 | * The AuthorizationToken which should be in a format that the appropriate AuthorizationService can understand 33 | */ 34 | private final String authorizationToken; 35 | 36 | public AuthorizationRequestContext(String requestUrl, String httpMethod, String requestDateString, String nonceToken, String hashedToken) { 37 | this.requestUrl = requestUrl; 38 | this.httpMethod = httpMethod; 39 | this.requestDateString = requestDateString; 40 | this.nonceToken = nonceToken; 41 | this.authorizationToken = hashedToken; 42 | } 43 | 44 | public String getRequestUrl() { 45 | return requestUrl; 46 | } 47 | 48 | public String getHttpMethod() { 49 | return httpMethod; 50 | } 51 | 52 | public String getRequestDateString() { 53 | return requestDateString; 54 | } 55 | 56 | public String getNonceToken() { 57 | return nonceToken; 58 | } 59 | 60 | public String getAuthorizationToken() { 61 | return authorizationToken; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/authorization/AuthorizationService.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.authorization; 2 | 3 | import com.porterhead.rest.user.api.ExternalUser; 4 | 5 | /** 6 | * 7 | * @author: Iain Porter 8 | */ 9 | public interface AuthorizationService { 10 | 11 | /** 12 | * Given an AuthorizationRequestContext validate and authorize a User 13 | * 14 | * @param authorizationRequestContext the context required to authorize a user for a particular request 15 | * @return ExternalUser 16 | */ 17 | public ExternalUser authorize(AuthorizationRequestContext authorizationRequestContext); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/authorization/exception/InvalidAuthorizationHeaderException.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.authorization.exception; 2 | 3 | import com.porterhead.rest.exception.BaseWebApplicationException; 4 | 5 | /** 6 | * 7 | * @version 1.0 8 | * @author: Iain Porter iain.porter@porterhead.com 9 | * @since 20/10/2012 10 | */ 11 | public class InvalidAuthorizationHeaderException extends BaseWebApplicationException { 12 | 13 | 14 | public static final String DEVELOPER_MESSAGE = "Authorization failed. This could be due to missing properties in the header or" + 15 | " the Authorization header may have been incorrectly hashed"; 16 | 17 | public InvalidAuthorizationHeaderException() { 18 | super(401, "40101", "Authorization failed", DEVELOPER_MESSAGE); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/authorization/impl/SecurityContextImpl.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.authorization.impl; 2 | 3 | import com.porterhead.rest.authorization.exception.InvalidAuthorizationHeaderException; 4 | import com.porterhead.rest.user.api.ExternalUser; 5 | import com.porterhead.rest.user.domain.Role; 6 | 7 | import javax.ws.rs.core.SecurityContext; 8 | import java.security.Principal; 9 | 10 | /** 11 | * Implementation of {@link javax.ws.rs.core.SecurityContext} 12 | * 13 | * User: porter 14 | * Date: 16/03/2012 15 | * Time: 16:13 16 | */ 17 | public class SecurityContextImpl implements SecurityContext { 18 | 19 | private final ExternalUser user; 20 | 21 | public SecurityContextImpl(ExternalUser user) { 22 | this.user = user; 23 | } 24 | 25 | public Principal getUserPrincipal() { 26 | return user; 27 | } 28 | 29 | public boolean isUserInRole(String role) { 30 | if(role.equalsIgnoreCase(Role.anonymous.name())) { 31 | return true; 32 | } 33 | if(user == null) { 34 | throw new InvalidAuthorizationHeaderException(); 35 | } 36 | return user.getRole().equalsIgnoreCase(role); 37 | } 38 | 39 | public boolean isSecure() { 40 | return false; 41 | } 42 | 43 | public String getAuthenticationScheme() { 44 | return SecurityContext.BASIC_AUTH; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/authorization/impl/SessionTokenAuthorizationService.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.authorization.impl; 2 | 3 | import com.porterhead.rest.authorization.AuthorizationRequestContext; 4 | import com.porterhead.rest.authorization.AuthorizationService; 5 | import com.porterhead.rest.user.UserRepository; 6 | import com.porterhead.rest.user.api.ExternalUser; 7 | import com.porterhead.rest.user.domain.AuthorizationToken; 8 | import com.porterhead.rest.user.domain.User; 9 | import com.porterhead.rest.user.exception.AuthorizationException; 10 | 11 | import java.util.Date; 12 | 13 | /** 14 | * 15 | * Simple authorization service that requires a session token in the Authorization header 16 | * This is then matched to a user 17 | * 18 | * @version 1.0 19 | * @author: Iain Porter 20 | * @since 29/01/2013 21 | */ 22 | public class SessionTokenAuthorizationService implements AuthorizationService { 23 | 24 | /** 25 | * directly access user objects 26 | */ 27 | private final UserRepository userRepository; 28 | 29 | public SessionTokenAuthorizationService(UserRepository repository) { 30 | this.userRepository = repository; 31 | } 32 | 33 | public ExternalUser authorize(AuthorizationRequestContext securityContext) { 34 | String token = securityContext.getAuthorizationToken(); 35 | ExternalUser externalUser = null; 36 | if(token == null) { 37 | return externalUser; 38 | } 39 | User user = userRepository.findBySession(token); 40 | if(user == null) { 41 | throw new AuthorizationException("Session token not valid"); 42 | } 43 | AuthorizationToken authorizationToken = user.getAuthorizationToken(); 44 | if (authorizationToken.getToken().equals(token)) { 45 | externalUser = new ExternalUser(user); 46 | } 47 | return externalUser; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/config/ApplicationConfig.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.PropertySource; 6 | import org.springframework.core.env.Environment; 7 | 8 | /** 9 | * User: porter 10 | * Date: 17/05/2012 11 | * Time: 19:07 12 | */ 13 | @Configuration 14 | @PropertySource({"classpath:/properties/app.properties"}) 15 | public class ApplicationConfig { 16 | 17 | private final static String HOSTNAME_PROPERTY = "hostNameUrl"; 18 | 19 | private final static String SECURITY_AUTHORIZATION_REQUIRE_SIGNED_REQUESTS = "security.authorization.requireSignedRequests"; 20 | private final static String AUTHORIZATION_EXPIRY_DURATION = "authorization.timeToLive.inSeconds"; 21 | private final static String SESSION_DATE_OFFSET_IN_MINUTES = "session.date.offset.inMinutes"; 22 | private final static String TOKEN_EMAIL_REGISTRATION_DURATION = "token.emailRegistration.timeToLive.inMinutes"; 23 | private final static String TOKEN_EMAIL_VERIFICATION_DURATION = "token.emailVerification.timeToLive.inMinutes"; 24 | private final static String TOKEN_LOST_PASSWORD_DURATION = "token.lostPassword.timeToLive.inMinutes"; 25 | private final static String EMAIL_SERVICES_FROM_ADDRESS = "email.services.fromAddress"; 26 | private final static String EMAIL_SERVICES_REPLYTO_ADDRESS = "email.services.replyTo"; 27 | private final static String EMAIL_SERVICES_VERIFICATION_EMAIL_SUBJECT_TEXT = "email.services.emailVerificationSubjectText"; 28 | private final static String EMAIL_SERVICES_REGISTRATION_EMAIL_SUBJECT_TEXT = "email.services.emailRegistrationSubjectText"; 29 | private final static String EMAIL_SERVICES_LOST_PASSWORD_SUBJECT_TEXT = "email.services.lostPasswordSubjectText"; 30 | 31 | 32 | @Autowired 33 | protected Environment environment; 34 | 35 | public String getHostNameUrl() { 36 | return environment.getProperty(HOSTNAME_PROPERTY); 37 | } 38 | 39 | public String getFacebookClientId() { 40 | return environment.getProperty("facebook.clientId"); 41 | } 42 | 43 | public String getFacebookClientSecret() { 44 | return environment.getProperty("facebook.clientSecret"); 45 | } 46 | 47 | public int getAuthorizationExpiryTimeInSeconds() { 48 | return Integer.parseInt(environment.getProperty(AUTHORIZATION_EXPIRY_DURATION)); 49 | } 50 | 51 | public int getSessionDateOffsetInMinutes() { 52 | return Integer.parseInt(environment.getProperty(SESSION_DATE_OFFSET_IN_MINUTES)); 53 | } 54 | 55 | public int getEmailRegistrationTokenExpiryTimeInMinutes() { 56 | return Integer.parseInt(environment.getProperty(TOKEN_EMAIL_REGISTRATION_DURATION)); 57 | } 58 | 59 | public int getEmailVerificationTokenExpiryTimeInMinutes() { 60 | return Integer.parseInt(environment.getProperty(TOKEN_EMAIL_VERIFICATION_DURATION)); 61 | } 62 | 63 | public int getLostPasswordTokenExpiryTimeInMinutes() { 64 | return Integer.parseInt(environment.getProperty(TOKEN_LOST_PASSWORD_DURATION)); 65 | } 66 | 67 | public String getEmailVerificationSubjectText() { 68 | return environment.getProperty(EMAIL_SERVICES_VERIFICATION_EMAIL_SUBJECT_TEXT); 69 | } 70 | 71 | public String getEmailRegistrationSubjectText() { 72 | return environment.getProperty(EMAIL_SERVICES_REGISTRATION_EMAIL_SUBJECT_TEXT); 73 | } 74 | 75 | public String getLostPasswordSubjectText() { 76 | return environment.getProperty(EMAIL_SERVICES_LOST_PASSWORD_SUBJECT_TEXT); 77 | } 78 | 79 | public String getEmailFromAddress() { 80 | return environment.getProperty(EMAIL_SERVICES_FROM_ADDRESS); 81 | } 82 | 83 | public String getEmailReplyToAddress() { 84 | return environment.getProperty(EMAIL_SERVICES_REPLYTO_ADDRESS); 85 | } 86 | 87 | public Boolean requireSignedRequests() { 88 | return environment.getProperty(SECURITY_AUTHORIZATION_REQUIRE_SIGNED_REQUESTS).equalsIgnoreCase("true"); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/config/ApplicationDevConfig.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Profile; 6 | import org.springframework.context.annotation.PropertySource; 7 | import org.springframework.security.crypto.encrypt.Encryptors; 8 | import org.springframework.security.crypto.encrypt.TextEncryptor; 9 | 10 | /** 11 | * @version 1.0 12 | * @author: Iain Porter iain.porter@porterhead.com 13 | * @since 21/09/2012 14 | */ 15 | @Configuration 16 | @Profile(value={"dev", "local"}) 17 | @PropertySource({"classpath:/properties/dev-app.properties"}) 18 | public class ApplicationDevConfig { 19 | 20 | @Bean 21 | public TextEncryptor textEncryptor() { 22 | return Encryptors.noOpText(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/config/ApplicationProductionConfig.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.context.annotation.PropertySource; 8 | import org.springframework.core.env.Environment; 9 | import org.springframework.security.crypto.encrypt.Encryptors; 10 | import org.springframework.security.crypto.encrypt.TextEncryptor; 11 | 12 | /** 13 | * @version 1.0 14 | * @author: Iain Porter iain.porter@porterhead.com 15 | * @since 21/09/2012 16 | */ 17 | @Configuration 18 | @Profile(value="production") 19 | @PropertySource({"classpath:/properties/production-app.properties"}) 20 | public class ApplicationProductionConfig { 21 | 22 | @Autowired 23 | Environment environment; 24 | 25 | @Bean 26 | public TextEncryptor textEncryptor() { 27 | return Encryptors.queryableText(environment.getProperty("security.encryptPassword"), 28 | environment.getProperty("security.encryptSalt")); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/config/ApplicationStagingConfig.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.context.annotation.PropertySource; 8 | import org.springframework.core.env.Environment; 9 | import org.springframework.security.crypto.encrypt.Encryptors; 10 | import org.springframework.security.crypto.encrypt.TextEncryptor; 11 | 12 | /** 13 | * 14 | * @version 1.0 15 | * @author: Iain Porter iain.porter@porterhead.com 16 | * @since 21/09/2012 17 | */ 18 | @Configuration 19 | @Profile(value="staging") 20 | @PropertySource({"classpath:/properties/staging-app.properties"}) 21 | public class ApplicationStagingConfig { 22 | 23 | @Autowired 24 | Environment environment; 25 | 26 | @Bean 27 | public TextEncryptor textEncryptor() { 28 | return Encryptors.queryableText(environment.getProperty("security.encryptPassword"), 29 | environment.getProperty("security.encryptSalt")); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/exception/ApplicationRuntimeException.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.exception; 2 | 3 | 4 | public class ApplicationRuntimeException extends BaseWebApplicationException { 5 | 6 | public ApplicationRuntimeException(String applicationMessage) { 7 | super(500, "50002", "Internal System error", applicationMessage); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/exception/BaseWebApplicationException.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.exception; 2 | 3 | import com.porterhead.rest.api.ErrorResponse; 4 | 5 | import javax.ws.rs.WebApplicationException; 6 | import javax.ws.rs.core.MediaType; 7 | import javax.ws.rs.core.Response; 8 | 9 | /** 10 | * @version 1.0 11 | * @author: Iain Porter iain.porter@porterhead.com 12 | * @since 19/10/2012 13 | */ 14 | public abstract class BaseWebApplicationException extends WebApplicationException { 15 | 16 | private final int status; 17 | private final String errorMessage; 18 | private final String errorCode; 19 | private final String developerMessage; 20 | 21 | public BaseWebApplicationException(int httpStatus, String errorCode, String errorMessage, String developerMessage) { 22 | this.status = httpStatus; 23 | this.errorMessage = errorMessage; 24 | this.errorCode = errorCode; 25 | this.developerMessage = developerMessage; 26 | } 27 | 28 | 29 | @Override 30 | public Response getResponse() { 31 | return Response.status(status).type(MediaType.APPLICATION_JSON_TYPE).entity(getErrorResponse()).build(); 32 | } 33 | 34 | public ErrorResponse getErrorResponse() { 35 | ErrorResponse response = new ErrorResponse(); 36 | response.setErrorCode(errorCode); 37 | response.setApplicationMessage(developerMessage); 38 | response.setConsumerMessage(errorMessage); 39 | return response; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/exception/NotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.exception; 2 | 3 | import javax.ws.rs.WebApplicationException; 4 | 5 | /** 6 | * User: porter 7 | * Date: 03/05/2012 8 | * Time: 12:27 9 | */ 10 | public class NotFoundException extends WebApplicationException { 11 | 12 | public NotFoundException() { 13 | super(404); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/exception/ValidationException.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.exception; 2 | 3 | import com.porterhead.rest.api.ErrorResponse; 4 | import com.porterhead.rest.api.ValidationError; 5 | 6 | import javax.validation.ConstraintViolation; 7 | import javax.ws.rs.WebApplicationException; 8 | import javax.ws.rs.core.MediaType; 9 | import javax.ws.rs.core.Response; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.Set; 13 | 14 | /** 15 | * User: porter 16 | * Date: 03/05/2012 17 | * Time: 21:43 18 | */ 19 | public class ValidationException extends WebApplicationException { 20 | 21 | private final int status = 400; 22 | private String errorMessage; 23 | private String developerMessage; 24 | private List errors = new ArrayList(); 25 | 26 | public ValidationException() { 27 | errorMessage = "Validation Error"; 28 | developerMessage = "The data passed in the request was invalid. Please check and resubmit"; 29 | } 30 | 31 | public ValidationException(String message) { 32 | super(); 33 | errorMessage = message; 34 | } 35 | 36 | public ValidationException(Set> violations) { 37 | this(); 38 | for(ConstraintViolation constraintViolation : violations) { 39 | ValidationError error = new ValidationError(); 40 | error.setMessage(constraintViolation.getMessage()); 41 | error.setPropertyName(constraintViolation.getPropertyPath().toString()); 42 | error.setPropertyValue(constraintViolation.getInvalidValue() != null ? constraintViolation.getInvalidValue().toString() : null); 43 | errors.add(error); 44 | } 45 | } 46 | 47 | @Override 48 | public Response getResponse() { 49 | return Response.status(status).type(MediaType.APPLICATION_JSON_TYPE).entity(getErrorResponse()).build(); 50 | } 51 | 52 | public ErrorResponse getErrorResponse() { 53 | ErrorResponse response = new ErrorResponse(); 54 | response.setApplicationMessage(developerMessage); 55 | response.setConsumerMessage(errorMessage); 56 | response.setValidationErrors(errors); 57 | return response; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/filter/ResourceFilterFactory.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.filter; 2 | 3 | import com.sun.jersey.api.container.filter.RolesAllowedResourceFilterFactory; 4 | import com.sun.jersey.api.model.AbstractMethod; 5 | import com.sun.jersey.spi.container.ResourceFilter; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.ws.rs.ext.Provider; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * Add the SecurityContextFilter to the list of Filters to apply to requests 15 | * 16 | * This factory is registered with the Web Context: 17 | * 18 | * 19 | * 20 | com.sun.jersey.spi.container.ResourceFilters 21 | com.porterhead.com.porterhead.rest.filter.ResourceFilterFactory 22 | 23 | * 24 | * 25 | * 26 | * @author: Iain Porter 27 | */ 28 | @Component 29 | @Provider 30 | public class ResourceFilterFactory extends RolesAllowedResourceFilterFactory { 31 | 32 | @Autowired 33 | private SecurityContextFilter securityContextFilter; 34 | 35 | @Override 36 | public List create(AbstractMethod am) { 37 | List filters = super.create(am); 38 | if (filters == null) { 39 | filters = new ArrayList(); 40 | } 41 | List securityFilters = new ArrayList(filters); 42 | //put the Security Filter first in line 43 | securityFilters.add(0, securityContextFilter); 44 | return securityFilters; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/filter/SecurityContextFilter.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.filter; 2 | 3 | import com.porterhead.rest.authorization.AuthorizationRequestContext; 4 | import com.porterhead.rest.authorization.AuthorizationService; 5 | import com.porterhead.rest.authorization.impl.RequestSigningAuthorizationService; 6 | import com.porterhead.rest.authorization.impl.SecurityContextImpl; 7 | import com.porterhead.rest.authorization.impl.SessionTokenAuthorizationService; 8 | import com.porterhead.rest.config.ApplicationConfig; 9 | import com.porterhead.rest.user.UserRepository; 10 | import com.porterhead.rest.user.UserService; 11 | import com.porterhead.rest.user.api.ExternalUser; 12 | import com.sun.jersey.spi.container.ContainerRequest; 13 | import com.sun.jersey.spi.container.ContainerRequestFilter; 14 | import com.sun.jersey.spi.container.ContainerResponseFilter; 15 | import com.sun.jersey.spi.container.ResourceFilter; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.stereotype.Component; 20 | 21 | import javax.ws.rs.ext.Provider; 22 | 23 | /** 24 | * A Servlet filter class for authorizing requests. 25 | * 26 | * 27 | * The role of this filter class is to set a {@link javax.ws.rs.core.SecurityContext} in the {@link com.sun.jersey.spi.container.ContainerRequest} 28 | * 29 | * @see {@link com.porterhead.rest.authorization.impl.SecurityContextImpl} 30 | * 31 | * @author: Iain Porter 32 | */ 33 | @Component 34 | @Provider 35 | public class SecurityContextFilter implements ResourceFilter, ContainerRequestFilter { 36 | 37 | private static final Logger LOG = LoggerFactory.getLogger(SecurityContextFilter.class); 38 | 39 | protected static final String HEADER_AUTHORIZATION = "Authorization"; 40 | 41 | protected static final String HEADER_DATE = "x-java-rest-date"; 42 | 43 | protected static final String HEADER_NONCE = "nonce"; 44 | 45 | private AuthorizationService authorizationService; 46 | 47 | ApplicationConfig config; 48 | 49 | @Autowired 50 | public SecurityContextFilter(UserRepository userRepository, UserService userService, ApplicationConfig config) { 51 | delegateAuthorizationService(userRepository, userService, config); 52 | this.config = config; 53 | 54 | } 55 | 56 | /** 57 | * If there is an Authorisation header in the request extract the session token and retrieve the user 58 | * 59 | * Delegate to the AuthorizationService to validate the request 60 | * 61 | * If the request has a valid session token and the user is validated then a user object will be added to the security context 62 | * 63 | * Any Resource Controllers can assume the user has been validated and can merely authorize based on the role 64 | * 65 | * Resources with @PermitAll annotation do not require an Authorization header but will still be filtered 66 | * 67 | * @param request the ContainerRequest to filter 68 | * 69 | * @return the ContainerRequest with a SecurityContext added 70 | */ 71 | public ContainerRequest filter(ContainerRequest request) { 72 | String authToken = request.getHeaderValue(HEADER_AUTHORIZATION); 73 | String requestDateString = request.getHeaderValue(HEADER_DATE); 74 | String nonce = request.getHeaderValue(HEADER_NONCE); 75 | AuthorizationRequestContext context = new AuthorizationRequestContext(request.getPath(), request.getMethod(), 76 | requestDateString, nonce, authToken); 77 | ExternalUser externalUser = authorizationService.authorize(context); 78 | request.setSecurityContext(new SecurityContextImpl(externalUser)); 79 | return request; 80 | } 81 | 82 | /** 83 | * Specify the AuthorizationService that the application should use 84 | * 85 | * @param userRepository 86 | * @param userService 87 | * @param config 88 | */ 89 | private void delegateAuthorizationService(UserRepository userRepository, UserService userService, ApplicationConfig config) { 90 | if(config.requireSignedRequests()) { 91 | this.authorizationService = new RequestSigningAuthorizationService(userRepository, userService, config); 92 | } else { 93 | this.authorizationService = new SessionTokenAuthorizationService(userRepository); 94 | } 95 | } 96 | 97 | 98 | public ContainerRequestFilter getRequestFilter() { 99 | return this; 100 | } 101 | 102 | public ContainerResponseFilter getResponseFilter() { 103 | return null; 104 | } 105 | 106 | @Autowired 107 | public void setConfig(ApplicationConfig config) { 108 | this.config = config; 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/gateway/EmailServicesGateway.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.gateway; 2 | 3 | import com.porterhead.rest.user.EmailServiceTokenModel; 4 | 5 | /** 6 | * 7 | * @version 1.0 8 | * @author: Iain Porter iain.porter@porterhead.com 9 | * @since 11/09/2012 10 | */ 11 | public interface EmailServicesGateway { 12 | 13 | public void sendVerificationToken(EmailServiceTokenModel model); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/model/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.model; 2 | 3 | import org.springframework.data.jpa.domain.AbstractPersistable; 4 | import org.springframework.util.Assert; 5 | 6 | import javax.persistence.Column; 7 | import javax.persistence.MappedSuperclass; 8 | import javax.persistence.Version; 9 | import java.util.Date; 10 | import java.util.UUID; 11 | 12 | /** 13 | * Base class for all JPA Entities 14 | * 15 | * @author : Iain Porter 16 | */ 17 | @MappedSuperclass 18 | public abstract class BaseEntity extends AbstractPersistable { 19 | 20 | @Version 21 | private int version; 22 | 23 | /** 24 | * All objects will have a unique UUID which allows for the decoupling from DB generated ids 25 | * 26 | */ 27 | @Column(length=36) 28 | private String uuid; 29 | 30 | private Date timeCreated; 31 | 32 | public BaseEntity() { 33 | this(UUID.randomUUID()); 34 | } 35 | 36 | public BaseEntity(UUID guid) { 37 | Assert.notNull(guid, "UUID is required"); 38 | setUuid(guid.toString()); 39 | this.timeCreated = new Date(); 40 | } 41 | 42 | public UUID getUuid() { 43 | return UUID.fromString(uuid); 44 | } 45 | 46 | public void setUuid(String uuid) { 47 | this.uuid = uuid; 48 | } 49 | 50 | public int hashCode() { 51 | return getUuid().hashCode(); 52 | } 53 | 54 | /** 55 | * In most instances we can rely on the UUID to identify the object. 56 | * Subclasses may want a user friendly identifier for constructing easy to read urls 57 | * 58 | * 59 | * /user/1883c578-76be-47fb-a5c1-7bbea3bf7fd0 using uuid as the identifier 60 | * 61 | * /user/jsmith using the username as the identifier 62 | * 63 | * 64 | * 65 | * @return Object unique identifier for the object 66 | */ 67 | public Object getIdentifier() { 68 | return getUuid().toString(); 69 | } 70 | 71 | public int getVersion() { 72 | return version; 73 | } 74 | 75 | public Date getTimeCreated() { 76 | return timeCreated; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/resource/GenericExceptionMapper.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.resource; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import javax.ws.rs.WebApplicationException; 7 | import javax.ws.rs.core.Response; 8 | import javax.ws.rs.ext.ExceptionMapper; 9 | import javax.ws.rs.ext.Provider; 10 | 11 | /** 12 | * User: porter 13 | * Date: 22/03/2012 14 | * Time: 15:56 15 | */ 16 | @Provider 17 | public class GenericExceptionMapper implements ExceptionMapper { 18 | 19 | private static Logger LOG = LoggerFactory.getLogger(GenericExceptionMapper.class); 20 | 21 | public Response toResponse(Exception exception) { 22 | if (exception instanceof WebApplicationException) { 23 | LOG.info("Web Application Exception: " + exception); 24 | return ((WebApplicationException) exception).getResponse(); 25 | } 26 | LOG.error("Internal Server Error: " + exception); 27 | LOG.error("Internal Server Error: " + exception.getCause()); 28 | return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/resource/HealthCheckResource.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.resource; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.PropertySource; 5 | import org.springframework.core.env.Environment; 6 | import org.springframework.stereotype.Component; 7 | 8 | import javax.annotation.security.PermitAll; 9 | import javax.ws.rs.GET; 10 | import javax.ws.rs.Path; 11 | import javax.ws.rs.Produces; 12 | import javax.ws.rs.core.MediaType; 13 | import javax.ws.rs.core.Response; 14 | 15 | /** 16 | * User: porter 17 | * Date: 11/04/2012 18 | * Time: 15:21 19 | */ 20 | @Path("/healthcheck") 21 | @Component 22 | @Produces({MediaType.TEXT_PLAIN}) 23 | @PropertySource("classpath:properties/app.properties") 24 | public class HealthCheckResource { 25 | 26 | @Autowired 27 | Environment env; 28 | 29 | @PermitAll 30 | @GET 31 | public Response ping() { 32 | return Response.ok().entity("Running version " + env.getProperty("application.version")).build(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/service/BaseService.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.service; 2 | 3 | import com.porterhead.rest.exception.ValidationException; 4 | 5 | import javax.validation.ConstraintViolation; 6 | import javax.validation.Validator; 7 | import java.util.Set; 8 | 9 | /** 10 | * @version 1.0 11 | * @author: Iain Porter 12 | * @since 08/05/2013 13 | */ 14 | public abstract class BaseService { 15 | 16 | private Validator validator; 17 | 18 | public BaseService(Validator validator) { 19 | this.validator = validator; 20 | } 21 | 22 | protected void validate(Object request) { 23 | Set> constraintViolations = validator.validate(request); 24 | if (constraintViolations.size() > 0) { 25 | throw new ValidationException(constraintViolations); 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/EmailServiceTokenModel.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user; 2 | 3 | import com.porterhead.rest.user.domain.User; 4 | import com.porterhead.rest.user.domain.VerificationToken; 5 | import org.apache.commons.codec.binary.Base64; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * 11 | * @version 1.0 12 | * @author: Iain Porter iain.porter@porterhead.com 13 | * @since 13/09/2012 14 | */ 15 | public class EmailServiceTokenModel implements Serializable { 16 | 17 | private final String emailAddress; 18 | private final String token; 19 | private final VerificationToken.VerificationTokenType tokenType; 20 | private final String hostNameUrl; 21 | 22 | 23 | public EmailServiceTokenModel(User user, VerificationToken token, String hostNameUrl) { 24 | this.emailAddress = user.getEmailAddress(); 25 | this.token = token.getToken(); 26 | this.tokenType = token.getTokenType(); 27 | this.hostNameUrl = hostNameUrl; 28 | } 29 | 30 | public String getEmailAddress() { 31 | return emailAddress; 32 | } 33 | 34 | public String getEncodedToken() { 35 | return new String(Base64.encodeBase64(token.getBytes())); 36 | } 37 | 38 | public String getToken() { 39 | return token; 40 | } 41 | 42 | public VerificationToken.VerificationTokenType getTokenType() { 43 | return tokenType; 44 | } 45 | 46 | public String getHostNameUrl() { 47 | return hostNameUrl; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/SocialUserRepository.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user; 2 | 3 | import com.porterhead.rest.user.domain.SocialUser; 4 | import com.porterhead.rest.user.domain.User; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.Query; 7 | import org.springframework.util.MultiValueMap; 8 | 9 | import java.util.List; 10 | import java.util.Set; 11 | 12 | /** 13 | * User: porter 14 | * Date: 15/05/2012 15 | * Time: 16:35 16 | */ 17 | public interface SocialUserRepository extends JpaRepository { 18 | 19 | List findAllByUser(User user); 20 | 21 | List findByUserAndProviderId(User user, String providerId); 22 | 23 | List findByProviderIdAndProviderUserId(String providerId, String providerUserId); 24 | 25 | //TODO will need a JPA Query here 26 | List findByUserAndProviderUserId(User user, MultiValueMap providerUserIds); 27 | 28 | @Query("Select userId from SocialUser where providerId = ? AND providerUserId in (?)") 29 | Set findByProviderIdAndProviderUserId(String providerId, Set providerUserIds); 30 | 31 | SocialUser findByUserAndProviderIdAndProviderUserId(User user, String providerId, String providerUserId); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user; 2 | 3 | import com.porterhead.rest.user.domain.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | 7 | import java.util.Date; 8 | import java.util.List; 9 | 10 | /** 11 | * 12 | * @version 1.0 13 | * @author: Iain Porter iain.porter@porterhead.com 14 | * @since 12/04/2012 15 | */ 16 | public interface UserRepository extends JpaRepository { 17 | 18 | User findByEmailAddress(String emailAddress); 19 | 20 | @Query("select u from User u where uuid = ?") 21 | User findByUuid(String uuid); 22 | 23 | @Query("select u from User u where u in (select user from AuthorizationToken where lastUpdated < ?)") 24 | List findByExpiredSession(Date lastUpdated); 25 | 26 | @Query("select u from User u where u = (select user from AuthorizationToken where token = ?)") 27 | User findBySession(String token); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/UserService.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user; 2 | 3 | import com.porterhead.rest.user.api.*; 4 | import com.porterhead.rest.user.domain.AuthorizationToken; 5 | import com.porterhead.rest.user.domain.Role; 6 | import com.porterhead.rest.user.domain.User; 7 | import org.springframework.social.connect.Connection; 8 | 9 | /** 10 | * @author: Iain Porter 11 | * 12 | * Service to manage users 13 | */ 14 | public interface UserService { 15 | 16 | 17 | /** 18 | * Create a new User with the given role 19 | * 20 | * @param request 21 | * @param role 22 | * @return AuthenticatedUserToken 23 | */ 24 | public AuthenticatedUserToken createUser(CreateUserRequest request, Role role); 25 | 26 | 27 | /** 28 | * Create a Default User with a given role 29 | * 30 | * @param role 31 | * @return AuthenticatedUserToken 32 | */ 33 | public AuthenticatedUserToken createUser(Role role); 34 | 35 | /** 36 | * Login a User 37 | * 38 | * @param request 39 | * @return AuthenticatedUserToken 40 | */ 41 | public AuthenticatedUserToken login(LoginRequest request); 42 | 43 | /** 44 | * Log in a User using Connection details from an authorized request from the User's supported Social provider 45 | * encapsulated in the {@link org.springframework.social.connect.Connection} parameter 46 | * 47 | * @param connection containing the details of the authorized user account form the Social provider 48 | * @return the User account linked to the {@link com.porterhead.rest.user.domain.SocialUser} account 49 | */ 50 | public AuthenticatedUserToken socialLogin(Connection connection); 51 | 52 | /** 53 | * Get a User based on a unique identifier 54 | * 55 | * Identifiers supported are uuid, emailAddress 56 | * 57 | * @param userIdentifier 58 | * @return User 59 | */ 60 | public ExternalUser getUser(ExternalUser requestingUser, String userIdentifier); 61 | 62 | /** 63 | * Delete user, only authenticated user accounts can be deleted 64 | * 65 | * @param userMakingRequest the user authorized to delete the user 66 | * @param userId the id of the user to delete 67 | */ 68 | public void deleteUser(ExternalUser userMakingRequest, String userId); 69 | 70 | /** 71 | * Save User 72 | * 73 | * @param userId 74 | * @param request 75 | */ 76 | public ExternalUser saveUser(String userId, UpdateUserRequest request); 77 | 78 | /** 79 | * Create an AuthorizationToken for the User 80 | * 81 | * @return 82 | */ 83 | public AuthorizationToken createAuthorizationToken(User user); 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/VerificationTokenRepository.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user; 2 | 3 | import com.porterhead.rest.user.domain.VerificationToken; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | 7 | /** 8 | * @version 1.0 9 | * @author: Iain Porter iain.porter@porterhead.com 10 | * @since 14/09/2012 11 | */ 12 | public interface VerificationTokenRepository extends JpaRepository { 13 | 14 | @Query("select t from VerificationToken t where uuid = ?") 15 | VerificationToken findByUuid(String uuid); 16 | 17 | @Query("select t from VerificationToken t where token = ?") 18 | VerificationToken findByToken(String token); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/VerificationTokenService.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user; 2 | 3 | import com.porterhead.rest.user.api.LostPasswordRequest; 4 | import com.porterhead.rest.user.api.PasswordRequest; 5 | import com.porterhead.rest.user.domain.VerificationToken; 6 | 7 | /** 8 | * 9 | * @version 1.0 10 | * @author: Iain Porter iain.porter@porterhead.com 11 | * @since 10/09/2012 12 | */ 13 | public interface VerificationTokenService { 14 | 15 | public VerificationToken sendEmailVerificationToken(String userId); 16 | 17 | public VerificationToken sendEmailRegistrationToken(String userId); 18 | 19 | public VerificationToken sendLostPasswordToken(LostPasswordRequest lostPasswordRequest); 20 | 21 | public VerificationToken verify(String base64EncodedToken); 22 | 23 | public VerificationToken generateEmailVerificationToken(String emailAddress); 24 | 25 | public VerificationToken resetPassword(String base64EncodedToken, PasswordRequest passwordRequest); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/api/AuthenticatedUserToken.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.api; 2 | 3 | import javax.xml.bind.annotation.XmlRootElement; 4 | 5 | /** 6 | * @author: Iain Porter 7 | */ 8 | @XmlRootElement 9 | public class AuthenticatedUserToken { 10 | 11 | private String userId; 12 | private String token; 13 | 14 | public AuthenticatedUserToken(){} 15 | 16 | public AuthenticatedUserToken(String userId, String sessionToken) { 17 | this.userId = userId; 18 | this.token = sessionToken; 19 | } 20 | 21 | public String getUserId() { 22 | return userId; 23 | } 24 | 25 | public String getToken() { 26 | return token; 27 | } 28 | 29 | public void setUserId(String userId) { 30 | this.userId = userId; 31 | } 32 | 33 | public void setToken(String token) { 34 | this.token = token; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/api/CreateUserRequest.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.api; 2 | 3 | import javax.validation.Valid; 4 | import javax.validation.constraints.NotNull; 5 | import javax.xml.bind.annotation.XmlRootElement; 6 | 7 | /** 8 | * @author: Iain Porter 9 | */ 10 | @XmlRootElement 11 | public class CreateUserRequest { 12 | 13 | @NotNull 14 | @Valid 15 | private ExternalUser user; 16 | 17 | @NotNull 18 | @Valid 19 | private PasswordRequest password; 20 | 21 | 22 | public CreateUserRequest() { 23 | } 24 | 25 | public CreateUserRequest(final ExternalUser user, final PasswordRequest password) { 26 | this.user = user; 27 | this.password = password; 28 | } 29 | 30 | public ExternalUser getUser() { 31 | return user; 32 | } 33 | 34 | public void setUser(ExternalUser user) { 35 | this.user = user; 36 | } 37 | 38 | public PasswordRequest getPassword() { 39 | return password; 40 | } 41 | 42 | public void setPassword(PasswordRequest password) { 43 | this.password = password; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/api/EmailVerificationRequest.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.api; 2 | 3 | import javax.validation.constraints.NotNull; 4 | import javax.xml.bind.annotation.XmlRootElement; 5 | 6 | /** 7 | * 8 | * @version 1.0 9 | * @author: Iain Porter iain.porter@porterhead.com 10 | * @since 15/09/2012 11 | */ 12 | @XmlRootElement 13 | public class EmailVerificationRequest { 14 | 15 | @NotNull 16 | private String emailAddress; 17 | 18 | public EmailVerificationRequest() {} 19 | 20 | public EmailVerificationRequest(String emailAddress) { 21 | this.emailAddress = emailAddress; 22 | } 23 | 24 | public String getEmailAddress() { 25 | return emailAddress; 26 | } 27 | 28 | public void setEmailAddress(String emailAddress) { 29 | this.emailAddress = emailAddress; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/api/ExternalUser.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.api; 2 | 3 | import com.porterhead.rest.user.domain.AuthorizationToken; 4 | import com.porterhead.rest.user.domain.SocialUser; 5 | import com.porterhead.rest.user.domain.User; 6 | import org.codehaus.jackson.annotate.JsonIgnore; 7 | import org.hibernate.validator.constraints.Email; 8 | import org.hibernate.validator.constraints.Length; 9 | 10 | import javax.validation.constraints.NotNull; 11 | import javax.xml.bind.annotation.XmlRootElement; 12 | import java.security.Principal; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | 17 | /** 18 | * 19 | * @author: Iain Porter 20 | */ 21 | @XmlRootElement 22 | public class ExternalUser implements Principal { 23 | 24 | private String id; 25 | 26 | @Length(max=50) 27 | private String firstName; 28 | 29 | @Length(max=50) 30 | private String lastName; 31 | 32 | @NotNull 33 | @Email 34 | private String emailAddress; 35 | 36 | private boolean isVerified; 37 | 38 | @JsonIgnore 39 | private String role; 40 | 41 | private List socialProfiles = new ArrayList(); 42 | 43 | public ExternalUser() {} 44 | 45 | public ExternalUser(String userId) { 46 | this.id = userId; 47 | } 48 | 49 | public ExternalUser(User user) { 50 | this.id = user.getUuid().toString(); 51 | this.emailAddress = user.getEmailAddress(); 52 | this.firstName = user.getFirstName(); 53 | this.lastName = user.getLastName(); 54 | this.isVerified = user.isVerified(); 55 | for(SocialUser socialUser: user.getSocialUsers()) { 56 | SocialProfile profile = new SocialProfile(); 57 | profile.setDisplayName(socialUser.getDisplayName()); 58 | profile.setImageUrl(socialUser.getImageUrl()); 59 | profile.setProfileUrl(socialUser.getProfileUrl()); 60 | profile.setProvider(socialUser.getProviderId()); 61 | profile.setProviderUserId(socialUser.getProviderUserId()); 62 | socialProfiles.add(profile); 63 | } 64 | role = user.getRole().toString(); 65 | } 66 | 67 | public ExternalUser(User user, AuthorizationToken activeSession) { 68 | this(user); 69 | } 70 | 71 | public String getFirstName() { 72 | return firstName; 73 | } 74 | 75 | public void setFirstName(String firstName) { 76 | this.firstName = firstName; 77 | } 78 | 79 | public String getLastName() { 80 | return lastName; 81 | } 82 | 83 | public void setLastName(String lastName) { 84 | this.lastName = lastName; 85 | } 86 | 87 | public String getEmailAddress() { 88 | return emailAddress; 89 | } 90 | 91 | public void setEmailAddress(String emailAddress) { 92 | this.emailAddress = emailAddress; 93 | } 94 | 95 | public List getSocialProfiles() { 96 | return socialProfiles; 97 | } 98 | 99 | public String getId() { 100 | return id; 101 | } 102 | 103 | public boolean isVerified() { 104 | return isVerified; 105 | } 106 | 107 | public String getName() { 108 | return emailAddress; 109 | } 110 | 111 | public String getRole() { 112 | return role; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/api/LoginRequest.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.api; 2 | 3 | import org.hibernate.validator.constraints.Length; 4 | 5 | import javax.validation.constraints.NotNull; 6 | import javax.xml.bind.annotation.XmlRootElement; 7 | 8 | 9 | /** 10 | * 11 | * @author: Iain Porter 12 | */ 13 | @XmlRootElement 14 | public class LoginRequest { 15 | 16 | @NotNull 17 | private String username; 18 | 19 | @Length(min=8, max=30) 20 | @NotNull 21 | private String password; 22 | 23 | public LoginRequest(){} 24 | 25 | public String getUsername() { 26 | return username; 27 | } 28 | 29 | public void setUsername(String username) { 30 | this.username = username; 31 | } 32 | 33 | public String getPassword() { 34 | return password; 35 | } 36 | 37 | public void setPassword(String password) { 38 | this.password = password; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/api/LostPasswordRequest.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.api; 2 | 3 | import javax.validation.constraints.NotNull; 4 | import javax.xml.bind.annotation.XmlRootElement; 5 | 6 | /** 7 | * 8 | * @version 1.0 9 | * @author: Iain Porter iain.porter@porterhead.com 10 | * @since 26/09/2012 11 | */ 12 | @XmlRootElement 13 | public class LostPasswordRequest { 14 | 15 | @NotNull 16 | private String emailAddress; 17 | 18 | public LostPasswordRequest() {} 19 | 20 | public LostPasswordRequest(final String emailAddress) { 21 | this.emailAddress = emailAddress; 22 | } 23 | 24 | public String getEmailAddress() { 25 | return emailAddress; 26 | } 27 | 28 | public void setEmailAddress(String emailAddress) { 29 | this.emailAddress = emailAddress; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/api/OAuth2Request.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.api; 2 | 3 | import javax.validation.constraints.NotNull; 4 | import javax.xml.bind.annotation.XmlRootElement; 5 | 6 | /** 7 | * User: porter 8 | * Date: 18/05/2012 9 | * Time: 09:59 10 | */ 11 | @XmlRootElement 12 | public class OAuth2Request { 13 | 14 | private String accessToken; 15 | 16 | @NotNull 17 | public String getAccessToken() { 18 | return accessToken; 19 | } 20 | 21 | public void setAccessToken(String accessToken) { 22 | this.accessToken = accessToken; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/api/PasswordRequest.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.api; 2 | 3 | import org.hibernate.validator.constraints.Length; 4 | 5 | import javax.validation.constraints.NotNull; 6 | import javax.xml.bind.annotation.XmlRootElement; 7 | 8 | /** 9 | * 10 | * @version 1.0 11 | * @author: Iain Porter iain.porter@porterhead.com 12 | * @since 28/09/2012 13 | */ 14 | @XmlRootElement 15 | public class PasswordRequest { 16 | 17 | @Length(min=8, max=30) 18 | @NotNull 19 | private String password; 20 | 21 | public PasswordRequest() {} 22 | 23 | public PasswordRequest(final String password) { 24 | this.password = password; 25 | } 26 | 27 | public String getPassword() { 28 | return password; 29 | } 30 | 31 | public void setPassword(String password) { 32 | this.password = password; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/api/SocialProfile.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.api; 2 | 3 | import javax.xml.bind.annotation.XmlRootElement; 4 | 5 | /** 6 | * @author: Iain Porter 7 | */ 8 | @XmlRootElement 9 | public class SocialProfile { 10 | 11 | String provider; 12 | String providerUserId; 13 | String profileUrl; 14 | String imageUrl; 15 | String displayName; 16 | 17 | public SocialProfile() {} 18 | 19 | public String getProvider() { 20 | return provider; 21 | } 22 | 23 | public void setProvider(String provider) { 24 | this.provider = provider; 25 | } 26 | 27 | public String getProviderUserId() { 28 | return providerUserId; 29 | } 30 | 31 | public void setProviderUserId(String providerUserId) { 32 | this.providerUserId = providerUserId; 33 | } 34 | 35 | public String getProfileUrl() { 36 | return profileUrl; 37 | } 38 | 39 | public void setProfileUrl(String profileUrl) { 40 | this.profileUrl = profileUrl; 41 | } 42 | 43 | public String getImageUrl() { 44 | return imageUrl; 45 | } 46 | 47 | public void setImageUrl(String imageUrl) { 48 | this.imageUrl = imageUrl; 49 | } 50 | 51 | public String getDisplayName() { 52 | return displayName; 53 | } 54 | 55 | public void setDisplayName(String displayName) { 56 | this.displayName = displayName; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/api/UpdateUserRequest.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.api; 2 | 3 | import org.hibernate.validator.constraints.Email; 4 | 5 | import javax.validation.constraints.NotNull; 6 | import javax.xml.bind.annotation.XmlRootElement; 7 | 8 | /** 9 | * 10 | * @version 1.0 11 | * @author: Iain Porter iain.porter@porterhead.com 12 | * @since 05/10/2012 13 | */ 14 | @XmlRootElement 15 | public class UpdateUserRequest { 16 | 17 | private String firstName; 18 | private String lastName; 19 | 20 | @Email 21 | @NotNull 22 | private String emailAddress; 23 | 24 | public UpdateUserRequest(){} 25 | 26 | public String getFirstName() { 27 | return firstName; 28 | } 29 | 30 | public void setFirstName(String firstName) { 31 | this.firstName = firstName; 32 | } 33 | 34 | public String getLastName() { 35 | return lastName; 36 | } 37 | 38 | public void setLastName(String lastName) { 39 | this.lastName = lastName; 40 | } 41 | 42 | public String getEmailAddress() { 43 | return emailAddress; 44 | } 45 | 46 | public void setEmailAddress(String emailAddress) { 47 | this.emailAddress = emailAddress; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/domain/AuthorizationToken.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.domain; 2 | 3 | import org.springframework.data.jpa.domain.AbstractPersistable; 4 | 5 | import javax.persistence.*; 6 | import java.util.Date; 7 | import java.util.UUID; 8 | 9 | /** 10 | * 11 | * @version 1.0 12 | * @author: Iain Porter iain.porter 13 | * @since 28/12/2012 14 | */ 15 | @Entity 16 | @Table(name="rest_authorization_token") 17 | public class AuthorizationToken extends AbstractPersistable { 18 | 19 | private final static Integer DEFAULT_TIME_TO_LIVE_IN_SECONDS = (60 * 60 * 24 * 30); //30 Days 20 | 21 | @Column(length=36) 22 | private String token; 23 | 24 | private Date timeCreated; 25 | 26 | private Date expirationDate; 27 | 28 | @JoinColumn(name = "user_id") 29 | @OneToOne(fetch = FetchType.LAZY) 30 | private User user; 31 | 32 | public AuthorizationToken() {} 33 | 34 | public AuthorizationToken(User user) { 35 | this(user, DEFAULT_TIME_TO_LIVE_IN_SECONDS); 36 | } 37 | 38 | public AuthorizationToken(User user, Integer timeToLiveInSeconds) { 39 | this.token = UUID.randomUUID().toString(); 40 | this.user = user; 41 | this.timeCreated = new Date(); 42 | this.expirationDate = new Date(System.currentTimeMillis() + (timeToLiveInSeconds * 1000L)); 43 | } 44 | 45 | public boolean hasExpired() { 46 | return this.expirationDate != null && this.expirationDate.before(new Date()); 47 | } 48 | 49 | public String getToken() { 50 | return token; 51 | } 52 | 53 | public User getUser() { 54 | return user; 55 | } 56 | 57 | public Date getTimeCreated() { 58 | return timeCreated; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/domain/Role.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.domain; 2 | 3 | /** 4 | * User: porter 5 | * Date: 03/04/2012 6 | * Time: 13:17 7 | */ 8 | public enum Role { 9 | 10 | authenticated, administrator, anonymous 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/domain/SocialUser.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.domain; 2 | 3 | import com.porterhead.rest.model.BaseEntity; 4 | 5 | import javax.persistence.*; 6 | 7 | /** 8 | * User: porter 9 | * Date: 15/05/2012 10 | * Time: 13:57 11 | */ 12 | @Entity 13 | @Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"userId", "providerId", "providerUserId"}), 14 | @UniqueConstraint(columnNames = {"userId", "providerId", "rank"})}) 15 | public class SocialUser extends BaseEntity { 16 | 17 | @ManyToOne(cascade = CascadeType.MERGE, fetch = FetchType.EAGER, optional = false) 18 | @JoinColumn(name = "userId", nullable = false, updatable = false) 19 | private User user; 20 | 21 | private String providerId; 22 | 23 | private String providerUserId; 24 | 25 | private int rank; 26 | 27 | private String displayName; 28 | 29 | private String profileUrl; 30 | 31 | private String imageUrl; 32 | 33 | @Column(length = 500) 34 | private String accessToken; 35 | 36 | private String secret; 37 | 38 | private String refreshToken; 39 | 40 | private Long expireTime; 41 | 42 | public String getProviderId() { 43 | return providerId; 44 | } 45 | 46 | public void setProviderId(String providerId) { 47 | this.providerId = providerId; 48 | } 49 | 50 | public String getProviderUserId() { 51 | return providerUserId; 52 | } 53 | 54 | public void setProviderUserId(String providerUserId) { 55 | this.providerUserId = providerUserId; 56 | } 57 | 58 | public int getRank() { 59 | return rank; 60 | } 61 | 62 | public void setRank(int rank) { 63 | this.rank = rank; 64 | } 65 | 66 | public String getDisplayName() { 67 | return displayName; 68 | } 69 | 70 | public void setDisplayName(String displayName) { 71 | this.displayName = displayName; 72 | } 73 | 74 | public String getProfileUrl() { 75 | return profileUrl; 76 | } 77 | 78 | public void setProfileUrl(String profileUrl) { 79 | this.profileUrl = profileUrl; 80 | } 81 | 82 | public String getImageUrl() { 83 | return imageUrl; 84 | } 85 | 86 | public void setImageUrl(String imageUrl) { 87 | this.imageUrl = imageUrl; 88 | } 89 | 90 | public String getAccessToken() { 91 | return accessToken; 92 | } 93 | 94 | public void setAccessToken(String accessToken) { 95 | this.accessToken = accessToken; 96 | } 97 | 98 | public String getSecret() { 99 | return secret; 100 | } 101 | 102 | public void setSecret(String secret) { 103 | this.secret = secret; 104 | } 105 | 106 | public String getRefreshToken() { 107 | return refreshToken; 108 | } 109 | 110 | public void setRefreshToken(String refreshToken) { 111 | this.refreshToken = refreshToken; 112 | } 113 | 114 | public Long getExpireTime() { 115 | return expireTime; 116 | } 117 | 118 | public void setExpireTime(Long expireTime) { 119 | this.expireTime = expireTime; 120 | } 121 | 122 | public User getUser() { 123 | return user; 124 | } 125 | 126 | public void setUser(User user) { 127 | this.user = user; 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/domain/SocialUserBuilder.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.domain; 2 | 3 | /** 4 | * User: porter 5 | * Date: 21/05/2012 6 | * Time: 08:12 7 | */ 8 | public class SocialUserBuilder { 9 | 10 | SocialUser user; 11 | 12 | public static SocialUserBuilder create() { 13 | return new SocialUserBuilder(); 14 | } 15 | 16 | public SocialUserBuilder() { 17 | user = new SocialUser(); 18 | } 19 | 20 | public SocialUser build() { 21 | return user; 22 | } 23 | 24 | public SocialUserBuilder withUser(User user) { 25 | this.user.setUser(user); 26 | return this; 27 | } 28 | 29 | public SocialUserBuilder withProviderId(String id) { 30 | this.user.setProviderId(id); 31 | return this; 32 | } 33 | 34 | public SocialUserBuilder withProviderUserId(String id) { 35 | this.user.setProviderUserId(id); 36 | return this; 37 | } 38 | 39 | public SocialUserBuilder withRank(int rank) { 40 | this.user.setRank(rank); 41 | return this; 42 | } 43 | 44 | public SocialUserBuilder withDisplayName(String name) { 45 | this.user.setDisplayName(name); 46 | return this; 47 | } 48 | 49 | public SocialUserBuilder withProfileUrl(String url) { 50 | this.user.setProfileUrl(url); 51 | return this; 52 | } 53 | 54 | public SocialUserBuilder withImageUrl(String url) { 55 | this.user.setImageUrl(url); 56 | return this; 57 | } 58 | 59 | public SocialUserBuilder withAccessToken(String token) { 60 | this.user.setAccessToken(token); 61 | return this; 62 | } 63 | 64 | public SocialUserBuilder withSecret(String secret) { 65 | this.user.setSecret(secret); 66 | return this; 67 | } 68 | 69 | public SocialUserBuilder withRefreshToken(String token) { 70 | this.user.setRefreshToken(token); 71 | return this; 72 | } 73 | 74 | public SocialUserBuilder withExpireTime(Long time) { 75 | this.user.setExpireTime(time); 76 | return this; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/domain/VerificationToken.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.domain; 2 | 3 | import com.porterhead.rest.model.BaseEntity; 4 | import org.joda.time.DateTime; 5 | 6 | import javax.persistence.*; 7 | import java.util.Date; 8 | import java.util.UUID; 9 | 10 | /** 11 | * A token that gives the user permission to carry out a specific task once within a determined time period. 12 | * An example would be a Lost Password token. The user receives the token embedded in a link. 13 | * They send the token back to the server by clicking the link and the action is processed 14 | * 15 | * @version 1.0 16 | * @author: Iain Porter iain.porter@porterhead.com 17 | * @since 10/09/2012 18 | */ 19 | @Entity 20 | @Table(name = "rest_verification_token") 21 | public class VerificationToken extends BaseEntity { 22 | 23 | private static final int DEFAULT_EXPIRY_TIME_IN_MINS = 60 * 24; //24 hours 24 | 25 | @Column(length=36) 26 | private final String token; 27 | 28 | private Date expiryDate; 29 | 30 | @Enumerated(EnumType.STRING) 31 | private VerificationTokenType tokenType; 32 | 33 | private boolean verified; 34 | 35 | @ManyToOne 36 | @JoinColumn(name = "user_id") 37 | User user; 38 | 39 | public VerificationToken() { 40 | super(); 41 | this.token = UUID.randomUUID().toString(); 42 | this.expiryDate = calculateExpiryDate(DEFAULT_EXPIRY_TIME_IN_MINS); 43 | } 44 | 45 | public VerificationToken(User user, VerificationTokenType tokenType, int expirationTimeInMinutes) { 46 | this(); 47 | this.user = user; 48 | this.tokenType = tokenType; 49 | this.expiryDate = calculateExpiryDate(expirationTimeInMinutes); 50 | } 51 | 52 | public VerificationTokenType getTokenType() { 53 | return tokenType; 54 | } 55 | 56 | public boolean isVerified() { 57 | return verified; 58 | } 59 | 60 | public void setVerified(boolean verified) { 61 | this.verified = verified; 62 | } 63 | 64 | public User getUser() { 65 | return user; 66 | } 67 | 68 | public void setUser(User user) { 69 | this.user = user; 70 | } 71 | 72 | public Date getExpiryDate() { 73 | return expiryDate; 74 | } 75 | 76 | public String getToken() { 77 | return token; 78 | } 79 | 80 | private Date calculateExpiryDate(int expiryTimeInMinutes) { 81 | DateTime now = new DateTime(); 82 | return now.plusMinutes(expiryTimeInMinutes).toDate(); 83 | } 84 | 85 | public enum VerificationTokenType { 86 | 87 | lostPassword, emailVerification, emailRegistration 88 | } 89 | 90 | public boolean hasExpired() { 91 | DateTime tokenDate = new DateTime(getExpiryDate()); 92 | return tokenDate.isBeforeNow(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/exception/AlreadyVerifiedException.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.exception; 2 | 3 | import com.porterhead.rest.exception.BaseWebApplicationException; 4 | 5 | /** 6 | * 7 | * @version 1.0 8 | * @author: Iain Porter iain.porter@porterhead.com 9 | * @since 14/09/2012 10 | */ 11 | public class AlreadyVerifiedException extends BaseWebApplicationException { 12 | 13 | public AlreadyVerifiedException() { 14 | super(409, "40905", "Already verified", "The token has already been verified"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/exception/AuthenticationException.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.exception; 2 | 3 | import com.porterhead.rest.exception.BaseWebApplicationException; 4 | 5 | /** 6 | * User: porter 7 | * Date: 13/03/2012 8 | * Time: 08:58 9 | */ 10 | public class AuthenticationException extends BaseWebApplicationException { 11 | 12 | public AuthenticationException() { 13 | super(401, "40102", "Authentication Error", "Authentication Error. The username or password were incorrect"); 14 | } 15 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/exception/AuthorizationException.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.exception; 2 | 3 | 4 | import com.porterhead.rest.exception.BaseWebApplicationException; 5 | 6 | /** 7 | * User: porter 8 | * Date: 04/04/2012 9 | * Time: 15:32 10 | */ 11 | public class AuthorizationException extends BaseWebApplicationException { 12 | 13 | public AuthorizationException(String applicationMessage) { 14 | super(403, "40301", "Not authorized", applicationMessage); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/exception/DuplicateUserException.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.exception; 2 | 3 | import com.porterhead.rest.exception.BaseWebApplicationException; 4 | 5 | /** 6 | * User: porter 7 | * Date: 12/03/2012 8 | * Time: 15:10 9 | */ 10 | public class DuplicateUserException extends BaseWebApplicationException { 11 | 12 | public DuplicateUserException() { 13 | super(409, "40901", "User already exists", "An attempt was made to create a user that already exists"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/exception/TokenHasExpiredException.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.exception; 2 | 3 | import com.porterhead.rest.exception.BaseWebApplicationException; 4 | 5 | /** 6 | * 7 | * @version 1.0 8 | * @author: Iain Porter iain.porter@porterhead.com 9 | * @since 14/09/2012 10 | */ 11 | public class TokenHasExpiredException extends BaseWebApplicationException { 12 | 13 | public TokenHasExpiredException() { 14 | super(403, "40304", "Token has expired", "An attempt was made to load a token that has expired"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/exception/TokenNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.exception; 2 | 3 | import com.porterhead.rest.exception.BaseWebApplicationException; 4 | 5 | /** 6 | * 7 | * @version 1.0 8 | * @author: Iain Porter iain.porter@porterhead.com 9 | * @since 14/09/2012 10 | */ 11 | public class TokenNotFoundException extends BaseWebApplicationException { 12 | 13 | public TokenNotFoundException() { 14 | super(404, "40407", "Token Not Found", "No token could be found for that Id"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/exception/UserNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.exception; 2 | 3 | import com.porterhead.rest.exception.BaseWebApplicationException; 4 | 5 | /** 6 | * 7 | * @version 1.0 8 | * @author: Iain Porter iain.porter@porterhead.com 9 | * @since 12/09/2012 10 | */ 11 | public class UserNotFoundException extends BaseWebApplicationException { 12 | 13 | public UserNotFoundException() { 14 | super(404, "40402", "User Not Found", "No User could be found for that Id"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/mail/MailSenderService.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.mail; 2 | 3 | import com.porterhead.rest.user.EmailServiceTokenModel; 4 | 5 | /** 6 | * 7 | * @version 1.0 8 | * @author: Iain Porter iain.porter@porterhead.com 9 | * @since 13/09/2012 10 | */ 11 | public interface MailSenderService { 12 | 13 | public EmailServiceTokenModel sendVerificationEmail(EmailServiceTokenModel emailServiceTokenModel); 14 | 15 | public EmailServiceTokenModel sendRegistrationEmail(EmailServiceTokenModel emailServiceTokenModel); 16 | 17 | public EmailServiceTokenModel sendLostPasswordEmail(EmailServiceTokenModel emailServiceTokenModel); 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/mail/MockJavaMailSender.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.mail; 2 | 3 | import org.springframework.mail.MailException; 4 | import org.springframework.mail.SimpleMailMessage; 5 | import org.springframework.mail.javamail.JavaMailSender; 6 | import org.springframework.mail.javamail.MimeMessagePreparator; 7 | 8 | import javax.mail.Session; 9 | import javax.mail.internet.MimeMessage; 10 | import java.io.InputStream; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.Properties; 14 | 15 | /** 16 | * @author: Iain Porter 17 | */ 18 | public class MockJavaMailSender implements JavaMailSender { 19 | 20 | List messages = new ArrayList(); 21 | 22 | public MimeMessage createMimeMessage() { 23 | MimeMessage message = new MimeMessage(Session.getInstance(new Properties())); 24 | return message; 25 | } 26 | 27 | public MimeMessage createMimeMessage(InputStream contentStream) throws MailException { 28 | return null; //To change body of implemented methods use File | Settings | File Templates. 29 | } 30 | 31 | public void send(MimeMessage mimeMessage) throws MailException { 32 | //To change body of implemented methods use File | Settings | File Templates. 33 | } 34 | 35 | public void send(MimeMessage[] mimeMessages) throws MailException { 36 | //To change body of implemented methods use File | Settings | File Templates. 37 | } 38 | 39 | public void send(MimeMessagePreparator mimeMessagePreparator) throws MailException { 40 | try { 41 | MimeMessage mimeMessage = createMimeMessage(); 42 | mimeMessagePreparator.prepare(mimeMessage); 43 | messages.add(mimeMessage); 44 | } catch(Exception e) { 45 | System.out.println("Exception while preparing Mail Message" + e); 46 | throw new RuntimeException(e); 47 | } 48 | 49 | 50 | } 51 | 52 | public void send(MimeMessagePreparator[] mimeMessagePreparators) throws MailException { 53 | //To change body of implemented methods use File | Settings | File Templates. 54 | } 55 | 56 | public void send(SimpleMailMessage simpleMessage) throws MailException { 57 | //To change body of implemented methods use File | Settings | File Templates. 58 | } 59 | 60 | public void send(SimpleMailMessage[] simpleMessages) throws MailException { 61 | //To change body of implemented methods use File | Settings | File Templates. 62 | } 63 | 64 | public List getMessages() { 65 | return messages; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/mail/impl/MailSenderServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.mail.impl; 2 | 3 | import com.porterhead.rest.config.ApplicationConfig; 4 | import com.porterhead.rest.user.EmailServiceTokenModel; 5 | import com.porterhead.rest.user.mail.*; 6 | import org.apache.velocity.app.VelocityEngine; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.core.io.ClassPathResource; 11 | import org.springframework.core.io.Resource; 12 | import org.springframework.mail.javamail.JavaMailSender; 13 | import org.springframework.mail.javamail.MimeMessageHelper; 14 | import org.springframework.mail.javamail.MimeMessagePreparator; 15 | import org.springframework.stereotype.Service; 16 | import org.springframework.ui.velocity.VelocityEngineUtils; 17 | 18 | import javax.mail.MessagingException; 19 | import javax.mail.internet.MimeMessage; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | /** 24 | * 25 | * @version 1.0 26 | * @author: Iain Porter iain.porter@porterhead.com 27 | * @since 13/09/2012 28 | */ 29 | @Service("mailSenderService") 30 | public class MailSenderServiceImpl implements MailSenderService { 31 | 32 | private static Logger LOG = LoggerFactory.getLogger(MailSenderServiceImpl.class); 33 | 34 | private final JavaMailSender mailSender; 35 | private final VelocityEngine velocityEngine; 36 | private ApplicationConfig config; 37 | 38 | @Autowired 39 | public MailSenderServiceImpl(JavaMailSender mailSender, VelocityEngine velocityEngine) { 40 | this.mailSender = mailSender; 41 | this.velocityEngine = velocityEngine; 42 | } 43 | 44 | 45 | public EmailServiceTokenModel sendVerificationEmail(final EmailServiceTokenModel emailVerificationModel) { 46 | Map resources = new HashMap(); 47 | return sendVerificationEmail(emailVerificationModel, config.getEmailVerificationSubjectText(), 48 | "META-INF/velocity/VerifyEmail.vm", resources); 49 | } 50 | 51 | public EmailServiceTokenModel sendRegistrationEmail(final EmailServiceTokenModel emailVerificationModel) { 52 | Map resources = new HashMap(); 53 | return sendVerificationEmail(emailVerificationModel, config.getEmailRegistrationSubjectText(), 54 | "META-INF/velocity/RegistrationEmail.vm", resources); 55 | } 56 | 57 | public EmailServiceTokenModel sendLostPasswordEmail(final EmailServiceTokenModel emailServiceTokenModel) { 58 | Map resources = new HashMap(); 59 | return sendVerificationEmail(emailServiceTokenModel, config.getLostPasswordSubjectText(), 60 | "META-INF/velocity/LostPasswordEmail.vm", resources); 61 | } 62 | 63 | 64 | private void addInlineResource(MimeMessageHelper messageHelper, String resourcePath, String resourceIdentifier) throws MessagingException { 65 | Resource resource = new ClassPathResource(resourcePath); 66 | messageHelper.addInline(resourceIdentifier, resource); 67 | } 68 | 69 | private EmailServiceTokenModel sendVerificationEmail(final EmailServiceTokenModel emailVerificationModel, final String emailSubject, 70 | final String velocityModel, final Map resources) { 71 | MimeMessagePreparator preparator = new MimeMessagePreparator() { 72 | public void prepare(MimeMessage mimeMessage) throws Exception { 73 | MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, MimeMessageHelper.MULTIPART_MODE_RELATED, "UTF-8"); 74 | messageHelper.setTo(emailVerificationModel.getEmailAddress()); 75 | messageHelper.setFrom(config.getEmailFromAddress()); 76 | messageHelper.setReplyTo(config.getEmailReplyToAddress()); 77 | messageHelper.setSubject(emailSubject); 78 | Map model = new HashMap(); 79 | model.put("model", emailVerificationModel); 80 | String text = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, velocityModel, model); 81 | messageHelper.setText(new String(text.getBytes(), "UTF-8"), true); 82 | for(String resourceIdentifier: resources.keySet()) { 83 | addInlineResource(messageHelper, resources.get(resourceIdentifier), resourceIdentifier); 84 | } 85 | } 86 | }; 87 | LOG.debug("Sending {} token to : {}",emailVerificationModel.getTokenType().toString(), emailVerificationModel.getEmailAddress()); 88 | this.mailSender.send(preparator); 89 | return emailVerificationModel; 90 | } 91 | 92 | @Autowired 93 | public void setConfig(ApplicationConfig config) { 94 | this.config = config; 95 | } 96 | 97 | public ApplicationConfig getConfig() { 98 | return this.config; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/resource/PasswordResource.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.resource; 2 | 3 | import com.porterhead.rest.user.VerificationTokenService; 4 | import com.porterhead.rest.user.api.LostPasswordRequest; 5 | import com.porterhead.rest.user.api.PasswordRequest; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.annotation.security.PermitAll; 10 | import javax.ws.rs.*; 11 | import javax.ws.rs.core.MediaType; 12 | import javax.ws.rs.core.Response; 13 | 14 | /** 15 | * 16 | * @version 1.0 17 | * @author: Iain Porter iain.porter@porterhead.com 18 | * @since 28/09/2012 19 | */ 20 | @Path("password") 21 | @Component 22 | @Produces({MediaType.APPLICATION_JSON}) 23 | @Consumes({MediaType.APPLICATION_JSON}) 24 | public class PasswordResource { 25 | 26 | @Autowired 27 | protected VerificationTokenService verificationTokenService; 28 | 29 | @PermitAll 30 | @Path("tokens") 31 | @POST 32 | public Response sendEmailToken(LostPasswordRequest request) { 33 | verificationTokenService.sendLostPasswordToken(request); 34 | return Response.ok().build(); 35 | } 36 | 37 | @PermitAll 38 | @Path("tokens/{token}") 39 | @POST 40 | public Response resetPassword(@PathParam("token") String base64EncodedToken, PasswordRequest request) { 41 | verificationTokenService.resetPassword(base64EncodedToken, request); 42 | return Response.ok().build(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/resource/VerificationResource.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.resource; 2 | 3 | import com.porterhead.rest.user.VerificationTokenService; 4 | import com.porterhead.rest.user.api.EmailVerificationRequest; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | 8 | import javax.annotation.security.PermitAll; 9 | import javax.ws.rs.*; 10 | import javax.ws.rs.core.MediaType; 11 | import javax.ws.rs.core.Response; 12 | 13 | /** 14 | * @version 1.0 15 | * @author: Iain Porter iain.porter@porterhead.com 16 | * @since 14/09/2012 17 | */ 18 | @Path("verify") 19 | @Component 20 | @Produces({MediaType.APPLICATION_JSON}) 21 | @Consumes({MediaType.APPLICATION_JSON}) 22 | public class VerificationResource { 23 | 24 | @Autowired 25 | protected VerificationTokenService verificationTokenService; 26 | 27 | @PermitAll 28 | @Path("tokens/{token}") 29 | @POST 30 | public Response verifyToken(@PathParam("token") String token) { 31 | verificationTokenService.verify(token); 32 | return Response.ok().build(); 33 | } 34 | 35 | @PermitAll 36 | @Path("tokens") 37 | @POST 38 | public Response sendEmailToken(EmailVerificationRequest request) { 39 | verificationTokenService.generateEmailVerificationToken(request.getEmailAddress()); 40 | return Response.ok().build(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/social/JpaUsersConnectionRepository.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.social; 2 | 3 | import com.porterhead.rest.user.domain.Role; 4 | import com.porterhead.rest.user.domain.SocialUser; 5 | import com.porterhead.rest.user.SocialUserRepository; 6 | import com.porterhead.rest.user.UserRepository; 7 | import com.porterhead.rest.user.UserService; 8 | import com.porterhead.rest.user.domain.User; 9 | import org.springframework.security.crypto.encrypt.TextEncryptor; 10 | import org.springframework.social.connect.*; 11 | import org.springframework.util.StringUtils; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Set; 16 | 17 | /** 18 | * User: porter 19 | * Date: 15/05/2012 20 | * Time: 15:01 21 | */ 22 | public class JpaUsersConnectionRepository implements UsersConnectionRepository { 23 | 24 | private SocialUserRepository socialUserRepository; 25 | 26 | private UserService userService; 27 | 28 | private UserRepository userRepository; 29 | 30 | private final ConnectionFactoryLocator connectionFactoryLocator; 31 | 32 | private final TextEncryptor textEncryptor; 33 | 34 | public JpaUsersConnectionRepository(final SocialUserRepository repository, final UserRepository userRepository, 35 | final ConnectionFactoryLocator connectionFactoryLocator, 36 | final TextEncryptor textEncryptor) { 37 | this.socialUserRepository = repository; 38 | this.userRepository = userRepository; 39 | this.connectionFactoryLocator = connectionFactoryLocator; 40 | this.textEncryptor = textEncryptor; 41 | } 42 | 43 | /** 44 | * Find User with the Connection profile (providerId and providerUserId) 45 | * If this is the first connection attempt there will be nor User so create one and 46 | * persist the Connection information 47 | * In reality there will only be one User associated with the Connection 48 | * 49 | * @param connection 50 | * @return List of User Ids (see User.getUuid()) 51 | */ 52 | public List findUserIdsWithConnection(Connection connection) { 53 | List userIds = new ArrayList(); 54 | ConnectionKey key = connection.getKey(); 55 | List users = socialUserRepository.findByProviderIdAndProviderUserId(key.getProviderId(), key.getProviderUserId()); 56 | if (!users.isEmpty()) { 57 | for (SocialUser user : users) { 58 | userIds.add(user.getUser().getUuid().toString()); 59 | } 60 | return userIds; 61 | } 62 | //First time connected so create a User account or find one that is already created with the email address 63 | User user = findUserFromSocialProfile(connection); 64 | String userId; 65 | if(user == null) { 66 | userId = userService.createUser(Role.authenticated).getUserId(); 67 | } else { 68 | userId = user.getUuid().toString(); 69 | } 70 | //persist the Connection 71 | createConnectionRepository(userId).addConnection(connection); 72 | userIds.add(userId); 73 | 74 | return userIds; 75 | } 76 | 77 | public Set findUserIdsConnectedTo(String providerId, Set providerUserIds) { 78 | return socialUserRepository.findByProviderIdAndProviderUserId(providerId, providerUserIds); 79 | } 80 | 81 | public ConnectionRepository createConnectionRepository(String userId) { 82 | if (userId == null) { 83 | throw new IllegalArgumentException("userId cannot be null"); 84 | } 85 | User user = userRepository.findByUuid(userId); 86 | if(user == null) { 87 | throw new IllegalArgumentException("User not Found"); 88 | } 89 | return new JpaConnectionRepository(socialUserRepository, userRepository, user, connectionFactoryLocator, textEncryptor); 90 | } 91 | 92 | private User findUserFromSocialProfile(Connection connection) { 93 | User user = null; 94 | UserProfile profile = connection.fetchUserProfile(); 95 | if(profile != null && StringUtils.hasText(profile.getEmail())) { 96 | user = userRepository.findByEmailAddress(profile.getEmail()); 97 | } 98 | return user; 99 | } 100 | 101 | public UserService getUserService() { 102 | return userService; 103 | } 104 | 105 | public void setUserService(UserService userService) { 106 | this.userService = userService; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/user/social/SocialConfig.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.user.social; 2 | 3 | import com.porterhead.rest.config.ApplicationConfig; 4 | import com.porterhead.rest.user.SocialUserRepository; 5 | import com.porterhead.rest.user.UserRepository; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.security.crypto.encrypt.TextEncryptor; 10 | import org.springframework.social.connect.ConnectionFactoryLocator; 11 | import org.springframework.social.connect.UsersConnectionRepository; 12 | import org.springframework.social.connect.support.ConnectionFactoryRegistry; 13 | import org.springframework.social.facebook.connect.FacebookConnectionFactory; 14 | 15 | /** 16 | * User: porter 17 | * Date: 13/03/2012 18 | * Time: 17:39 19 | */ 20 | @Configuration 21 | public class SocialConfig { 22 | 23 | @Autowired 24 | ApplicationConfig config; 25 | 26 | @Autowired 27 | SocialUserRepository socialUserRepository; 28 | 29 | @Autowired 30 | UserRepository userRepository; 31 | 32 | @Autowired 33 | TextEncryptor textEncryptor; 34 | 35 | @Bean 36 | public ConnectionFactoryLocator connectionFactoryLocator() { 37 | ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry(); 38 | registry.addConnectionFactory(new FacebookConnectionFactory( 39 | config.getFacebookClientId(), 40 | config.getFacebookClientSecret())); 41 | return registry; 42 | } 43 | 44 | @Bean 45 | public UsersConnectionRepository usersConnectionRepository() { 46 | JpaUsersConnectionRepository usersConnectionRepository = new JpaUsersConnectionRepository(socialUserRepository, userRepository, 47 | connectionFactoryLocator(), textEncryptor); 48 | 49 | return usersConnectionRepository; 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/util/DateUtil.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.util; 2 | 3 | import org.joda.time.DateTime; 4 | import org.joda.time.format.DateTimeFormatter; 5 | import org.joda.time.format.ISODateTimeFormat; 6 | 7 | import java.util.Date; 8 | 9 | /** 10 | * @author: Iain Porter 11 | */ 12 | public class DateUtil { 13 | 14 | private static final DateTimeFormatter ISO8061_FORMATTER = ISODateTimeFormat.dateTimeNoMillis(); 15 | 16 | public static Date getDateFromIso8061DateString(String dateString) { 17 | return ISO8061_FORMATTER.parseDateTime(dateString).toDate(); 18 | } 19 | 20 | public static String getCurrentDateAsIso8061String() { 21 | DateTime today = new DateTime(); 22 | return ISO8061_FORMATTER.print(today); 23 | } 24 | 25 | public static String getDateDateAsIso8061String(DateTime date) { 26 | return ISO8061_FORMATTER.print(date); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/util/HashUtil.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.util; 2 | 3 | import org.apache.commons.codec.binary.Base64; 4 | import org.apache.commons.codec.digest.DigestUtils; 5 | 6 | import java.io.IOException; 7 | 8 | 9 | public class HashUtil { 10 | 11 | public static void main(String[] args) { 12 | if (args.length < 1) { 13 | System.out.println("Enter a String to sign"); 14 | System.exit(-1); 15 | } 16 | System.out.println("Signed String: " + signString(args[0])); 17 | } 18 | 19 | /** 20 | * From a base 64 representation, returns the corresponding byte[] 21 | * 22 | * @param data String The base64 representation 23 | * @return byte[] 24 | * @throws java.io.IOException 25 | */ 26 | public static byte[] base64ToByte(String data) throws IOException { 27 | return Base64.decodeBase64(data); 28 | } 29 | 30 | /** 31 | * From a byte[] returns a base 64 representation 32 | * 33 | * @param data byte[] 34 | * @return String 35 | * @throws IOException 36 | */ 37 | public static String byteToBase64(byte[] data) { 38 | return new String(Base64.encodeBase64(data)); 39 | } 40 | 41 | private static String signString(String request) { 42 | byte[] digest = DigestUtils.sha256(request); 43 | return new String(Base64.encodeBase64(digest)); 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/porterhead/rest/util/StringUtil.java: -------------------------------------------------------------------------------- 1 | package com.porterhead.rest.util; 2 | 3 | import org.springframework.util.StringUtils; 4 | 5 | import java.util.regex.Pattern; 6 | 7 | import static org.springframework.util.Assert.hasText; 8 | 9 | /** 10 | * Author: Iain porter 11 | */ 12 | public class StringUtil { 13 | 14 | private static final Pattern UUID_PATTERN = Pattern.compile("^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$"); 15 | 16 | public static void minLength(String str, int len) throws IllegalArgumentException { 17 | hasText(str); 18 | if (str.length() < len) { 19 | throw new IllegalArgumentException(); 20 | } 21 | } 22 | 23 | public static void maxLength(String str, int len) throws IllegalArgumentException { 24 | hasText(str); 25 | if (str.length() > len) { 26 | throw new IllegalArgumentException(); 27 | } 28 | } 29 | 30 | public static void validEmail(String email) throws IllegalArgumentException { 31 | minLength(email, 4); 32 | maxLength(email, 255); 33 | if (!email.contains("@") || StringUtils.containsWhitespace(email)) { 34 | throw new IllegalArgumentException(); 35 | } 36 | } 37 | 38 | public static boolean isValidUuid(String uuid) { 39 | return UUID_PATTERN.matcher(uuid).matches(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | org.hibernate.ejb.HibernatePersistence 7 | com.porterhead.rest.user.domain.User 8 | com.porterhead.rest.user.domain.SocialUser 9 | com.porterhead.rest.user.domain.AuthorizationToken 10 | com.porterhead.rest.user.domain.VerificationToken 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring/component-scan-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring/data-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring/email-services-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring/email-template-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | resource.loader=class 12 | class.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | false 28 | true 29 | true 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring/root-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring/social-configuration-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/velocity/LostPasswordEmail.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sample Rest Application 5 | 6 | 7 |
8 | 9 | 53 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 29 | 49 | 50 |

Reset Password

20 | 21 |
26 | 27 | 28 | 30 | 31 | 32 | 33 |

A request was submitted to reset the password for your account. Click through on the link below to reset your password within the next 24 hours.

34 | 35 |

Password Reset

36 | 37 | 38 | 39 |
40 |

Please don't reply to this automatically generated email.

41 | 42 |

What is this doing in my mailbox?

43 | 44 |

If you did not intend to reset your password, you do not need to click through on the link in this email. If you did not request a link to reset your password, and are concerned that your account is at risk, then please contact us through customer support at http://XXXXXXXXXX.

45 | 46 |

Thanks for using the Sample java REST Application!

47 |
48 |
51 | 52 |
54 |
55 | 56 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/velocity/RegistrationEmail.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sample Web 5 | 6 | 7 | 8 | 47 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 43 | 44 |

Welcome to the java-rest project

21 | 22 | 23 | 24 |

Thanks for coming on board to the sample java-rest project.
25 | Your registration is complete when you click here to validate your email address.

26 |
27 | 28 |
29 |
30 | 31 | 32 | 33 |
34 |

Please don't reply to this automatically generated email.

35 | 36 |

What is this doing in my mailbox?

37 | 38 |

If you do not wish to sign up for the java-rest project, you do not need to click through on the link above, and we will not contact you again unless you request it. As such, there is no need to unsubscribe.

39 | 40 |

Thanks for trying the java-rest project!

41 |
42 |
45 | 46 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/velocity/VerifyEmail.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Verify Your Email 5 | 6 | 7 |
8 | 9 | 59 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 29 | 55 | 56 |

Email Verification

20 | 21 |
26 | 27 | 28 | 30 | 31 | 32 | 33 |

34 | To verify your email address for your XXXXXXXXXXX account click through on the link below within the next 48 hours. 35 |

36 |

37 | Verify my Email Address 38 |

39 |

40 | Thanks for using XXXXXXXXX! 41 |

42 | 43 | 44 | 45 |
46 |

Please don't reply to this automatically generated email.

47 | 48 |

What is this doing in my mailbox?

49 | 50 |

If you do not wish to sign up for XXXXXXXXX, you do not need to click through on the link above, and we will not contact you again unless you request it. As such, there is no need to unsubscribe.

51 | 52 |

Thanks for trying XXXXXXXXX!

53 |
54 |
57 | 58 |
60 |
61 | 62 | 63 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | logbak: %d{HH:mm:ss.SSS} %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/resources/properties/app.properties: -------------------------------------------------------------------------------- 1 | #The Facebook app credentials for supporting Facebook login 2 | facebook.clientId=133718006790561 3 | facebook.clientSecret=2f489f0978614f958101b3d6b1776990 4 | 5 | #encryption keys to use for encrypt/decrypt functions 6 | security.encryptPassword=yoursecurepassword!!! 7 | security.encryptSalt=45ea2b40ef910eef32 8 | 9 | #if true all authorized requests will need to be signed and include the appropriate header fields. 10 | #See com.porterheadead.rest.authorization.impl.RequestSigningAuthorizationService 11 | security.authorization.requireSignedRequests=true 12 | 13 | #How long in seconds before authorizationToken expires 14 | authorization.timeToLive.inSeconds=2592000 15 | 16 | #How long in minutes to allow the request timestamp to differ from the server time 17 | session.date.offset.inMinutes=15 18 | 19 | #How long in minutes that the email registration token should remain active 20 | token.emailRegistration.timeToLive.inMinutes=1440 21 | 22 | #How long in minutes that the email verification token should remain active 23 | token.emailVerification.timeToLive.inMinutes=1440 24 | 25 | #How long in minutes that the lost password token should remain active 26 | token.lostPassword.timeToLive.inMinutes=30 27 | 28 | #The address that mail sent to users will see as the from 29 | email.services.fromAddress=foo@example.com 30 | 31 | #The address that mail sent to users will see as the replyTo 32 | email.services.replyTo=foo@example.com 33 | 34 | #Email subject text for the various emails sent 35 | email.services.emailVerificationSubjectText=[Java-REST sample application] Please verify your email Address 36 | email.services.emailRegistrationSubjectText=Welcome to the Java-REST sample application 37 | email.services.lostPasswordSubjectText=[Java-REST sample application] Reset Password 38 | 39 | application.version=1.0.0 40 | -------------------------------------------------------------------------------- /src/main/resources/properties/dev-app.properties: -------------------------------------------------------------------------------- 1 | hostNameUrl=http://localhost:8080/java-rest 2 | -------------------------------------------------------------------------------- /src/main/resources/properties/production-app.properties: -------------------------------------------------------------------------------- 1 | hostNameUrl=http://localhost:8080/java-rest 2 | -------------------------------------------------------------------------------- /src/main/resources/properties/staging-app.properties: -------------------------------------------------------------------------------- 1 | hostNameUrl=http://localhost:8080/java-rest 2 | -------------------------------------------------------------------------------- /src/main/resources/schema/indexes.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE UNIQUE INDEX user_uuid_IDX ON rest_user ( uuid); 3 | CREATE INDEX user_email_address_IDX on rest_user (email_address); 4 | CREATE UNIQUE INDEX verification_token_uuid_IDX ON rest_verification_token (uuid); 5 | CREATE UNIQUE INDEX verification_token_token_IDX ON rest_verification_token (token); 6 | CREATE UNIQUE INDEX session_token_last_updated_IDX ON rest_session_token (last_updated); 7 | CREATE UNIQUE INDEX session_token_token_IDX ON rest_session_token (token); 8 | -------------------------------------------------------------------------------- /src/main/resources/schema/message_store.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE INT_MESSAGE ( 2 | MESSAGE_ID CHAR(36) NOT NULL PRIMARY KEY, 3 | REGION VARCHAR(100), 4 | CREATED_DATE DATETIME NOT NULL, 5 | MESSAGE_BYTES BLOB 6 | ) ENGINE=InnoDB; 7 | 8 | CREATE INDEX INT_MESSAGE_IX1 ON INT_MESSAGE (CREATED_DATE); 9 | 10 | CREATE TABLE INT_GROUP_TO_MESSAGE ( 11 | GROUP_KEY CHAR(36) NOT NULL, 12 | MESSAGE_ID CHAR(36) NOT NULL, 13 | constraint MESSAGE_GROUP_PK primary key (GROUP_KEY, MESSAGE_ID) 14 | ) ENGINE=InnoDB; 15 | 16 | CREATE TABLE INT_MESSAGE_GROUP ( 17 | GROUP_KEY CHAR(36) NOT NULL PRIMARY KEY, 18 | REGION VARCHAR(100), 19 | MARKED BIGINT, 20 | COMPLETE BIGINT, 21 | LAST_RELEASED_SEQUENCE BIGINT, 22 | CREATED_DATE DATETIME NOT NULL, 23 | UPDATED_DATE DATETIME DEFAULT NULL 24 | ) ENGINE=InnoDB; 25 | 26 | alter table INT_MESSAGE modify MESSAGE_BYTES MEDIUMBLOB; -------------------------------------------------------------------------------- /src/main/resources/schema/truncate_data.sql: -------------------------------------------------------------------------------- 1 | -- truncate all data 2 | TRUNCATE table social_user; 3 | TRUNCATE table rest_session_token; 4 | TRUNCATE table rest_verification_token; 5 | TRUNCATE table rest_user; 6 | -------------------------------------------------------------------------------- /src/main/webapp/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Class-Path: 3 | 4 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/spring/appservlet/servlet-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 5 | Example Java REST Application 6 | 7 | 8 | contextConfigLocation 9 | /WEB-INF/spring/appservlet/servlet-context.xml 10 | 11 | 12 | 13 | spring.profiles.default 14 | dev 15 | 16 | 17 | 18 | org.springframework.web.context.ContextLoaderListener 19 | 20 | 21 | 22 | jersey-servlet 23 | /* 24 | 25 | 26 | 27 | jersey-servlet 28 | com.sun.jersey.spi.spring.container.servlet.SpringServlet 29 | 30 | com.sun.jersey.config.property.packages 31 | com.porterhead.rest.resource, com.porterhead.rest.user.resource 32 | 33 | 34 | 35 | com.sun.jersey.api.json.POJOMappingFeature 36 | true 37 | 38 | 39 | com.sun.jersey.spi.container.ResourceFilters 40 | com.porterhead.rest.filter.ResourceFilterFactory 41 | 42 | 43 | com.sun.jersey.config.property.JSPTemplatesBasePath 44 | /WEB-INF/jsp 45 | 46 | 47 | com.sun.jersey.config.property.WebPageContentRegex 48 | /(.*\.jsp|.*\.css|.*\.png|.*\.js|.*\.gif|.*\.jpg|.*\.pdf|.*\.html|(WEB-INF/jsp)) 49 | 50 | 51 | 52 | 53 | 54 | CharacterEncodingFilter 55 | org.springframework.web.filter.CharacterEncodingFilter 56 | 57 | encoding 58 | UTF-8 59 | 60 | 61 | 62 | 63 | CharacterEncodingFilter 64 | /* 65 | 66 | 67 | 68 | 69 | index.html 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/main/webapp/dashboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Java Rest sample application 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | Welcome to the Java Rest Sample Application 14 |
15 | 16 | 17 |
18 |

19 |
20 |
21 | 22 |
23 | 24 | 25 | 27 | 28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 66 | 67 | -------------------------------------------------------------------------------- /src/main/webapp/forgot_password.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Java REST Sample Application 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | Login 19 | 20 | 21 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 54 | 55 | -------------------------------------------------------------------------------- /src/main/webapp/img/fb_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iainporter/rest-java/30a904561b856584830ac47b2a348d7de933bd1e/src/main/webapp/img/fb_image.png -------------------------------------------------------------------------------- /src/main/webapp/img/fbline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iainporter/rest-java/30a904561b856584830ac47b2a348d7de933bd1e/src/main/webapp/img/fbline.png -------------------------------------------------------------------------------- /src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sample Java Rest Application 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 |
14 | 15 |
Login with Facebook
16 | 17 |
Or
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | Sign Up
32 | 33 | Forgot Password 34 | 35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 111 | 112 | -------------------------------------------------------------------------------- /src/main/webapp/js/cookie.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Holds cookie methods 3 | */ 4 | javaRest.cookie = {} 5 | 6 | /** 7 | * Get the value of a cookie. 8 | * @param {string} 9 | * @return {string} 10 | */ 11 | javaRest.cookie.get = function (name) { 12 | var pairs = document.cookie.split(/\; /g) 13 | var cookie = {} 14 | for (var i in pairs) { 15 | var parts = pairs[i].split(/\=/) 16 | cookie[parts[0]] = unescape(parts[1]) 17 | } 18 | return cookie[name] 19 | } 20 | 21 | /** 22 | * Delete a cookie 23 | * @param {string} 24 | */ 25 | javaRest.cookie.remove = function (name) { 26 | document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;' 27 | } 28 | 29 | /** 30 | * Set a cookie 31 | * @param {string} 32 | * @param {string} 33 | */ 34 | javaRest.cookie.set = function (name, value) { 35 | // document.cookie = "name=value[; expires=UTCString][; domain=domainName][; path=pathName][; secure]"; 36 | document.cookie = name + '=' + value; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/main/webapp/js/enc-base64-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.0.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2012 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function(){var h=CryptoJS,i=h.lib.WordArray;h.enc.Base64={stringify:function(b){var e=b.words,f=b.sigBytes,c=this._map;b.clamp();for(var b=[],a=0;a>>2]>>>24-8*(a%4)&255)<<16|(e[a+1>>>2]>>>24-8*((a+1)%4)&255)<<8|e[a+2>>>2]>>>24-8*((a+2)%4)&255,g=0;4>g&&a+0.75*g>>6*(3-g)&63));if(e=c.charAt(64))for(;b.length%4;)b.push(e);return b.join("")},parse:function(b){var b=b.replace(/\s/g,""),e=b.length,f=this._map,c=f.charAt(64);c&&(c=b.indexOf(c),-1!=c&&(e=c)); 8 | for(var c=[],a=0,d=0;d>>6-2*(d%4);c[a>>>2]|=(g|h)<<24-8*(a%4);a++}return i.create(c,a)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})(); 9 | -------------------------------------------------------------------------------- /src/main/webapp/js/grid.locale-en.js: -------------------------------------------------------------------------------- 1 | ;(function($){ 2 | /** 3 | * jqGrid English Translation 4 | * Tony Tomov tony@trirand.com 5 | * http://trirand.com/blog/ 6 | * Dual licensed under the MIT and GPL licenses: 7 | * http://www.opensource.org/licenses/mit-license.php 8 | * http://www.gnu.org/licenses/gpl.html 9 | **/ 10 | $.jgrid = $.jgrid || {}; 11 | $.extend($.jgrid,{ 12 | defaults : { 13 | recordtext: "View {0} - {1} of {2}", 14 | emptyrecords: "No records to view", 15 | loadtext: "Loading...", 16 | pgtext : "Page {0} of {1}" 17 | }, 18 | search : { 19 | caption: "Search...", 20 | Find: "Find", 21 | Reset: "Reset", 22 | odata : ['equal', 'not equal', 'less', 'less or equal','greater','greater or equal', 'begins with','does not begin with','is in','is not in','ends with','does not end with','contains','does not contain'], 23 | groupOps: [ { op: "AND", text: "all" }, { op: "OR", text: "any" } ], 24 | matchText: " match", 25 | rulesText: " rules" 26 | }, 27 | edit : { 28 | addCaption: "Add Record", 29 | editCaption: "Edit Record", 30 | bSubmit: "Submit", 31 | bCancel: "Cancel", 32 | bClose: "Close", 33 | saveData: "Data has been changed! Save changes?", 34 | bYes : "Yes", 35 | bNo : "No", 36 | bExit : "Cancel", 37 | msg: { 38 | required:"Field is required", 39 | number:"Please, enter valid number", 40 | minValue:"value must be greater than or equal to ", 41 | maxValue:"value must be less than or equal to", 42 | email: "is not a valid e-mail", 43 | integer: "Please, enter valid integer value", 44 | date: "Please, enter valid date value", 45 | url: "is not a valid URL. Prefix required ('http://' or 'https://')", 46 | nodefined : " is not defined!", 47 | novalue : " return value is required!", 48 | customarray : "Custom function should return array!", 49 | customfcheck : "Custom function should be present in case of custom checking!" 50 | 51 | } 52 | }, 53 | view : { 54 | caption: "View Record", 55 | bClose: "Close" 56 | }, 57 | del : { 58 | caption: "Delete", 59 | msg: "Delete selected record(s)?", 60 | bSubmit: "Delete", 61 | bCancel: "Cancel" 62 | }, 63 | nav : { 64 | edittext: "", 65 | edittitle: "Edit selected row", 66 | addtext:"", 67 | addtitle: "Add new row", 68 | deltext: "", 69 | deltitle: "Delete selected row", 70 | searchtext: "", 71 | searchtitle: "Find records", 72 | refreshtext: "", 73 | refreshtitle: "Reload Grid", 74 | alertcap: "Warning", 75 | alerttext: "Please, select row", 76 | viewtext: "", 77 | viewtitle: "View selected row" 78 | }, 79 | col : { 80 | caption: "Select columns", 81 | bSubmit: "Ok", 82 | bCancel: "Cancel" 83 | }, 84 | errors : { 85 | errcap : "Error", 86 | nourl : "No url is set", 87 | norecords: "No records to process", 88 | model : "Length of colNames <> colModel!" 89 | }, 90 | formatter : { 91 | integer : {thousandsSeparator: " ", defaultValue: '0'}, 92 | number : {decimalSeparator:".", thousandsSeparator: " ", decimalPlaces: 2, defaultValue: '0.00'}, 93 | currency : {decimalSeparator:".", thousandsSeparator: " ", decimalPlaces: 2, prefix: "", suffix:"", defaultValue: '0.00'}, 94 | date : { 95 | dayNames: [ 96 | "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat", 97 | "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" 98 | ], 99 | monthNames: [ 100 | "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 101 | "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" 102 | ], 103 | AmPm : ["am","pm","AM","PM"], 104 | S: function (j) {return j < 11 || j > 13 ? ['st', 'nd', 'rd', 'th'][Math.min((j - 1) % 10, 3)] : 'th'}, 105 | srcformat: 'Y-m-d', 106 | newformat: 'd/m/Y', 107 | masks : { 108 | ISO8601Long:"Y-m-d H:i:s", 109 | ISO8601Short:"Y-m-d", 110 | ShortDate: "n/j/Y", 111 | LongDate: "l, F d, Y", 112 | FullDateTime: "l, F d, Y g:i:s A", 113 | MonthDay: "F d", 114 | ShortTime: "g:i A", 115 | LongTime: "g:i:s A", 116 | SortableDateTime: "Y-m-d\\TH:i:s", 117 | UniversalSortableDateTime: "Y-m-d H:i:sO", 118 | YearMonth: "F, Y" 119 | }, 120 | reformatAfterEdit : false 121 | }, 122 | baseLinkUrl: '', 123 | showAction: '', 124 | target: '', 125 | checkbox : {disabled:true}, 126 | idName : 'id' 127 | } 128 | }); 129 | })(jQuery); 130 | -------------------------------------------------------------------------------- /src/main/webapp/js/javarest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Singleton used for Namespace 3 | */ 4 | function javaRest() { 5 | 6 | } 7 | 8 | /** 9 | * Wrap the API so we can proxy calls while testing. 10 | */ 11 | javaRest.get = function (url, data, success, error) { 12 | 13 | var time = javaRest.get_iso_date() 14 | var nonce = makeRandomString() 15 | var string_to_hash = javaRest.cookie.get('token') + ':' + url + ',GET,' + time + "," + nonce 16 | var authorization = javaRest.cookie.get('userId') + ':' + javaRest.hash(string_to_hash) 17 | 18 | var request = $.ajax({ 19 | url: url, 20 | type: "GET", 21 | data: data, 22 | headers: { 23 | 'Authorization' : authorization, 24 | 'x-java-rest-date' : time, 25 | 'nonce' : nonce 26 | }, 27 | dataType: "json" 28 | }) 29 | 30 | request.done(success) 31 | 32 | request.fail(error) 33 | 34 | } 35 | 36 | function makeRandomString() { 37 | return Math.random().toString(36).substring(2, 15) + 38 | Math.random().toString(36).substring(2, 15); 39 | } 40 | 41 | /** 42 | * Return the current time as an ISO 8061 Date 43 | * @return {string} 2012-06-30T12:00:00+01:00 44 | */ 45 | javaRest.get_iso_date = function () { 46 | var d = new Date() 47 | function pad(n) {return n<10 ? '0'+n : n} 48 | return d.getUTCFullYear()+'-' 49 | + pad(d.getUTCMonth()+1)+'-' 50 | + pad(d.getUTCDate())+'T' 51 | + pad(d.getUTCHours())+':' 52 | + pad(d.getUTCMinutes())+':' 53 | + pad(d.getUTCSeconds())+'Z' 54 | } 55 | 56 | /** 57 | * Get a query string var 58 | * @param {string} 59 | * @return {string} 60 | */ 61 | javaRest.get_query = function (name) { 62 | var query = window.location.search.substring(1) 63 | var vars = query.split('&') 64 | for (var i = 0; i < vars.length; i++) { 65 | var pair = vars[i].split('=') 66 | if (decodeURIComponent(pair[0]) == name) { 67 | return decodeURIComponent(pair[1]) 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * SHA256, then base64 encode a string 74 | * @param {string} 75 | * @return {string} 76 | */ 77 | javaRest.hash = function (string) { 78 | var hash = CryptoJS.SHA256(string) 79 | return hash.toString(CryptoJS.enc.Base64) 80 | } 81 | 82 | /** 83 | * Is the visitor on iPhone or Ipad? 84 | * @return {bool} 85 | */ 86 | javaRest.isIos = function () { 87 | return (navigator.userAgent.match(/iPad|iPhone|iPod/i) != null) 88 | } 89 | 90 | /** 91 | * Wrap the API so we can proxy calls while testing. 92 | */ 93 | javaRest.post = function (url, data, success, error) { 94 | 95 | $.ajax({ 96 | url: url, 97 | type: "POST", 98 | contentType: "application/json", // send as JSON 99 | data: JSON.stringify(data), 100 | dataType: "json", 101 | success : success, 102 | error : error 103 | }) 104 | 105 | 106 | } 107 | 108 | /** 109 | * Post with authentication 110 | */ 111 | javaRest.postAuth = function (url, data, success, error) { 112 | 113 | var time = javaRest.get_iso_date() 114 | var nonce = makeRandomString() 115 | var string_to_hash = javaRest.cookie.get('token') + ':' + url + ',POST,' + time + "," + nonce 116 | var authorization = javaRest.cookie.get('userId') + ':' + javaRest.hash(string_to_hash) 117 | 118 | $.ajax({ 119 | url: url, 120 | type: "POST", 121 | contentType: "application/json", // send as JSON 122 | data: JSON.stringify(data), 123 | headers: { 124 | 'Authorization' : authorization, 125 | 'x-java-rest-date' : time , 126 | 'nonce' : nonce 127 | }, 128 | dataType: "json", 129 | success : success, 130 | error : error 131 | }) 132 | 133 | 134 | } 135 | 136 | /** 137 | * Wrap the API so we can proxy calls while testing. 138 | */ 139 | javaRest.put = function (url, data, success, error) { 140 | 141 | var time = javaRest.get_iso_date() 142 | var nonce = makeRandomString() 143 | var string_to_hash = javaRest.cookie.get('token') + ':' + url + ',PUT,' + time + "," + nonce 144 | var authorization = javaRest.cookie.get('userId') + ':' + javaRest.hash(string_to_hash) 145 | 146 | $.ajax({ 147 | url: url, 148 | type: "PUT", 149 | contentType: "application/json", // send as JSON 150 | data: JSON.stringify(data), 151 | headers: { 152 | 'Authorization' : authorization, 153 | 'x-java-rest-date' : time, 154 | 'nonce' : nonce 155 | }, 156 | dataType: "json", 157 | success : success, 158 | error : error 159 | }) 160 | 161 | 162 | } 163 | 164 | 165 | -------------------------------------------------------------------------------- /src/main/webapp/js/jquery-full-house.js: -------------------------------------------------------------------------------- 1 | /* 2 | Based on this script by Marcus Ekwall 3 | http://jsfiddle.net/mekwall/fNyHs/ 4 | 5 | Examples, support and the newest version of this script is here: 6 | https://github.com/kuchumovn/jquery-full-house 7 | 8 | Author: Nikolay Kuchumov 9 | github: kuchumovn 10 | email: kuchumovn@gmail.com 11 | */ 12 | 13 | (function($) 14 | { 15 | var Algorythm = 16 | { 17 | // you can write your own algorythm 18 | Interface: function(options) 19 | { 20 | // called if the 'x' font size is too big, and the text with this font size doesn't fit the container 21 | this.too_big = function(x) {} 22 | 23 | // called if the text with font size 'x' fits the container (e.g. font_size=0 fits any container) 24 | this.fits = function(x) {} 25 | 26 | // this.retry(x) function will be set automatically 27 | }, 28 | 29 | // just for reference 30 | Linear: function(options) 31 | { 32 | var largest_fit = 0 33 | 34 | this.too_big = function(x) 35 | { 36 | if (x - 1 === largest_fit) 37 | return largest_fit 38 | 39 | return this.retry(x - 1) 40 | } 41 | 42 | this.fits = function(x) 43 | { 44 | largest_fit = x 45 | return this.retry(x + 1) 46 | } 47 | }, 48 | 49 | // the faster algorythm 50 | Binary: function(options) 51 | { 52 | var largest_fit 53 | var minimum_too_big 54 | 55 | var step = options.Font_size_increment_step || 10 56 | 57 | this.too_big = function(x) 58 | { 59 | minimum_too_big = x 60 | 61 | if (largest_fit) 62 | { 63 | if (largest_fit === x - 1) 64 | return largest_fit 65 | 66 | return this.retry(largest_fit + (x - largest_fit) / 2) 67 | } 68 | else 69 | { 70 | if (x === 1) 71 | return 1 72 | 73 | return this.retry(x - step) 74 | } 75 | } 76 | 77 | this.fits = function(x) 78 | { 79 | largest_fit = x 80 | 81 | if (minimum_too_big) 82 | { 83 | if (minimum_too_big === x + 1) 84 | return x 85 | 86 | return this.retry(x + (minimum_too_big - x) / 2) 87 | } 88 | else 89 | { 90 | return this.retry(x + step) 91 | } 92 | } 93 | } 94 | } 95 | 96 | function get_initial_font_size(container) 97 | { 98 | if (container.css('fontSize')) 99 | { 100 | var check = container.css('fontSize').match(/[\d]+px/) 101 | if (check.length) 102 | return parseInt(check[0]) 103 | } 104 | 105 | return 1 106 | } 107 | 108 | function find_max_font_size(container, options) 109 | { 110 | var initial_font_size = get_initial_font_size(container) 111 | container.css('fontSize', 0) 112 | 113 | var html = container.html() 114 | 115 | container.empty() 116 | 117 | var overflow = container.css('overflow') 118 | container.css('overflow', 'hidden') 119 | 120 | var sandbox = $('').html(html).appendTo(container) 121 | 122 | var available_height = container[0].scrollHeight 123 | var available_width = container[0].scrollWidth 124 | 125 | function try_font_size(font_size) 126 | { 127 | container.css({ fontSize: font_size + 'px' }) 128 | } 129 | 130 | function recursive_search(algorythm, start_with) 131 | { 132 | var find_max_font_size_starting_with = function(font_size) 133 | { 134 | font_size = Math.ceil(font_size) 135 | if (font_size < 1) 136 | font_size = 1 137 | 138 | try_font_size(font_size) 139 | 140 | var current_height = container[0].scrollHeight 141 | var current_width = container[0].scrollWidth 142 | 143 | var height_proportion = current_height / available_height 144 | var width_proportion = current_width / available_width 145 | 146 | if (height_proportion > 1 || width_proportion > 1) 147 | return algorythm.too_big(font_size) 148 | else 149 | return algorythm.fits(font_size) 150 | } 151 | 152 | algorythm.retry = find_max_font_size_starting_with 153 | return find_max_font_size_starting_with(start_with) 154 | } 155 | 156 | options.algorythm = options.algorythm || 'Binary' 157 | var algorythm = new Algorythm[options.algorythm](options) 158 | 159 | var font_size = recursive_search(algorythm, initial_font_size) 160 | 161 | container.css('overflow', overflow) 162 | container.empty().html(html) 163 | 164 | return font_size 165 | } 166 | 167 | $.fn.fill_with_text = function(options) 168 | { 169 | options = options || {} 170 | 171 | return $(this).each(function() 172 | { 173 | var container = $(this) 174 | container.css({ fontSize: find_max_font_size(container, options) + 'px' }) 175 | if (options.collapse) 176 | container.css('height', 'auto') 177 | }) 178 | } 179 | })(jQuery) -------------------------------------------------------------------------------- /src/main/webapp/js/sha256.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.0.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2012 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | var CryptoJS=CryptoJS||function(i,p){var f={},q=f.lib={},j=q.Base=function(){function a(){}return{extend:function(h){a.prototype=this;var d=new a;h&&d.mixIn(h);d.$super=this;return d},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var d in a)a.hasOwnProperty(d)&&(this[d]=a[d]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.$super.extend(this)}}}(),k=q.WordArray=j.extend({init:function(a,h){a= 8 | this.words=a||[];this.sigBytes=h!=p?h:4*a.length},toString:function(a){return(a||m).stringify(this)},concat:function(a){var h=this.words,d=a.words,c=this.sigBytes,a=a.sigBytes;this.clamp();if(c%4)for(var b=0;b>>2]|=(d[b>>>2]>>>24-8*(b%4)&255)<<24-8*((c+b)%4);else if(65535>>2]=d[b>>>2];else h.push.apply(h,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,b=this.sigBytes;a[b>>>2]&=4294967295<<32-8*(b%4);a.length=i.ceil(b/4)},clone:function(){var a= 9 | j.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var b=[],d=0;d>>2]>>>24-8*(c%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c>>3]|=parseInt(a.substr(c,2),16)<<24-4*(c%8);return k.create(d,b/2)}},s=r.Latin1={stringify:function(a){for(var b= 10 | a.words,a=a.sigBytes,d=[],c=0;c>>2]>>>24-8*(c%4)&255));return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c>>2]|=(a.charCodeAt(c)&255)<<24-8*(c%4);return k.create(d,b)}},g=r.Utf8={stringify:function(a){try{return decodeURIComponent(escape(s.stringify(a)))}catch(b){throw Error("Malformed UTF-8 data");}},parse:function(a){return s.parse(unescape(encodeURIComponent(a)))}},b=q.BufferedBlockAlgorithm=j.extend({reset:function(){this._data=k.create(); 11 | this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=g.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var b=this._data,d=b.words,c=b.sigBytes,e=this.blockSize,f=c/(4*e),f=a?i.ceil(f):i.max((f|0)-this._minBufferSize,0),a=f*e,c=i.min(4*a,c);if(a){for(var g=0;ge;)f(b)&&(8>e&&(k[e]=g(i.pow(b,0.5))),r[e]=g(i.pow(b,1/3)),e++),b++})();var m=[],j=j.SHA256=f.extend({_doReset:function(){this._hash=q.create(k.slice(0))},_doProcessBlock:function(f,g){for(var b=this._hash.words,e=b[0],a=b[1],h=b[2],d=b[3],c=b[4],i=b[5],j=b[6],k=b[7],l=0;64> 14 | l;l++){if(16>l)m[l]=f[g+l]|0;else{var n=m[l-15],o=m[l-2];m[l]=((n<<25|n>>>7)^(n<<14|n>>>18)^n>>>3)+m[l-7]+((o<<15|o>>>17)^(o<<13|o>>>19)^o>>>10)+m[l-16]}n=k+((c<<26|c>>>6)^(c<<21|c>>>11)^(c<<7|c>>>25))+(c&i^~c&j)+r[l]+m[l];o=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&a^e&h^a&h);k=j;j=i;i=c;c=d+n|0;d=h;h=a;a=e;e=n+o|0}b[0]=b[0]+e|0;b[1]=b[1]+a|0;b[2]=b[2]+h|0;b[3]=b[3]+d|0;b[4]=b[4]+c|0;b[5]=b[5]+i|0;b[6]=b[6]+j|0;b[7]=b[7]+k|0},_doFinalize:function(){var f=this._data,g=f.words,b=8*this._nDataBytes, 15 | e=8*f.sigBytes;g[e>>>5]|=128<<24-e%32;g[(e+64>>>9<<4)+15]=b;f.sigBytes=4*g.length;this._process()}});p.SHA256=f._createHelper(j);p.HmacSHA256=f._createHmacHelper(j)})(Math); -------------------------------------------------------------------------------- /src/main/webapp/js/store.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010-2012 Marcus Westin */ 2 | (function(){function h(){try{return d in b&&b[d]}catch(a){return!1}}function i(){try{return e in b&&b[e]&&b[e][b.location.hostname]}catch(a){return!1}}var a={},b=window,c=b.document,d="localStorage",e="globalStorage",f="__storejs__",g;a.disabled=!1,a.set=function(a,b){},a.get=function(a){},a.remove=function(a){},a.clear=function(){},a.transact=function(b,c,d){var e=a.get(b);d==null&&(d=c,c=null),typeof e=="undefined"&&(e=c||{}),d(e),a.set(b,e)},a.getAll=function(){},a.serialize=function(a){return JSON.stringify(a)},a.deserialize=function(a){if(typeof a!="string")return undefined;try{return JSON.parse(a)}catch(b){return a||undefined}};if(h())g=b[d],a.set=function(b,c){return c===undefined?a.remove(b):(g.setItem(b,a.serialize(c)),c)},a.get=function(b){return a.deserialize(g.getItem(b))},a.remove=function(a){g.removeItem(a)},a.clear=function(){g.clear()},a.getAll=function(){var b={};for(var c=0;cdocument.w=window