From 32b186219b18df11bb481c8e772581316494eaac Mon Sep 17 00:00:00 2001 From: Reto Breitenmoser Date: Thu, 12 Oct 2017 16:42:07 +0200 Subject: [PATCH] [Major] added single sign on support --- .../rest/DefaultStrolchSessionHandler.java | 10 + .../strolch/rest/StrolchSessionHandler.java | 2 + .../rest/endpoint/AuthenticationService.java | 212 +++++++++++------- 3 files changed, 144 insertions(+), 80 deletions(-) diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java b/li.strolch.rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java index 93e97882b..f5e2845dc 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java @@ -144,6 +144,16 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St return certificate; } + + @Override + public Certificate authenticateSingleSignOn(Object data) { + Certificate certificate = this.privilegeHandler.authenticateSingleSignOn(data); + + this.certificateMap.put(certificate.getAuthToken(), certificate); + logger.info(MessageFormat.format("{0} sessions currently active.", this.certificateMap.size())); //$NON-NLS-1$ + + return certificate; + } @Override public Certificate validate(String authToken) throws StrolchNotAuthenticatedException { diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/StrolchSessionHandler.java b/li.strolch.rest/src/main/java/li/strolch/rest/StrolchSessionHandler.java index 356b04fe9..bddfa2942 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/StrolchSessionHandler.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/StrolchSessionHandler.java @@ -31,6 +31,8 @@ import li.strolch.rest.model.UserSession; public interface StrolchSessionHandler { Certificate authenticate(String username, char[] password); + + Certificate authenticateSingleSignOn(Object data); Certificate validate(String authToken) throws StrolchNotAuthenticatedException; diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java index ba5226247..da8d8aaed 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java @@ -15,16 +15,37 @@ */ package li.strolch.rest.endpoint; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.*; -import javax.ws.rs.core.*; -import javax.ws.rs.core.Response.Status; import java.text.MessageFormat; import java.util.Base64; import java.util.Set; import java.util.concurrent.TimeUnit; -import com.google.gson.*; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.HEAD; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.NewCookie; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; + import li.strolch.exception.StrolchException; import li.strolch.privilege.base.AccessDeniedException; import li.strolch.privilege.base.InvalidCredentialsException; @@ -39,8 +60,6 @@ import li.strolch.rest.StrolchSessionHandler; import li.strolch.rest.helper.ResponseUtil; import li.strolch.runtime.privilege.PrivilegeHandler; import li.strolch.utils.helper.ExceptionHelper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * @author Robert von Burg @@ -73,9 +92,8 @@ public class AuthenticationService { sb.append("Password was not given!"); //$NON-NLS-1$ } - char[] password = passwordE == null ? - new char[] {} : - new String(Base64.getDecoder().decode(passwordE.getAsString())).toCharArray(); + char[] password = passwordE == null ? new char[] {} + : new String(Base64.getDecoder().decode(passwordE.getAsString())).toCharArray(); if (password.length < 3) { if (sb.length() > 0) sb.append("\n"); @@ -83,74 +101,14 @@ public class AuthenticationService { } if (sb.length() != 0) { - loginResult.addProperty("msg", - MessageFormat.format("Could not log in due to: {0}", sb.toString())); //$NON-NLS-1$ + loginResult.addProperty("msg", MessageFormat.format("Could not log in due to: {0}", sb.toString())); //$NON-NLS-2$ return Response.status(Status.BAD_REQUEST).entity(loginResult).build(); } StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getSessionHandler(); Certificate certificate = sessionHandler.authenticate(usernameE.getAsString(), password); - PrivilegeHandler privilegeHandler = RestfulStrolchComponent.getInstance().getContainer() - .getPrivilegeHandler(); - PrivilegeContext privilegeContext = privilegeHandler.validate(certificate); - loginResult.addProperty("sessionId", certificate.getSessionId()); - loginResult.addProperty("authToken", certificate.getAuthToken()); - loginResult.addProperty("username", certificate.getUsername()); - loginResult.addProperty("firstname", certificate.getFirstname()); - loginResult.addProperty("lastname", certificate.getLastname()); - loginResult.addProperty("locale", certificate.getLocale().toString()); - - if (!certificate.getPropertyMap().isEmpty()) { - JsonObject propObj = new JsonObject(); - loginResult.add("properties", propObj); - for (String propKey : certificate.getPropertyMap().keySet()) { - propObj.addProperty(propKey, certificate.getPropertyMap().get(propKey)); - } - } - - if (!certificate.getUserRoles().isEmpty()) { - JsonArray rolesArr = new JsonArray(); - loginResult.add("roles", rolesArr); - for (String role : certificate.getUserRoles()) { - rolesArr.add(new JsonPrimitive(role)); - } - } - - if (!privilegeContext.getPrivilegeNames().isEmpty()) { - JsonArray privArr = new JsonArray(); - loginResult.add("privileges", privArr); - - for (String name : privilegeContext.getPrivilegeNames()) { - IPrivilege privilege = privilegeContext.getPrivilege(name); - - JsonObject privObj = new JsonObject(); - privArr.add(privObj); - - privObj.addProperty("name", name); - privObj.addProperty("allAllowed", privilege.isAllAllowed()); - - Set allowSet = privilege.getAllowList(); - if (!allowSet.isEmpty()) { - JsonArray allowArr = new JsonArray(); - privObj.add("allowList", allowArr); - for (String allow : allowSet) { - allowArr.add(new JsonPrimitive(allow)); - } - } - } - } - - boolean secureCookie = RestfulStrolchComponent.getInstance().isSecureCookie(); - if (secureCookie && !request.getScheme().equals("https")) { - logger.warn( - "Authorization cookie is secure, but connection is not secure! Cookie won't be passed to client!"); - } - NewCookie cookie = new NewCookie(StrolchRestfulConstants.STROLCH_AUTHORIZATION, certificate.getAuthToken(), - "/", null, "Authorization header", (int) TimeUnit.DAYS.toSeconds(1), secureCookie); - - return Response.ok().entity(loginResult.toString())// - .header(HttpHeaders.AUTHORIZATION, certificate.getAuthToken()).cookie(cookie).build(); + return getAuthenticationResponse(request, loginResult, certificate); } catch (InvalidCredentialsException e) { logger.error("Authentication failed due to: " + e.getMessage()); @@ -158,13 +116,45 @@ public class AuthenticationService { return Response.status(Status.UNAUTHORIZED).entity(loginResult.toString()).build(); } catch (AccessDeniedException e) { logger.error("Authentication failed due to: " + e.getMessage()); - loginResult.addProperty("msg", - MessageFormat.format("Could not log in due to: {0}", e.getMessage())); //$NON-NLS-1$ + loginResult.addProperty("msg", MessageFormat.format("Could not log in due to: {0}", e.getMessage())); //$NON-NLS-2$ return Response.status(Status.UNAUTHORIZED).entity(loginResult.toString()).build(); } catch (StrolchException | PrivilegeException e) { logger.error(e.getMessage(), e); - loginResult.addProperty("msg", - MessageFormat.format("Could not log in due to: {0}", e.getMessage())); //$NON-NLS-1$ + loginResult.addProperty("msg", MessageFormat.format("Could not log in due to: {0}", e.getMessage())); //$NON-NLS-2$ + return Response.status(Status.FORBIDDEN).entity(loginResult.toString()).build(); + } catch (Exception e) { + logger.error(e.getMessage(), e); + String msg = e.getMessage(); + loginResult.addProperty("msg", MessageFormat.format("{0}: {1}", e.getClass().getName(), msg)); //$NON-NLS-1$ + return Response.serverError().entity(loginResult.toString()).build(); + } + } + + @POST + @Produces(MediaType.APPLICATION_JSON) + @Path("sso") + public Response authenticateSingleSignOn(@Context HttpServletRequest request, @Context HttpHeaders headers) { + + JsonObject loginResult = new JsonObject(); + + try { + + StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getSessionHandler(); + Certificate certificate = sessionHandler.authenticateSingleSignOn(request.getUserPrincipal()); + + return getAuthenticationResponse(request, loginResult, certificate); + + } catch (InvalidCredentialsException e) { + logger.error("Authentication failed due to: " + e.getMessage()); + loginResult.addProperty("msg", "Could not log in as the given credentials are invalid"); //$NON-NLS-1$ + return Response.status(Status.UNAUTHORIZED).entity(loginResult.toString()).build(); + } catch (AccessDeniedException e) { + logger.error("Authentication failed due to: " + e.getMessage()); + loginResult.addProperty("msg", MessageFormat.format("Could not log in due to: {0}", e.getMessage())); //$NON-NLS-2$ + return Response.status(Status.UNAUTHORIZED).entity(loginResult.toString()).build(); + } catch (StrolchException | PrivilegeException e) { + logger.error(e.getMessage(), e); + loginResult.addProperty("msg", MessageFormat.format("Could not log in due to: {0}", e.getMessage())); //$NON-NLS-2$ return Response.status(Status.FORBIDDEN).entity(loginResult.toString()).build(); } catch (Exception e) { logger.error(e.getMessage(), e); @@ -196,14 +186,12 @@ public class AuthenticationService { } catch (StrolchException | PrivilegeException e) { logger.error("Failed to invalidate session due to: " + e.getMessage()); - logoutResult.addProperty("msg", - MessageFormat.format("Could not logout due to: {0}", e.getMessage())); //$NON-NLS-1$ + logoutResult.addProperty("msg", MessageFormat.format("Could not logout due to: {0}", e.getMessage())); //$NON-NLS-2$ return Response.status(Status.UNAUTHORIZED).entity(logoutResult.toString()).build(); } catch (Exception e) { logger.error(e.getMessage(), e); String msg = e.getMessage(); - logoutResult - .addProperty("msg", MessageFormat.format("{0}: {1}", e.getClass().getName(), msg)); //$NON-NLS-1$ + logoutResult.addProperty("msg", MessageFormat.format("{0}: {1}", e.getClass().getName(), msg)); //$NON-NLS-1$ return Response.serverError().entity(logoutResult.toString()).build(); } } @@ -298,4 +286,68 @@ public class AuthenticationService { return Response.status(Status.UNAUTHORIZED).entity(json).build(); } } + + private Response getAuthenticationResponse(HttpServletRequest request, JsonObject loginResult, + Certificate certificate) { + + PrivilegeHandler privilegeHandler = RestfulStrolchComponent.getInstance().getContainer().getPrivilegeHandler(); + PrivilegeContext privilegeContext = privilegeHandler.validate(certificate); + loginResult.addProperty("sessionId", certificate.getSessionId()); + loginResult.addProperty("authToken", certificate.getAuthToken()); + loginResult.addProperty("username", certificate.getUsername()); + loginResult.addProperty("firstname", certificate.getFirstname()); + loginResult.addProperty("lastname", certificate.getLastname()); + loginResult.addProperty("locale", certificate.getLocale().toString()); + + if (!certificate.getPropertyMap().isEmpty()) { + JsonObject propObj = new JsonObject(); + loginResult.add("properties", propObj); + for (String propKey : certificate.getPropertyMap().keySet()) { + propObj.addProperty(propKey, certificate.getPropertyMap().get(propKey)); + } + } + + if (!certificate.getUserRoles().isEmpty()) { + JsonArray rolesArr = new JsonArray(); + loginResult.add("roles", rolesArr); + for (String role : certificate.getUserRoles()) { + rolesArr.add(new JsonPrimitive(role)); + } + } + + if (!privilegeContext.getPrivilegeNames().isEmpty()) { + JsonArray privArr = new JsonArray(); + loginResult.add("privileges", privArr); + + for (String name : privilegeContext.getPrivilegeNames()) { + IPrivilege privilege = privilegeContext.getPrivilege(name); + + JsonObject privObj = new JsonObject(); + privArr.add(privObj); + + privObj.addProperty("name", name); + privObj.addProperty("allAllowed", privilege.isAllAllowed()); + + Set allowSet = privilege.getAllowList(); + if (!allowSet.isEmpty()) { + JsonArray allowArr = new JsonArray(); + privObj.add("allowList", allowArr); + for (String allow : allowSet) { + allowArr.add(new JsonPrimitive(allow)); + } + } + } + } + + boolean secureCookie = RestfulStrolchComponent.getInstance().isSecureCookie(); + if (secureCookie && !request.getScheme().equals("https")) { + logger.warn( + "Authorization cookie is secure, but connection is not secure! Cookie won't be passed to client!"); + } + NewCookie cookie = new NewCookie(StrolchRestfulConstants.STROLCH_AUTHORIZATION, certificate.getAuthToken(), "/", + null, "Authorization header", (int) TimeUnit.DAYS.toSeconds(1), secureCookie); + + return Response.ok().entity(loginResult.toString())// + .header(HttpHeaders.AUTHORIZATION, certificate.getAuthToken()).cookie(cookie).build(); + } }