This is an automated email from the git hooks/post-receive script. New commit to branch feature/pollen-riot-js in repository pollen. See https://gitlab.nuiton.org/chorem/pollen.git commit c9359ffbd941d89d4e719953a9955a360be96c1d Author: Tony CHEMIT <dev@tchemit.fr> Date: Sun Jan 15 13:41:37 2017 +0100 Review security and use now cookies to store authentication informations --- .../rest/api/PollenRestApiRequestFilter.java | 85 ++++++++- .../org/chorem/pollen/rest/api/v1/AuthApi.java | 52 +++++- pollen-rest-api/src/main/resources/mapping | 1 + pollen-rest-api/src/main/webapp/WEB-INF/web.xml | 35 +++- pollen-services/pom.xml | 5 + pollen-services/src/main/config/PollenServices.ini | 16 +- .../security/DefaultPollenSecurityContext.java | 1 + ...xt.java => MissingAuthenticationException.java} | 28 +-- .../security/PollenCypherTechnicalException.java | 16 ++ .../service/security/PollenSecurityContext.java | 2 + .../services/service/security/SecurityService.java | 202 ++++++++++++++++++--- .../i18n/pollen-services_en_GB.properties | 1 + .../i18n/pollen-services_fr_FR.properties | 1 + .../service/PollenUIUrlRenderServiceTest.java | 14 +- pollen-ui-riot-js/src/main/web/index.js | 7 +- pollen-ui-riot-js/src/main/web/js/AuthService.js | 17 +- pollen-ui-riot-js/src/main/web/js/FetchService.js | 7 - pollen-ui-riot-js/src/main/web/js/I18nHelper.js | 4 + pollen-ui-riot-js/src/main/web/js/Session.js | 9 +- pollen-ui-riot-js/src/main/web/tag/SignIn.tag | 2 +- pom.xml | 13 +- 21 files changed, 408 insertions(+), 110 deletions(-) diff --git a/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/PollenRestApiRequestFilter.java b/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/PollenRestApiRequestFilter.java index 72103b6..eb64c6c 100644 --- a/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/PollenRestApiRequestFilter.java +++ b/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/PollenRestApiRequestFilter.java @@ -28,15 +28,18 @@ import org.chorem.pollen.persistence.PollenPersistenceContext; import org.chorem.pollen.persistence.entity.PollenPrincipal; import org.chorem.pollen.persistence.entity.SessionToken; import org.chorem.pollen.services.PollenServiceContext; +import org.chorem.pollen.services.service.security.PollenCypherTechnicalException; import org.chorem.pollen.services.service.security.PollenInvalidSessionTokenException; import org.chorem.pollen.services.service.security.PollenSecurityContext; import org.chorem.pollen.services.service.security.SecurityService; import org.debux.webmotion.server.WebMotionFilter; import org.debux.webmotion.server.call.Call; +import org.debux.webmotion.server.call.CookieManager; import org.debux.webmotion.server.call.HttpContext; import org.debux.webmotion.server.render.Render; import org.debux.webmotion.server.render.RenderStatus; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import java.util.Locale; import java.util.Map; @@ -52,6 +55,10 @@ public class PollenRestApiRequestFilter extends WebMotionFilter { private static final String HEADER_ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; private static final String HEADER_ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; + private static final String COOKIE_POLLEN_AUTH = "pollen-auth"; + private static final String COOKIE_POLLEN_CONNECTED = "pollen-connected"; + private final static int COOKIE_MAX_AGE = 60 * 60 * 24 * 365; // 1 year + public static final String REQUEST_PERMISSION_PARAMETER = "permission"; public static final String REQUEST_HEADER_SESSION_TOKEN = "X-Pollen-Session-Token"; @@ -59,9 +66,9 @@ public class PollenRestApiRequestFilter extends WebMotionFilter { private static final Log log = LogFactory.getLog(PollenRestApiRequestFilter.class); @SuppressWarnings("unused") - public void inject(Call call, HttpContext context) throws PollenInvalidSessionTokenException { + public void inject(Call call, HttpContext context) throws PollenInvalidSessionTokenException, PollenCypherTechnicalException { - prepareRequestContext(context); + PollenRestApiRequestContext pollenRestApiRequestContext = prepareRequestContext(context); if (HttpContext.METHOD_OPTIONS.equals(context.getMethod())) { @@ -95,9 +102,49 @@ public class PollenRestApiRequestFilter extends WebMotionFilter { doProcess(); + PollenSecurityContext securityContext = pollenRestApiRequestContext.getSecurityContext(); + if (securityContext.isConnected()) { + + // add auth cookies + + SessionToken sessionToken = securityContext.getSessionToken(); + String value = pollenRestApiRequestContext.getSecurityService().encrypt( + sessionToken.getPollenUser().getTopiaId(), + sessionToken.getPollenToken().getToken() + ); + Cookie authCookie = new Cookie(COOKIE_POLLEN_AUTH, value); + authCookie.setPath("/"); + authCookie.setMaxAge(COOKIE_MAX_AGE); + response.addCookie(authCookie); + + Cookie connectedCookie = new Cookie(COOKIE_POLLEN_CONNECTED, "true"); + connectedCookie.setPath("/"); + connectedCookie.setMaxAge(COOKIE_MAX_AGE); + response.addCookie(connectedCookie); + + if (log.isDebugEnabled()) { + log.debug("Add auth cookie:: " + authCookie.getValue()); + } + + } else { + + // remove auth cookies + + Cookie authCookie = new Cookie(COOKIE_POLLEN_AUTH, ""); + authCookie.setPath("/"); + authCookie.setMaxAge(0); + response.addCookie(authCookie); + + Cookie connectedCookie = new Cookie(COOKIE_POLLEN_CONNECTED, ""); + connectedCookie.setPath("/"); + connectedCookie.setMaxAge(0); + response.addCookie(connectedCookie); + + } + } - protected PollenRestApiRequestContext prepareRequestContext(HttpContext context) throws PollenInvalidSessionTokenException { + private PollenRestApiRequestContext prepareRequestContext(HttpContext context) throws PollenInvalidSessionTokenException, PollenCypherTechnicalException { Locale locale = getUserLocale(context); @@ -121,16 +168,38 @@ public class PollenRestApiRequestFilter extends WebMotionFilter { } - protected PollenSecurityContext createSecurityContext(HttpContext context, - PollenRestApiApplicationContext applicationContext, - PollenRestApiRequestContext requestContext) throws PollenInvalidSessionTokenException { + private PollenSecurityContext createSecurityContext(HttpContext context, + PollenRestApiApplicationContext applicationContext, + PollenRestApiRequestContext requestContext) throws PollenInvalidSessionTokenException, PollenCypherTechnicalException { SecurityService securityService = requestContext.getSecurityService(); // --- get session token (from request parameters) --- // String sessionTokenHeader = context.getHeader(REQUEST_HEADER_SESSION_TOKEN); - SessionToken sessionToken = securityService.getSessionTokenByToken(sessionTokenHeader); + if (StringUtils.isEmpty(sessionTokenHeader)) { + + // --- get session token (from request cookies) --- // + CookieManager.CookieEntity cookieEntity = context.getCookieManager().get(COOKIE_POLLEN_AUTH); + if (cookieEntity != null) { + + if (log.isDebugEnabled()) { + log.debug("Found pollen-auth cookie:: " + cookieEntity.getValue()); + } + sessionTokenHeader = securityService.decrypt(cookieEntity.getValue()); + } + + } + + SessionToken sessionToken = null; + try { + sessionToken = securityService.getSessionTokenByToken(sessionTokenHeader); + } catch (PollenInvalidSessionTokenException e) { + // session token is not valid + if (log.isErrorEnabled()) { + log.error("Found invalid session token, won't use it: " + sessionTokenHeader, e); + } + } // --- get mainPrincipal (from request parameters) --- // Map<String, String[]> parameters = context.getParameters(); @@ -144,7 +213,7 @@ public class PollenRestApiRequestFilter extends WebMotionFilter { } - protected Locale getUserLocale(HttpContext context) { + private Locale getUserLocale(HttpContext context) { String language = context.getHeader(HttpContext.HEADER_LANGUAGE); if (log.isDebugEnabled()) { diff --git a/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/v1/AuthApi.java b/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/v1/AuthApi.java index a2909e7..474b408 100644 --- a/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/v1/AuthApi.java +++ b/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/v1/AuthApi.java @@ -21,11 +21,24 @@ package org.chorem.pollen.rest.api.v1; * #L% */ +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.shiro.codec.Base64; +import org.chorem.pollen.persistence.entity.PollenToken; +import org.chorem.pollen.persistence.entity.PollenTokenImpl; import org.chorem.pollen.persistence.entity.PollenUser; +import org.chorem.pollen.persistence.entity.SessionToken; +import org.chorem.pollen.persistence.entity.SessionTokenImpl; +import org.chorem.pollen.rest.api.PollenRestApiRequestContext; import org.chorem.pollen.services.bean.PollenEntityRef; +import org.chorem.pollen.services.service.security.DefaultPollenSecurityContext; +import org.chorem.pollen.services.service.security.MissingAuthenticationException; import org.chorem.pollen.services.service.security.PollenAuthenticationException; +import org.chorem.pollen.services.service.security.PollenInvalidSessionTokenException; import org.chorem.pollen.services.service.security.SecurityService; import org.debux.webmotion.server.WebMotionController; +import org.debux.webmotion.server.call.HttpContext; /** * TODO @@ -35,16 +48,51 @@ import org.debux.webmotion.server.WebMotionController; */ public class AuthApi extends WebMotionController { - public PollenEntityRef<PollenUser> login(SecurityService securityService, String login, String password, Boolean rememberMe) throws PollenAuthenticationException { + /** Logger */ + private static final Log log = LogFactory.getLog(AuthApi.class); + + public PollenEntityRef<PollenUser> login(HttpContext requestContext, SecurityService securityService) throws PollenAuthenticationException, MissingAuthenticationException, PollenInvalidSessionTokenException { + + String authHeader = requestContext.getHeader("Authorization"); + + if (StringUtils.startsWith(authHeader, "Basic ")) { + String s = new String(Base64.decode(StringUtils.substringAfter(authHeader, "Basic "))); + String[] lp = s.split(":"); + String login = lp[0]; + String password = lp[1]; + + if (log.isInfoEnabled()) { + log.info("login@password:: " + login + "@" + password); + } + + PollenEntityRef<PollenUser> userPollenEntityRef = securityService.login(login, password, false); + + // Inject the session token in security context + PollenRestApiRequestContext pollenRestApiRequestContext = PollenRestApiRequestContext.getRequestContext(requestContext); + + SessionToken sessionTokenByToken = securityService.getSessionTokenByToken(userPollenEntityRef.getPermission()); + pollenRestApiRequestContext.getSecurityContext().setSessionToken(sessionTokenByToken); + + return userPollenEntityRef; + } + + throw new MissingAuthenticationException(); + + } + + public PollenEntityRef<PollenUser> login2(SecurityService securityService, String login, String password, Boolean rememberMe) throws PollenAuthenticationException { return securityService.login(login, password, rememberMe); } - public void logout(SecurityService securityService) { + public void logout(HttpContext requestContext, SecurityService securityService) { securityService.logout(); + // Remove the session token from security context + PollenRestApiRequestContext.getRequestContext(requestContext).getSecurityContext().setSessionToken(null); + } public void lostPassword(SecurityService securityService, String login) { diff --git a/pollen-rest-api/src/main/resources/mapping b/pollen-rest-api/src/main/resources/mapping index 3d07c2b..7ffb99e 100644 --- a/pollen-rest-api/src/main/resources/mapping +++ b/pollen-rest-api/src/main/resources/mapping @@ -54,6 +54,7 @@ GET /v1/doc DocApi.showMapping # AuthApi POST,PUT /v1/login AuthApi.login +POST,PUT /v1/login2 AuthApi.login2 GET /v1/lostpassword/{login} AuthApi.lostPassword GET /v1/logout AuthApi.logout diff --git a/pollen-rest-api/src/main/webapp/WEB-INF/web.xml b/pollen-rest-api/src/main/webapp/WEB-INF/web.xml index 4c902fc..269ec67 100644 --- a/pollen-rest-api/src/main/webapp/WEB-INF/web.xml +++ b/pollen-rest-api/src/main/webapp/WEB-INF/web.xml @@ -20,7 +20,7 @@ #L% --> -<web-app version="3.0" id="pollen" +<web-app version="3.0" id="pollen" metadata-complete="true" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee @@ -28,6 +28,11 @@ <display-name>Pollen REST Api</display-name> + <context-param> + <param-name>wm.skip.conventionScan</param-name> + <param-value>true</param-value> + </context-param> + <filter> <filter-name>topiaTransaction</filter-name> <filter-class> @@ -35,9 +40,37 @@ </filter-class> </filter> + <filter> + <filter-name>WebMotionServer</filter-name> + <filter-class>org.debux.webmotion.server.WebMotionServer</filter-class> + <async-supported>true</async-supported> + </filter> + <filter-mapping> <filter-name>topiaTransaction</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> + <filter-mapping> + <filter-name>WebMotionServer</filter-name> + <url-pattern>/*</url-pattern> + <dispatcher>REQUEST</dispatcher> + <dispatcher>INCLUDE</dispatcher> + <dispatcher>FORWARD</dispatcher> + <dispatcher>ERROR</dispatcher> + </filter-mapping> + + <listener> + <listener-class>org.apache.commons.fileupload.servlet.FileCleanerCleanup</listener-class> + </listener> + + <listener> + <listener-class>org.debux.webmotion.server.WebMotionServletContextListener</listener-class> + </listener> + + <!-- Force jsessionid into cookie --> + <session-config> + <tracking-mode>COOKIE</tracking-mode> + </session-config> + </web-app> diff --git a/pollen-services/pom.xml b/pollen-services/pom.xml index dc8dddb..5255520 100644 --- a/pollen-services/pom.xml +++ b/pollen-services/pom.xml @@ -75,6 +75,11 @@ <artifactId>yamlbeans</artifactId> </dependency> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk16</artifactId> + </dependency> + <!--dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> diff --git a/pollen-services/src/main/config/PollenServices.ini b/pollen-services/src/main/config/PollenServices.ini index 3674e01..ff86426 100644 --- a/pollen-services/src/main/config/PollenServices.ini +++ b/pollen-services/src/main/config/PollenServices.ini @@ -15,6 +15,14 @@ type = file transient = true final = true +[option secret] +description = pollen.configuration.secret +key = pollen.secret +type = string +defaultValue = !secret# +transient = true +final = true + [option defaultPollType] description = pollen.configuration.defaultPollType key = pollen.default.pollType @@ -123,19 +131,19 @@ final = true description = pollen.configuration.uiEndPoint key = pollen.ui.host type = url -defaultValue = http://localhost:8888/index.html +defaultValue = http://localhost:8888/app [option uiUrlPollEdit] description = pollen.configuration.uiUrlPollEdit key = pollen.ui.url.poll.edit type = string -defaultValue = ${pollen.ui.host}#/poll/edit/{pollId}/{pollToken} +defaultValue = ${pollen.ui.host}#poll/edit/{pollId}/{pollToken} [option uiUrlPollVote] description = pollen.configuration.uiUrlPollVote key = pollen.ui.url.poll.vote type = url -defaultValue = ${pollen.ui.host}#/poll/vote/{pollId}/{pollToken} +defaultValue = ${pollen.ui.host}#poll/vote/{pollId}/{pollToken} [option uiUrlPollVoteEdit] description = pollen.configurqtion.uiUrlPollVoteEdit @@ -147,4 +155,4 @@ defaultValue = ${pollen.ui.url.poll.vote}/vote/{voteToken} description = pollen.configurqtion.uiUrlUserValidate key = pollen.ui.url.user.validate type = url -defaultValue = ${pollen.ui.host}#/user/{userId}/{token} +defaultValue = ${pollen.ui.host}#signcheck/{userId}/{token} diff --git a/pollen-services/src/main/java/org/chorem/pollen/services/service/security/DefaultPollenSecurityContext.java b/pollen-services/src/main/java/org/chorem/pollen/services/service/security/DefaultPollenSecurityContext.java index 9baeadd..c2f80cf 100644 --- a/pollen-services/src/main/java/org/chorem/pollen/services/service/security/DefaultPollenSecurityContext.java +++ b/pollen-services/src/main/java/org/chorem/pollen/services/service/security/DefaultPollenSecurityContext.java @@ -92,6 +92,7 @@ public class DefaultPollenSecurityContext implements Serializable, PollenSecurit this.subject = subject; } + @Override public void setSessionToken(SessionToken sessionToken) { this.sessionToken = sessionToken; diff --git a/pollen-services/src/main/java/org/chorem/pollen/services/service/security/PollenSecurityContext.java b/pollen-services/src/main/java/org/chorem/pollen/services/service/security/MissingAuthenticationException.java similarity index 56% copy from pollen-services/src/main/java/org/chorem/pollen/services/service/security/PollenSecurityContext.java copy to pollen-services/src/main/java/org/chorem/pollen/services/service/security/MissingAuthenticationException.java index a0b254e..1414aff 100644 --- a/pollen-services/src/main/java/org/chorem/pollen/services/service/security/PollenSecurityContext.java +++ b/pollen-services/src/main/java/org/chorem/pollen/services/service/security/MissingAuthenticationException.java @@ -21,36 +21,12 @@ package org.chorem.pollen.services.service.security; * #L% */ -import org.apache.shiro.subject.Subject; -import org.chorem.pollen.persistence.entity.PollenPrincipal; -import org.chorem.pollen.persistence.entity.PollenUser; -import org.chorem.pollen.persistence.entity.SessionToken; - /** - * Created on 5/1/14. - * * @author Tony Chemit - dev@tchemit.fr * @since 2.0 */ -public interface PollenSecurityContext { - - /** - * Get an extra credential, this is needed for protected resources, for example an private poll. - * - * @return optional credential given. - */ - PollenPrincipal getMainPrincipal(); - - SessionToken getSessionToken(); - - Subject getSubject(); - - PollenUser getPollenUser(); - - boolean isConnected(); - - boolean isAdmin(); +public class MissingAuthenticationException extends Exception { - void setSubject(Subject subject); + private static final long serialVersionUID = 1L; } diff --git a/pollen-services/src/main/java/org/chorem/pollen/services/service/security/PollenCypherTechnicalException.java b/pollen-services/src/main/java/org/chorem/pollen/services/service/security/PollenCypherTechnicalException.java new file mode 100644 index 0000000..c440c6d --- /dev/null +++ b/pollen-services/src/main/java/org/chorem/pollen/services/service/security/PollenCypherTechnicalException.java @@ -0,0 +1,16 @@ +package org.chorem.pollen.services.service.security; + +/** + * Created on 15/01/17. + * + * @author Tony Chemit - dev@tchemit.fr + * @since 2.0 + */ +public class PollenCypherTechnicalException extends Exception { + + private static final long serialVersionUID = 1L; + + public PollenCypherTechnicalException(Throwable cause) { + super(cause); + } +} diff --git a/pollen-services/src/main/java/org/chorem/pollen/services/service/security/PollenSecurityContext.java b/pollen-services/src/main/java/org/chorem/pollen/services/service/security/PollenSecurityContext.java index a0b254e..c3744a1 100644 --- a/pollen-services/src/main/java/org/chorem/pollen/services/service/security/PollenSecurityContext.java +++ b/pollen-services/src/main/java/org/chorem/pollen/services/service/security/PollenSecurityContext.java @@ -43,6 +43,8 @@ public interface PollenSecurityContext { SessionToken getSessionToken(); + void setSessionToken(SessionToken sessionToken); + Subject getSubject(); PollenUser getPollenUser(); diff --git a/pollen-services/src/main/java/org/chorem/pollen/services/service/security/SecurityService.java b/pollen-services/src/main/java/org/chorem/pollen/services/service/security/SecurityService.java index 6adb3f7..b716e1a 100644 --- a/pollen-services/src/main/java/org/chorem/pollen/services/service/security/SecurityService.java +++ b/pollen-services/src/main/java/org/chorem/pollen/services/service/security/SecurityService.java @@ -31,6 +31,16 @@ import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.SimplePrincipalCollection; import org.apache.shiro.subject.Subject; +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.BufferedBlockCipher; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.engines.RijndaelEngine; +import org.bouncycastle.crypto.modes.CBCBlockCipher; +import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; +import org.bouncycastle.crypto.paddings.ZeroBytePadding; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.util.encoders.Base64; import org.chorem.pollen.persistence.entity.Choice; import org.chorem.pollen.persistence.entity.Comment; import org.chorem.pollen.persistence.entity.CommentVisibility; @@ -46,11 +56,20 @@ import org.chorem.pollen.persistence.entity.ResultVisibility; import org.chorem.pollen.persistence.entity.SessionToken; import org.chorem.pollen.persistence.entity.Vote; import org.chorem.pollen.persistence.entity.VoteVisibility; +import org.chorem.pollen.services.PollenServiceContext; import org.chorem.pollen.services.bean.PaginationParameterBean; import org.chorem.pollen.services.bean.PollenEntityRef; import org.chorem.pollen.services.service.PollenServiceSupport; import org.nuiton.topia.persistence.TopiaEntity; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.security.Security; +import java.time.Clock; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.Calendar; import java.util.Date; import java.util.HashSet; @@ -69,6 +88,40 @@ public class SecurityService extends PollenServiceSupport { /** Logger. */ private static final Log log = LogFactory.getLog(SecurityService.class); + static { + Security.addProvider(new BouncyCastleProvider()); + } + + private BufferedBlockCipher cipher; + + @Override + public void setServiceContext(PollenServiceContext serviceContext) { + super.setServiceContext(serviceContext); + BlockCipher engine = new RijndaelEngine(256); + cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(engine), new ZeroBytePadding()); + + } + + @Override + public void checkIsConnected() { + + PollenSecurityContext securityContext = getSecurityContext(); + if (!securityContext.isConnected()) { + throw new PollenUnauthorizedException("connected"); + } + + } + + @Override + public void checkIsAdmin() { + + PollenSecurityContext securityContext = getSecurityContext(); + if (!securityContext.isAdmin()) { + throw new PollenUnauthorizedException("administrator"); + } + + } + public PollenEntityRef<PollenUser> login(String login, String password, Boolean rememberMe) throws PollenAuthenticationException { Subject subject = getSubject(); @@ -285,47 +338,137 @@ public class SecurityService extends PollenServiceSupport { } - public void checkIsConnected() { + public boolean isPermitted(String permission) { - PollenSecurityContext securityContext = getSecurityContext(); - if (!securityContext.isConnected()) { - throw new PollenUnauthorizedException("connected"); + Subject subject = getSubject(); + + if (log.isInfoEnabled()) { + log.info("Check permission: " + permission); } + return subject.isPermitted(permission); + } - public void checkIsAdmin() { + public void checkPermission(String permission) { - PollenSecurityContext securityContext = getSecurityContext(); - if (!securityContext.isAdmin()) { - throw new PollenUnauthorizedException("administrator"); + boolean valid = isPermitted(permission); + + if (!valid) { + throw new PollenInvalidPermissionException(permission); } } - public boolean isPermitted(String permission) { + public String encrypt(String userId, String token) throws PollenCypherTechnicalException { + try { + LocalDateTime date = LocalDateTime.now(Clock.systemUTC()); + date = date.plusDays(1); + long expired = date.toEpochSecond(ZoneOffset.UTC); - Subject subject = getSubject(); + String secret = getPollenServiceConfig().getSecret(); + String key = hashSha1(userId + expired, secret); + String encrytedValue = encrypt0(token, key); + String verifKey = hashSha1(userId + expired + token, key); - if (log.isInfoEnabled()) { - log.info("Check permission: " + permission); + return userId + "|" + expired + "|" + encrytedValue + "|" + verifKey; + + } catch (Exception e) { + throw new PollenCypherTechnicalException(e); } + } - return subject.isPermitted(permission); + public String decrypt(String encrytedValue) throws PollenCypherTechnicalException { + try { + if (encrytedValue == null) { + return null; + } + + String[] split = encrytedValue.split("\\|"); + if (split.length < 4) { + return null; + } + + LocalDateTime date = LocalDateTime.now(Clock.systemUTC()); + long now = date.toEpochSecond(ZoneOffset.UTC); + + long expired = Long.parseLong(split[1]); + if (expired < now) { + return null; + } + + String verifKey = split[3]; + String userId = split[0]; + String token = split[2]; + + String secret = getPollenServiceConfig().getSecret(); + String key = hashSha1(userId + expired, secret); + String decryptedValue = decrypt0(token, key); + String valueVerifKey = hashSha1(userId + expired + decryptedValue, key); + + if (!verifKey.equals(valueVerifKey)) { + return null; + } + return decryptedValue; + + } catch (Exception e) { + throw new PollenCypherTechnicalException(e); + } } - public void checkPermission(String permission) { + private String encrypt0(String value, String key) throws InvalidCipherTextException { - boolean valid = isPermitted(permission); + byte[] keyBytes = key.getBytes(); + cipher.init(true, new KeyParameter(keyBytes)); - if (!valid) { - throw new PollenInvalidPermissionException(permission); + byte[] input = value.getBytes(); + byte[] cipherText = new byte[cipher.getOutputSize(input.length)]; + + int cipherLength = cipher.processBytes(input, 0, input.length, cipherText, 0); + cipher.doFinal(cipherText, cipherLength); + + return new String(Base64.encode(cipherText)); + } + + private String decrypt0(String value, String key) throws InvalidCipherTextException { + + byte[] keyBytes = key.getBytes(); + cipher.init(false, new KeyParameter(keyBytes)); + + byte[] output = Base64.decode(value.getBytes()); + byte[] cipherText = new byte[cipher.getOutputSize(output.length)]; + + int cipherLength = cipher.processBytes(output, 0, output.length, cipherText, 0); + int outputLength = cipher.doFinal(cipherText, cipherLength); + outputLength += cipherLength; + + byte[] resultBytes = cipherText; + if (outputLength != output.length) { + resultBytes = new byte[outputLength]; + System.arraycopy(cipherText, 0, resultBytes, 0, outputLength); } + return new String(resultBytes); + } + + private String hashSha1(String value, String key) throws Exception { + + byte[] keyBytes = key.getBytes(); + SecretKey secretKey = new SecretKeySpec(keyBytes, "HMac-SHA1"); + + Mac mac = Mac.getInstance("HMac-SHA1", "BC"); + mac.init(secretKey); + mac.reset(); + + byte[] input = value.getBytes(); + mac.update(input, 0, input.length); + byte[] out = mac.doFinal(); + + return new String(Base64.encode(out)); } - protected Subject getSubject() { + private Subject getSubject() { PollenSecurityContext securityContext = getSecurityContext(); Preconditions.checkNotNull(securityContext); @@ -348,11 +491,10 @@ public class SecurityService extends PollenServiceSupport { principalCollection.addAll(permissions, PollenSecurityRealm.REALM_NAME); } - subject = new Subject. - Builder(). - authenticated(securityContext.isConnected()). - principals(principalCollection). - buildSubject(); + subject = new Subject.Builder() + .authenticated(securityContext.isConnected()) + .principals(principalCollection) + .buildSubject(); securityContext.setSubject(subject); } @@ -361,7 +503,7 @@ public class SecurityService extends PollenServiceSupport { } - protected Set<String> generatePermissions(PollenSecurityContext securityContext) { + private Set<String> generatePermissions(PollenSecurityContext securityContext) { boolean userIsAdmin = securityContext.isAdmin(); @@ -497,7 +639,7 @@ public class SecurityService extends PollenServiceSupport { } - protected void generatePollPublicPermission(Set<String> permissions, Poll poll) { + private void generatePollPublicPermission(Set<String> permissions, Poll poll) { permissions.add(createSubjectPermission(PermissionVerb.readPoll, poll)); permissions.add(createSubjectPermission(PermissionVerb.addComment, poll)); permissions.add(createSubjectPermission(PermissionVerb.addVote, poll)); @@ -581,7 +723,7 @@ public class SecurityService extends PollenServiceSupport { } } - protected PrincipalByType resolvePrincipals(Set<PollenPrincipal> principals) { + private PrincipalByType resolvePrincipals(Set<PollenPrincipal> principals) { PrincipalByType principalByType = new PrincipalByType(); for (PollenPrincipal principal : principals) { @@ -605,7 +747,7 @@ public class SecurityService extends PollenServiceSupport { } - protected void resolvePrincipal(PrincipalByType principalByType, PollenPrincipal principal) { + private void resolvePrincipal(PrincipalByType principalByType, PollenPrincipal principal) { // try a poll Poll poll = getPollDao().forCreatorEquals(principal).findUniqueOrNull(); @@ -639,19 +781,19 @@ public class SecurityService extends PollenServiceSupport { } - protected String createSubjectPermission(String people, PermissionVerb verb, TopiaEntity entity) { + private String createSubjectPermission(String people, PermissionVerb verb, TopiaEntity entity) { return people + ":" + verb.name() + ":" + entity.getTopiaId(); } - protected String createSubjectPermission(PermissionVerb verb, TopiaEntity entity) { + private String createSubjectPermission(PermissionVerb verb, TopiaEntity entity) { return createSubjectPermission("*", verb, entity); } - protected String createWildcardSubjectPermission(TopiaEntity entity) { + private String createWildcardSubjectPermission(TopiaEntity entity) { return "*:*:" + entity.getTopiaId(); diff --git a/pollen-services/src/main/resources/i18n/pollen-services_en_GB.properties b/pollen-services/src/main/resources/i18n/pollen-services_en_GB.properties index 7d0b986..a3475c4 100644 --- a/pollen-services/src/main/resources/i18n/pollen-services_en_GB.properties +++ b/pollen-services/src/main/resources/i18n/pollen-services_en_GB.properties @@ -17,6 +17,7 @@ pollen.configuration.defaultVoteCountingType=Default vote counting type used whe pollen.configuration.defaultVoteVisibility=Default vote visiblity pollen.configuration.devMode=Dev mode pollen.configuration.logConfigurationFile=Path to log configuration file +pollen.configuration.secret= pollen.configuration.sessionTimeoutDelay=Inactivity delay before invalidate the session of a user (in seconds) pollen.configuration.smptHost=Smtp Host pollen.configuration.smtpFrom=Smtp From diff --git a/pollen-services/src/main/resources/i18n/pollen-services_fr_FR.properties b/pollen-services/src/main/resources/i18n/pollen-services_fr_FR.properties index f3d3d62..4366d77 100644 --- a/pollen-services/src/main/resources/i18n/pollen-services_fr_FR.properties +++ b/pollen-services/src/main/resources/i18n/pollen-services_fr_FR.properties @@ -17,6 +17,7 @@ pollen.configuration.defaultVoteCountingType=Type de dépouillement par défaut pollen.configuration.defaultVoteVisibility=Visibilité des votes par défaut pollen.configuration.devMode=Mode développement pollen.configuration.logConfigurationFile=Chemin vers le fichier de configuration des logs +pollen.configuration.secret= pollen.configuration.sessionTimeoutDelay=Temps autorisé d'inactivité avant d'invalider une session utilisateur (en secondes) pollen.configuration.smptHost=Hôye smtp pollen.configuration.smtpFrom=Expéditeur diff --git a/pollen-services/src/test/java/org/chorem/pollen/services/service/PollenUIUrlRenderServiceTest.java b/pollen-services/src/test/java/org/chorem/pollen/services/service/PollenUIUrlRenderServiceTest.java index d270687..1a7d57d 100644 --- a/pollen-services/src/test/java/org/chorem/pollen/services/service/PollenUIUrlRenderServiceTest.java +++ b/pollen-services/src/test/java/org/chorem/pollen/services/service/PollenUIUrlRenderServiceTest.java @@ -54,11 +54,11 @@ public class PollenUIUrlRenderServiceTest extends AbstractPollenServiceTest { url = service.getPollEditUrl("PollId", null); Assert.assertNotNull(url); - Assert.assertEquals(serviceContext.getPollenServicesConfig().getUiEndPoint() + "#/poll/edit/PollId", url); + Assert.assertEquals(serviceContext.getPollenServicesConfig().getUiEndPoint() + "#poll/edit/PollId", url); url = service.getPollEditUrl("PollId", "Token"); Assert.assertNotNull(url); - Assert.assertEquals(serviceContext.getPollenServicesConfig().getUiEndPoint() + "#/poll/edit/PollId/Token", url); + Assert.assertEquals(serviceContext.getPollenServicesConfig().getUiEndPoint() + "#poll/edit/PollId/Token", url); } @Test @@ -67,11 +67,11 @@ public class PollenUIUrlRenderServiceTest extends AbstractPollenServiceTest { url = service.getPollVoteUrl("PollId", null); Assert.assertNotNull(url); - Assert.assertEquals(serviceContext.getPollenServicesConfig().getUiEndPoint() + "#/poll/vote/PollId", url); + Assert.assertEquals(serviceContext.getPollenServicesConfig().getUiEndPoint() + "#poll/vote/PollId", url); url = service.getPollVoteUrl("PollId", "Token"); Assert.assertNotNull(url); - Assert.assertEquals(serviceContext.getPollenServicesConfig().getUiEndPoint() + "#/poll/vote/PollId/Token", url); + Assert.assertEquals(serviceContext.getPollenServicesConfig().getUiEndPoint() + "#poll/vote/PollId/Token", url); } @Test @@ -80,11 +80,11 @@ public class PollenUIUrlRenderServiceTest extends AbstractPollenServiceTest { url = service.getPollVoteEditUrl("PollId", null, "VoteToken"); Assert.assertNotNull(url); - Assert.assertEquals(serviceContext.getPollenServicesConfig().getUiEndPoint() + "#/poll/vote/PollId/vote/VoteToken", url); + Assert.assertEquals(serviceContext.getPollenServicesConfig().getUiEndPoint() + "#poll/vote/PollId/vote/VoteToken", url); url = service.getPollVoteEditUrl("PollId", "Token", "VoteToken"); Assert.assertNotNull(url); - Assert.assertEquals(serviceContext.getPollenServicesConfig().getUiEndPoint() + "#/poll/vote/PollId/Token/vote/VoteToken", url); + Assert.assertEquals(serviceContext.getPollenServicesConfig().getUiEndPoint() + "#poll/vote/PollId/Token/vote/VoteToken", url); } @Test @@ -94,7 +94,7 @@ public class PollenUIUrlRenderServiceTest extends AbstractPollenServiceTest { url = service.getUserValidateUrl("UserId", "Token"); Assert.assertNotNull(url); - Assert.assertEquals(serviceContext.getPollenServicesConfig().getUiEndPoint() + "#/user/UserId/Token", url); + Assert.assertEquals(serviceContext.getPollenServicesConfig().getUiEndPoint() + "#signcheck/UserId/Token", url); } } diff --git a/pollen-ui-riot-js/src/main/web/index.js b/pollen-ui-riot-js/src/main/web/index.js index d25dda0..a432beb 100644 --- a/pollen-ui-riot-js/src/main/web/index.js +++ b/pollen-ui-riot-js/src/main/web/index.js @@ -1,9 +1,4 @@ -let i18n = require("./js/I18nHelper"); -i18n.bundles = require("./i18n.json"); -riot.mixin(i18n); - -let session = require("./js/Session"); -session.configuration = require("./conf.json"); +riot.mixin(require("./js/I18nHelper")); require("./tag/Pollen.tag"); riot.mount("*"); diff --git a/pollen-ui-riot-js/src/main/web/js/AuthService.js b/pollen-ui-riot-js/src/main/web/js/AuthService.js index 0efdabb..c2444c4 100644 --- a/pollen-ui-riot-js/src/main/web/js/AuthService.js +++ b/pollen-ui-riot-js/src/main/web/js/AuthService.js @@ -6,14 +6,15 @@ let FetchService = require("./FetchService"); class AuthService extends FetchService { signIn(login, password) { - return this.form("/v1/login", {login: login, password: password}) - .then((auth) => { - if (!auth) { - return Promise.reject(false); - } - session.signIn(auth, this); - return true; - }); + return this.fetch("/v1/login","POST", { + Authorization: "Basic " + btoa(login + ":" + password) + }, null).then((auth) => { + if (!auth) { + return Promise.reject(false); + } + session.signIn(auth, this); + return true; + }); } signUp(user) { diff --git a/pollen-ui-riot-js/src/main/web/js/FetchService.js b/pollen-ui-riot-js/src/main/web/js/FetchService.js index 41d36e4..bb285bf 100644 --- a/pollen-ui-riot-js/src/main/web/js/FetchService.js +++ b/pollen-ui-riot-js/src/main/web/js/FetchService.js @@ -9,13 +9,6 @@ class FetchService { headers["Content-Type"] = "application/json"; } - if (session.isConnected()) { - console.info("CONNNNNNECTED!!!") - - headers["X-Pollen-Session-Token"] = session.auth.permission; - } else { - console.info("NONONONOONONONON CONNNNNNECTED!!!") - } return fetch( session.configuration.endPoint + url, { headers, diff --git a/pollen-ui-riot-js/src/main/web/js/I18nHelper.js b/pollen-ui-riot-js/src/main/web/js/I18nHelper.js index 6d3143f..d91991b 100644 --- a/pollen-ui-riot-js/src/main/web/js/I18nHelper.js +++ b/pollen-ui-riot-js/src/main/web/js/I18nHelper.js @@ -1,5 +1,9 @@ module.exports = { + init() { + this.bundles = require("../i18n.json"); + }, + installBundle(locale, value, emitter) { this.generateBundle(locale, value); emitter.onLocaleChanged((locale) => { diff --git a/pollen-ui-riot-js/src/main/web/js/Session.js b/pollen-ui-riot-js/src/main/web/js/Session.js index 2959f13..2b25621 100644 --- a/pollen-ui-riot-js/src/main/web/js/Session.js +++ b/pollen-ui-riot-js/src/main/web/js/Session.js @@ -4,12 +4,10 @@ let emitter = require("./EmitterService"); class Session { constructor() { - // pour contenir les informations de connexion - this.auth = null; // pour contenir la locale à utiliser this.locale = null; // pour contenir la configuration - this.configuration = null; + this.configuration = require('../conf.json'); // pour contenir l'utillisateur connecté this.user = null; @@ -25,12 +23,10 @@ class Session { } isConnected() { - return !!this.auth; - // return document.cookie.indexOf("pollen-connected=true") !== -1; + return document.cookie.indexOf("pollen-connected=true") !== -1; } signIn(auth, userService) { - this.auth = auth; console.info("SignIn::"); console.info(auth); userService.userPromise(auth).then((user) => { @@ -49,7 +45,6 @@ class Session { } signOut() { - delete this.auth; delete this.user; } diff --git a/pollen-ui-riot-js/src/main/web/tag/SignIn.tag b/pollen-ui-riot-js/src/main/web/tag/SignIn.tag index 8460923..c1a0863 100644 --- a/pollen-ui-riot-js/src/main/web/tag/SignIn.tag +++ b/pollen-ui-riot-js/src/main/web/tag/SignIn.tag @@ -46,7 +46,7 @@ require("./popup/NewPassword.tag"); route(this.opts.link || "/"); }) .catch((code) => { - this.message = this.t("error_signin"); + this.message = this.__.error_signin; this.update(); }); }; diff --git a/pom.xml b/pom.xml index a6bf0a4..fb29f64 100644 --- a/pom.xml +++ b/pom.xml @@ -184,7 +184,7 @@ <topiaVersion>3.2</topiaVersion> <nuitonWebVersion>1.19-SNAPSHOT</nuitonWebVersion> - <nuitonUtilsVersion>3.0-rc-16</nuitonUtilsVersion> + <nuitonUtilsVersion>3.0-rc-17</nuitonUtilsVersion> <nuitonConfigVersion>3.1.1</nuitonConfigVersion> <nuitonCsvVersion>3.0-alpha-3</nuitonCsvVersion> <nuitonValidatorVersion>3.0.1</nuitonValidatorVersion> @@ -290,7 +290,7 @@ <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> - <version>20.0</version> + <version>21.0</version> </dependency> <!-- persistence module dependencies --> @@ -571,6 +571,13 @@ <scope>provided</scope> </dependency> + + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk16</artifactId> + <version>1.46</version> + </dependency> + <!--dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> @@ -644,7 +651,7 @@ <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> - <version>2.5.3</version> + <version>2.6.2</version> <scope>test</scope> </dependency> <dependency> -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.