From 6e1afc4cfd6532a8b3dfd9e49565e63cb1fa72a7 Mon Sep 17 00:00:00 2001 From: "ar.anjomshoaa@gmail.com" Date: Fri, 21 Jul 2023 09:51:39 -0500 Subject: [PATCH] Update the pac4j libraries, implement the DefaultLogic for logout and login and callback, replace the j2e with jee package --- build.gradle | 7 +- .../moqui/authentication/AuthorizeJWT.groovy | 5 +- .../org/mk/moqui/authentication/Login.groovy | 86 ++++++++++--------- .../authentication/MoquiProfileManager.groovy | 43 ---------- .../authentication/OidcClientFactory.groovy | 8 +- 5 files changed, 56 insertions(+), 93 deletions(-) delete mode 100644 src/main/groovy/org/mk/moqui/authentication/MoquiProfileManager.groovy diff --git a/build.gradle b/build.gradle index 8d013ea..c0938d5 100644 --- a/build.gradle +++ b/build.gradle @@ -18,9 +18,10 @@ dependencies { implementation 'org.apache.commons:commons-collections4:4.4' // pac4j - implementation 'org.pac4j:pac4j-oidc:3.0.2' - implementation 'org.pac4j:pac4j-jwt:3.0.2' -// implementation 'org.pac4j:jee-pac4j:5.0.0' + implementation 'org.pac4j:pac4j-core:5.7.1' + implementation 'org.pac4j:pac4j-javaee:5.7.1' + implementation 'org.pac4j:pac4j-oidc:5.7.1' + implementation 'org.pac4j:pac4j-oauth:5.7.1' } check.dependsOn.clear() diff --git a/src/main/groovy/org/mk/moqui/authentication/AuthorizeJWT.groovy b/src/main/groovy/org/mk/moqui/authentication/AuthorizeJWT.groovy index d273a21..7138dd1 100644 --- a/src/main/groovy/org/mk/moqui/authentication/AuthorizeJWT.groovy +++ b/src/main/groovy/org/mk/moqui/authentication/AuthorizeJWT.groovy @@ -4,6 +4,7 @@ import com.nimbusds.jose.JOSEException import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory import com.nimbusds.jose.jwk.JWKSet import com.nimbusds.jose.jwk.KeyConverter +import com.nimbusds.jose.proc.SimpleSecurityContext import com.nimbusds.jwt.SignedJWT import com.nimbusds.jwt.proc.BadJWTException import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier @@ -68,7 +69,7 @@ if (eci.user.userId) { def set = JWKSet.load(metadata.getJWKSetURI().toURL()) // Parse the token so we can use the header - def jwt = SignedJWT.parse(context.token) + def jwt = SignedJWT.parse(ec.context.token) // grab the sessionId String sessionId = jwt.payload.toJSONObject().get("session_state") as String @@ -93,7 +94,7 @@ if (eci.user.userId) { jwt.verify(verifier) // Verify JWT Claims (expiration/not before date) - claimsVerifier.verify(jwt.JWTClaimsSet) + claimsVerifier.verify(jwt.JWTClaimsSet, new SimpleSecurityContext()) // Log user in using the username in the token eci.userFacade.internalLoginUser(jwt.JWTClaimsSet.getStringClaim("preferred_username")) diff --git a/src/main/groovy/org/mk/moqui/authentication/Login.groovy b/src/main/groovy/org/mk/moqui/authentication/Login.groovy index a107552..85eae7b 100644 --- a/src/main/groovy/org/mk/moqui/authentication/Login.groovy +++ b/src/main/groovy/org/mk/moqui/authentication/Login.groovy @@ -3,27 +3,25 @@ package org.mk.moqui.authentication import groovy.transform.CompileStatic import org.moqui.context.ExecutionContext import org.moqui.entity.EntityFacade +import org.moqui.impl.context.UserFacadeImpl import org.pac4j.core.client.Client import org.pac4j.core.config.Config -import org.pac4j.core.context.DefaultAuthorizers -import org.pac4j.core.context.J2EContext +import org.pac4j.core.authorization.authorizer.DefaultAuthorizers +import org.pac4j.core.context.session.SessionStore +import org.pac4j.jee.context.JEEContext +import org.pac4j.core.profile.ProfileManager import org.pac4j.core.context.WebContext -import org.pac4j.core.context.session.J2ESessionStore -import org.pac4j.core.engine.DefaultCallbackLogic +import org.pac4j.jee.context.session.JEESessionStore import org.pac4j.core.engine.DefaultLogoutLogic import org.pac4j.core.engine.DefaultSecurityLogic +import org.pac4j.core.engine.DefaultCallbackLogic import org.pac4j.core.engine.SecurityGrantedAccessAdapter -import org.pac4j.core.http.adapter.J2ENopHttpActionAdapter -import org.pac4j.core.profile.CommonProfile -import org.pac4j.core.profile.ProfileManager +import org.pac4j.core.profile.UserProfile +import org.pac4j.jee.http.adapter.JEEHttpActionAdapter -import javax.servlet.http.HttpServletResponse -import javax.servlet.http.WebConnection -import java.util.function.Function @CompileStatic class Login { - static final J2ESessionStore sessionStore = new J2ESessionStore() static Config globalConfig static List clientFactories = [ @@ -46,27 +44,23 @@ class Login { .collect { entity -> entity.clientId as String } } - static J2EContext buildContext(ExecutionContext ec) { + static JEEContext buildContext(ExecutionContext ec) { def request = ec.getWeb().getRequest() def response = ec.getWeb().getResponse() - return new J2EContext(request, response, sessionStore) + return new JEEContext(request, response) } static String getMoquiUrl(ExecutionContext ec) { return ec.web.getWebappRootUrl(true, true) } - static Function getProfileManagerFactory(ExecutionContext ec) { - return { WebContext ctx -> new MoquiProfileManager(ctx, ec) } as Function - } static login(ExecutionContext ec) { ec.artifactExecution.disableAuthz() def logger = ec.getLogger() + JEESessionStore sessionStore = JEESessionStore.INSTANCE - DefaultSecurityLogic logic = new DefaultSecurityLogic() - logic.setProfileManagerFactory(getProfileManagerFactory(ec)) def clients = getEnabledClients(ec.entity) if (clients.size() < 1) { @@ -75,15 +69,15 @@ class Login { } try { - def result = logic.perform( + def result = DefaultSecurityLogic.INSTANCE.perform( buildContext(ec), + sessionStore, getConfig(ec), new MoquiAccessGrantedAdapter(), - J2ENopHttpActionAdapter.INSTANCE, + JEEHttpActionAdapter.INSTANCE, clients.join(','), DefaultAuthorizers.IS_AUTHENTICATED, - '', - false + '' ) } catch (Exception e) { @@ -107,20 +101,29 @@ class Login { ec.artifactExecution.disableAuthz() def logger = ec.getLogger() def context = buildContext(ec) + JEESessionStore sessionStore = JEESessionStore.INSTANCE - DefaultCallbackLogic callback = new DefaultCallbackLogic() - callback.setProfileManagerFactory(getProfileManagerFactory(ec)) try { - def result = callback.perform( - context, - getConfig(ec), - J2ENopHttpActionAdapter.INSTANCE, + DefaultCallbackLogic.INSTANCE.perform( + context, + sessionStore, + getConfig(ec), + JEEHttpActionAdapter.INSTANCE, null, - true, - false, - true, - getEnabledClients(ec.entity).join(',') + false, + null ) + + // handle incoming profiles + ProfileManager profileManager = new ProfileManager(context, sessionStore) + new MoquiAccessGrantedAdapter().adapt(context, sessionStore, profileManager.getProfiles()) + + // login user + Optional optionalProfile = profileManager.getProfile() + if (optionalProfile.isPresent()) { + UserProfile profile = optionalProfile.get() + ((UserFacadeImpl)ec.user).internalLoginUser(profile.username) + } } catch (Exception e) { e.printStackTrace() @@ -131,29 +134,32 @@ class Login { static void logout(ExecutionContext ec) { ec.artifactExecution.disableAuthz() - DefaultLogoutLogic logout = new DefaultLogoutLogic() - logout.setProfileManagerFactory(getProfileManagerFactory(ec)) + def loginUrl = "${getMoquiUrl(ec)}/Login" + JEESessionStore sessionStore = JEESessionStore.INSTANCE try { - logout.perform( + DefaultLogoutLogic.INSTANCE.perform( buildContext(ec), + sessionStore, getConfig(ec), - J2ENopHttpActionAdapter.INSTANCE, + JEEHttpActionAdapter.INSTANCE, loginUrl, '/', - true, - true, + false, + false, true ) + + ec.user.logoutUser() } finally { ec.artifactExecution.enableAuthz() } } } -class MoquiAccessGrantedAdapter implements SecurityGrantedAccessAdapter { - Object adapt(WebContext context, Collection profiles, Object... parameters) { +class MoquiAccessGrantedAdapter implements SecurityGrantedAccessAdapter { + Object adapt(WebContext context, SessionStore sessionStore, Collection profiles, Object... parameters) throws Exception { return null } } diff --git a/src/main/groovy/org/mk/moqui/authentication/MoquiProfileManager.groovy b/src/main/groovy/org/mk/moqui/authentication/MoquiProfileManager.groovy deleted file mode 100644 index ef9297f..0000000 --- a/src/main/groovy/org/mk/moqui/authentication/MoquiProfileManager.groovy +++ /dev/null @@ -1,43 +0,0 @@ -package org.mk.moqui.authentication - -import org.moqui.impl.context.ExecutionContextFactoryImpl -import org.moqui.impl.context.ExecutionContextImpl -import org.moqui.impl.context.UserFacadeImpl -import org.moqui.impl.util.MoquiShiroRealm -import org.moqui.context.ExecutionContext -import org.pac4j.core.context.WebContext -import org.pac4j.core.profile.CommonProfile -import org.pac4j.core.profile.ProfileManager - -import javax.naming.AuthenticationException - -/** - * Creates and registers a subject with the moqui SecurityManager - */ -class MoquiProfileManager extends ProfileManager { - ExecutionContext ec - MoquiProfileManager(WebContext context, ExecutionContext ec) { - super(context) - this.ec = ec - } - - @Override - void save(final boolean saveInSession, final CommonProfile profile, final boolean multiProfile) { - super.save(saveInSession, profile, multiProfile) - - try { - // TODO: Make username/email configurable - ((UserFacadeImpl) ec.user).internalLoginUser(profile.username) - } catch (final AuthenticationException e) { - super.remove(saveInSession) - throw e - } - } - - @Override - void remove(final boolean removeFromSession) { - super.remove(removeFromSession) - - ec.user.logoutUser() - } -} \ No newline at end of file diff --git a/src/main/groovy/org/mk/moqui/authentication/OidcClientFactory.groovy b/src/main/groovy/org/mk/moqui/authentication/OidcClientFactory.groovy index b39abd1..4ad817d 100644 --- a/src/main/groovy/org/mk/moqui/authentication/OidcClientFactory.groovy +++ b/src/main/groovy/org/mk/moqui/authentication/OidcClientFactory.groovy @@ -1,11 +1,10 @@ package org.mk.moqui.authentication -import com.nimbusds.jose.JWSAlgorithm -import com.nimbusds.jose.Requirement import org.moqui.entity.EntityFacade import org.pac4j.core.client.Client import org.pac4j.oidc.client.OidcClient import org.pac4j.oidc.config.OidcConfiguration +import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod class OidcClientFactory implements AuthenticationClientFactory{ @@ -18,9 +17,8 @@ class OidcClientFactory implements AuthenticationClientFactory{ config.setDiscoveryURI(entity.discoveryUri) config.setClientId(entity.id) config.setSecret(entity.secret) - if (entity.preferredJwsAlgorithm) { - config.setPreferredJwsAlgorithm(new JWSAlgorithm(entity.preferredJwsAlgorithm, Requirement.RECOMMENDED)) - } + config.setClientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) + config.setPreferredJwsAlgorithmAsString(entity.preferredJwsAlgorithm as String) config.setUseNonce(entity.useNonce == 'Y') def client = new OidcClient(config) client.setName(entity.clientId)