Skip to content
This repository has been archived by the owner on Nov 3, 2017. It is now read-only.

CAS MFA v1.0.0 RC8 with CAS SAML AuthN Extension

Misagh Moayyed edited this page May 10, 2016 · 3 revisions

CAS SAML AuthN Extension: https://github.com/UniconLabs/cas-saml-auth

CAS MFA Version: 1.0.0-RC8

  1. Additional changes were needed in samlSecurityContext.xml for <mvc:annotation-driven/> to get the plugin working
  2. The saml.xml file had to be extracted into login-webflow.xml
  3. The saml webflow portion had to finish with initiatingAuthenticationViaFormAction instead of authenticatSamlCredentials
  4. In the saml-plugin changes were needed like CREDENTIAL_KEY from “samlCredentails” to “credentials”
  5. In the cas-mfa plugin the only change needed was for GenerateMultiFactorCredentialsAction because it assumed UsernamePasswordCredentials
  6. The saml plugin had to be additionally modified to fit our needs

Inside CAS

To get the saml plugin working properly this addition is needed in the samlSecurityContext.xml (which we imported into CAS):

Added http://www.springframework.org/schema/mvc/spring-mvc.xsd Into the xsi:schemaLocation and also added this at the top xmlns:mvc="http://www.springframework.org/schema/mvc"

Added

<mvc:annotation-driven/> 
<bean class="org.jasig.cas.web.flow.LoginFlowResumingController"/>

After

<context:component-scan base-package="org.springframework.security.saml"/>

The rest is getting it to work with the mfa-duo-two-factor. We had to extract the saml.xml from the plugin into the normal login-webflow since I had trouble hooking mfa into the plugin.

We used a requestParameter to indicate whether to use the saml or not. This is also who the request is from, also the idpCode.

We changed idpAuthnFinished to

<transition on="idpAuthnFinished" to="bindAgain">
    <evaluate expression="samlCredentialAdaptingAction.wrapSamlCredentialAndPlaceInFlowScope(flowRequestContext, flowScope.credentialType)"/>
</transition>

And added

<action-state id="bindAgain">
    <evaluate expression="generateLoginTicketAction.generate(flowRequestContext)" />
    <transition on="generated" to="viewSaml"/>
</action-state>

<view-state id="viewSaml" view="casLoginTicketSaml">
    <transition on="submit" to="authenticateExternalSamlPrincipal" />
</view-state>

<action-state id="authenticateExternalSamlPrincipal">

    <evaluate expression="initiatingAuthenticationViaFormAction"/>
    <transition on="warn" to="warn" />
    <!--
      To enable LPPE on the 'success' replace the below transition with:
      <transition on="success" to="passwordPolicyCheck" />
    -->
    <transition on="success" to="sendTicketGrantingTicketConfirmed" />
    <transition on="mfa-duo-two-factor" to="mfa-duo-two-factor"/>
    <transition on="error" to="needToAssociateAccount" />
    <transition on="accountDisabled" to="casAccountDisabledView" />
    <transition on="mustChangePassword" to="casMustChangePassView" />
    <transition on="accountLocked" to="casAccountLockedView" />
    <transition on="badHours" to="casBadHoursView" />
    <transition on="badWorkstation" to="casBadWorkstationView" />
    <transition on="passwordExpired" to="casExpiredPassView" />
</action-state>

<view-state id="needToAssociateAccount" view="casAssociationNotFound">
    <transition on="accountExists" to="generateLoginTicketSaml" />
    <transition on="accountDoesNotExist" to="generateLoginTicket" />
</view-state>
<action-state id="generateLoginTicketSaml">
    <on-entry>
        <set name="flowScope.credentials" value="credentialsUP" type="org.jasig.cas.authentication.principal.UsernamePasswordCredentials"/>
        <set name="flowScope.samlPath" value="'samlPath'"/>
    </on-entry>
    <evaluate expression="generateLoginTicketAction.generate(flowRequestContext)" />
    <transition on="generated" to="viewLoginFormSaml" />
</action-state>

<view-state id="viewLoginFormSaml" view="casLoginView" model="credentials">
    <binder>
        <binding property="username" />
        <binding property="password" />
    </binder>
    <on-entry>
        <set name="viewScope.commandName" value="'credentials'" />
    </on-entry>
    <transition on="submit" bind="true" validate="true" to="realSubmit">
        <evaluate expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" />
    </transition>
</view-state>

And

<decision-state id="sendTicketGrantingTicket">
    <if test="flowScope.samlPath == 'samlPath'" then="associateCredentials" else="sendTicketGrantingTicketConfirmed" />
</decision-state>

<action-state id="sendTicketGrantingTicketConfirmed">
    <evaluate expression="sendTicketGrantingTicketAction" />
    <transition to="serviceCheck" />
</action-state>
```language

Inside cas-saml-auth

To get it to build on our local boxes we disabled the signing in build.gradle.

We set response.setContentType("text/html; charset=UTF-8"); in the LoginFlowResumingController.groovy

In our particular case we needed to know who the provider was and what their unique id was from that provider. So we added a String whoFrom in SpringSecuritySamlCredentials

In SpringSecuritySamlCredentialsToPrincipalResolver Changed the resolve Principal to this

    /** Repository of principal attributes to be retrieved */
    @NotNull
    private IPersonAttributeDao attributeRepository = new StubPersonAttributeDao(new HashMap<String, List<Object>>());

    @Override
    public Principal resolvePrincipal(Credentials credentials) {
        if (log.isDebugEnabled()) {
            log.debug("Attempting to resolve a principal...");
        }

        final String principalId2 = idpService.extractPrincipalId(credentials);

        if (log.isDebugEnabled()) {
            log.debug("Received PrimaryPrincipal (NameID and provider) [" + principalId2 + "]");
        }

        final IPersonAttributes personAttributes = this.attributeRepository.getPerson(principalId2);

        if(personAttributes == null){
//            Return null and error to the need to associate account page
            return null;
        }

        final String principalId = (String) personAttributes.getAttributeValue("netId");

        if (log.isDebugEnabled()) {
            log.debug("Creating SimplePrincipal for [" + principalId + "]");
        }

        final Map<String, List<Object>> attributes;

        if (personAttributes == null) {
            attributes = null;
        } else {
            attributes = personAttributes.getAttributes();
        }

        if (attributes == null & !this.returnNullIfNoAttributes) {
            return new SimplePrincipal(principalId);
        }

        if (attributes == null) {
            return null;
        }

        final Map<String, Object> convertedAttributes = new HashMap<String, Object>();

        for (final Map.Entry<String, List<Object>> entry : attributes.entrySet()) {
            final String key = entry.getKey();
            final Object value = entry.getValue().size() == 1 ? entry.getValue().get(0) : entry.getValue();
            convertedAttributes.put(key, value);
        }
        return new SimplePrincipal(principalId, convertedAttributes);
    }

Changed the SimpleJsonIdpService.groovy extract Principal ID to

return ((idAttribute.equals("NameID")?samlCredential.getNameID().getValue().trim():(samlCredential.getAttribute(idAttribute) ?: samlCredential.attributes.find {
    it.friendlyName == idAttribute
})?.DOM?.textContent.trim()) + "#" + springSecuritySamlCredentials.whoFrom)

In SamlCredentialAdaptingAction.groovy we added a String whoFrom and in SpringSecuritySamlCredentials we added whoFrom as a parameter. Also changed CREDENTIAL_KEY from “samlCredentials” to “credentials”. Also had to added context.flowScope.put("credentialName", samlCredential.getNameID().getValue().trim() in the same file for our configuration.

Inside CAS-MFA

GenerateMultiFactorCredentialsAction to

@Override
protected Event doExecute(final RequestContext context) {
    final FlowSession session = context.getFlowExecutionContext().getActiveSession();
    LOGGER.debug("Authentication has entered the flow [{}] executing state [{}",
            context.getActiveFlow().getId(), session.getState().getId());
    Object creds = session.getScope().getRequired("credentials");
    if(creds instanceof UsernamePasswordCredentials){
        final UsernamePasswordCredentials credsUsernamePassword = (UsernamePasswordCredentials)
                session.getScope().getRequired("credentials", UsernamePasswordCredentials.class);
        final String id = credsUsernamePassword != null ? credsUsernamePassword.getUsername() : null;
        final Credentials mfaCreds = createCredentials(context, credsUsernamePassword, id);
        final AttributeMap map = new LocalAttributeMap(ATTRIBUTE_ID_MFA_CREDENTIALS, mfaCreds);
        return new Event(this, EVENT_ID_SUCCESS, map);
    }else {
        final SpringSecuritySamlCredentials credsSpringSaml = (SpringSecuritySamlCredentials)
                session.getScope().getRequired("credentials", SpringSecuritySamlCredentials.class);
        final Principal principal = springSamlResolver.resolvePrincipal(credsSpringSaml);
        final String id = principal.getId();
        final Credentials mfaCreds = createCredentials(context, credsSpringSaml, id);
        final AttributeMap map = new LocalAttributeMap(ATTRIBUTE_ID_MFA_CREDENTIALS, mfaCreds);
        return new Event(this, EVENT_ID_SUCCESS, map);
    }
}

And added

@NotNull
private SpringSecuritySamlCredentialsToPrincipalResolver springSamlResolver;

public final void setSpringSamlResolver(SpringSecuritySamlCredentialsToPrincipalResolver springSamlResolver){
    this.springSamlResolver = springSamlResolver;
}

And in the cas-servlet-override-context made this change

<!-- Generate and chain multifactor credentials based on current authenticated credentials. -->
<bean id="generateMfaCredentialsAction" class="net.unicon.cas.mfa.web.flow.GenerateMultiFactorCredentialsAction"
      p:authenticationSupport-ref="authenticationSupport"
      p:springSamlResolver-ref="springSamlResolver"
      />

<bean id="springSamlResolver" class="org.jasig.cas.authentication.saml.SpringSecuritySamlCredentialsToPrincipalResolver">
    <property name="attributeRepository" ref="attributeRepository" />
</bean>
Clone this wiki locally