diff --git a/framework/service/org/moqui/impl/UserServices.xml b/framework/service/org/moqui/impl/UserServices.xml index a82c80322..2cb01c913 100644 --- a/framework/service/org/moqui/impl/UserServices.xml +++ b/framework/service/org/moqui/impl/UserServices.xml @@ -96,7 +96,7 @@ along with this software (see the LICENSE.md file). If not, see - + diff --git a/framework/src/main/groovy/org/moqui/impl/context/ArtifactExecutionInfoImpl.java b/framework/src/main/groovy/org/moqui/impl/context/ArtifactExecutionInfoImpl.java index 67fe40d54..fa4f0e680 100644 --- a/framework/src/main/groovy/org/moqui/impl/context/ArtifactExecutionInfoImpl.java +++ b/framework/src/main/groovy/org/moqui/impl/context/ArtifactExecutionInfoImpl.java @@ -180,7 +180,7 @@ public long getChildrenRunningTime() { @Override public ArtifactExecutionInfo getParent() { return parentAeii; } @Override - public BigDecimal getPercentOfParentTime() { return parentAeii != null && endTimeNanos != 0 ? + public BigDecimal getPercentOfParentTime() { return parentAeii != null && endTimeNanos != 0 && parentAeii.endTimeNanos != 0 ? new BigDecimal((getRunningTime() / parentAeii.getRunningTime()) * 100).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO; } diff --git a/framework/src/main/groovy/org/moqui/impl/context/ExecutionContextFactoryImpl.groovy b/framework/src/main/groovy/org/moqui/impl/context/ExecutionContextFactoryImpl.groovy index c1804561d..f531f9216 100644 --- a/framework/src/main/groovy/org/moqui/impl/context/ExecutionContextFactoryImpl.groovy +++ b/framework/src/main/groovy/org/moqui/impl/context/ExecutionContextFactoryImpl.groovy @@ -1642,6 +1642,7 @@ class ExecutionContextFactoryImpl implements ExecutionContextFactory { if (overrideNode.hasChild("user-facade")) { MNode ufBaseNode = baseNode.first("user-facade") MNode ufOverrideNode = overrideNode.first("user-facade") + ufBaseNode.attributes.putAll(ufOverrideNode.attributes) ufBaseNode.mergeSingleChild(ufOverrideNode, "password") ufBaseNode.mergeSingleChild(ufOverrideNode, "login-key") ufBaseNode.mergeSingleChild(ufOverrideNode, "login") diff --git a/framework/src/main/groovy/org/moqui/impl/context/ResourceFacadeImpl.groovy b/framework/src/main/groovy/org/moqui/impl/context/ResourceFacadeImpl.groovy index e45937d9b..840600012 100644 --- a/framework/src/main/groovy/org/moqui/impl/context/ResourceFacadeImpl.groovy +++ b/framework/src/main/groovy/org/moqui/impl/context/ResourceFacadeImpl.groovy @@ -24,6 +24,7 @@ import org.moqui.impl.context.runner.JavaxScriptRunner import org.moqui.impl.context.runner.XmlActionsScriptRunner import org.moqui.impl.entity.EntityValueBase import org.moqui.jcache.MCache +import org.moqui.security.SingleSignOnTokenLoginHandler import org.moqui.util.ContextBinding import org.moqui.util.ContextStack import org.moqui.util.MNode @@ -59,6 +60,7 @@ class ResourceFacadeImpl implements ResourceFacade { final FtlTemplateRenderer ftlTemplateRenderer final XmlActionsScriptRunner xmlActionsScriptRunner + protected final ToolFactory ssoTokenHandlerFactory = null // the groovy Script object is not thread safe, so have one per thread per expression; can be reused as thread is reused protected final ThreadLocal> threadScriptByExpression = new ThreadLocal<>() @@ -164,6 +166,16 @@ class ResourceFacadeImpl implements ResourceFacade { logger.error("Error getting JCR Repository ${repositoryNode.attribute("name")}: ${e.toString()}") } } + + MNode userFacadeNode = ecfi.confXmlRoot.first("user-facade") + if (userFacadeNode.attribute("sso-access-token-handler-factory")) { + ssoTokenHandlerFactory = ecfi.getToolFactory(userFacadeNode.attribute("sso-access-token-handler-factory")) + if (ssoTokenHandlerFactory != null) { + logger.info("Using sso-access-token-handler-factory ${userFacadeNode.attribute("sso-access-token-handler-factory")} (${ssoTokenHandlerFactory.class.name})") + } else { + logger.warn("Could not find sso-access-token-handler-factory with name ${userFacadeNode.attribute("sso-access-token-handler-factory")}") + } + } } void destroyAllInThread() { diff --git a/framework/src/main/groovy/org/moqui/impl/context/UserFacadeImpl.groovy b/framework/src/main/groovy/org/moqui/impl/context/UserFacadeImpl.groovy index abd9f7b23..d23c7b7e9 100644 --- a/framework/src/main/groovy/org/moqui/impl/context/UserFacadeImpl.groovy +++ b/framework/src/main/groovy/org/moqui/impl/context/UserFacadeImpl.groovy @@ -17,6 +17,7 @@ import groovy.transform.CompileStatic import org.apache.shiro.authc.AuthenticationToken import org.apache.shiro.authc.ExpiredCredentialsException import org.moqui.context.PasswordChangeRequiredException +import org.moqui.security.SingleSignOnTokenLoginHandler import javax.websocket.server.HandshakeRequest import java.sql.Timestamp @@ -172,6 +173,14 @@ class UserFacadeImpl implements UserFacade { if (loginKey != null && !loginKey.isEmpty() && !"null".equals(loginKey) && !"undefined".equals(loginKey)) this.loginUserKey(loginKey) } + if (currentInfo.username == null && request.getHeader("sso_access_token")) { + String ssoAccessToken = request.getHeader("sso_access_token").trim() + String ssoAuthFlowId = request.getHeader("sso_auth_flow") + if (ssoAuthFlowId) + ssoAuthFlowId = ssoAuthFlowId.trim() + if (!ssoAccessToken.isEmpty() && !"null".equals(ssoAccessToken) && !"undefined".equals(ssoAccessToken)) + this.loginSsoToken(ssoAccessToken, ssoAuthFlowId, request, response) + } if (currentInfo.username == null && secureParameters.authUsername) { // try the Moqui-specific parameters for instant login // if we have credentials coming in anywhere other than URL parameters, try logging in @@ -802,6 +811,15 @@ class UserFacadeImpl implements UserFacade { return loginKey } + @Override boolean loginSsoToken(String ssoAccessToken, String ssoAuthFlowId, HttpServletRequest request, HttpServletResponse response) { + if (eci.resourceFacade.ssoTokenHandlerFactory == null) { + eci.logger.error("No SingleSignOnTokenLoginHandler ToolFactory configured, cannot handle SsoToken login") + return false + } + final SingleSignOnTokenLoginHandler ssoTokenLoginHandler = eci.resourceFacade.ssoTokenHandlerFactory.getInstance() + return ssoTokenLoginHandler.handleSsoLoginToken(eci, request, response, ssoAccessToken, ssoAuthFlowId) + } + @Override boolean loginAnonymousIfNoUser() { if (currentInfo.username == null && !currentInfo.loggedInAnonymous) { currentInfo.loggedInAnonymous = true diff --git a/framework/src/main/java/org/moqui/context/UserFacade.java b/framework/src/main/java/org/moqui/context/UserFacade.java index 209534a85..a52b11567 100644 --- a/framework/src/main/java/org/moqui/context/UserFacade.java +++ b/framework/src/main/java/org/moqui/context/UserFacade.java @@ -15,6 +15,8 @@ import org.moqui.entity.EntityValue; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.sql.Timestamp; import java.util.*; @@ -105,6 +107,12 @@ public interface UserFacade { String getLoginKey(); String getLoginKey(float expireHours); + /** Authenticate a user and make active using a SSO access token + * @param ssoAccessToken the accessToken provided by the SSO server + * @param ssoAuthFlowId the (optional) authFlowId for identifying the SSO server + */ + boolean loginSsoToken(String ssoAccessToken, String ssoAuthFlowId, HttpServletRequest request, HttpServletResponse response); + /** If no user is logged in consider an anonymous user logged in. For internal purposes to run things that require authentication. */ boolean loginAnonymousIfNoUser(); diff --git a/framework/src/main/java/org/moqui/security/SingleSignOnTokenLoginHandler.java b/framework/src/main/java/org/moqui/security/SingleSignOnTokenLoginHandler.java new file mode 100644 index 000000000..9668942e1 --- /dev/null +++ b/framework/src/main/java/org/moqui/security/SingleSignOnTokenLoginHandler.java @@ -0,0 +1,10 @@ +package org.moqui.security; + +import org.moqui.context.ExecutionContext; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public interface SingleSignOnTokenLoginHandler { + public boolean handleSsoLoginToken(ExecutionContext ec, HttpServletRequest request, HttpServletResponse response, String ssoAccessToken, String ssoAuthFlowId); +} diff --git a/framework/xsd/moqui-conf-3.xsd b/framework/xsd/moqui-conf-3.xsd index d060a6176..7d623cf51 100644 --- a/framework/xsd/moqui-conf-3.xsd +++ b/framework/xsd/moqui-conf-3.xsd @@ -335,6 +335,7 @@ along with this software (see the LICENSE.md file). If not, see +