Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SSO login using access token #638

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion framework/service/org/moqui/impl/UserServices.xml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ along with this software (see the LICENSE.md file). If not, see
</else-if></if>
</actions>
</service>
<service verb="update" noun="UserAccount">
<service verb="update" noun="UserAccount" authenticate="anonymous-all" allow-remote="false">
<in-parameters>
<parameter name="userId" required="true"/>
<auto-parameters entity-name="moqui.security.UserAccount" include="nonpk">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; }


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -59,6 +60,7 @@ class ResourceFacadeImpl implements ResourceFacade {

final FtlTemplateRenderer ftlTemplateRenderer
final XmlActionsScriptRunner xmlActionsScriptRunner
protected final ToolFactory<SingleSignOnTokenLoginHandler> 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<Map<String, Script>> threadScriptByExpression = new ThreadLocal<>()
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions framework/src/main/java/org/moqui/context/UserFacade.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;

Expand Down Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
1 change: 1 addition & 0 deletions framework/xsd/moqui-conf-3.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ along with this software (see the LICENSE.md file). If not, see
<xs:element minOccurs="0" ref="login-key"/>
<xs:element minOccurs="0" ref="login"/>
</xs:sequence>
<xs:attribute name="sso-access-token-handler-factory" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="password">
Expand Down
Loading