Skip to content

Commit

Permalink
allow better control over WS-FED principal resolver
Browse files Browse the repository at this point in the history
# Conflicts:
#	cas-server-core-authentication/src/main/java/org/jasig/cas/authentication/principal/PersonDirectoryPrincipalResolver.java
#	cas-server-support-wsfederation/src/main/java/org/jasig/cas/support/wsfederation/web/flow/WsFederationAction.java
  • Loading branch information
SavvasMisaghMoayyed committed Jan 28, 2016
1 parent 50f0844 commit 51dd4b9
Show file tree
Hide file tree
Showing 14 changed files with 285 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,33 @@
*
* @author Marvin S. Addison
* @since 4.0.0
*
*/
@Component("personDirectoryPrincipalResolver")
public class PersonDirectoryPrincipalResolver implements PrincipalResolver {

/** Log instance. */
protected final Logger logger = LoggerFactory.getLogger(this.getClass());

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

/** Factory to create the principal type. **/
/**
* Factory to create the principal type.
**/
@NotNull
protected PrincipalFactory principalFactory = new DefaultPrincipalFactory();

/** return null if no attributes are found. */
/**
* return null if no attributes are found.
*/
@Value("${cas.principal.resolver.persondir.return.null:false}")
protected boolean returnNullIfNoAttributes;

/** Optional principal attribute name. */
/**
* Optional principal attribute name.
*/
protected String principalAttributeName;

@Autowired
Expand Down Expand Up @@ -99,14 +105,21 @@ public Principal resolve(final Credential credential) {

logger.debug("Creating SimplePrincipal for [{}]", principalId);

final Map<String, List<Object>> attributes = retrievePersonAttributes(principalId);
final Map<String, List<Object>> attributes = retrievePersonAttributes(principalId, credential);
logger.debug("Principal id [{}] could not be found", principalId);

if (attributes == null || attributes.isEmpty()) {
logger.debug("Principal id [{}] did not specify any attributes", principalId);

if (!this.returnNullIfNoAttributes) {
logger.debug("Returning the principal with id [{}] without any attributes", principalId);
return this.principalFactory.createPrincipal(principalId);
}
logger.debug("[{}] is configured to return null if no attributes are found for [{}]",
this.getClass().getName(), principalId);
return null;
}
logger.debug("Retrieved [{}] attribute(s) from the repository", attributes.size());


final Pair<String, Map<String, Object>> pair = convertPersonAttributesToPrincipal(principalId, attributes);
Expand All @@ -128,7 +141,7 @@ protected Pair<String, Map<String, Object>> convertPersonAttributesToPrincipal(f
final String key = entry.getKey();
final List<Object> values = entry.getValue();
if (StringUtils.isNotBlank(this.principalAttributeName)
&& key.equalsIgnoreCase(this.principalAttributeName)) {
&& key.equalsIgnoreCase(this.principalAttributeName)) {
if (values.isEmpty()) {
logger.debug("{} is empty, using {} for principal", this.principalAttributeName, extractedPrincipalId);
} else {
Expand All @@ -150,9 +163,11 @@ protected Pair<String, Map<String, Object>> convertPersonAttributesToPrincipal(f
* Retrieve person attributes map.
*
* @param principalId the principal id
* @param credential the credential whose id we have extracted. This is passed so that implementations
* can extract useful bits of authN info such as attributes into the principal.
* @return the map
*/
protected Map<String, List<Object>> retrievePersonAttributes(final String principalId) {
protected Map<String, List<Object>> retrievePersonAttributes(final String principalId, final Credential credential) {
final IPersonAttributes personAttributes = this.attributeRepository.getPerson(principalId);
final Map<String, List<Object>> attributes;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@
import org.springframework.binding.mapping.Mapper;
import org.springframework.binding.mapping.impl.DefaultMapper;
import org.springframework.binding.mapping.impl.DefaultMapping;
import org.springframework.context.expression.BeanExpressionContextAccessor;
import org.springframework.context.expression.EnvironmentAccessor;
import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.WebApplicationContext;
Expand Down Expand Up @@ -234,6 +238,7 @@ protected DecisionState createDecisionState(final Flow flow, final String id, fi
return decisionState;

}

/**
* Sets start state.
*
Expand Down Expand Up @@ -264,7 +269,7 @@ protected void setStartState(final Flow flow, final TransitionableState state) {
* @param clazz the exception class
*/
protected void createGlobalTransition(final Flow flow, final String targetStateId,
final Class<? extends Throwable> clazz) {
final Class<? extends Throwable> clazz) {

try {
final TransitionExecutingFlowExecutionExceptionHandler handler = new TransitionExecutingFlowExecutionExceptionHandler();
Expand Down Expand Up @@ -325,13 +330,14 @@ protected EvaluateAction createEvaluateAction(final String expression) {
.parseExpression(expression, ctx);
final EvaluateAction newAction = new EvaluateAction(action, null);

logger.debug("Created evaluate action for expression", action.getExpressionString());
logger.debug("Created evaluate action for expression {}", action.getExpressionString());
return newAction;
}

/**
* Add a default transition to a given state.
* @param state the state to include the default transition
*
* @param state the state to include the default transition
* @param targetState the id of the destination state to which the flow should transfer
*/
protected void createStateDefaultTransition(final TransitionableState state, final String targetState) {
Expand All @@ -346,12 +352,12 @@ protected void createStateDefaultTransition(final TransitionableState state, fin
/**
* Add transition to action state.
*
* @param state the action state
* @param state the action state
* @param criteriaOutcome the criteria outcome
* @param targetState the target state
*/
protected void createTransitionForState(final TransitionableState state,
final String criteriaOutcome, final String targetState) {
final String criteriaOutcome, final String targetState) {
try {
final Transition transition = createTransition(criteriaOutcome, targetState);
state.getTransitionSet().add(transition);
Expand Down Expand Up @@ -434,13 +440,19 @@ protected SpringELExpressionParser getSpringExpressionParser() {
parser.addPropertyAccessor(new MapAdaptablePropertyAccessor());
parser.addPropertyAccessor(new MessageSourcePropertyAccessor());
parser.addPropertyAccessor(new ScopeSearchingPropertyAccessor());
parser.addPropertyAccessor(new BeanExpressionContextAccessor());
parser.addPropertyAccessor(new MapAccessor());
parser.addPropertyAccessor(new MapAdaptablePropertyAccessor());
parser.addPropertyAccessor(new EnvironmentAccessor());
parser.addPropertyAccessor(new ReflectivePropertyAccessor());
return parser;

}

/**
* Create transition without a criteria.
*
* @param targetState the target state
* @param targetState the target state
* @return the transition
*/
protected Transition createTransition(final String targetState) {
Expand All @@ -456,19 +468,41 @@ protected Transition createTransition(final String targetState) {
* @param viewId the view id
*/
protected void createEndState(final Flow flow, final String id, final String viewId) {
createEndState(flow, id, new LiteralExpression(viewId));
}

/**
* Add end state backed by view.
*
* @param flow the flow
* @param id the id
* @param expression the expression
*/
protected void createEndState(final Flow flow, final String id, final Expression expression) {
final ViewFactory viewFactory = this.flowBuilderServices.getViewFactoryCreator().createViewFactory(
expression,
this.flowBuilderServices.getExpressionParser(),
this.flowBuilderServices.getConversionService(),
null,
this.flowBuilderServices.getValidator(),
this.flowBuilderServices.getValidationHintResolver());

createEndState(flow, id, viewFactory);
}

/**
* Add end state backed by view.
*
* @param flow the flow
* @param id the id
* @param viewFactory the view factory
*/
protected void createEndState(final Flow flow, final String id, final ViewFactory viewFactory) {
try {
final EndState endState = new EndState(flow, id);
final ViewFactory viewFactory = this.flowBuilderServices.getViewFactoryCreator().createViewFactory(
new LiteralExpression(viewId),
this.flowBuilderServices.getExpressionParser(),
this.flowBuilderServices.getConversionService(),
null,
this.flowBuilderServices.getValidator(),
this.flowBuilderServices.getValidationHintResolver());

final Action finalResponseAction = new ViewFactoryActionAdapter(viewFactory);
endState.setFinalResponseAction(finalResponseAction);
logger.debug("Created end state state {} on flow id {}, backed by view {}", id, flow.getId(), viewId);
logger.debug("Created end state state {} on flow id {}, backed by view factory {}", id, flow.getId(), viewFactory);
} catch (final Exception e) {
logger.error(e.getMessage(), e);
}
Expand All @@ -477,15 +511,15 @@ protected void createEndState(final Flow flow, final String id, final String vie
/**
* Add view state.
*
* @param flow the flow
* @param id the id
* @param viewId the view id
* @param flow the flow
* @param id the id
* @param expression the expression
* @return the view state
*/
protected ViewState createViewState(final Flow flow, final String id, final String viewId) {
protected ViewState createViewState(final Flow flow, final String id, final Expression expression) {
try {
final ViewFactory viewFactory = this.flowBuilderServices.getViewFactoryCreator().createViewFactory(
new LiteralExpression(viewId),
expression,
this.flowBuilderServices.getExpressionParser(),
this.flowBuilderServices.getConversionService(),
null,
Expand All @@ -501,12 +535,24 @@ protected ViewState createViewState(final Flow flow, final String id, final Stri
return null;
}

/**
* Add view state.
*
* @param flow the flow
* @param id the id
* @param viewId the view id
* @return the view state
*/
protected ViewState createViewState(final Flow flow, final String id, final String viewId) {
return createViewState(flow, id, new LiteralExpression(viewId));
}

/**
* Create subflow state.
*
* @param flow the flow
* @param id the id
* @param subflow the subflow
* @param flow the flow
* @param id the id
* @param subflow the subflow
* @param entryAction the entry action
* @return the subflow state
*/
Expand Down Expand Up @@ -538,10 +584,10 @@ protected Mapper createMapperToSubflowState(final List<DefaultMapping> mappings)
/**
* Create mapping to subflow state.
*
* @param name the name
* @param value the value
* @param name the name
* @param value the value
* @param required the required
* @param type the type
* @param type the type
* @return the default mapping
*/
protected DefaultMapping createMappingToSubflowState(final String name, final String value,
Expand All @@ -564,7 +610,7 @@ protected DefaultMapping createMappingToSubflowState(final String name, final St
/**
* Create subflow attribute mapper.
*
* @param inputMapper the input mapper
* @param inputMapper the input mapper
* @param outputMapper the output mapper
* @return the subflow attribute mapper
*/
Expand Down
9 changes: 6 additions & 3 deletions cas-server-documentation/integration/ADFS-Integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ title: CAS - ADFS Integration
---

#Overview
The integration between the CAS Server and ADFS delegates user authentication from CAS Server to ADFS, making CAS Server a WS-Federation client. Claims released from ADFS are made available as attributes to CAS Server, and by extension CAS Clients.
The integration between the CAS Server and ADFS delegates user authentication from CAS Server to ADFS, making CAS Server a WS-Federation client.
Claims released from ADFS are made available as attributes to CAS Server, and by extension CAS Clients.

Support is enabled by including the following dependency in the Maven WAR overlay:

Expand Down Expand Up @@ -43,6 +44,9 @@ cas.wsfed.idp.signingcerts=classpath:adfs-signing.crt
#
# Slack dealing with time-drift between the ADFS Server and the CAS Server.
# cas.wsfed.idp.tolerance=10000

# cas.wsfed.idp.attribute.resolver.enabled=true
# cas.wsfed.idp.attribute.resolver.type=WSFED
{% endhighlight %}


Expand All @@ -54,7 +58,7 @@ to put together an implementation of `WsFederationAttributeMutator` that changes
package org.jasig.cas.support.wsfederation;

public class WsFederationAttributeMutatorImpl implements WsFederationAttributeMutator {
public void modifyAttributes(final Map<String, Object> attributes) {
public void modifyAttributes(...) {
...
}
}
Expand All @@ -71,7 +75,6 @@ The mutator then needs to be declared in your configuration:
Finally, ensure that the attributes sent from ADFS are available and mapped in
your `attributeRepository` configuration.


### Handling CAS Logout

An optional step, it is recommended that the `casLogoutView.jsp` be replace to redirect to ADFS's logout page.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package org.jasig.cas.web.flow;

import org.springframework.binding.expression.Expression;
import org.springframework.stereotype.Component;
import org.springframework.webflow.action.ExternalRedirectAction;
import org.springframework.webflow.engine.ActionState;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.engine.TargetStateResolver;
import org.springframework.webflow.engine.Transition;
import org.springframework.webflow.engine.TransitionableState;
import org.springframework.webflow.engine.support.ActionExecutingViewFactory;

import java.util.Iterator;

Expand All @@ -23,7 +26,12 @@ public class WsFederationWebflowConfigurer extends AbstractCasWebflowConfigurer
protected void doInitialize() throws Exception {
final Flow flow = getLoginFlow();

createViewState(flow, "wsFederationRedirect", "externalRedirect:#{flowScope.WsFederationIdentityProviderUrl}");

final Expression expression = createExpression(flow, "flowScope.WsFederationIdentityProviderUrl", String.class);
final ActionExecutingViewFactory viewFactory = new ActionExecutingViewFactory(
new ExternalRedirectAction(expression));

createEndState(flow, "wsFederationRedirect", viewFactory);
final ActionState actionState = createActionState(flow, "wsFederationAction", createEvaluateAction("wsFederationAction"));
actionState.getTransitionSet().add(createTransition(TRANSITION_ID_SUCCESS, TRANSITION_ID_SEND_TICKET_GRANTING_TICKET));
actionState.getTransitionSet().add(createTransition(TRANSITION_ID_ERROR, getStartState(flow).getId()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.jasig.cas.web.AbstractServletContextInitializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.servlet.annotation.WebListener;
Expand All @@ -28,10 +29,16 @@ public class WsFedServletContextListener extends AbstractServletContextInitializ
@Qualifier("adfsPrincipalResolver")
private PrincipalResolver adfsPrincipalResolver;

@Value("${cas.wsfed.idp.attribute.resolver.enabled:true}")
private boolean useResolver;

@Override
protected void initializeRootApplicationContext() {
addAuthenticationHandlerPrincipalResolver(adfsAuthNHandler, adfsPrincipalResolver);
if (!this.useResolver) {
addAuthenticationHandler(adfsAuthNHandler);
} else {
addAuthenticationHandlerPrincipalResolver(adfsAuthNHandler, adfsPrincipalResolver);
}
}
}

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jasig.cas.support.wsfederation;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

/**
Expand All @@ -17,5 +18,5 @@ public interface WsFederationAttributeMutator extends Serializable {
*
* @param attributes the attribute returned by the IdP.
*/
void modifyAttributes(Map<String, Object> attributes);
void modifyAttributes(Map<String, List<Object>> attributes);
}
Loading

0 comments on commit 51dd4b9

Please sign in to comment.