diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ab21821 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +# https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties + +# top-most EditorConfig file +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{js,java}] +charset = utf-8 +indent_style = space +curly_bracket_next_line = false +indent_brace_style = K&R + +[*.java] +indent_size = 4 +continuation_indent_size = 8 + +[*.js] +indent_size = 2 +continuation_indent_size = 2 diff --git a/app/config/AuthorizationServerAuthAction.java b/app/config/AuthorizationServerAuthAction.java index 3d0abab..e0cec29 100644 --- a/app/config/AuthorizationServerAuthAction.java +++ b/app/config/AuthorizationServerAuthAction.java @@ -26,16 +26,16 @@ public AuthorizationServerAuthAction(JwtUtils jwtUtils) { public CompletionStage call(Http.Context ctx) { Http.Cookie ltat = ctx.request().cookie("ltat"); boolean valid = configuration.optional(); - if(ltat != null) { + if (ltat != null) { User user = jwtUtils.validateCookie(ltat.value()); - if(user != null) { + if (user != null) { if (user.isDisabled()) { flash("error", "Your account was disabled."); Http.Cookie ltatRemove = Http.Cookie.builder("ltat", "") .withPath("/").withHttpOnly(true).withMaxAge(Duration.ZERO).build(); return CompletableFuture.completedFuture(redirect(routes.LoginController.get(null)).withCookies(ltatRemove)); } - if(!configuration.requireAdmin() || user.isAdmin()){ + if (!configuration.requireAdmin() || user.isAdmin()) { ctx = ctx.withRequest(ctx.request().addAttr(AuthorizationServerSecure.USER, user)); valid = true; } else { @@ -45,7 +45,7 @@ public CompletionStage call(Http.Context ctx) { } } } - if(!valid) { + if (!valid) { return CompletableFuture.completedFuture(redirect(routes.LoginController.get(ctx.request().uri()))); } else { // pass on return delegate.call(ctx); diff --git a/app/config/AuthorizationServerManager.java b/app/config/AuthorizationServerManager.java index 664e29a..6a1f3aa 100644 --- a/app/config/AuthorizationServerManager.java +++ b/app/config/AuthorizationServerManager.java @@ -2,7 +2,10 @@ import com.fasterxml.jackson.databind.JsonNode; import com.typesafe.config.Config; -import dtos.*; +import dtos.MeDto; +import dtos.OAuthContext; +import dtos.OAuthProvider; +import dtos.Token; import play.Logger; import scala.Tuple2; @@ -22,7 +25,7 @@ public class AuthorizationServerManager { private List> providerList; @Inject - public AuthorizationServerManager(Config config){ + public AuthorizationServerManager(Config config) { this.providerMap = new LinkedHashMap<>(); try { List providers = config.getConfigList("oauth.providers"); @@ -77,7 +80,7 @@ public OAuthProvider getProvider(String provider) { return providerMap.get(provider); } - public List> getProviders(){ + public List> getProviders() { return providerList; } } diff --git a/app/config/AuthorizationServerSecure.java b/app/config/AuthorizationServerSecure.java index eb23908..40bb453 100644 --- a/app/config/AuthorizationServerSecure.java +++ b/app/config/AuthorizationServerSecure.java @@ -14,6 +14,8 @@ @Retention(RetentionPolicy.RUNTIME) public @interface AuthorizationServerSecure { TypedKey USER = TypedKey.create("user_a"); + boolean requireAdmin() default false; + boolean optional() default false; } diff --git a/app/config/CurrentUserIdentifierOverUserInfoUrl.java b/app/config/CurrentUserIdentifierOverUserInfoUrl.java index 2b8625f..1e23463 100644 --- a/app/config/CurrentUserIdentifierOverUserInfoUrl.java +++ b/app/config/CurrentUserIdentifierOverUserInfoUrl.java @@ -19,7 +19,7 @@ public CurrentUserIdentifierOverUserInfoUrl(Function response public CompletionStage apply(OAuthContext context) { return context.getWs() .url(context.getProvider().getUserInfoUrl()) - .addHeader("Authorization", "Bearer "+ context.getToken().getAccessToken()) + .addHeader("Authorization", "Bearer " + context.getToken().getAccessToken()) .get() .thenApplyAsync(responseToUserFunction); } diff --git a/app/config/FacebookTokenRetriever.java b/app/config/FacebookTokenRetriever.java index 6dd0656..5e60929 100644 --- a/app/config/FacebookTokenRetriever.java +++ b/app/config/FacebookTokenRetriever.java @@ -19,10 +19,10 @@ public CompletionStage apply(OAuthContext context) { .addQueryParameter("grant_type", "authorization_code") .get() .handleAsync((res, e) -> { - if(e != null) { + if (e != null) { play.Logger.error("retrieveToken: exception", e); throw new CompletionException(e); - } else if(res.getStatus() != 200) { + } else if (res.getStatus() != 200) { String message = String.format("retrieveToken: status=%s, body=%s", res.getStatus(), res.getBody()); play.Logger.error(message); throw new CompletionException(new IllegalStateException(message)); diff --git a/app/config/GoogleTokenRetriever.java b/app/config/GoogleTokenRetriever.java index ef3da9e..b072473 100644 --- a/app/config/GoogleTokenRetriever.java +++ b/app/config/GoogleTokenRetriever.java @@ -22,10 +22,10 @@ public CompletionStage apply(OAuthContext context) { URLEncoder.encode(context.getProvider().getClientId(), "utf-8"), URLEncoder.encode(context.getProvider().getClientSecret(), "utf-8"))) .handleAsync((res, e) -> { - if(e != null) { + if (e != null) { play.Logger.error("retrieveToken: exception", e); throw new CompletionException(e); - } else if(res.getStatus() != 200) { + } else if (res.getStatus() != 200) { String message = String.format("retrieveToken: status=%s, body=%s", res.getStatus(), res.getBody()); play.Logger.error(message); throw new CompletionException(new IllegalStateException(message)); diff --git a/app/config/JwtUtils.java b/app/config/JwtUtils.java index 3b595c9..f7158fb 100644 --- a/app/config/JwtUtils.java +++ b/app/config/JwtUtils.java @@ -11,21 +11,23 @@ import models.Client; import models.Grant; import models.User; +import play.Logger; import play.mvc.Http; import play.mvc.Result; import repositories.ClientRepository; import repositories.GrantRepository; import repositories.UserRepository; +import scala.Tuple2; import javax.inject.Inject; import javax.inject.Singleton; import java.security.InvalidKeyException; import java.security.SignatureException; import java.time.Duration; -import java.util.*; - -import play.Logger; -import scala.Tuple2; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; import static play.mvc.Results.redirect; @@ -72,31 +74,31 @@ public String prepareResetCode(User user) { return signer.sign(claims); } - public User validateResetCode(String reset_code){ - if(reset_code == null) + public User validateResetCode(String reset_code) { + if (reset_code == null) return null; try { final JWTVerifier verifier = new JWTVerifier(jwtSecret); - final Map claims = verifier.verify(reset_code); + final Map claims = verifier.verify(reset_code); // first check version int type = (int) claims.get("vt"); - if(type != 5){ + if (type != 5) { return null; } // check expiry date int exp = (int) claims.get("exp"); - if(exp < (System.currentTimeMillis() / 1000L)) + if (exp < (System.currentTimeMillis() / 1000L)) return null; // check user String user_id = (String) claims.get("user"); User user = userRepository.findById(user_id); - if(user == null){ + if (user == null) { return null; } // check hash value int receivedHash = (int) claims.get("hash"); int correctHash = Objects.hash(user.getUsername(), user.getEmail(), user.getPassword(), user.getLastUpdateTime()); - if(receivedHash != correctHash) { + if (receivedHash != correctHash) { return null; } return user; @@ -118,34 +120,34 @@ public String prepareEmailConfirmationCode(User user, String linkId) { claims.put("exp", exp); claims.put("email", user.getEmail()); claims.put("username", user.getUsername()); - if(user.getPassword() != null) + if (user.getPassword() != null) claims.put("password", user.getPassword()); - if(linkId != null) + if (linkId != null) claims.put("linkId", linkId); return signer.sign(claims); } - public Tuple2 validateEmailConfirmationCode(String code){ - if(code == null) + public Tuple2 validateEmailConfirmationCode(String code) { + if (code == null) return null; try { final JWTVerifier verifier = new JWTVerifier(jwtSecret); - final Map claims = verifier.verify(code); + final Map claims = verifier.verify(code); // first check version int type = (int) claims.get("vt"); - if(type != 6){ + if (type != 6) { return null; } // check expiry date int exp = (int) claims.get("exp"); - if(exp < (System.currentTimeMillis() / 1000L)) + if (exp < (System.currentTimeMillis() / 1000L)) return null; // check user User user = new User(); user.setEmail((String) claims.get("email")); user.setUsername((String) claims.get("username")); - if(claims.containsKey("password")) + if (claims.containsKey("password")) user.setPassword((String) claims.get("password")); String linkId = claims.containsKey("linkId") ? (String) claims.get("linkId") : null; return Tuple2.apply(user, linkId); @@ -173,27 +175,27 @@ public String prepareEmailChangeConfirmationCode(User user, String newEmail) { } - public Tuple2 validateEmailChangeConfirmationCode(String code){ - if(code == null) + public Tuple2 validateEmailChangeConfirmationCode(String code) { + if (code == null) return null; try { final JWTVerifier verifier = new JWTVerifier(jwtSecret); - final Map claims = verifier.verify(code); + final Map claims = verifier.verify(code); // first check version int type = (int) claims.get("vt"); - if(type != 7){ + if (type != 7) { return null; } // check expiry date int exp = (int) claims.get("exp"); - if(exp < (System.currentTimeMillis() / 1000L)) + if (exp < (System.currentTimeMillis() / 1000L)) return null; // check user User user = userRepository.findById((String) claims.get("user")); - if(user == null) return null; + if (user == null) return null; // check hash int hash = Objects.hash(user.getUsername(), user.getEmail(), user.getPassword()); - if((Integer) claims.get("hash") != hash) return null; + if ((Integer) claims.get("hash") != hash) return null; String newEmail = (String) claims.get("new_email"); return Tuple2.apply(user, newEmail); } catch (JWTVerifyException | SignatureException | InvalidKeyException | NullPointerException e) { @@ -204,15 +206,15 @@ public Tuple2 validateEmailChangeConfirmationCode(String code){ } } - public Result prepareCookieThenRedirect(User user, String next){ + public Result prepareCookieThenRedirect(User user, String next) { Http.Cookie ltat = prepareCookie(user); - if(next != null && next.matches("^/.*$")) + if (next != null && next.matches("^/.*$")) return redirect(next).withCookies(ltat); else return redirect(routes.ProfileController.get()).withCookies(ltat); } - private Http.Cookie prepareCookie(User user){ + private Http.Cookie prepareCookie(User user) { return Http.Cookie.builder("ltat", prepareCookieValue(user)).withPath("/").withHttpOnly(true).withMaxAge(expireCookie).withSecure(useSecureSessionCookie).build(); } @@ -231,31 +233,31 @@ private String prepareCookieValue(User user) { return signer.sign(claims); } - public User validateCookie(String cookie_value){ - if(cookie_value == null) + public User validateCookie(String cookie_value) { + if (cookie_value == null) return null; try { final JWTVerifier verifier = new JWTVerifier(jwtSecret); - final Map claims = verifier.verify(cookie_value); + final Map claims = verifier.verify(cookie_value); // first check version int type = (int) claims.get("vt"); - if(type != 4){ + if (type != 4) { return null; } // check expiry date int exp = (int) claims.get("exp"); - if(exp < (System.currentTimeMillis() / 1000L)) + if (exp < (System.currentTimeMillis() / 1000L)) return null; // check user String user_id = (String) claims.get("user"); User user = userRepository.findById(user_id); - if(user == null){ + if (user == null) { return null; } // check hash value int receivedHash = (int) claims.get("hash"); int correctHash = Objects.hash(user.getUsername(), user.getEmail(), user.getPassword()); - if(receivedHash != correctHash) { + if (receivedHash != correctHash) { return null; } return user; @@ -283,39 +285,39 @@ public String prepareAuthorizationCode(String client_id, String client_secret, S return signer.sign(claims); } - public Grant validateAuthorizationCode(String code, String redirect_uri){ - if(code == null || redirect_uri == null) + public Grant validateAuthorizationCode(String code, String redirect_uri) { + if (code == null || redirect_uri == null) return null; try { final JWTVerifier verifier = new JWTVerifier(jwtSecret); - final Map claims = verifier.verify(code); + final Map claims = verifier.verify(code); // first check version int type = (int) claims.get("vt"); - if(type != 3){ + if (type != 3) { return null; } // check expiry date int exp = (int) claims.get("exp"); - if(exp < (System.currentTimeMillis() / 1000L)) + if (exp < (System.currentTimeMillis() / 1000L)) return null; // check grant String grant_id = (String) claims.get("g"); Grant grant = grantRepository.findById(grant_id); - if(grant == null){ + if (grant == null) { return null; } // check hash value int receivedHash = (int) claims.get("h"); Client client = clientRepository.findById(grant.getClientId()); - if(client == null){ + if (client == null) { return null; } int correctHash = Objects.hash(client.getId(), client.getSecret()); - if(receivedHash != correctHash) { + if (receivedHash != correctHash) { return null; } // check redirect_uri - if(!Objects.equals(claims.get("r"), redirect_uri)){ + if (!Objects.equals(claims.get("r"), redirect_uri)) { return null; } return grant; @@ -370,41 +372,42 @@ public Token prepareToken(String client_id, String client_secret, String grant_i /** * Validate given token and return its type + * * @see TokenStatus */ - public Tuple2 getTokenStatus(String access_token){ - if(access_token == null) + public Tuple2 getTokenStatus(String access_token) { + if (access_token == null) return new Tuple2<>(null, TokenStatus.INVALID); try { final JWTVerifier verifier = new JWTVerifier(jwtSecret); - final Map claims = verifier.verify(access_token); + final Map claims = verifier.verify(access_token); // first check version int type = (int) claims.get("vt"); - if(type != 1 && type != 2){ + if (type != 1 && type != 2) { return new Tuple2<>(null, TokenStatus.INVALID); } // check expiry date int exp = (int) claims.get("exp"); - if(exp < (System.currentTimeMillis() / 1000L)) + if (exp < (System.currentTimeMillis() / 1000L)) return new Tuple2<>(null, TokenStatus.INVALID); // check grant String grant_id = (String) claims.get("grant"); Grant grant = grantRepository.findById(grant_id); - if(grant == null){ + if (grant == null) { return new Tuple2<>(null, TokenStatus.INVALID); } Client client = clientRepository.findById(grant.getClientId()); - if(client == null){ + if (client == null) { return new Tuple2<>(null, TokenStatus.INVALID); // how can this be? } // check hash value int receivedHash = (int) claims.get("h"); int correctHash = Objects.hash(client.getId(), client.getSecret()); - if(receivedHash != correctHash) { + if (receivedHash != correctHash) { return new Tuple2<>(null, TokenStatus.INVALID); } // check token type & version - if(type == 1){ + if (type == 1) { return new Tuple2<>(grant, TokenStatus.VALID_ACCESS); } else { return new Tuple2<>(grant, TokenStatus.VALID_REFRESH); diff --git a/app/config/LoggingFilter.java b/app/config/LoggingFilter.java index 033fe24..4121ba7 100644 --- a/app/config/LoggingFilter.java +++ b/app/config/LoggingFilter.java @@ -1,12 +1,16 @@ package config; -import java.util.concurrent.CompletionStage; -import java.util.function.Function; -import javax.inject.Inject; import akka.stream.Materializer; import play.Logger; import play.libs.typedmap.TypedKey; -import play.mvc.*; +import play.mvc.Filter; +import play.mvc.Http; +import play.mvc.Result; + +import javax.inject.Inject; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + public class LoggingFilter extends Filter { public static final TypedKey KEY = TypedKey.create(); @@ -21,13 +25,13 @@ public CompletionStage apply( Function> nextFilter, Http.RequestHeader requestHeader) { long startTime = System.currentTimeMillis(); - Http.RequestHeader requestHeader1 = requestHeader.addAttr(KEY, requestHeader.path()+"|"+requestHeader.uri()); + Http.RequestHeader requestHeader1 = requestHeader.addAttr(KEY, requestHeader.path() + "|" + requestHeader.uri()); return nextFilter.apply(requestHeader1).thenApply(result -> { long endTime = System.currentTimeMillis(); long requestTime = endTime - startTime; Logger.info("{} {} took {}ms and returned {} (host:{}, uri:{})", - requestHeader.method(), requestHeader.uri(), requestTime, result.status(), requestHeader.uri(), requestHeader.host()); + requestHeader.method(), requestHeader.uri(), requestTime, result.status(), requestHeader.uri(), requestHeader.host()); return result.withHeader("Request-Time", "" + requestTime); }); diff --git a/app/config/MailgunService.java b/app/config/MailgunService.java index 4d37889..aac6b2e 100644 --- a/app/config/MailgunService.java +++ b/app/config/MailgunService.java @@ -13,9 +13,8 @@ /** * Created by Selim Eren Bekçe on 6.09.2017. - * + *

* TODO Use circuit breaker or akka for retries https://github.com/jhalterman/failsafe#circuit-breakers - * */ @Singleton public class MailgunService implements MailService { @@ -45,9 +44,9 @@ public CompletableFuture sendEmail(String to, String subject, String con new DataPart("subject", subject), new DataPart("html", content)))) .handleAsync((response, throwable) -> { - if(throwable != null) { + if (throwable != null) { log.error("sendEmail: Exception", throwable); - } else if(response.getStatus() != 200) { + } else if (response.getStatus() != 200) { log.error("sendEmail: Non-200 response, status={}, body={}", response.getStatus(), response.getBody()); } else { log.error("sendEmail: OK, status={}, body={}", response.getStatus(), response.getBody()); diff --git a/app/config/RecaptchaAction.java b/app/config/RecaptchaAction.java index 69d2f18..e1a0f89 100644 --- a/app/config/RecaptchaAction.java +++ b/app/config/RecaptchaAction.java @@ -28,16 +28,18 @@ public CompletionStage call(Http.Context ctx) { recaptchaResponse = ctx.request().body().asFormUrlEncoded().get("g-recaptcha-response")[0]; Logger.debug("Found g-recaptcha-response by FormUrlEncoded"); ctx.request().body().asFormUrlEncoded().remove("g-recaptcha-response"); - } catch (Exception ignored){} - if(recaptchaResponse == null) { + } catch (Exception ignored) { + } + if (recaptchaResponse == null) { try { recaptchaResponse = ctx.request().body().asMultipartFormData().asFormUrlEncoded().get("g-recaptcha-response")[0]; Logger.debug("Found g-recaptcha-response by MultipartFormData"); ctx.request().body().asMultipartFormData().asFormUrlEncoded().remove("g-recaptcha-response"); - } catch (Exception ignored){} + } catch (Exception ignored) { + } } return recaptchaUtil.check(clientIp, recaptchaResponse).thenComposeAsync(passed -> { - if(passed) { + if (passed) { Logger.debug("Recaptcha passes"); return delegate.call(ctx); } else { diff --git a/app/config/RecaptchaUtil.java b/app/config/RecaptchaUtil.java index 6d99596..7a6b422 100644 --- a/app/config/RecaptchaUtil.java +++ b/app/config/RecaptchaUtil.java @@ -23,7 +23,7 @@ public RecaptchaUtil(Config config, WSClient ws) { this.ws = ws; } - public CompletionStage check(String clientIpAddress, String response){ + public CompletionStage check(String clientIpAddress, String response) { return ws.url("https://www.google.com/recaptcha/api/siteverify") .setRequestTimeout(Duration.ofSeconds(3)) // after this duration, regard as passed to prevent user frustration .setContentType("application/x-www-form-urlencoded") diff --git a/app/config/ResourceServerAuthAction.java b/app/config/ResourceServerAuthAction.java index 6d057f0..0020c96 100644 --- a/app/config/ResourceServerAuthAction.java +++ b/app/config/ResourceServerAuthAction.java @@ -29,24 +29,24 @@ public CompletionStage call(Http.Context ctx) { .orElseGet(() -> requestHeader.getQueryString("access_token")); Tuple2 tuple = jwtUtils.getTokenStatus(token); - if(tuple._2 == TokenStatus.VALID_ACCESS) { + if (tuple._2 == TokenStatus.VALID_ACCESS) { Grant grant = tuple._1; boolean scopeOK = true; List scopes = Arrays.asList(configuration.scope().split(" ")); for (String s : scopes) { - if(!grant.getScopes().contains(s)){ + if (!grant.getScopes().contains(s)) { scopeOK = false; break; } } - if(scopeOK) { + if (scopeOK) { ctx = ctx.withRequest(ctx.request().addAttr(ResourceServerSecure.GRANT, grant)); return delegate.call(ctx); } else { return CompletableFuture.completedFuture(unauthorized(Json.newObject() - .put("message", "insufficient scope") - .put("scope", configuration.scope())) + .put("message", "insufficient scope") + .put("scope", configuration.scope())) ); } } diff --git a/app/config/ResourceServerSecure.java b/app/config/ResourceServerSecure.java index f233a20..6176051 100644 --- a/app/config/ResourceServerSecure.java +++ b/app/config/ResourceServerSecure.java @@ -17,7 +17,8 @@ /** * Whitespace separated list of scopes for this endpoint. + * * @return */ String scope(); -} \ No newline at end of file +} diff --git a/app/config/Utils.java b/app/config/Utils.java index d7b21d3..225c298 100644 --- a/app/config/Utils.java +++ b/app/config/Utils.java @@ -7,23 +7,23 @@ * Created by Selim Eren Bekçe on 25.08.2017. */ public class Utils { - public static String normalizeUsername(String username){ - if(username == null) return null; - return username.replaceAll("[^A-Za-z0-9]","_").toLowerCase(Locale.ENGLISH); + private static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + private static final SecureRandom rnd = new SecureRandom(); + + public static String normalizeUsername(String username) { + if (username == null) return null; + return username.replaceAll("[^A-Za-z0-9]", "_").toLowerCase(Locale.ENGLISH); } - public static String normalizeEmail(String email){ - if(email == null) return null; + public static String normalizeEmail(String email) { + if (email == null) return null; return email.toLowerCase(Locale.ENGLISH); } - private static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - private static final SecureRandom rnd = new SecureRandom(); - - private static String randomString( int len ){ - StringBuilder sb = new StringBuilder( len ); - for( int i = 0; i < len; i++ ) - sb.append( AB.charAt( rnd.nextInt(AB.length()) ) ); + private static String randomString(int len) { + StringBuilder sb = new StringBuilder(len); + for (int i = 0; i < len; i++) + sb.append(AB.charAt(rnd.nextInt(AB.length()))); return sb.toString(); } @@ -31,6 +31,7 @@ public static String newId() { return randomString(20); // return RandomStringUtils.randomAlphanumeric(20); } + public static String newSecret() { return randomString(32); } diff --git a/app/config/ValidateUniqueEmail.java b/app/config/ValidateUniqueEmail.java index 437a873..e90b88e 100644 --- a/app/config/ValidateUniqueEmail.java +++ b/app/config/ValidateUniqueEmail.java @@ -14,7 +14,9 @@ @Constraint(validatedBy = ValidateUniqueEmailValidator.class) public @interface ValidateUniqueEmail { String message() default "error.invalid"; + Class[] groups() default {}; + Class[] payload() default {}; } diff --git a/app/config/ValidateUniqueUsername.java b/app/config/ValidateUniqueUsername.java index 08bd915..0e2b08b 100644 --- a/app/config/ValidateUniqueUsername.java +++ b/app/config/ValidateUniqueUsername.java @@ -1,21 +1,22 @@ package config; -import static java.lang.annotation.ElementType.ANNOTATION_TYPE; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - +import javax.validation.Constraint; +import javax.validation.Payload; import java.lang.annotation.Retention; import java.lang.annotation.Target; -import javax.validation.Constraint; -import javax.validation.Payload; +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; @Target({TYPE, ANNOTATION_TYPE}) @Retention(RUNTIME) @Constraint(validatedBy = ValidateUniqueUsernameValidator.class) public @interface ValidateUniqueUsername { String message() default "error.invalid"; + Class[] groups() default {}; + Class[] payload() default {}; } diff --git a/app/controllers/ClientController.java b/app/controllers/ClientController.java index 9696ce9..a05aff0 100644 --- a/app/controllers/ClientController.java +++ b/app/controllers/ClientController.java @@ -1,8 +1,8 @@ package controllers; import config.AuthorizationServerSecure; -import dtos.ClientDto; import config.Utils; +import dtos.ClientDto; import models.Client; import models.User; import play.data.Form; @@ -37,9 +37,9 @@ public Result create() { public Result edit(String id) { User user = request().attrs().get(AuthorizationServerSecure.USER); Client client = clientRepository.findById(id); - if(client == null) + if (client == null) return badRequest("client not found"); - if(!Objects.equals(client.getOwnerId(), user.getId())) + if (!Objects.equals(client.getOwnerId(), user.getId())) return badRequest("not allowed"); return ok(views.html.client.render(user, client)); } @@ -49,7 +49,7 @@ public Result addUpdateClient(String id) { try { Form form = formFactory.form(ClientDto.class).bindFromRequest(); ClientDto dto = form.get(); - if(id == null || id.isEmpty()){ + if (id == null || id.isEmpty()) { Client client = new Client(); client.setId(Utils.newId()); client.setSecret(Utils.newSecret()); @@ -62,7 +62,7 @@ public Result addUpdateClient(String id) { flash("info", "Create client successful"); } else { Client client = clientRepository.findById(id); - if(!client.getOwnerId().equals(user.getId())){ + if (!client.getOwnerId().equals(user.getId())) { throw new IllegalAccessException(); } client.setName(dto.name); @@ -75,7 +75,7 @@ public Result addUpdateClient(String id) { return redirect(routes.ClientController.get()); } catch (Exception e) { flash("error", e.getMessage()); - if(id == null || id.isEmpty()) { + if (id == null || id.isEmpty()) { return redirect(routes.ClientController.create()); } else { return redirect(routes.ClientController.edit(id)); diff --git a/app/controllers/DiscourseController.java b/app/controllers/DiscourseController.java index 009253f..d766024 100644 --- a/app/controllers/DiscourseController.java +++ b/app/controllers/DiscourseController.java @@ -37,7 +37,7 @@ public class DiscourseController extends Controller { private DiscourseSetting getSetting() { DiscourseSetting setting = settingRepository.findById(DiscourseSetting.class); - if(setting == null) setting = new DiscourseSetting(); + if (setting == null) setting = new DiscourseSetting(); return setting; } @@ -57,10 +57,10 @@ public Result updateSettings() { setting.setEnabled(form.get().isEnabled()); Logger.info(form.get().toString()); - if(form.get().isEnabled() && StringUtils.isBlank(setting.getSecret())){ + if (form.get().isEnabled() && StringUtils.isBlank(setting.getSecret())) { setting.setSecret(Utils.newSecret()); } - if(!form.get().isEnabled()){ + if (!form.get().isEnabled()) { setting.setRedirectUri(null); setting.setSecret(null); } @@ -101,13 +101,13 @@ public Result updateSettings() { // } @AuthorizationServerSecure - public Result sso(String sso, String sig){ + public Result sso(String sso, String sig) { DiscourseSetting setting = getSetting(); - if(!setting.isEnabled()){ + if (!setting.isEnabled()) { return notFound(); } User user = request().attrs().get(AuthorizationServerSecure.USER); - if(!user.isEmailVerified()) { + if (!user.isEmailVerified()) { return redirect(routes.ProfileController.changeEmailPage(ctx().request().uri())); } @@ -116,7 +116,7 @@ public Result sso(String sso, String sig){ SecretKeySpec secret_key = new SecretKeySpec(setting.getSecret().getBytes("UTF-8"), "HmacSHA256"); sha256_HMAC.init(secret_key); String hexString = Hex.encodeHexString(sha256_HMAC.doFinal(sso.getBytes("UTF-8"))); - if(!Objects.equals(hexString, sig)){ + if (!Objects.equals(hexString, sig)) { return badRequest(Json.newObject().put("message", "signature mismatch")); } @@ -125,7 +125,7 @@ public Result sso(String sso, String sig){ payload = Base64.encodeBase64String(payload.getBytes(StandardCharsets.UTF_8)); String sig_new = Hex.encodeHexString(sha256_HMAC.doFinal(payload.getBytes("UTF-8"))); - return redirect(setting.getRedirectUri()+"?sso="+URLEncoder.encode(payload, "utf-8")+"&sig="+sig_new); + return redirect(setting.getRedirectUri() + "?sso=" + URLEncoder.encode(payload, "utf-8") + "&sig=" + sig_new); } catch (Exception e) { Logger.error(e.getMessage(), e); diff --git a/app/controllers/HomeController.java b/app/controllers/HomeController.java index 5335801..11c3417 100644 --- a/app/controllers/HomeController.java +++ b/app/controllers/HomeController.java @@ -1,6 +1,7 @@ package controllers; -import play.mvc.*; +import play.mvc.Controller; +import play.mvc.Result; /** * This controller contains an action to handle HTTP requests diff --git a/app/controllers/LoginController.java b/app/controllers/LoginController.java index c1772d2..16c13c7 100644 --- a/app/controllers/LoginController.java +++ b/app/controllers/LoginController.java @@ -8,7 +8,9 @@ import models.User; import play.data.Form; import play.data.FormFactory; -import play.mvc.*; +import play.mvc.Controller; +import play.mvc.Http; +import play.mvc.Result; import repositories.EventRepository; import repositories.UserRepository; @@ -36,8 +38,8 @@ public LoginController(FormFactory formFactory, UserRepository userRepository, E @AuthorizationServerSecure(optional = true) public Result get(String next) { Optional user = request().attrs().getOptional(AuthorizationServerSecure.USER); - if(user.isPresent()){ // already authenticated - if(next != null && next.matches("^/.*$")) + if (user.isPresent()) { // already authenticated + if (next != null && next.matches("^/.*$")) return redirect(next); else return redirect(routes.ProfileController.get()); @@ -47,7 +49,7 @@ public Result get(String next) { public Result post(String next) { Form form = formFactory.form(RegistrationDto.class, ConstraintGroups.Login.class).bindFromRequest(); - if(form.hasErrors()) { + if (form.hasErrors()) { flash("warning", "Please fill in the form"); return redirect(routes.LoginController.get(next)); } @@ -55,7 +57,7 @@ public Result post(String next) { User validUser = null; boolean disabled = false; for (User user : list) { - if(user != null && user.checkPassword(form.get().getPassword())) { + if (user != null && user.checkPassword(form.get().getPassword())) { if (user.isDisabled()) { disabled = true; continue; @@ -79,14 +81,14 @@ public Result post(String next) { } @AuthorizationServerSecure(optional = true) - public Result logout(){ + public Result logout() { Optional user = request().attrs().getOptional(AuthorizationServerSecure.USER); Http.Cookie ltat = Http.Cookie.builder("ltat", "") .withPath("/").withHttpOnly(true).withMaxAge(Duration.ZERO).build(); Optional referer = request().header("Referer"); eventRepository.logout(request(), user.orElse(null)); flash("info", "Logout successful"); - if(referer.isPresent()){ + if (referer.isPresent()) { return redirect(referer.get()).withCookies(ltat); } else { return redirect(routes.LoginController.get(null)); diff --git a/app/controllers/MeController.java b/app/controllers/MeController.java index 855d4d6..43af764 100644 --- a/app/controllers/MeController.java +++ b/app/controllers/MeController.java @@ -23,13 +23,13 @@ public class MeController extends Controller { private ProviderLinkRepository providerLinkRepository; @ResourceServerSecure(scope = "profile") - public Result get(){ + public Result get() { Grant grant = request().attrs().get(ResourceServerSecure.GRANT); User user = userRepository.findById(grant.getUserId()); MeDto dto = new MeDto(); - if(grant.getScopes().contains("user_social_links")){ + if (grant.getScopes().contains("user_social_links")) { dto.setSocialLinks(providerLinkRepository.findByUserId(user.getId()).stream().collect( - Collectors.toMap(ProviderLink::getProviderKey, c-> Tuple2.apply(c.getRemoteUserId(), c.getToken())) + Collectors.toMap(ProviderLink::getProviderKey, c -> Tuple2.apply(c.getRemoteUserId(), c.getToken())) )); } dto.setName(user.getUsername()); diff --git a/app/controllers/OAuthAuthorizationServerController.java b/app/controllers/OAuthAuthorizationServerController.java index 9b02889..486a6a6 100644 --- a/app/controllers/OAuthAuthorizationServerController.java +++ b/app/controllers/OAuthAuthorizationServerController.java @@ -2,9 +2,9 @@ import config.AuthorizationServerSecure; import config.JwtUtils; +import config.Utils; import dtos.Token; import dtos.TokenStatus; -import config.Utils; import models.Client; import models.Grant; import models.User; @@ -30,7 +30,7 @@ * It uses JWT to issue tokens, so tokens are self contained and there is no token store.
* It works with ResourceServerFilter to protect resources.
* Currently it works with a single user and client id, but it is easy to extend for multiple users.

- * + *

* Created by Selim Eren Bekçe on 2016-08-25. */ public class OAuthAuthorizationServerController extends play.mvc.Controller { @@ -51,7 +51,7 @@ public OAuthAuthorizationServerController(FormFactory formFactory, JwtUtils jwtU /** * Issues new tokens. - * + *

* client_id id of the client (required) * client_secret secret of the client (required) * grant_type supported grant types: 'client_credentials', 'authorization_code', 'password' or 'refresh_token' (required) @@ -61,18 +61,19 @@ public OAuthAuthorizationServerController(FormFactory formFactory, JwtUtils jwtU * redirect_uri redirect_uri parameter while retrieving authorization code from /authorize, when grant_type is 'authorization_code' * code retrieved code when grant_type is 'authorization_code' * scope requested scope when grant_type is 'password' + * * @return token */ public Result token() { DynamicForm form = formFactory.form().bindFromRequest(); String grant_type = form.get("grant_type"); - if(grant_type == null){ + if (grant_type == null) { return badRequest(Json.newObject().put("message", "missing grant_type")); } String client_id = form.get("client_id"); String client_secret = form.get("client_secret"); Client client = clientRepository.findById(client_id); - if(client == null || !Objects.equals(client.getSecret(), client_secret)){ + if (client == null || !Objects.equals(client.getSecret(), client_secret)) { return badRequest(Json.newObject().put("message", "either client_id or client_secret is missing or invalid")); } String username = form.get("username"); @@ -86,7 +87,7 @@ public Result token() { case "client_credentials": { /* In this mode, we use client_id as user_id in Grant */ Grant grant = grantRepository.findByClientAndUser(client_id, client_id); - if(grant == null){ + if (grant == null) { grant = new Grant(); grant.setId(Utils.newId()); grant.setClientId(client_id); @@ -98,31 +99,31 @@ public Result token() { } case "authorization_code": { Grant grant = jwtUtils.validateAuthorizationCode(code, redirect_uri); - if(grant == null){ + if (grant == null) { return badRequest(Json.newObject().put("message", "invalid code or redirect_uri")); } Token token = jwtUtils.prepareToken(client_id, client_secret, grant.getId(), grant.getScopes()); return ok(Json.toJson(token)); } case "password": { - if(!client.isTrusted()){ + if (!client.isTrusted()) { return badRequest(Json.newObject().put("message", "client not trusted for password type grant")); } User user = userRepository.findByUsernameNormalized(Utils.normalizeUsername(username)); - if(user == null){ + if (user == null) { user = userRepository.findByEmail(username); } - if(user == null || !user.checkPassword(password)){ + if (user == null || !user.checkPassword(password)) { return badRequest(Json.newObject().put("message", "invalid username or password")); } Grant grant = grantRepository.findByClientAndUser(client_id, user.getId()); - if(grant == null){ + if (grant == null) { // create new grant automatically because this is a trusted app grant = new Grant(); grant.setId(Utils.newId()); grant.setUserId(user.getId()); grant.setClientId(client_id); - if(scope != null){ + if (scope != null) { grant.setScopes(new HashSet<>(Arrays.asList(scope.split(" ")))); } grantRepository.save(grant); @@ -152,53 +153,53 @@ public Result token() { public Result authorize(String client_id, String response_type, String redirect_uri, String scope, String state) { Client client = clientRepository.findById(client_id); - if(client == null){ + if (client == null) { return badRequest(Json.newObject().put("message", "invalid client_id")); } - if(!redirect_uri.startsWith(client.getRedirectUri())){ + if (!redirect_uri.startsWith(client.getRedirectUri())) { return badRequest(Json.newObject().put("message", "invalid redirect_uri")); } - if(!"code".equals(response_type)){ + if (!"code".equals(response_type)) { return badRequest(Json.newObject().put("message", "invalid response_type")); } User user = request().attrs().get(AuthorizationServerSecure.USER); - if(!user.isEmailVerified()) { + if (!user.isEmailVerified()) { return redirect(routes.ProfileController.changeEmailPage(ctx().request().uri())); } Grant grant = grantRepository.findByClientAndUser(client_id, user.getId()); - if(client.isTrusted()) { - if(grant == null){ + if (client.isTrusted()) { + if (grant == null) { grant = new Grant(); grant.setId(Utils.newId()); grant.setUserId(user.getId()); grant.setClientId(client_id); } - if(scope != null){ + if (scope != null) { List scopes = Arrays.asList(scope.split(" ")); grant.getScopes().addAll(scopes); } grantRepository.save(grant); } - if(grant != null){ + if (grant != null) { boolean scopeOK = true; - if(scope != null){ + if (scope != null) { List scopes = Arrays.asList(scope.split(" ")); for (String s : scopes) { - if(!grant.getScopes().contains(s)){ + if (!grant.getScopes().contains(s)) { scopeOK = false; break; } } } - if(scopeOK){ + if (scopeOK) { String code = jwtUtils.prepareAuthorizationCode(client.getId(), client.getSecret(), grant.getId(), redirect_uri); String uri = String.format("%s%scode=%s", redirect_uri, redirect_uri.contains("?") ? "&" : "?", code); - if(state != null){ - uri += "&state="+state; + if (state != null) { + uri += "&state=" + state; } return redirect(uri); } @@ -211,26 +212,26 @@ public Result authorize(String client_id, String response_type, String redirect_ public Result authorizeDo(String client_id, String response_type, String redirect_uri, String scope, String state) { Client client = clientRepository.findById(client_id); - if(client == null){ + if (client == null) { return badRequest(Json.newObject().put("message", "invalid client_id")); } - if(!redirect_uri.startsWith(client.getRedirectUri())){ + if (!redirect_uri.startsWith(client.getRedirectUri())) { return badRequest(Json.newObject().put("message", "invalid redirect_uri")); } - if(!"code".equals(response_type)){ + if (!"code".equals(response_type)) { return badRequest(Json.newObject().put("message", "invalid response_type")); } User user = request().attrs().get(AuthorizationServerSecure.USER); Grant grant = grantRepository.findByClientAndUser(client_id, user.getId()); - if(grant == null){ + if (grant == null) { grant = new Grant(); grant.setId(Utils.newId()); grant.setUserId(user.getId()); grant.setClientId(client_id); } - if(scope != null){ + if (scope != null) { List scopes = Arrays.asList(scope.split(" ")); grant.getScopes().addAll(scopes); } @@ -238,8 +239,8 @@ public Result authorizeDo(String client_id, String response_type, String redirec String code = jwtUtils.prepareAuthorizationCode(client.getId(), client.getSecret(), grant.getId(), redirect_uri); String uri = String.format("%s%scode=%s", redirect_uri, redirect_uri.contains("?") ? "&" : "?", code); - if(state != null){ - uri += "&state="+state; + if (state != null) { + uri += "&state=" + state; } return redirect(uri); } diff --git a/app/controllers/OAuthClientController.java b/app/controllers/OAuthClientController.java index 7041833..5f86d38 100644 --- a/app/controllers/OAuthClientController.java +++ b/app/controllers/OAuthClientController.java @@ -23,96 +23,96 @@ public class OAuthClientController extends Controller { - @Inject - private AuthorizationServerManager manager; - @Inject - private WSClient ws; - @Inject - private HttpExecutionContext httpExecutionContext; - @Inject - private JwtUtils jwtUtils; - @Inject - private UserRepository userRepository; - @Inject - private ProviderLinkRepository providerLinkRepository; + @Inject + private AuthorizationServerManager manager; + @Inject + private WSClient ws; + @Inject + private HttpExecutionContext httpExecutionContext; + @Inject + private JwtUtils jwtUtils; + @Inject + private UserRepository userRepository; + @Inject + private ProviderLinkRepository providerLinkRepository; - public Result authorize(String providerKey, String next) { - OAuthProvider provider = manager.getProvider(providerKey); - if(provider == null) { - return notFound("N/A"); - } - OAuthContext context = new OAuthContext(provider, ws); - String state = Base64.encodeBase64String(String.format("%s,%s", Utils.newId(), next == null ? "" : next).getBytes(StandardCharsets.UTF_8)); - context.setState(state); - context.setRedirectUri(routes.OAuthClientController.callback(providerKey, Optional.empty(), Optional.empty(), Optional.empty()).absoluteURL(request())); - flash("state", state); - return redirect(context.prepareAuthorizeUrl()); - } - - @config.AuthorizationServerSecure(optional = true) - public CompletionStage callback(String providerKey, Optional code, Optional error, Optional state) { - OAuthProvider provider = manager.getProvider(providerKey); - if(provider == null) { - return CompletableFuture.completedFuture(notFound("N/A")); - } - if(!state.isPresent() || !state.get().equals(flash("state"))){ - flash("error", "Request failed (states don't match), please enable cookies and try again"); - return CompletableFuture.completedFuture(redirect(routes.LoginController.get(null))); - } - String decodedState = new String(Base64.decodeBase64(state.get()), StandardCharsets.UTF_8); - String next = org.apache.commons.lang3.StringUtils.trimToNull(decodedState.split(",", -1)[1]); - if(error.isPresent()){ - flash("error", provider.getDisplayName() + " returned "+error.get()); - return CompletableFuture.completedFuture(redirect(routes.LoginController.get(next))); - } - if(!code.isPresent()){ - return CompletableFuture.completedFuture(badRequest("no code parameter")); - } - Optional authenticatedUser = request().attrs().getOptional(config.AuthorizationServerSecure.USER); - play.Logger.info("authenticatedUser.isPresent():{}", authenticatedUser.isPresent()); + public Result authorize(String providerKey, String next) { + OAuthProvider provider = manager.getProvider(providerKey); + if (provider == null) { + return notFound("N/A"); + } + OAuthContext context = new OAuthContext(provider, ws); + String state = Base64.encodeBase64String(String.format("%s,%s", Utils.newId(), next == null ? "" : next).getBytes(StandardCharsets.UTF_8)); + context.setState(state); + context.setRedirectUri(routes.OAuthClientController.callback(providerKey, Optional.empty(), Optional.empty(), Optional.empty()).absoluteURL(request())); + flash("state", state); + return redirect(context.prepareAuthorizeUrl()); + } - OAuthContext context = new OAuthContext(provider, ws); - context.setState(state.get()); - context.setRedirectUri(routes.OAuthClientController.callback(providerKey, Optional.empty(), Optional.empty(), Optional.empty()).absoluteURL(request())); - context.setCode(code.get()); + @config.AuthorizationServerSecure(optional = true) + public CompletionStage callback(String providerKey, Optional code, Optional error, Optional state) { + OAuthProvider provider = manager.getProvider(providerKey); + if (provider == null) { + return CompletableFuture.completedFuture(notFound("N/A")); + } + if (!state.isPresent() || !state.get().equals(flash("state"))) { + flash("error", "Request failed (states don't match), please enable cookies and try again"); + return CompletableFuture.completedFuture(redirect(routes.LoginController.get(null))); + } + String decodedState = new String(Base64.decodeBase64(state.get()), StandardCharsets.UTF_8); + String next = org.apache.commons.lang3.StringUtils.trimToNull(decodedState.split(",", -1)[1]); + if (error.isPresent()) { + flash("error", provider.getDisplayName() + " returned " + error.get()); + return CompletableFuture.completedFuture(redirect(routes.LoginController.get(next))); + } + if (!code.isPresent()) { + return CompletableFuture.completedFuture(badRequest("no code parameter")); + } + Optional authenticatedUser = request().attrs().getOptional(config.AuthorizationServerSecure.USER); + play.Logger.info("authenticatedUser.isPresent():{}", authenticatedUser.isPresent()); - return context.retrieveToken() - .thenApplyAsync(token -> context) - .thenComposeAsync(provider.getCurrentUserIdentifier()) - .thenApplyAsync(dto -> { - ProviderLink link = providerLinkRepository.findByProvider(providerKey, dto.getId()); - User user = null; - if(link == null) { - link = new ProviderLink(); - link.setId(Utils.newId()); - link.setProviderKey(providerKey); - link.setRemoteUserId(dto.getId()); - link.setRemoteUserName(dto.getName()); - link.setRemoteUserEmail(Utils.normalizeEmail(dto.getEmail())); - link.setToken(context.getToken()); - providerLinkRepository.save(link); - } else if(link.getUserId() != null) { - user = userRepository.findById(link.getUserId()); - } - if(user == null) { // need to register or link - if(authenticatedUser.isPresent()){ // already authenticated, link it - return redirect(routes.ProfileController.linkProvider(link.getId())); - } else { // not authenticated, register - return redirect(routes.RegisterController.step2(next, link.getId())); - } - } else { // we have a valid user here! - if(authenticatedUser.isPresent() && !user.getId().equals(authenticatedUser.get().getId())) { - // the linked account is connected to another account, we cannot allow this - flash("warning", "Warning: The "+providerKey+" account you tried to link is already linked to another account with email address "+user.getEmail()+". To proceed, you need to unlink it first"); - return redirect(routes.ProfileController.get()); - } - if(user.isDisabled()){ - flash("error", "Your account was disabled."); - return redirect(routes.LoginController.get(next)); - } - return jwtUtils.prepareCookieThenRedirect(user, next); - } - }, httpExecutionContext.current()); - } + OAuthContext context = new OAuthContext(provider, ws); + context.setState(state.get()); + context.setRedirectUri(routes.OAuthClientController.callback(providerKey, Optional.empty(), Optional.empty(), Optional.empty()).absoluteURL(request())); + context.setCode(code.get()); + + return context.retrieveToken() + .thenApplyAsync(token -> context) + .thenComposeAsync(provider.getCurrentUserIdentifier()) + .thenApplyAsync(dto -> { + ProviderLink link = providerLinkRepository.findByProvider(providerKey, dto.getId()); + User user = null; + if (link == null) { + link = new ProviderLink(); + link.setId(Utils.newId()); + link.setProviderKey(providerKey); + link.setRemoteUserId(dto.getId()); + link.setRemoteUserName(dto.getName()); + link.setRemoteUserEmail(Utils.normalizeEmail(dto.getEmail())); + link.setToken(context.getToken()); + providerLinkRepository.save(link); + } else if (link.getUserId() != null) { + user = userRepository.findById(link.getUserId()); + } + if (user == null) { // need to register or link + if (authenticatedUser.isPresent()) { // already authenticated, link it + return redirect(routes.ProfileController.linkProvider(link.getId())); + } else { // not authenticated, register + return redirect(routes.RegisterController.step2(next, link.getId())); + } + } else { // we have a valid user here! + if (authenticatedUser.isPresent() && !user.getId().equals(authenticatedUser.get().getId())) { + // the linked account is connected to another account, we cannot allow this + flash("warning", "Warning: The " + providerKey + " account you tried to link is already linked to another account with email address " + user.getEmail() + ". To proceed, you need to unlink it first"); + return redirect(routes.ProfileController.get()); + } + if (user.isDisabled()) { + flash("error", "Your account was disabled."); + return redirect(routes.LoginController.get(next)); + } + return jwtUtils.prepareCookieThenRedirect(user, next); + } + }, httpExecutionContext.current()); + } } diff --git a/app/controllers/ProfileController.java b/app/controllers/ProfileController.java index a13df83..b8a46de 100644 --- a/app/controllers/ProfileController.java +++ b/app/controllers/ProfileController.java @@ -1,11 +1,7 @@ package controllers; import com.typesafe.config.Config; -import config.AuthorizationServerSecure; -import config.JwtUtils; -import config.MailService; -import config.RecaptchaProtected; -import config.AuthorizationServerManager; +import config.*; import dtos.ConstraintGroups; import dtos.RegistrationDto; import models.ProviderLink; @@ -53,25 +49,25 @@ public Result get() { return ok(views.html.profile.render(user, authorizationServerManager.getProviders(), providerLinkRepository.findMapByUserId(user.getId()), formFactory.form(RegistrationDto.class), formFactory.form(RegistrationDto.class))); } - public Result changePasswordPage(){ + public Result changePasswordPage() { User user = request().attrs().get(AuthorizationServerSecure.USER); return ok(changePasswordTemplate.render(user, formFactory.form(RegistrationDto.class))); } @RecaptchaProtected - public Result changePassword(){ + public Result changePassword() { User user = request().attrs().get(AuthorizationServerSecure.USER); Form form; - if(user.getPassword() == null){ + if (user.getPassword() == null) { form = formFactory.form(RegistrationDto.class, ConstraintGroups.SetPassword.class).bindFromRequest(); } else { form = formFactory.form(RegistrationDto.class, ConstraintGroups.ChangePassword.class).bindFromRequest(); - if(!user.checkPassword(form.value().get().getOldPassword())){ + if (!user.checkPassword(form.value().get().getOldPassword())) { form = form.withError("oldPassword", "Current password is invalid"); } } - if(form.hasErrors()) { + if (form.hasErrors()) { flash("warning", "Form has errors"); return badRequest(changePasswordTemplate.render(user, form)); } @@ -83,9 +79,9 @@ public Result changePassword(){ return jwtUtils.prepareCookieThenRedirect(user, null); } - public Result changeEmailPage(String next){ + public Result changeEmailPage(String next) { User user = request().attrs().get(AuthorizationServerSecure.USER); - if(user.getPassword() == null){ + if (user.getPassword() == null) { flash("warning", "To change your email address, please first set a password first."); return redirect(routes.ProfileController.changePasswordPage()); } @@ -93,43 +89,43 @@ public Result changeEmailPage(String next){ } @RecaptchaProtected - public Result changeEmail(String next){ + public Result changeEmail(String next) { User user = request().attrs().get(AuthorizationServerSecure.USER); Form form = formFactory.form(RegistrationDto.class, ConstraintGroups.ChangeEmail.class).bindFromRequest(); - if(!user.checkPassword(form.value().get().getOldPassword())){ + if (!user.checkPassword(form.value().get().getOldPassword())) { form = form.withError("oldPassword", "Current password is invalid"); } ValidationError validateUniqueEmail = form.value().get().validateUniqueEmail(userRepository, user.getId()); - if(validateUniqueEmail != null){ + if (validateUniqueEmail != null) { form = form.withError(validateUniqueEmail); } - if(form.hasErrors()) { + if (form.hasErrors()) { flash("warning", "Form has errors"); return badRequest(changeEmailTemplate.render(user, form, next)); } String confirmationCode = jwtUtils.prepareEmailChangeConfirmationCode(user, form.get().getEmail()); String confirmationUrl = routes.ProfileController.changeEmailConfirm(confirmationCode, next).absoluteURL(request()); - Logger.info("Confirmation URL for account "+user.getEmail()+": "+confirmationUrl); + Logger.info("Confirmation URL for account " + user.getEmail() + ": " + confirmationUrl); String content = emails.html.confirm.render( confirmationUrl, config.getString("brand.name")).toString(); mailService.sendEmail(form.get().getEmail(), "Confirm your new email address", content); - flash("info", "A confirmation email has been sent to "+form.get().getEmail()+". The change will be applied after clicking the confirmation link and authenticating yourself. Therefore, please login with your old credentials, if required."); - if(user.isEmailVerified()){ + flash("info", "A confirmation email has been sent to " + form.get().getEmail() + ". The change will be applied after clicking the confirmation link and authenticating yourself. Therefore, please login with your old credentials, if required."); + if (user.isEmailVerified()) { return redirect(routes.ProfileController.get()); } else { return redirect(routes.ProfileController.changeEmailPage(next)); } } - public Result changeEmailConfirm(String code, String next){ + public Result changeEmailConfirm(String code, String next) { User user = request().attrs().get(AuthorizationServerSecure.USER); Tuple2 tuple2 = jwtUtils.validateEmailChangeConfirmationCode(code); - if(tuple2 == null){ + if (tuple2 == null) { flash("error", "Email change confirm code was invalid, please try again"); return redirect(routes.ProfileController.get()); } - if(!user.getId().equals(tuple2._1.getId())){ + if (!user.getId().equals(tuple2._1.getId())) { return badRequest("users don't match"); } String oldEmail = user.getEmail(); @@ -138,7 +134,7 @@ public Result changeEmailConfirm(String code, String next){ user.setLastUpdateTime(System.currentTimeMillis()); userRepository.save(user); eventRepository.changeEmail(request(), user, oldEmail); - flash("success", "Success, your email address was changed to "+user.getEmail()+". Please use the new address to login from now on."); + flash("success", "Success, your email address was changed to " + user.getEmail() + ". Please use the new address to login from now on."); // refresh the cookie here return jwtUtils.prepareCookieThenRedirect(user, next); } @@ -146,27 +142,27 @@ public Result changeEmailConfirm(String code, String next){ public Result linkProvider(String linkId) { User user = request().attrs().get(AuthorizationServerSecure.USER); ProviderLink link = providerLinkRepository.findById(linkId); - if(link == null){ + if (link == null) { return badRequest("bad parameter"); } - if(link.getUserId() != null){ + if (link.getUserId() != null) { flash("error", "Account is already linked"); return redirect(routes.ProfileController.get()); } link.setUserId(user.getId()); providerLinkRepository.save(link); eventRepository.providerLink(request(), user, link); - flash("info", "You have successfully linked your "+link.getProviderKey()+" with us. You can now login with it"); + flash("info", "You have successfully linked your " + link.getProviderKey() + " with us. You can now login with it"); return redirect(routes.ProfileController.get()); } - public Result unlinkProvider(String linkId){ + public Result unlinkProvider(String linkId) { User user = request().attrs().get(AuthorizationServerSecure.USER); ProviderLink link = providerLinkRepository.findById(linkId); - if(link == null || !Objects.equals(link.getUserId(), user.getId())){ + if (link == null || !Objects.equals(link.getUserId(), user.getId())) { return badRequest("bad parameter"); } - if(user.getPassword() == null){ + if (user.getPassword() == null) { flash("warning", "Please set a password to unlink"); return redirect(routes.ProfileController.get()); } diff --git a/app/controllers/RegisterController.java b/app/controllers/RegisterController.java index d90465a..310e6ae 100644 --- a/app/controllers/RegisterController.java +++ b/app/controllers/RegisterController.java @@ -46,10 +46,10 @@ public Result step1(String next) { public Result step2(String next, String linkId) { ProviderLink link = providerLinkRepository.findById(linkId); - if(link != null && link.getUserId() == null){ + if (link != null && link.getUserId() == null) { String remoteUserEmail = link.getRemoteUserEmail(); - if(remoteUserEmail != null && userRepository.findByEmail(remoteUserEmail) != null){ - flash("warning", "The email "+ remoteUserEmail +" is already registered with us and it is probably yours. To use "+link.getProviderKey()+" login feature with this account, please first login then use the connection on your profile."); + if (remoteUserEmail != null && userRepository.findByEmail(remoteUserEmail) != null) { + flash("warning", "The email " + remoteUserEmail + " is already registered with us and it is probably yours. To use " + link.getProviderKey() + " login feature with this account, please first login then use the connection on your profile."); return redirect(routes.LoginController.get(next)); } int state = remoteUserEmail != null ? 3 : 2; @@ -65,11 +65,11 @@ public Result post1(String next, String linkId) { int state; ProviderLink link = null; String providerName = null; - if(linkId == null){ + if (linkId == null) { state = 1; } else { link = providerLinkRepository.findById(linkId); - if(link != null && link.getUserId() == null) { + if (link != null && link.getUserId() == null) { // TODO validate the remote user email with EmailValidator. If fails, treat as state=2 state = link.getRemoteUserEmail() != null ? 3 : 2; providerName = authorizationServerManager.getProvider(link.getProviderKey()).getDisplayName(); @@ -91,30 +91,30 @@ public Result post1(String next, String linkId) { validationGroupClass = ConstraintGroups.Register3.class; break; default: - throw new IllegalStateException("Unknown state="+state); + throw new IllegalStateException("Unknown state=" + state); } Form form = formFactory.form(RegistrationDto.class, validationGroupClass).bindFromRequest(); - if(form.hasErrors()){ + if (form.hasErrors()) { flash("warning", "Form has errors"); return badRequest(template.render(state, providerName, link != null ? link.getRemoteUserEmail() : null, linkId, form, next, Html.apply(config.getString("tos.text")))); } RegistrationDto dto = form.get(); - if(state == 1 || state == 2){ + if (state == 1 || state == 2) { User user1 = new User(); user1.setEmail(dto.getEmail()); user1.setUsername(dto.getUsername()); - if(state == 1) + if (state == 1) user1.encryptThenSetPassword(dto.getPassword()); String confirmationCode = jwtUtils.prepareEmailConfirmationCode(user1, state == 2 ? linkId : null); String confirmationUrl = routes.RegisterController.step5(next, confirmationCode).absoluteURL(request()); - Logger.info("Confirmation URL for account "+dto.getEmail()+": "+confirmationUrl); + Logger.info("Confirmation URL for account " + dto.getEmail() + ": " + confirmationUrl); String content = emails.html.confirm.render( confirmationUrl, config.getString("brand.name")).toString(); mailService.sendEmail(dto.getEmail(), "Confirm your account", content); - flash("info", "Success, confirmation email sent to "+dto.getEmail()+"."); + flash("info", "Success, confirmation email sent to " + dto.getEmail() + "."); return redirect(routes.RegisterController.await()); - } else if (state == 3){ + } else if (state == 3) { User user = new User(); user.setId(Utils.newId()); user.setEmail(link.getRemoteUserEmail()); @@ -122,7 +122,7 @@ public Result post1(String next, String linkId) { user.setUsername(dto.getUsername()); user.setUsernameNormalized(dto.getUsernameNormalized()); user.setCreationTime(System.currentTimeMillis()); - if(userRepository.count() == 0) + if (userRepository.count() == 0) user.setAdmin(true); userRepository.save(user); eventRepository.register(request(), user); @@ -133,34 +133,34 @@ public Result post1(String next, String linkId) { flash("info", "Registration successful"); return jwtUtils.prepareCookieThenRedirect(user, next); } else { - throw new IllegalStateException("Unknown state="+state); + throw new IllegalStateException("Unknown state=" + state); } } public Result step5(String next, String code) { Tuple2 tuple2 = jwtUtils.validateEmailConfirmationCode(code); - if(tuple2 == null){ + if (tuple2 == null) { flash("warning", "Cannot create your account: Invalid or expired confirmation code, please start over"); return redirect(routes.LoginController.get(next)); } User dto = tuple2._1; User byEmail = userRepository.findByEmail(dto.getEmail()); - if(byEmail != null){ + if (byEmail != null) { flash("info", "This email address was already confirmed, please log in"); return redirect(routes.LoginController.get(next)); } dto.setUsernameNormalized(Utils.normalizeUsername(dto.getUsername())); if (userRepository.findByUsernameNormalized(dto.getUsernameNormalized()) != null) { // somebody has registered with this username before this one could click the link - flash("warning", "Cannot create your account: the username you entered ("+dto.getUsername()+") is no longer available, please start over"); + flash("warning", "Cannot create your account: the username you entered (" + dto.getUsername() + ") is no longer available, please start over"); redirect(routes.RegisterController.step1(next)); } String linkId = tuple2._2; ProviderLink link = null; - if(linkId != null){ + if (linkId != null) { link = providerLinkRepository.findById(linkId); - if(link == null || link.getUserId() != null){ + if (link == null || link.getUserId() != null) { flash("warning", "Cannot link your account with this provider, please start over"); redirect(routes.RegisterController.step1(next)); } @@ -173,15 +173,15 @@ public Result step5(String next, String code) { user.setUsername(dto.getUsername()); user.setUsernameNormalized(dto.getUsernameNormalized()); user.setCreationTime(System.currentTimeMillis()); - if(dto.getPassword() != null){ + if (dto.getPassword() != null) { user.setPassword(dto.getPassword()); //already encrypted } - if(userRepository.count() == 0) + if (userRepository.count() == 0) user.setAdmin(true); userRepository.save(user); eventRepository.register(request(), user); - if(link != null){ + if (link != null) { link.setUserId(user.getId()); providerLinkRepository.save(link); eventRepository.providerLink(request(), user, link); @@ -191,7 +191,7 @@ public Result step5(String next, String code) { return jwtUtils.prepareCookieThenRedirect(user, next); } - public Result await(){ + public Result await() { return ok(views.html.generic.render("Register", "Thank you for registering, we have sent a confirmation link to your email address. Please check your inbox and click on the provided link to finalize account creation.")); } } diff --git a/app/controllers/ResetPasswordController.java b/app/controllers/ResetPasswordController.java index 471881e..4a3ed21 100644 --- a/app/controllers/ResetPasswordController.java +++ b/app/controllers/ResetPasswordController.java @@ -34,31 +34,19 @@ public class ResetPasswordController extends Controller { @Inject private views.html.resetPassword1 template; - public static class Step1Dto { - @Constraints.Required - public String login; - } - - public static class Step3Dto { - @Constraints.Required - @Constraints.MaxLength(32) - @Constraints.MinLength(4) - public String password; - } - - public Result step1(String next){ + public Result step1(String next) { return ok(template.render(next, formFactory.form(Step1Dto.class))); } @RecaptchaProtected - public Result step2(String next){ + public Result step2(String next) { Form form = formFactory.form(Step1Dto.class).bindFromRequest(); - if(form.hasErrors()){ + if (form.hasErrors()) { return badRequest(template.render(next, form)); } Step1Dto dto = form.get(); User user = userRepository.findByUsernameOrEmail(dto.login); - if(user == null){ + if (user == null) { flash("warning", "No account was found with this login"); return redirect(routes.ResetPasswordController.step1(next)); } @@ -69,30 +57,30 @@ public Result step2(String next){ user.getEmail(), (int) config.getDuration("jwt.expire.resetCode").toHours(), config.getString("brand.name")).toString(); - Logger.info("Confirmation URL: "+confirmationUrl); + Logger.info("Confirmation URL: " + confirmationUrl); mailService.sendEmail(user.getEmail(), "Reset your password", content); eventRepository.resetPasswordSend(request(), user); flash("info", "Your password reset link was just sent to your email address. Please check your inbox and click on the provided link to continue."); return redirect(routes.LoginController.get(next)); } - public Result step3(String code, String next){ + public Result step3(String code, String next) { User user = jwtUtils.validateResetCode(code); - if(user == null){ + if (user == null) { flash("warning", "Invalid reset code, please get another one"); return redirect(routes.ResetPasswordController.step1(next)); } return ok(views.html.resetPassword3.render(code, user.getEmail(), next, formFactory.form(Step3Dto.class))); } - public Result step4(String code, String next){ + public Result step4(String code, String next) { User user = jwtUtils.validateResetCode(code); - if(user == null){ + if (user == null) { flash("warning", "Reset code isn't valid anymore, please get another one"); return redirect(routes.ResetPasswordController.step1(next)); } Form form = formFactory.form(Step3Dto.class).bindFromRequest(); - if(form.hasErrors()){ + if (form.hasErrors()) { return badRequest(views.html.resetPassword3.render(code, user.getEmail(), next, form)); } user.encryptThenSetPassword(form.get().password); @@ -101,4 +89,16 @@ public Result step4(String code, String next){ flash("success", "Your password was reset! Please login to continue"); return redirect(routes.LoginController.get(next)); } + + public static class Step1Dto { + @Constraints.Required + public String login; + } + + public static class Step3Dto { + @Constraints.Required + @Constraints.MaxLength(32) + @Constraints.MinLength(4) + public String password; + } } diff --git a/app/controllers/UserController.java b/app/controllers/UserController.java index 18a9135..b08feb9 100644 --- a/app/controllers/UserController.java +++ b/app/controllers/UserController.java @@ -35,10 +35,10 @@ public Result get() { @ResourceServerSecure(scope = "user:create") @BodyParser.Of(BodyParser.Json.class) - public Result apiCreate(){ + public Result apiCreate() { Grant grant = request().attrs().get(ResourceServerSecure.GRANT); User user = userRepository.findById(grant.getUserId()); - if(!user.isAdmin()) { + if (!user.isAdmin()) { return unauthorized("need admin"); } JsonNode json = request().body().asJson(); @@ -54,31 +54,31 @@ public Result apiCreate(){ long lastUpdateTime = json.path("user").path("lastUpdateTime").asLong(System.currentTimeMillis()); String disabledReason = json.path("user").path("disabledReason").asText(null); User byId = userRepository.findById(id); - if(!update && byId != null) { + if (!update && byId != null) { return badRequest("duplicate id"); } - if(update && byId == null){ - return badRequest("no user found with id "+id); + if (update && byId == null) { + return badRequest("no user found with id " + id); } User byEmail = userRepository.findByEmail(Utils.normalizeEmail(email)); - if(!bypassEmailCheck && email != null && byEmail != null && !byEmail.getId().equals(id)){ + if (!bypassEmailCheck && email != null && byEmail != null && !byEmail.getId().equals(id)) { return badRequest("duplicate email"); } User byUsernameNormalized = userRepository.findByUsernameNormalized(Utils.normalizeUsername(username)); - if(!bypassUsernameCheck && username != null && byUsernameNormalized != null && !byUsernameNormalized.getId().equals(id)){ + if (!bypassUsernameCheck && username != null && byUsernameNormalized != null && !byUsernameNormalized.getId().equals(id)) { return badRequest("duplicate username"); } - if(email == null && username == null) { + if (email == null && username == null) { return badRequest("either username or email is necessary"); } - if(password == null && email == null){ + if (password == null && email == null) { return badRequest("either email or password is necessary (come on!)"); } - if(password != null && password.isEmpty()) { + if (password != null && password.isEmpty()) { return badRequest("password cannot be empty (seriously!)"); } User user1 = update ? byId : new User(); - if(user1.isAdmin()) { + if (user1.isAdmin()) { return badRequest("cannot update admins with this"); } user1.setId(id); @@ -90,7 +90,7 @@ public Result apiCreate(){ user1.setLastUpdateTime(lastUpdateTime); user1.setAdmin(false); user1.setDisabledReason(disabledReason); - if(password != null) user1.encryptThenSetPassword(password); + if (password != null) user1.encryptThenSetPassword(password); else user1.setPassword(null); userRepository.save(user1); eventRepository.addUpdateUserViaApi(request(), byId, user1); diff --git a/app/dtos/ConstraintGroups.java b/app/dtos/ConstraintGroups.java index 4ee0a7d..15005e5 100644 --- a/app/dtos/ConstraintGroups.java +++ b/app/dtos/ConstraintGroups.java @@ -4,11 +4,24 @@ * Created by Selim Eren Bekçe on 16.12.2017. */ public class ConstraintGroups { - public interface Login {} - public interface Register1 {} - public interface Register2 {} - public interface Register3 {} - public interface ChangePassword {} - public interface SetPassword {} - public interface ChangeEmail {} + public interface Login { + } + + public interface Register1 { + } + + public interface Register2 { + } + + public interface Register3 { + } + + public interface ChangePassword { + } + + public interface SetPassword { + } + + public interface ChangeEmail { + } } diff --git a/app/dtos/OAuthContext.java b/app/dtos/OAuthContext.java index 43cc2a9..f340c26 100644 --- a/app/dtos/OAuthContext.java +++ b/app/dtos/OAuthContext.java @@ -1,123 +1,121 @@ package dtos; -import play.libs.Json; import play.libs.ws.WSClient; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; -import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; public class OAuthContext { - private Token token; - private String code; - private String redirectUri; - private String scope; - private String state; - private OAuthProvider provider; - private WSClient ws; - - public OAuthContext(OAuthProvider provider, WSClient ws) { - this.provider = provider; - this.ws = ws; - } - - public String prepareAuthorizeUrl() { - if(redirectUri == null){ - throw new IllegalStateException("No redirectUri present"); - } - if(state == null){ - throw new IllegalStateException("No state present"); - } - try { - StringBuilder builder = new StringBuilder(); - builder.append(provider.getAuthorizeUrl()) - .append("?client_id=").append(provider.getClientId()) - .append("&response_type=code") - .append("&redirect_uri=").append(URLEncoder.encode(redirectUri, "utf-8")) - .append("&state=").append(state); - if (scope != null) { - builder.append("&scope=").append(URLEncoder.encode(scope, "utf-8")); - } else if (provider.getScopes() != null) { - builder.append("&scope=").append(URLEncoder.encode(provider.getScopes(), "utf-8")); - } - return builder.toString(); - } catch (UnsupportedEncodingException e) { - throw new Error(e); - } - } - - public CompletionStage retrieveToken() { - if(code == null){ - throw new IllegalStateException("No code present"); - } - if(redirectUri == null){ - throw new IllegalStateException("No redirectUri present"); - } + private Token token; + private String code; + private String redirectUri; + private String scope; + private String state; + private OAuthProvider provider; + private WSClient ws; + + public OAuthContext(OAuthProvider provider, WSClient ws) { + this.provider = provider; + this.ws = ws; + } + + public String prepareAuthorizeUrl() { + if (redirectUri == null) { + throw new IllegalStateException("No redirectUri present"); + } + if (state == null) { + throw new IllegalStateException("No state present"); + } + try { + StringBuilder builder = new StringBuilder(); + builder.append(provider.getAuthorizeUrl()) + .append("?client_id=").append(provider.getClientId()) + .append("&response_type=code") + .append("&redirect_uri=").append(URLEncoder.encode(redirectUri, "utf-8")) + .append("&state=").append(state); + if (scope != null) { + builder.append("&scope=").append(URLEncoder.encode(scope, "utf-8")); + } else if (provider.getScopes() != null) { + builder.append("&scope=").append(URLEncoder.encode(provider.getScopes(), "utf-8")); + } + return builder.toString(); + } catch (UnsupportedEncodingException e) { + throw new Error(e); + } + } + + public CompletionStage retrieveToken() { + if (code == null) { + throw new IllegalStateException("No code present"); + } + if (redirectUri == null) { + throw new IllegalStateException("No redirectUri present"); + } // return ws.url(provider.getTokenUrl()) // .setContentType("application/x-www-form-urlencoded") // .post(String.format("client_id=%s&client_secret=%s&grant_type=authorization_code&code=%s&redirect_uri=%s", provider.getClientId(), provider.getClientSecret(), code, redirectUri)) - return provider.getTokenRetriever().apply(this); - } - - public Token getToken() { - return token; - } - - public void setToken(Token token) { - this.token = token; - } - - public String getCode() { - return code; - } - - public void setCode(String code) { - this.code = code; - } - - public String getRedirectUri() { - return redirectUri; - } - - public void setRedirectUri(String redirectUri) { - this.redirectUri = redirectUri; - } - - public String getScope() { - return scope; - } - - public void setScope(String scope) { - this.scope = scope; - } - - public String getState() { - return state; - } - - public void setState(String state) { - this.state = state; - } - - public OAuthProvider getProvider() { - return provider; - } - - public WSClient getWs() { - return ws; - } - - @Override - public String toString() { - return "OAuthContext{" + - "token=" + token + - ", code='" + code + '\'' + - ", redirectUri='" + redirectUri + '\'' + - ", scope='" + scope + '\'' + - ", state='" + state + '\'' + - ", provider=" + provider + - '}'; - } + return provider.getTokenRetriever().apply(this); + } + + public Token getToken() { + return token; + } + + public void setToken(Token token) { + this.token = token; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getRedirectUri() { + return redirectUri; + } + + public void setRedirectUri(String redirectUri) { + this.redirectUri = redirectUri; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public OAuthProvider getProvider() { + return provider; + } + + public WSClient getWs() { + return ws; + } + + @Override + public String toString() { + return "OAuthContext{" + + "token=" + token + + ", code='" + code + '\'' + + ", redirectUri='" + redirectUri + '\'' + + ", scope='" + scope + '\'' + + ", state='" + state + '\'' + + ", provider=" + provider + + '}'; + } } diff --git a/app/dtos/OAuthProvider.java b/app/dtos/OAuthProvider.java index b9b8402..e7aa834 100644 --- a/app/dtos/OAuthProvider.java +++ b/app/dtos/OAuthProvider.java @@ -5,76 +5,76 @@ public class OAuthProvider { - private final String displayName; - private final String tokenUrl; + private final String displayName; + private final String tokenUrl; private final String authorizeUrl; - private final String clientId; - private final String clientSecret; - private final String scopes; - private final String userInfoUrl; - private final Function> tokenRetriever; - private final Function> currentUserIdentifier; + private final String clientId; + private final String clientSecret; + private final String scopes; + private final String userInfoUrl; + private final Function> tokenRetriever; + private final Function> currentUserIdentifier; - public OAuthProvider(String displayName, String tokenUrl, String authorizeUrl, String clientId, String clientSecret, String scopes, String userInfoUrl, Function> tokenRetriever, Function> currentUserIdentifier) { - this.displayName = displayName; - this.tokenUrl = tokenUrl; - this.authorizeUrl = authorizeUrl; - this.clientId = clientId; - this.clientSecret = clientSecret; - this.scopes = scopes; - this.userInfoUrl = userInfoUrl; - this.tokenRetriever = tokenRetriever; - this.currentUserIdentifier = currentUserIdentifier; - } + public OAuthProvider(String displayName, String tokenUrl, String authorizeUrl, String clientId, String clientSecret, String scopes, String userInfoUrl, Function> tokenRetriever, Function> currentUserIdentifier) { + this.displayName = displayName; + this.tokenUrl = tokenUrl; + this.authorizeUrl = authorizeUrl; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.scopes = scopes; + this.userInfoUrl = userInfoUrl; + this.tokenRetriever = tokenRetriever; + this.currentUserIdentifier = currentUserIdentifier; + } - public String getDisplayName() { - return displayName; - } + public String getDisplayName() { + return displayName; + } - public String getTokenUrl() { - return tokenUrl; - } + public String getTokenUrl() { + return tokenUrl; + } - public String getAuthorizeUrl() { - return authorizeUrl; - } + public String getAuthorizeUrl() { + return authorizeUrl; + } - public String getClientId() { - return clientId; - } + public String getClientId() { + return clientId; + } - public String getClientSecret() { - return clientSecret; - } + public String getClientSecret() { + return clientSecret; + } - public String getScopes() { - return scopes; - } + public String getScopes() { + return scopes; + } - public String getUserInfoUrl() { - return userInfoUrl; - } + public String getUserInfoUrl() { + return userInfoUrl; + } - public Function> getCurrentUserIdentifier() { - return currentUserIdentifier; - } + public Function> getCurrentUserIdentifier() { + return currentUserIdentifier; + } - public Function> getTokenRetriever() { - return tokenRetriever; - } + public Function> getTokenRetriever() { + return tokenRetriever; + } - @Override - public String toString() { - return "OAuthProvider{" + - "displayName='" + displayName + '\'' + - ", tokenUrl='" + tokenUrl + '\'' + - ", authorizeUrl='" + authorizeUrl + '\'' + - ", clientId='" + clientId + '\'' + - ", clientSecret='" + clientSecret + '\'' + - ", scopes='" + scopes + '\'' + - ", userInfoUrl='" + userInfoUrl + '\'' + - ", tokenRetriever=" + tokenRetriever + - ", currentUserIdentifier=" + currentUserIdentifier + - '}'; - } + @Override + public String toString() { + return "OAuthProvider{" + + "displayName='" + displayName + '\'' + + ", tokenUrl='" + tokenUrl + '\'' + + ", authorizeUrl='" + authorizeUrl + '\'' + + ", clientId='" + clientId + '\'' + + ", clientSecret='" + clientSecret + '\'' + + ", scopes='" + scopes + '\'' + + ", userInfoUrl='" + userInfoUrl + '\'' + + ", tokenRetriever=" + tokenRetriever + + ", currentUserIdentifier=" + currentUserIdentifier + + '}'; + } } diff --git a/app/dtos/RegistrationDto.java b/app/dtos/RegistrationDto.java index 352001b..6b1923b 100644 --- a/app/dtos/RegistrationDto.java +++ b/app/dtos/RegistrationDto.java @@ -119,7 +119,7 @@ public List validate() { @Override public ValidationError validateUniqueUsername(UserRepository userRepository) { usernameNormalized = Utils.normalizeUsername(username); - if(usernameNormalized == null) return null; + if (usernameNormalized == null) return null; if (userRepository.findByUsernameNormalized(usernameNormalized) != null) { return new ValidationError("username", "Username already exists"); } @@ -135,7 +135,7 @@ public ValidationError validateUniqueEmail(UserRepository userRepository, String // } email = Utils.normalizeEmail(email); - if(email == null) return null; + if (email == null) return null; User byEmail = userRepository.findByEmail(email); if (byEmail != null && !byEmail.getId().equals(currentUserId)) { return new ValidationError("email", "Email is already registered"); diff --git a/app/emails/confirm.scala.html b/app/emails/confirm.scala.html index 93a768b..a2f4309 100644 --- a/app/emails/confirm.scala.html +++ b/app/emails/confirm.scala.html @@ -1,167 +1,306 @@ -@(url:String, brandName:String)() +@(url: String, brandName: String)() +"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - - - - Confirm your email - - - - - - - - - - - + + +
-
- - - - - -
- - - - - - - - - - - - - + + - -
- Please confirm your email address by clicking the link below. -
- We may need to send you critical information about our service and it is - important that we have an accurate email address. We shall never spam. -
- -
- If you did not request this, please ignore this email or contact support if you have questions. + margin: 0;" + bgcolor="#f6f6f6"> +
+
+ + + + +
+ + + + + + + + + + + + + + +
+ Please confirm your email address by clicking the link below. +
+ We may need to send you critical information about our service and it is + important that we have an accurate email address. We shall never spam. +
+ +
+ If you did not request this, please ignore this email or contact support if you have questions. +
+
+
- - -
- - \ No newline at end of file +
+ +
+ + diff --git a/app/emails/resetPassword.scala.html b/app/emails/resetPassword.scala.html index 31b2cb4..d3d2a3e 100644 --- a/app/emails/resetPassword.scala.html +++ b/app/emails/resetPassword.scala.html @@ -1,165 +1,304 @@ -@(url:String, name:String, expireHours:Int, brandName:String)() +@(url: String, name: String, expireHours: Int, brandName: String)() +"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - - - - Reset your password - - - - - - - - - - - + + +
-
- - - - - -
- - - - - - - - - - - - - + + - -
- Hi @name, -
- You recently requested to reset your password for your @brandName account. Use the button below to reset it. This password reset is only valid for the next @expireHours hours. -
- -
- If you did not request a password reset, please ignore this email or contact support if you have questions. + margin: 0;" + bgcolor="#f6f6f6"> +
+
+ + + + +
+ + + + + + + + + + + + + + +
+ Hi @name, +
+ You recently requested to reset your password for your @brandName account. Use the button below to reset it. This password reset is only valid for the next @expireHours hours. +
+ +
+ If you did not request a password reset, please ignore this email or contact support if you have questions. +
+
+
- - -
- - \ No newline at end of file +
+ +
+ + diff --git a/app/models/EventType.java b/app/models/EventType.java index 41bb0cd..71f9d88 100644 --- a/app/models/EventType.java +++ b/app/models/EventType.java @@ -1,5 +1,5 @@ package models; public enum EventType { - LOGIN, BAD_LOGIN, REGISTER, LOGOUT, PROVIDER_LINK, PROVIDER_UNLINK, CHANGE_PASSWORD, CHANGE_EMAIL, ADD_UPDATE_USER_API, RESET_PASSWORD_SEND, RESET_PASSWORD_COMPLETE + LOGIN, BAD_LOGIN, REGISTER, LOGOUT, PROVIDER_LINK, PROVIDER_UNLINK, CHANGE_PASSWORD, CHANGE_EMAIL, ADD_UPDATE_USER_API, RESET_PASSWORD_SEND, RESET_PASSWORD_COMPLETE } diff --git a/app/models/User.java b/app/models/User.java index 39a0954..0cacf1a 100644 --- a/app/models/User.java +++ b/app/models/User.java @@ -10,11 +10,11 @@ public class User { @MongoId private String id; -// @Indexed + // @Indexed private String username; -// @Indexed + // @Indexed private String usernameNormalized; -// @Indexed + // @Indexed private String email; private String password; /** @@ -40,16 +40,16 @@ public class User { */ private String disabledReason; - public void encryptThenSetPassword(String password_plaintext){ + public void encryptThenSetPassword(String password_plaintext) { String salt = BCrypt.gensalt(12); this.password = BCrypt.hashpw(password_plaintext, salt); this.lastUpdateTime = System.currentTimeMillis(); } - public boolean checkPassword(String password_plaintext){ - if(this.password == null || password_plaintext == null) + public boolean checkPassword(String password_plaintext) { + if (this.password == null || password_plaintext == null) return false; - if(!this.password.startsWith("$2a$")) + if (!this.password.startsWith("$2a$")) throw new IllegalArgumentException("Invalid hash provided for comparison"); return BCrypt.checkpw(password_plaintext, password); } @@ -126,7 +126,7 @@ public void setEmailVerified(boolean emailVerified) { this.emailVerified = emailVerified; } - public boolean isDisabled(){ + public boolean isDisabled() { return disabledReason != null; } diff --git a/app/repositories/ClientRepository.java b/app/repositories/ClientRepository.java index 8534e1d..dd2a218 100644 --- a/app/repositories/ClientRepository.java +++ b/app/repositories/ClientRepository.java @@ -25,11 +25,11 @@ public Client findById(String id) { return collection.findOne("{_id:#}", id).as(Client.class); } - public void save(Client u){ + public void save(Client u) { collection.save(u); } - public List findByOwnerId(String ownerId){ + public List findByOwnerId(String ownerId) { MongoCursor cursor = collection.find("{ownerId:#}", ownerId).as(Client.class); return StreamSupport.stream(cursor.spliterator(), false).collect(Collectors.toList()); } diff --git a/app/repositories/EventRepository.java b/app/repositories/EventRepository.java index ab340b4..69bd6b3 100644 --- a/app/repositories/EventRepository.java +++ b/app/repositories/EventRepository.java @@ -21,12 +21,12 @@ public EventRepository(PlayJongo playJongo) { this.collection = playJongo.jongo().getCollection("event"); } - public void save(Event u){ + public void save(Event u) { collection.save(u); } - private void fillAndSave(Event event, Request request){ - if(request != null){ + private void fillAndSave(Event event, Request request) { + if (request != null) { event.setIpAddress(request.remoteAddress()); event.setUserAgent(request.header("User-Agent").orElse(null)); } @@ -36,34 +36,44 @@ private void fillAndSave(Event event, Request request){ public void login(Request request, User user, String login) { fillAndSave(new Event(user.getId(), null, null, EventType.LOGIN), request); } + public void badLogin(Request request, String userId, String login) { fillAndSave(new Event(userId, login, null, EventType.BAD_LOGIN), request); } + public void register(Request request, User user) { fillAndSave(new Event(user.getId(), null, user, EventType.REGISTER), request); } + public void logout(Request request, User user) { fillAndSave(new Event(user == null ? null : user.getId(), null, null, EventType.LOGOUT), request); } + public void resetPasswordSend(Request request, User user) { fillAndSave(new Event(user.getId(), null, null, EventType.RESET_PASSWORD_SEND), request); } + public void resetPasswordComplete(Request request, User user) { fillAndSave(new Event(user.getId(), null, user.getPassword(), EventType.RESET_PASSWORD_COMPLETE), request); } - public void changePassword(Request request, User user){ + + public void changePassword(Request request, User user) { fillAndSave(new Event(user.getId(), null, user.getPassword(), EventType.CHANGE_PASSWORD), request); } - public void changeEmail(Request request, User user, String oldEmail){ + + public void changeEmail(Request request, User user, String oldEmail) { fillAndSave(new Event(user.getId(), oldEmail, user.getEmail(), EventType.CHANGE_EMAIL), request); } - public void providerLink(Request request, User user, ProviderLink providerLink){ + + public void providerLink(Request request, User user, ProviderLink providerLink) { fillAndSave(new Event(user.getId(), null, providerLink, EventType.PROVIDER_LINK), request); } - public void providerUnlink(Request request, User user, ProviderLink providerLink){ + + public void providerUnlink(Request request, User user, ProviderLink providerLink) { fillAndSave(new Event(user.getId(), providerLink, null, EventType.PROVIDER_UNLINK), request); } - public void addUpdateUserViaApi(Request request, User oldValue, User newValue){ + + public void addUpdateUserViaApi(Request request, User oldValue, User newValue) { fillAndSave(new Event(newValue.getId(), oldValue, newValue, EventType.ADD_UPDATE_USER_API), request); } } diff --git a/app/repositories/GrantRepository.java b/app/repositories/GrantRepository.java index 7e40fdb..4183d3a 100644 --- a/app/repositories/GrantRepository.java +++ b/app/repositories/GrantRepository.java @@ -21,7 +21,7 @@ public Grant findById(String id) { return collection.findOne("{_id:#}", id).as(Grant.class); } - public void save(Grant u){ + public void save(Grant u) { collection.save(u); } diff --git a/app/repositories/ProviderLinkRepository.java b/app/repositories/ProviderLinkRepository.java index ef097e3..b465bbe 100644 --- a/app/repositories/ProviderLinkRepository.java +++ b/app/repositories/ProviderLinkRepository.java @@ -4,13 +4,12 @@ import org.jongo.MongoCollection; import uk.co.panaxiom.playjongo.PlayJongo; +import javax.inject.Inject; +import javax.inject.Singleton; import java.util.List; import java.util.stream.Collectors; import java.util.stream.StreamSupport; -import javax.inject.Inject; -import javax.inject.Singleton; - @Singleton public class ProviderLinkRepository { @@ -30,19 +29,19 @@ public ProviderLink findByProvider(String providerKey, String remoteUserId) { return collection.findOne("{providerKey:#, remoteUserId:#}", providerKey, remoteUserId).as(ProviderLink.class); } - public List findByUserId(String userId){ - return StreamSupport.stream(collection.find("{userId:#}", userId).as(ProviderLink.class).spliterator(), false).collect(Collectors.toList()); + public List findByUserId(String userId) { + return StreamSupport.stream(collection.find("{userId:#}", userId).as(ProviderLink.class).spliterator(), false).collect(Collectors.toList()); } - public java.util.Map findMapByUserId(String userId){ - return StreamSupport.stream(collection.find("{userId:#}", userId).as(ProviderLink.class).spliterator(), false).collect(Collectors.toMap(i -> i.getProviderKey(), i -> i.getId())); + public java.util.Map findMapByUserId(String userId) { + return StreamSupport.stream(collection.find("{userId:#}", userId).as(ProviderLink.class).spliterator(), false).collect(Collectors.toMap(i -> i.getProviderKey(), i -> i.getId())); } - public void save(ProviderLink u){ + public void save(ProviderLink u) { collection.save(u); } - - public void delete(String id){ - collection.remove("{_id:#}", id); + + public void delete(String id) { + collection.remove("{_id:#}", id); } } diff --git a/app/repositories/SettingRepository.java b/app/repositories/SettingRepository.java index b4e5c54..abd1a57 100644 --- a/app/repositories/SettingRepository.java +++ b/app/repositories/SettingRepository.java @@ -22,7 +22,7 @@ public T findById(Class clazz) { // return setting != null ? (T) setting.getValue() : null; } - public void save(Setting u){ + public void save(Setting u) { u.setId(u.getClass().getSimpleName()); collection.save(u); // Setting setting = collection.findOne("{_id:#}", u.getClass().getSimpleName()).as(Setting.class); diff --git a/app/repositories/UserRepository.java b/app/repositories/UserRepository.java index e8199d1..a41992f 100644 --- a/app/repositories/UserRepository.java +++ b/app/repositories/UserRepository.java @@ -25,16 +25,16 @@ public User findById(String id) { return collection.findOne("{_id:#}", id).as(User.class); } - public void save(User u){ + public void save(User u) { collection.save(u); } public User findByUsernameNormalized(String usernameNormalized) { - return collection.findOne("{usernameNormalized:#}",usernameNormalized).as(User.class); + return collection.findOne("{usernameNormalized:#}", usernameNormalized).as(User.class); } public User findByEmail(String email) { - return collection.findOne("{email:#}",email).as(User.class); + return collection.findOne("{email:#}", email).as(User.class); } public User findByUsernameOrEmail(String login) { @@ -61,7 +61,7 @@ public List findByUsernameOrEmailMulti(String login) { return list; } - public Iterable findAll(){ + public Iterable findAll() { return collection.find().as(User.class); } diff --git a/app/views/alert.scala.html b/app/views/alert.scala.html index 0450915..70910c9 100644 --- a/app/views/alert.scala.html +++ b/app/views/alert.scala.html @@ -1,6 +1,14 @@ @(implicit flash: play.mvc.Http.Flash) -@if(flash.get("info")) {

} -@if(flash.get("success")) {} -@if(flash.get("warning")) {} -@if(flash.get("error")) {} \ No newline at end of file +@if(flash.get("info")) { + +} +@if(flash.get("success")) { + +} +@if(flash.get("warning")) { + +} +@if(flash.get("error")) { + +} diff --git a/app/views/authorize.scala.html b/app/views/authorize.scala.html index 1bb5d65..403eef3 100644 --- a/app/views/authorize.scala.html +++ b/app/views/authorize.scala.html @@ -1,9 +1,10 @@ -@(app_name:String, client_id:String, response_type:String, redirect_uri:String, scope:String, state:String) +@(app_name: String, client_id: String, response_type: String, redirect_uri: String, scope: String, state: String) @import helper._ @main("Authorize") {

Authorize @app_name

-

@app_name would like to know your name and e-mail address. Your password will not be shared.

+

@app_name + would like to know your name and e-mail address. Your password will not be shared.

@form(action = routes.OAuthAuthorizationServerController.authorizeDo(client_id, response_type, redirect_uri, scope, state), 'class -> "form-signin") { @alert(flash()) diff --git a/app/views/changeEmailPage.scala.html b/app/views/changeEmailPage.scala.html index 904f428..cf71a85 100644 --- a/app/views/changeEmailPage.scala.html +++ b/app/views/changeEmailPage.scala.html @@ -1,50 +1,63 @@ @this(cfg: com.typesafe.config.Config) @()(user: models.User, changeEmailForm: play.data.Form[dtos.RegistrationDto], next: String) @import helper._ -@title = @{if(user.isEmailVerified) "Change Email" else "Verify Email"} +@title = @{ + if(user.isEmailVerified) "Change Email" else "Verify Email" +} @mainWithNavigation(title, "changeEmail", user) { -@alert(flash()) -
-
-

@title

-
-
+ @alert(flash()) +
+
+

@title

+
+
@form(action = routes.ProfileController.changeEmail(next), 'class -> "form-horizontal") { - @CSRF.formField - @if(!user.isEmailVerified) {} -

To not get locked out of your account, it is important to have a valid email address. Please note that you will need to confirm your new email address via a confirmation link. @if(user.isEmailVerified) { Changing your email will also log you out from all other devices/browsers. }

-
- -
-

@user.getEmail

@if(!user.isEmailVerified) {

(unverified)

} + @CSRF.formField + @if(!user.isEmailVerified) { + + } +

+ To not get locked out of your account, it is important to have a valid email address. Please note that you will need to confirm your new email address via a confirmation link. @if(user.isEmailVerified) { Changing your email will also log you out from all other devices/browsers. }

+
+ +
+

@user.getEmail

@if(!user.isEmailVerified) { +

(unverified)

+ } +
-
-
- -
- - @for(error <- changeEmailForm("email").errors) {

@error.format(messages())

} +
+ +
+ + @for(error <- changeEmailForm("email").errors) { +

@error.format(messages())

+ } +
-
-
- -
- - @for(error <- changeEmailForm("oldPassword").errors) {

@error.format(messages())

} +
+ +
+ + @for(error <- changeEmailForm("oldPassword").errors) { +

@error.format(messages())

+ } +
-
-
-
-
+
+
+
+
-
-
-
- +
+
+ +
-
} +
-
-} \ No newline at end of file +} diff --git a/app/views/changePasswordPage.scala.html b/app/views/changePasswordPage.scala.html index 44d94ed..b5240a2 100644 --- a/app/views/changePasswordPage.scala.html +++ b/app/views/changePasswordPage.scala.html @@ -1,47 +1,54 @@ @this(cfg: com.typesafe.config.Config) @()(user: models.User, changePasswordForm: play.data.Form[dtos.RegistrationDto]) @import helper._ -@title = @{if(user.getPassword == null) "Set Password" else "Change Password"} +@title = @{ + if(user.getPassword == null) "Set Password" else "Change Password" +} @mainWithNavigation(title, "changePassword", user) { -@alert(flash()) -
-
-

@title

-
-
- @form(action = routes.ProfileController.changePassword, 'class -> "form-horizontal") { - @CSRF.formField - @if(user.getPassword != null) { -

Warning: Changing your password will log you out from all other devices/browsers.

-
- -
- - @for(error <- changePasswordForm("oldPassword").errors) {

@error.format(messages())

} -
+ @alert(flash()) +
+
+

@title

- } else { -

Setting a password allows you to use the regular login form.

- } -
- -
- - @for(error <- changePasswordForm("password").errors) {

@error.format(messages())

} +
+ @form(action = routes.ProfileController.changePassword, 'class -> "form-horizontal") { + @CSRF.formField + @if(user.getPassword != null) { +

+ Warning: Changing your password will log you out from all other devices/browsers.

+
+ +
+ + @for(error <- changePasswordForm("oldPassword").errors) { +

@error.format(messages())

+ } +
+
+ } else { +

Setting a password allows you to use the regular login form.

+ } +
+ +
+ + @for(error <- changePasswordForm("password").errors) { +

@error.format(messages())

+ } +
-
-
+
+
-
-
-
- +
+
+ +
-
} +
-
-} \ No newline at end of file +} diff --git a/app/views/client.scala.html b/app/views/client.scala.html index fd3a127..fbe6f40 100644 --- a/app/views/client.scala.html +++ b/app/views/client.scala.html @@ -2,66 +2,68 @@ @import helper._ @mainWithNavigation("Add/Update Client", "clients", user) { -@alert(flash()) + @alert(flash()) -
-
-

@{ if(client.getId == null) "Create New Client" else "View Client" }

-
-
+
+
+

@{ + if(client.getId == null) "Create New Client" else "View Client" + }

+
+
@form(action = routes.ClientController.addUpdateClient(client.getId), 'class -> "form-horizontal") { - @CSRF.formField - @if(client.getId != null) { + @CSRF.formField + @if(client.getId != null) { +
+ +
+

@client.getId

+
+
+
+ +
+

@client.getSecret

+
+
+ }
- +
-

@client.getId

+
- +
-

@client.getSecret

+
- } -
- -
- -
-
-
- -
- -
-
-
- -
- +
+ +
+ +
-
-
-
-
- +
+
+
+ +
-
- -
-
- + +
+
+ +
-
} +
-
} diff --git a/app/views/clients.scala.html b/app/views/clients.scala.html index 1b34a8d..54d860b 100644 --- a/app/views/clients.scala.html +++ b/app/views/clients.scala.html @@ -1,27 +1,30 @@ @()(user: models.User, clients: List[models.Client]) -@import helper._ + @mainWithNavigation("Client Management", "clients", user) { -@alert(flash()) + @alert(flash()) -
-
-

Client Management

-
-
- Create New - - - - @for(client <- clients) { - - - - - } - -
Client IdName
@client.getId@client.getName
+
+
+

Client Management

+
+
+ Create New + + + + @for(client <- clients) { + + + + + } + +
Client IdName
@client.getId + @client.getName
+
-
} diff --git a/app/views/discourse.scala.html b/app/views/discourse.scala.html index 17cc508..1b23896 100644 --- a/app/views/discourse.scala.html +++ b/app/views/discourse.scala.html @@ -2,18 +2,18 @@ @import helper._ @mainWithNavigation("Discourse SSO Settings", "discourse", user) { -@alert(flash()) + @alert(flash()) -
-
-

Discourse SSO Settings

-
-
+
+
+

Discourse SSO Settings

+
+
@form(action = routes.DiscourseController.updateSettings) { @CSRF.formField
@@ -21,10 +21,12 @@

Discourse SSO Settings

-

Secret: @{discourse.getSecret()}

+

Secret: @{ + discourse.getSecret() + }

} +
-
-} \ No newline at end of file +} diff --git a/app/views/generic.scala.html b/app/views/generic.scala.html index 086aaec..69c2905 100644 --- a/app/views/generic.scala.html +++ b/app/views/generic.scala.html @@ -1,5 +1,5 @@ -@(title:String, message:String) -@import helper._ +@(title: String, message: String) + @main(title) {