Skip to content

Commit

Permalink
Initial implementation of Moqui SSO.
Browse files Browse the repository at this point in the history
  • Loading branch information
aabiabdallah committed Oct 13, 2023
1 parent 41970a7 commit 8ee25e7
Show file tree
Hide file tree
Showing 9 changed files with 825 additions and 12 deletions.
4 changes: 2 additions & 2 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Written in 2016 by Qiushi Yan - yanqiushi
Written in 2017 by Oleg Andrieiev - oandreyev
Written in 2018 by Zhang Wei - zhangwei1979
Written in 2018 by Nirendra Singh - nirendra10695
Written in 2018-2021 by Ayman Abi Abdallah - aabiabdallah
Written in 2018-2023 by Ayman Abi Abdallah - aabiabdallah
Written in 2019 by Daniel Taylor - danieltaylor-nz
Written in 2020 by Jacob Barnes - Tellan
Written in 2020 by Amir Anjomshoaa - amiranjom
Expand Down Expand Up @@ -102,7 +102,7 @@ Written in 2016 by Qiushi Yan - yanqiushi
Written in 2017 by Oleg Andrieiev - oandreyev
Written in 2018 by Zhang Wei - zhangwei1979
Written in 2018 by Nirendra Singh - nirendra10695
Written in 2018-2020 by Ayman Abi Abdallah - aabiabdallah
Written in 2018-2023 by Ayman Abi Abdallah - aabiabdallah
Written in 2019 by Daniel Taylor - danieltaylor-nz
Written in 2020 by Jacob Barnes - Tellan
Written in 2020 by Amir Anjomshoaa - amiranjom
Expand Down
15 changes: 15 additions & 0 deletions framework/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ dependencyUpdates.resolutionStrategy { componentSelection { rules -> rules.all {
repositories {
flatDir name: 'localLib', dirs: projectDir.absolutePath + '/lib'
mavenCentral()
maven { url "https://build.shibboleth.net/maven/releases" }
}

sourceCompatibility = 11
Expand Down Expand Up @@ -187,6 +188,20 @@ dependencies {
// Liquibase (for future reference, not used yet)
// api 'org.liquibase:liquibase-core:3.4.2' // Apache 2.0

// pac4j
implementation 'org.pac4j:pac4j-core:5.7.1'
implementation 'org.pac4j:pac4j-javaee:5.7.1'
implementation 'org.pac4j:pac4j-oauth:5.7.1'
implementation 'org.pac4j:pac4j-oidc:5.7.1'
implementation ('org.pac4j:pac4j-saml:5.7.1') {
exclude group: 'org.springframework'
exclude group: 'org.bouncycastle'
}

// explicit dependencies to get newer versions
implementation 'org.springframework:spring-core:5.3.29'
implementation 'org.bouncycastle:bcprov-jdk18on:1.75'

// ========== test dependencies ==========

// junit-platform-launcher is a dependency from spock-core, included explicitly to get more recent version as needed
Expand Down
198 changes: 198 additions & 0 deletions framework/entity/SecurityEntities.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ along with this software (see the LICENSE.md file). If not, see
<!-- ========================================================= -->
<!-- moqui.security -->
<!-- moqui.security.user -->
<!-- moqui.security.auth -->
<!-- ========================================================= -->

<!-- ========== Artifact Group ========== -->
Expand Down Expand Up @@ -545,4 +546,201 @@ along with this software (see the LICENSE.md file). If not, see
<alias entity-alias="NMU" name="userSentDate" field="sentDate"/>
<alias entity-alias="NMU" name="viewedDate"/>
</view-entity>

<!-- ========== Authentication Flows ========== -->

<entity entity-name="AuthFlow" package="moqui.security.auth">
<field name="authFlowId" type="id" is-pk="true"/>
<field name="authFlowTypeEnumId" type="id"/>
<field name="systemMessageRemoteId" type="id">
<description>Used for inbound authentication flows</description>
</field>
<field name="description" type="text-medium"/>
<field name="iconName" type="text-short"/>
<field name="defaultUserGroupId" type="id"/>
<field name="sequenceNum" type="number-integer"/>
<field name="inbound" type="text-indicator"/>
<field name="disabled" type="text-indicator" enable-audit-log="true"/>

<relationship type="one-nofk" related="moqui.security.auth.OauthFlow" short-alias="oauth" mutable="true">
<key-map field-name="authFlowId"/>
</relationship>
<relationship type="one-nofk" related="moqui.security.auth.OidcFlow" short-alias="oidc" mutable="true">
<key-map field-name="authFlowId"/>
</relationship>
<relationship type="one-nofk" related="moqui.security.auth.SamlFlow" short-alias="saml" mutable="true">
<key-map field-name="authFlowId"/>
</relationship>
<relationship type="one" title="AuthFlowType" related="moqui.basic.Enumeration" short-alias="authFlowType">
<key-map field-name="authFlowTypeEnumId"/>
</relationship>
<relationship type="one" related="moqui.service.message.SystemMessageRemote" short-alias="systemMessageRemote">
<key-map field-name="systemMessageRemoteId"/>
</relationship>
<relationship type="one" related="moqui.security.UserGroup" short-alias="defaultUserGroup">
<key-map field-name="defaultUserGroupId"/>
</relationship>
<relationship type="many" related="moqui.security.auth.AuthFlowFieldMap" short-alias="fieldMaps">
<key-map field-name="authFlowId"/>
</relationship>
<relationship type="many" related="moqui.security.auth.AuthFlowRoleMap" short-alias="roleMaps">
<key-map field-name="authFlowId"/>
</relationship>

<seed-data>
<moqui.basic.EnumerationType enumTypeId="AuthFlowType" description="Auth Flow Type"/>
<moqui.basic.Enumeration enumTypeId="AuthFlowType" enumId="AftOidc" description="OpenID Connect"/>
<moqui.basic.Enumeration enumTypeId="AuthFlowType" enumId="AftOauth" description="OAuth"/>
<moqui.basic.Enumeration enumTypeId="AuthFlowType" enumId="AftSaml" description="SAML"/>
</seed-data>
</entity>
<entity entity-name="AuthFlowFieldMap" package="moqui.security.auth">
<field name="authFlowId" type="id" is-pk="true"/>
<field name="ruleSeqId" type="id" is-pk="true"/>
<field name="srcFieldName" type="text-medium"/>
<field name="dstFieldName" type="text-medium"/>
<field name="dstFieldTypeEnumId" type="id"/>
<field name="dstFieldExpression" type="text-medium"/>
<field name="mappingServiceRegisterId" type="id"/>

<relationship type="one" related="moqui.security.auth.AuthFlow">
<key-map field-name="authFlowId"/>
</relationship>
<relationship type="one" title="DataFieldType" related="moqui.basic.Enumeration" short-alias="destinationFieldType">
<key-map field-name="dstFieldTypeEnumId"/>
</relationship>
<relationship type="one" related="moqui.service.ServiceRegister" short-alias="mappingServiceRegister">
<key-map field-name="mappingServiceRegisterId"/>
</relationship>

<seed-data>
<moqui.basic.EnumerationType enumTypeId="DataFieldType" description="Data Field Type"/>
<moqui.basic.Enumeration enumTypeId="DataFieldType" enumId="DftString" description="String"/>
<moqui.basic.Enumeration enumTypeId="DataFieldType" enumId="DftInteger" description="Integer"/>
<moqui.basic.Enumeration enumTypeId="DataFieldType" enumId="DftLong" description="Long"/>
<moqui.basic.Enumeration enumTypeId="DataFieldType" enumId="DftFloat" description="Float"/>
<moqui.basic.Enumeration enumTypeId="DataFieldType" enumId="DftDouble" description="Double"/>
<moqui.basic.Enumeration enumTypeId="DataFieldType" enumId="DftBigDecimal" description="BigDecimal"/>
<moqui.basic.Enumeration enumTypeId="DataFieldType" enumId="DftBigInteger" description="BigInteger"/>
<moqui.basic.Enumeration enumTypeId="DataFieldType" enumId="DftTime" description="Time"/>
<moqui.basic.Enumeration enumTypeId="DataFieldType" enumId="DftDate" description="Date"/>
<moqui.basic.Enumeration enumTypeId="DataFieldType" enumId="DftTimestamp" description="Timestamp"/>
<moqui.basic.Enumeration enumTypeId="DataFieldType" enumId="DftList" description="List"/>
<moqui.basic.Enumeration enumTypeId="DataFieldType" enumId="DftMap" description="Map"/>
</seed-data>
</entity>
<entity entity-name="AuthFlowRoleMap" package="moqui.security.auth">
<field name="authFlowId" type="id" is-pk="true"/>
<field name="roleName" type="text-medium" is-pk="true">
<description>Role name in IdP server</description>
</field>
<field name="userGroupId" type="id"/>
<field name="roleTypeId" type="id"/>

<relationship type="one" related="moqui.security.auth.AuthFlow">
<key-map field-name="authFlowId"/>
</relationship>
<relationship type="one" related="moqui.security.UserGroup">
<key-map field-name="userGroupId"/>
</relationship>
</entity>
<entity entity-name="OauthFlow" package="moqui.security.auth">
<field name="authFlowId" type="id" is-pk="true"/>
<field name="clientTypeEnumId" type="id"/>
<field name="key" type="text-medium"/>
<field name="secret" type="text-medium"/>

<relationship type="one" related="moqui.security.auth.AuthFlow" short-alias="authFlow">
<key-map field-name="authFlowId"/>
</relationship>
<relationship type="one" title="OauthClientType" related="moqui.basic.Enumeration" short-alias="clientType">
<key-map field-name="clientTypeEnumId"/>
</relationship>

<seed-data>
<moqui.basic.EnumerationType enumTypeId="OauthClientType" description="OAuth Client Type"/>
<moqui.basic.Enumeration enumTypeId="OauthClientType" enumId="OctBitBucket" description="BitBucket"/>
<moqui.basic.Enumeration enumTypeId="OauthClientType" enumId="OctDropBox" description="DropBox"/>
<moqui.basic.Enumeration enumTypeId="OauthClientType" enumId="OctFacebook" description="Facebook"/>
<moqui.basic.Enumeration enumTypeId="OauthClientType" enumId="OctFoursquare" description="Foursquare"/>
<moqui.basic.Enumeration enumTypeId="OauthClientType" enumId="OctGitHub" description="GitHub"/>
<moqui.basic.Enumeration enumTypeId="OauthClientType" enumId="OctGoogle" description="Google"/>
<moqui.basic.Enumeration enumTypeId="OauthClientType" enumId="OctLinkedIn" description="LinkedIn"/>
<moqui.basic.Enumeration enumTypeId="OauthClientType" enumId="OctPaypal" description="Paypal"/>
<moqui.basic.Enumeration enumTypeId="OauthClientType" enumId="OctTwitter" description="Twitter"/>
<moqui.basic.Enumeration enumTypeId="OauthClientType" enumId="OctWordPress" description="Word Press"/>
<moqui.basic.Enumeration enumTypeId="OauthClientType" enumId="OctYahoo" description="Yahoo"/>
<moqui.basic.Enumeration enumTypeId="OauthClientType" enumId="OctOauth10" description="OAuth v1.0"/>
<moqui.basic.Enumeration enumTypeId="OauthClientType" enumId="OctOauth20" description="OAuth v2.0"/>
</seed-data>
</entity>
<entity entity-name="OidcFlow" package="moqui.security.auth">
<field name="authFlowId" type="id" is-pk="true"/>
<field name="clientTypeEnumId" type="id"/>
<field name="clientId" type="text-medium"/>
<field name="secret" type="text-medium"/>
<field name="realm" type="text-medium">
<description>Specific to Keycloak.</description>
</field>
<field name="baseUri" type="text-medium">
<description>Specific to Keycloak.</description>
</field>
<field name="discoveryUri" type="text-medium"/>
<field name="preferredJwsAlgorithmEnumId" type="id"/>
<field name="useNonce" type="text-indicator">
<description>
A nonce is String value used to associate a Client session with an ID Token, and to mitigate replay attacks.
Required for implicit flows.
</description>
</field>

<relationship type="one" related="moqui.security.auth.AuthFlow" short-alias="authFlow">
<key-map field-name="authFlowId"/>
</relationship>
<relationship type="one" title="OidcClientType" related="moqui.basic.Enumeration" short-alias="clientType" fk-name="OIDC_CLIENT_TYPE_FK">
<key-map field-name="clientTypeEnumId"/>
</relationship>
<relationship type="one" title="OidcJwsAlgorithm" related="moqui.basic.Enumeration" short-alias="preferredJwsAlgorithm" fk-name="OIDC_JWS_ALG_FK">
<key-map field-name="preferredJwsAlgorithmEnumId"/>
</relationship>

<seed-data>
<moqui.basic.EnumerationType enumTypeId="OidcClientType" description="OpenID Connect Client Type"/>
<moqui.basic.Enumeration enumTypeId="OidcClientType" enumId="OctApple" description="Apple"/>
<moqui.basic.Enumeration enumTypeId="OidcClientType" enumId="OctAzureAd" description="Azure AD"/>
<moqui.basic.Enumeration enumTypeId="OidcClientType" enumId="OctGoogle" description="Google"/>
<moqui.basic.Enumeration enumTypeId="OidcClientType" enumId="OctKeycloak" description="Keycloak"/>
<moqui.basic.Enumeration enumTypeId="OidcClientType" enumId="OctOther" description="Other"/>

<moqui.basic.EnumerationType enumTypeId="OidcJwsAlgorithm" description="OpenID Connect JWS Algorithm"/>
<moqui.basic.Enumeration enumTypeId="OidcJwsAlgorithm" enumId="OjaHS256" description="HMAC using SHA-256 (HS256)" optionValue="HS256"/>
<moqui.basic.Enumeration enumTypeId="OidcJwsAlgorithm" enumId="OjaHS384" description="HMAC using SHA-384 (HS384)" optionValue="HS384"/>
<moqui.basic.Enumeration enumTypeId="OidcJwsAlgorithm" enumId="OjaHS512" description="HMAC using SHA-512 (HS512)" optionValue="HS512"/>
<moqui.basic.Enumeration enumTypeId="OidcJwsAlgorithm" enumId="OjaRS256" description="RSASSA-PKCS-v1_5 using SHA-256 (RS256)" optionValue="RS256"/>
<moqui.basic.Enumeration enumTypeId="OidcJwsAlgorithm" enumId="OjaRS384" description="RSASSA-PKCS-v1_5 using SHA-384 (RS384)" optionValue="RS384"/>
<moqui.basic.Enumeration enumTypeId="OidcJwsAlgorithm" enumId="OjaRS512" description="RSASSA-PKCS-v1_5 using SHA-512 (RS512)" optionValue="RS512"/>
<moqui.basic.Enumeration enumTypeId="OidcJwsAlgorithm" enumId="OjaES256" description="ECDSA using P-256 curve and SHA-256 (ES256)" optionValue="ES256"/>
<moqui.basic.Enumeration enumTypeId="OidcJwsAlgorithm" enumId="OjaES384" description="ECDSA using P-384 curve and SHA-384 (ES384)" optionValue="ES384"/>
<moqui.basic.Enumeration enumTypeId="OidcJwsAlgorithm" enumId="OjaES512" description="ECDSA using P-521 curve and SHA-512 (ES512)" optionValue="ES512"/>
<moqui.basic.Enumeration enumTypeId="OidcJwsAlgorithm" enumId="OjaPS256" description="RSASSA-PSS using SHA-256 (PS256)" optionValue="PS256"/>
<moqui.basic.Enumeration enumTypeId="OidcJwsAlgorithm" enumId="OjaPS384" description="RSASSA-PSS using SHA-384 (PS384)" optionValue="PS384"/>
<moqui.basic.Enumeration enumTypeId="OidcJwsAlgorithm" enumId="OjaPS512" description="RSASSA-PSS using SHA-512 (PS512)" optionValue="PS512"/>
<moqui.basic.Enumeration enumTypeId="OidcJwsAlgorithm" enumId="OjaEdDSA" description="EdDSA" optionValue="EdDSA"/>
</seed-data>
</entity>
<entity entity-name="SamlFlow" package="moqui.security.auth">
<field name="authFlowId" type="id" is-pk="true"/>
<field name="keystoreLocation" type="text-medium"/>
<field name="keystorePassword" type="text-medium"/>
<field name="privateKeyPassword" type="text-medium"/>
<field name="serviceProviderEntityId" type="text-medium"/>
<field name="identityProviderMetadataLocation" type="text-medium"/>
<field name="forceAuth" type="text-indicator"/>
<field name="passive" type="text-indicator"/>

<relationship type="one" related="moqui.security.auth.AuthFlow" short-alias="authFlow">
<key-map field-name="authFlowId"/>
</relationship>
</entity>

</entities>
28 changes: 28 additions & 0 deletions framework/service/org/moqui/impl/AuthServices.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<services xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://moqui.org/xsd/service-definition-3.xsd">

<!-- SSO -->
<service verb="perform" noun="Login" type="java" location="org.moqui.impl.security.AuthenticationFlow" method="performLogin" authenticate="anonymous-all">
<description>
Performs a login operation on the IdP server.
</description>
<in-parameters>
<parameter name="authFlowId" required="true"/>
<parameter name="returnTo"/>
</in-parameters>
</service>
<service verb="handle" noun="Callback" type="java" location="org.moqui.impl.security.AuthenticationFlow" method="handleCallback" authenticate="anonymous-all">
<description>
Handles the login callback and logs the user in locally.
</description>
</service>
<service verb="perform" noun="Logout" type="java" location="org.moqui.impl.security.AuthenticationFlow" method="performLogout" authenticate="anonymous-all">
<description>
Performs a logout operation both locally and on the IdP server.
</description>
<in-parameters>
<parameter name="returnTo"/>
</in-parameters>
</service>

</services>
22 changes: 12 additions & 10 deletions framework/service/org/moqui/impl/UserServices.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ along with this software (see the LICENSE.md file). If not, see
<exclude field-name="disabledDateTime"/><exclude field-name="successiveFailedLogins"/>
</auto-parameters>
<parameter name="username" required="true"/>
<parameter name="newPassword" required="true"/>
<parameter name="newPasswordVerify" required="true"/>
<parameter name="newPassword"/>
<parameter name="newPasswordVerify"/>
<parameter name="requirePasswordChange" default-value="N"/>
<parameter name="disabled" default-value="N"/>
<parameter name="emailAddress"><text-email/></parameter>
Expand All @@ -51,15 +51,17 @@ along with this software (see the LICENSE.md file). If not, see
</if>

<service-call name="create#moqui.security.UserAccount" out-map="context" in-map="context"/>
<service-call name="org.moqui.impl.UserServices.update#PasswordInternal" out-map="updateOut"
in-map="[userId:userId, newPassword:newPassword, newPasswordVerify:newPasswordVerify,
<if condition="newPassword || newPasswordVerify">
<service-call name="org.moqui.impl.UserServices.update#PasswordInternal" out-map="updateOut"
in-map="[userId:userId, newPassword:newPassword, newPasswordVerify:newPasswordVerify,
requirePasswordChange:requirePasswordChange]"/>
<if condition="updateOut.updateSuccessful &amp;&amp; !ec.message.hasError()"><then>
<message public="true" type="success">Account created with username ${username}</message>
</then><else-if condition="updateOut.passwordIssues">
<message public="true" type="danger">Because of password issues not creating account with username ${username}</message>
<return error="true" message="Removed temporary account with username ${username} for password issues"/>
</else-if></if>
<if condition="updateOut.updateSuccessful &amp;&amp; !ec.message.hasError()"><then>
<message public="true" type="success">Account created with username ${username}</message>
</then><else-if condition="updateOut.passwordIssues">
<message public="true" type="danger">Because of password issues not creating account with username ${username}</message>
<return error="true" message="Removed temporary account with username ${username} for password issues"/>
</else-if></if>
</if>
</actions>
</service>
<service verb="update" noun="UserAccount">
Expand Down
Loading

0 comments on commit 8ee25e7

Please sign in to comment.