-
Notifications
You must be signed in to change notification settings - Fork 19
CAS MFA v1.0.0 RC8 with CAS SAML AuthN Extension
CAS SAML AuthN Extension: https://github.com/UniconLabs/cas-saml-auth
CAS MFA Version: 1.0.0-RC8
- Additional changes were needed in samlSecurityContext.xml for
<mvc:annotation-driven/>
to get the plugin working - The
saml.xml
file had to be extracted intologin-webflow.xml
- The saml webflow portion had to finish with
initiatingAuthenticationViaFormAction
instead ofauthenticatSamlCredentials
- In the saml-plugin changes were needed like CREDENTIAL_KEY from “samlCredentails” to “credentials”
- In the cas-mfa plugin the only change needed was for
GenerateMultiFactorCredentialsAction
because it assumedUsernamePasswordCredentials
- The saml plugin had to be additionally modified to fit our needs
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
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.
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>