You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by lh...@apache.org on 2012/01/04 00:12:50 UTC
svn commit: r1226983 - in /shiro/trunk/support: ./ cas/ cas/src/
cas/src/main/ cas/src/main/java/ cas/src/main/java/org/
cas/src/main/java/org/apache/ cas/src/main/java/org/apache/shiro/
cas/src/main/java/org/apache/shiro/cas/ cas/src/test/ cas/src/tes...
Author: lhazlewood
Date: Tue Jan 3 23:12:49 2012
New Revision: 1226983
URL: http://svn.apache.org/viewvc?rev=1226983&view=rev
Log:
SHIRO-285: Initial CAS support implementations.
Added:
shiro/trunk/support/cas/ (with props)
shiro/trunk/support/cas/pom.xml
shiro/trunk/support/cas/src/
shiro/trunk/support/cas/src/main/
shiro/trunk/support/cas/src/main/java/
shiro/trunk/support/cas/src/main/java/org/
shiro/trunk/support/cas/src/main/java/org/apache/
shiro/trunk/support/cas/src/main/java/org/apache/shiro/
shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/
shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasAuthenticationException.java
shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasFilter.java
shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasRealm.java
shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasRememberMeSecurityManager.java
shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasSubjectFactory.java
shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasToken.java
shiro/trunk/support/cas/src/test/
shiro/trunk/support/cas/src/test/groovy/
shiro/trunk/support/cas/src/test/groovy/org/
shiro/trunk/support/cas/src/test/groovy/org/apache/
shiro/trunk/support/cas/src/test/groovy/org/apache/shiro/
shiro/trunk/support/cas/src/test/groovy/org/apache/shiro/cas/
shiro/trunk/support/cas/src/test/groovy/org/apache/shiro/cas/CasRealmTest.groovy
shiro/trunk/support/cas/src/test/groovy/org/apache/shiro/cas/CasTokenTest.groovy
shiro/trunk/support/cas/src/test/groovy/org/apache/shiro/cas/MockServiceTicketValidator.groovy
Modified:
shiro/trunk/support/pom.xml
Propchange: shiro/trunk/support/cas/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Tue Jan 3 23:12:49 2012
@@ -0,0 +1 @@
+*.iml
Added: shiro/trunk/support/cas/pom.xml
URL: http://svn.apache.org/viewvc/shiro/trunk/support/cas/pom.xml?rev=1226983&view=auto
==============================================================================
--- shiro/trunk/support/cas/pom.xml (added)
+++ shiro/trunk/support/cas/pom.xml Tue Jan 3 23:12:49 2012
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.apache.shiro</groupId>
+ <artifactId>shiro-root</artifactId>
+ <version>1.2.0-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>shiro-cas</artifactId>
+ <name>Apache Shiro :: Support :: CAS</name>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.shiro</groupId>
+ <artifactId>shiro-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jasig.cas.client</groupId>
+ <artifactId>cas-client-core</artifactId>
+ <version>3.2.1</version>
+ </dependency>
+ <!-- for SAML ticket validation -->
+ <dependency>
+ <groupId>org.opensaml</groupId>
+ <artifactId>opensaml</artifactId>
+ <version>1.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.santuario</groupId>
+ <artifactId>xmlsec</artifactId>
+ <version>1.4.3</version>
+ </dependency>
+ <!-- for SAML ticket validation -->
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-SymbolicName>org.apache.shiro.cas</Bundle-SymbolicName>
+ <Export-Package>org.apache.shiro.cas*;version=${project.version}</Export-Package>
+ <Import-Package>
+ org.apache.shiro*;version="${shiro.osgi.importRange}",
+ org.jasig.cas*;version="[3.1.3, 4)",
+ *
+ </Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
Added: shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasAuthenticationException.java
URL: http://svn.apache.org/viewvc/shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasAuthenticationException.java?rev=1226983&view=auto
==============================================================================
--- shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasAuthenticationException.java (added)
+++ shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasAuthenticationException.java Tue Jan 3 23:12:49 2012
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cas;
+
+import org.apache.shiro.authc.AuthenticationException;
+
+/**
+ * @since 1.2
+ */
+public class CasAuthenticationException extends AuthenticationException {
+
+ public CasAuthenticationException() {
+ super();
+ }
+
+ public CasAuthenticationException(String message) {
+ super(message);
+ }
+
+ public CasAuthenticationException(Throwable cause) {
+ super(cause);
+ }
+
+ public CasAuthenticationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
Added: shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasFilter.java
URL: http://svn.apache.org/viewvc/shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasFilter.java?rev=1226983&view=auto
==============================================================================
--- shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasFilter.java (added)
+++ shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasFilter.java Tue Jan 3 23:12:49 2012
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cas;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
+import org.apache.shiro.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+/**
+ * This filter validates the CAS service ticket to authenticate the user. It must be configured on the URL recognized
+ * by the CAS server. For example, in {@code shiro.ini}:
+ * <pre>
+ * [main]
+ * casFilter = org.apache.shiro.cas.CasFilter
+ * ...
+ *
+ * [urls]
+ * /shiro-cas = casFilter
+ * ...
+ * </pre>
+ * (example : http://host:port/mycontextpath/shiro-cas)
+ *
+ * @since 1.2
+ */
+public class CasFilter extends AuthenticatingFilter {
+
+ private static Logger logger = LoggerFactory.getLogger(CasFilter.class);
+
+ // the name of the parameter service ticket in url
+ private static final String TICKET_PARAMETER = "ticket";
+
+ // the url where the application is redirected if the CAS service ticket validation failed (example : /mycontextpatch/cas_error.jsp)
+ private String failureUrl;
+
+ /**
+ * The token created for this authentication is a CasToken containing the CAS service ticket received on the CAS service url (on which
+ * the filter must be configured).
+ *
+ * @param request the incoming request
+ * @param response the outgoing response
+ * @throws Exception if there is an error processing the request.
+ */
+ @Override
+ protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ String ticket = httpRequest.getParameter(TICKET_PARAMETER);
+ return new CasToken(ticket);
+ }
+
+ /**
+ * Execute login by creating {@link #createToken(javax.servlet.ServletRequest, javax.servlet.ServletResponse) token} and logging subject
+ * with this token.
+ *
+ * @param request the incoming request
+ * @param response the outgoing response
+ * @throws Exception if there is an error processing the request.
+ */
+ @Override
+ protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
+ return executeLogin(request, response);
+ }
+
+ /**
+ * Returns <code>false</code> to always force authentication (user is never considered authenticated by this filter).
+ *
+ * @param request the incoming request
+ * @param response the outgoing response
+ * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
+ * @return <code>false</code>
+ */
+ @Override
+ protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
+ return false;
+ }
+
+ /**
+ * If login has been successful, redirect user to the original protected url.
+ *
+ * @param token the token representing the current authentication
+ * @param subject the current authenticated subjet
+ * @param request the incoming request
+ * @param response the outgoing response
+ * @throws Exception if there is an error processing the request.
+ */
+ @Override
+ protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
+ ServletResponse response) throws Exception {
+ issueSuccessRedirect(request, response);
+ return false;
+ }
+
+ /**
+ * If login has failed, redirect user to the CAS error page (no ticket or ticket validation failed) except if the user is already
+ * authenticated, in which case redirect to the default success url.
+ *
+ * @param token the token representing the current authentication
+ * @param ae the current authentication exception
+ * @param request the incoming request
+ * @param response the outgoing response
+ */
+ @Override
+ protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ae, ServletRequest request,
+ ServletResponse response) {
+ // is user authenticated or in remember me mode ?
+ Subject subject = getSubject(request, response);
+ if (subject.isAuthenticated() || subject.isRemembered()) {
+ try {
+ issueSuccessRedirect(request, response);
+ } catch (Exception e) {
+ logger.error("Cannot redirect to the default success url", e);
+ }
+ } else {
+ try {
+ WebUtils.issueRedirect(request, response, failureUrl);
+ } catch (IOException e) {
+ logger.error("Cannot redirect to failure url : {}", failureUrl, e);
+ }
+ }
+ return false;
+ }
+
+ public void setFailureUrl(String failureUrl) {
+ this.failureUrl = failureUrl;
+ }
+}
Added: shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasRealm.java
URL: http://svn.apache.org/viewvc/shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasRealm.java?rev=1226983&view=auto
==============================================================================
--- shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasRealm.java (added)
+++ shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasRealm.java Tue Jan 3 23:12:49 2012
@@ -0,0 +1,304 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cas;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.StringUtils;
+import org.jasig.cas.client.authentication.AttributePrincipal;
+import org.jasig.cas.client.validation.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This realm implementation acts as a CAS client to a CAS server for authentication and basic authorization.
+ * <p/>
+ * This realm functions by inspecting a submitted {@link org.apache.shiro.cas.CasToken CasToken} (which essentially
+ * wraps a CAS service ticket) and validates it against the CAS server using a configured CAS
+ * {@link org.jasig.cas.client.validation.TicketValidator TicketValidator}.
+ * <p/>
+ * The {@link #getValidationProtocol() validationProtocol} is {@code CAS} by default, which indicates that a
+ * a {@link org.jasig.cas.client.validation.Cas20ServiceTicketValidator Cas20ServiceTicketValidator}
+ * will be used for ticket validation. You can alternatively set
+ * or {@link org.jasig.cas.client.validation.Saml11TicketValidator Saml11TicketValidator} of CAS client. It is based on
+ * {@link AuthorizingRealm AuthorizingRealm} for both authentication and authorization. User id and attributes are retrieved from the CAS
+ * service ticket validation response during authentication phase. Roles and permissions are computed during authorization phase (according
+ * to the attributes previously retrieved).
+ *
+ * @since 1.2
+ */
+public class CasRealm extends AuthorizingRealm {
+
+ // default name of the CAS attribute for remember me authentication (CAS 3.4.10+)
+ public static final String DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME = "longTermAuthenticationRequestTokenUsed";
+ public static final String DEFAULT_VALIDATION_PROTOCOL = "CAS";
+
+ private static Logger log = LoggerFactory.getLogger(CasRealm.class);
+
+ // this is the url of the CAS server (example : http://host:port/cas)
+ private String casServerUrlPrefix;
+
+ // this is the CAS service url of the application (example : http://host:port/mycontextpath/shiro-cas)
+ private String casService;
+
+ /* CAS protocol to use for ticket validation : CAS (default) or SAML :
+ - CAS protocol can be used with CAS server version < 3.1 : in this case, no user attributes can be retrieved from the CAS ticket validation response (except if there are some customizations on CAS server side)
+ - SAML protocol can be used with CAS server version >= 3.1 : in this case, user attributes can be extracted from the CAS ticket validation response
+ */
+ private String validationProtocol = DEFAULT_VALIDATION_PROTOCOL;
+
+ // default name of the CAS attribute for remember me authentication (CAS 3.4.10+)
+ private String rememberMeAttributeName = DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME;
+
+ // this class from the CAS client is used to validate a service ticket on CAS server
+ private TicketValidator ticketValidator;
+
+ // default roles to applied to authenticated user
+ private String defaultRoles;
+
+ // default permissions to applied to authenticated user
+ private String defaultPermissions;
+
+ // names of attributes containing roles
+ private String roleAttributeNames;
+
+ // names of attributes containing permissions
+ private String permissionAttributeNames;
+
+ public CasRealm() {
+ setAuthenticationTokenClass(CasToken.class);
+ }
+
+ protected TicketValidator ensureTicketValidator() {
+ if (this.ticketValidator == null) {
+ this.ticketValidator = createTicketValidator();
+ }
+ return this.ticketValidator;
+ }
+
+ protected TicketValidator createTicketValidator() {
+ String urlPrefix = getCasServerUrlPrefix();
+ if ("saml".equalsIgnoreCase(getValidationProtocol())) {
+ return new Saml11TicketValidator(urlPrefix);
+ }
+ return new Cas20ServiceTicketValidator(urlPrefix);
+ }
+
+ /**
+ * Authenticates a user and retrieves its information.
+ *
+ * @param token the authentication token
+ * @throws AuthenticationException if there is an error during authentication.
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+ CasToken casToken = (CasToken) token;
+ if (token == null) {
+ return null;
+ }
+
+ String ticket = (String)casToken.getCredentials();
+ if (!StringUtils.hasText(ticket)) {
+ return null;
+ }
+
+ TicketValidator ticketValidator = ensureTicketValidator();
+
+ try {
+ // contact CAS server to validate service ticket
+ Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
+ // get principal, user id and attributes
+ AttributePrincipal casPrincipal = casAssertion.getPrincipal();
+ String userId = casPrincipal.getName();
+ log.debug("Validate ticket : {} in CAS server : {} to retrieve user : {}", new Object[]{
+ ticket, getCasServerUrlPrefix(), userId
+ });
+
+ Map<String, Object> attributes = casPrincipal.getAttributes();
+ // refresh authentication token (user id + remember me)
+ casToken.setUserId(userId);
+ String rememberMeAttributeName = getRememberMeAttributeName();
+ String rememberMeStringValue = (String)attributes.get(rememberMeAttributeName);
+ boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);
+ if (isRemembered) {
+ casToken.setRememberMe(true);
+ }
+ // create simple authentication info
+ List<Object> principals = CollectionUtils.asList(userId, attributes);
+ PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());
+ return new SimpleAuthenticationInfo(principalCollection, ticket);
+ } catch (TicketValidationException e) {
+ throw new CasAuthenticationException("Unable to validate ticket [" + ticket + "]", e);
+ }
+ }
+
+ /**
+ * Retrieves the AuthorizationInfo for the given principals (the CAS previously authenticated user : id + attributes).
+ *
+ * @param principals the primary identifying principals of the AuthorizationInfo that should be retrieved.
+ * @return the AuthorizationInfo associated with this principals.
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+ // retrieve user information
+ SimplePrincipalCollection principalCollection = (SimplePrincipalCollection) principals;
+ List<Object> listPrincipals = principalCollection.asList();
+ Map<String, String> attributes = (Map<String, String>) listPrincipals.get(1);
+ // create simple authorization info
+ SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
+ // add default roles
+ addRoles(simpleAuthorizationInfo, split(defaultRoles));
+ // add default permissions
+ addPermissions(simpleAuthorizationInfo, split(defaultPermissions));
+ // get roles from attributes
+ List<String> attributeNames = split(roleAttributeNames);
+ for (String attributeName : attributeNames) {
+ String value = attributes.get(attributeName);
+ addRoles(simpleAuthorizationInfo, split(value));
+ }
+ // get permissions from attributes
+ attributeNames = split(permissionAttributeNames);
+ for (String attributeName : attributeNames) {
+ String value = attributes.get(attributeName);
+ addPermissions(simpleAuthorizationInfo, split(value));
+ }
+ return simpleAuthorizationInfo;
+ }
+
+ /**
+ * Split a string into a list of not empty and trimmed strings, delimiter is a comma.
+ *
+ * @param s the input string
+ * @return the list of not empty and trimmed strings
+ */
+ private List<String> split(String s) {
+ List<String> list = new ArrayList<String>();
+ String[] elements = StringUtils.split(s, ',');
+ if (elements != null && elements.length > 0) {
+ for (String element : elements) {
+ if (StringUtils.hasText(element)) {
+ list.add(element.trim());
+ }
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Add roles to the simple authorization info.
+ *
+ * @param simpleAuthorizationInfo
+ * @param roles the list of roles to add
+ */
+ private void addRoles(SimpleAuthorizationInfo simpleAuthorizationInfo, List<String> roles) {
+ for (String role : roles) {
+ simpleAuthorizationInfo.addRole(role);
+ }
+ }
+
+ /**
+ * Add permissions to the simple authorization info.
+ *
+ * @param simpleAuthorizationInfo
+ * @param permissions the list of permissions to add
+ */
+ private void addPermissions(SimpleAuthorizationInfo simpleAuthorizationInfo, List<String> permissions) {
+ for (String permission : permissions) {
+ simpleAuthorizationInfo.addStringPermission(permission);
+ }
+ }
+
+ public String getCasServerUrlPrefix() {
+ return casServerUrlPrefix;
+ }
+
+ public void setCasServerUrlPrefix(String casServerUrlPrefix) {
+ this.casServerUrlPrefix = casServerUrlPrefix;
+ }
+
+ public String getCasService() {
+ return casService;
+ }
+
+ public void setCasService(String casService) {
+ this.casService = casService;
+ }
+
+ public String getValidationProtocol() {
+ return validationProtocol;
+ }
+
+ public void setValidationProtocol(String validationProtocol) {
+ this.validationProtocol = validationProtocol;
+ }
+
+ public String getRememberMeAttributeName() {
+ return rememberMeAttributeName;
+ }
+
+ public void setRememberMeAttributeName(String rememberMeAttributeName) {
+ this.rememberMeAttributeName = rememberMeAttributeName;
+ }
+
+ public String getDefaultRoles() {
+ return defaultRoles;
+ }
+
+ public void setDefaultRoles(String defaultRoles) {
+ this.defaultRoles = defaultRoles;
+ }
+
+ public String getDefaultPermissions() {
+ return defaultPermissions;
+ }
+
+ public void setDefaultPermissions(String defaultPermissions) {
+ this.defaultPermissions = defaultPermissions;
+ }
+
+ public String getRoleAttributeNames() {
+ return roleAttributeNames;
+ }
+
+ public void setRoleAttributeNames(String roleAttributeNames) {
+ this.roleAttributeNames = roleAttributeNames;
+ }
+
+ public String getPermissionAttributeNames() {
+ return permissionAttributeNames;
+ }
+
+ public void setPermissionAttributeNames(String permissionAttributeNames) {
+ this.permissionAttributeNames = permissionAttributeNames;
+ }
+}
Added: shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasRememberMeSecurityManager.java
URL: http://svn.apache.org/viewvc/shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasRememberMeSecurityManager.java?rev=1226983&view=auto
==============================================================================
--- shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasRememberMeSecurityManager.java (added)
+++ shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasRememberMeSecurityManager.java Tue Jan 3 23:12:49 2012
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cas;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.SubjectContext;
+import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
+
+/**
+ * This security manager is specifically dedicated to CAS authentication. Remember me is not managed by RememberMeManager but during context
+ * initialization in subject creation.
+ */
+public class CasRememberMeSecurityManager extends DefaultWebSecurityManager {
+
+ /**
+ * Construct the security manager for CAS. The manager for remember me is explicitly set to null : no {@code RememberMeManager} is
+ * required for CAS.
+ */
+ public CasRememberMeSecurityManager() {
+ setRememberMeManager(null);
+ }
+
+ /**
+ * Creates a {@code Subject} instance for the user represented by the given method arguments. As a CAS authentication, the authenticated
+ * flag is computed according to the level of authentication : login/password authentication or remember me mode.
+ *
+ * @param token the {@code AuthenticationToken} submitted for the successful authentication.
+ * @param info the {@code AuthenticationInfo} of a newly authenticated user.
+ * @param existing the existing {@code Subject} instance that initiated the authentication attempt
+ * @return the {@code Subject} instance that represents the context and session data for the newly authenticated subject.
+ */
+ @Override
+ protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {
+ SubjectContext context = createSubjectContext();
+ // set the authenticated flag of the context to true only if the CAS subject is not in a remember me mode
+ if (token != null && token instanceof CasToken) {
+ CasToken casToken = (CasToken) token;
+ if (casToken.isRememberMe()) {
+ context.setAuthenticated(false);
+ } else {
+ context.setAuthenticated(true);
+ }
+ }
+ context.setAuthenticationToken(token);
+ context.setAuthenticationInfo(info);
+ if (existing != null) {
+ context.setSubject(existing);
+ }
+ return createSubject(context);
+ }
+}
Added: shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasSubjectFactory.java
URL: http://svn.apache.org/viewvc/shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasSubjectFactory.java?rev=1226983&view=auto
==============================================================================
--- shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasSubjectFactory.java (added)
+++ shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasSubjectFactory.java Tue Jan 3 23:12:49 2012
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cas;
+
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.SubjectContext;
+import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
+
+/**
+ * {@link org.apache.shiro.mgt.SubjectFactory Subject} implementation to be used in CAS-enabled applications.
+ *
+ * @since 1.2
+ */
+public class CasSubjectFactory extends DefaultWebSubjectFactory {
+
+ @Override
+ public Subject createSubject(SubjectContext context) {
+
+ //the authenticated flag is only set by the SecurityManager after a successful authentication attempt.
+ boolean authenticated = context.isAuthenticated();
+
+ //although the SecurityManager 'sees' the submission as a successful authentication, in reality, the
+ //login might have been just a CAS rememberMe login. If so, set the authenticated flag appropriately:
+ if (authenticated) {
+
+ AuthenticationToken token = context.getAuthenticationToken();
+
+ if (token != null && token instanceof CasToken) {
+ CasToken casToken = (CasToken) token;
+ // set the authenticated flag of the context to true only if the CAS subject is not in a remember me mode
+ if (casToken.isRememberMe()) {
+ context.setAuthenticated(false);
+ }
+ }
+ }
+
+ return super.createSubject(context);
+ }
+}
Added: shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasToken.java
URL: http://svn.apache.org/viewvc/shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasToken.java?rev=1226983&view=auto
==============================================================================
--- shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasToken.java (added)
+++ shiro/trunk/support/cas/src/main/java/org/apache/shiro/cas/CasToken.java Tue Jan 3 23:12:49 2012
@@ -0,0 +1,46 @@
+package org.apache.shiro.cas;
+
+import org.apache.shiro.authc.RememberMeAuthenticationToken;
+
+/**
+ * This class represents a token for a CAS authentication (service ticket + user id + remember me).
+ *
+ * @since 1.2
+ */
+public class CasToken implements RememberMeAuthenticationToken {
+
+ private static final long serialVersionUID = 8587329689973009598L;
+
+ // the service ticket returned by the CAS server
+ private String ticket = null;
+
+ // the user identifier
+ private String userId = null;
+
+ // is the user in a remember me mode ?
+ private boolean isRememberMe = false;
+
+ public CasToken(String ticket) {
+ this.ticket = ticket;
+ }
+
+ public Object getPrincipal() {
+ return userId;
+ }
+
+ public Object getCredentials() {
+ return ticket;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public boolean isRememberMe() {
+ return isRememberMe;
+ }
+
+ public void setRememberMe(boolean isRememberMe) {
+ this.isRememberMe = isRememberMe;
+ }
+}
Added: shiro/trunk/support/cas/src/test/groovy/org/apache/shiro/cas/CasRealmTest.groovy
URL: http://svn.apache.org/viewvc/shiro/trunk/support/cas/src/test/groovy/org/apache/shiro/cas/CasRealmTest.groovy?rev=1226983&view=auto
==============================================================================
--- shiro/trunk/support/cas/src/test/groovy/org/apache/shiro/cas/CasRealmTest.groovy (added)
+++ shiro/trunk/support/cas/src/test/groovy/org/apache/shiro/cas/CasRealmTest.groovy Tue Jan 3 23:12:49 2012
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cas
+
+import org.apache.shiro.authc.AuthenticationInfo
+import org.apache.shiro.authz.AuthorizationInfo
+
+/**
+ * Unit tests for the {@link CasRealm} implementation.
+ *
+ * @since 1.2
+ */
+class CasRealmTest extends GroovyTestCase {
+
+ /**
+ * Creates a CAS realm with a ticket validator mock.
+ *
+ * @return CasRealm The CAS realm for testing.
+ */
+ private CasRealm createCasRealm() {
+ new CasRealm(ticketValidator: new MockServiceTicketValidator());
+ }
+
+ void testNoAttribute() {
+ CasRealm casRealm = createCasRealm();
+ CasToken casToken = new CasToken('$=defaultId');
+ AuthenticationInfo authenticationInfo = casRealm.doGetAuthenticationInfo(casToken);
+ def principals = authenticationInfo.principals
+ assertEquals "defaultId", principals.primaryPrincipal
+ def attributes = principals.asList()[1] //returns a map
+ assertEquals 0, attributes.size()
+ AuthorizationInfo authorizationInfo = casRealm.doGetAuthorizationInfo(principals);
+ assertNull authorizationInfo.stringPermissions
+ assertNull authorizationInfo.roles
+ }
+
+ void testNoAttributeDefaultRoleAndPermission() {
+ CasRealm casRealm = createCasRealm();
+ casRealm.defaultRoles = "defaultRole"
+ casRealm.defaultPermissions = "defaultPermission"
+ CasToken casToken = new CasToken('$=defaultId');
+ AuthenticationInfo authenticationInfo = casRealm.doGetAuthenticationInfo(casToken);
+ def principals = authenticationInfo.principals
+ assertEquals "defaultId", principals.primaryPrincipal
+ def attributes = principals.oneByType(Map)
+ assertEquals 0, attributes.size()
+ AuthorizationInfo authorizationInfo = casRealm.doGetAuthorizationInfo(principals);
+ assertTrue authorizationInfo.roles.contains("defaultRole")
+ assertTrue authorizationInfo.stringPermissions.contains("defaultPermission")
+ }
+
+ void testNoAttributeDefaultRolesAndPermissions() {
+ CasRealm casRealm = createCasRealm();
+ casRealm.defaultRoles = "defaultRole1, defaultRole2"
+ casRealm.defaultPermissions = "defaultPermission1,defaultPermission2"
+ CasToken casToken = new CasToken('$=defaultId');
+ AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
+ def principals = authcInfo.principals
+ assertEquals "defaultId", principals.primaryPrincipal
+ def attributes = principals.oneByType(Map)
+ assertEquals 0, attributes.size()
+ AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals)
+ assertEquals 2, authzInfo.roles.size()
+ assertTrue authzInfo.roles.contains("defaultRole1")
+ assertTrue authzInfo.roles.contains("defaultRole2")
+ assertEquals 2, authzInfo.stringPermissions.size()
+ assertTrue authzInfo.stringPermissions.contains("defaultPermission1")
+ assertTrue authzInfo.stringPermissions.contains("defaultPermission2")
+ }
+
+ void testRoleAndPermission() {
+ CasRealm casRealm = createCasRealm();
+ casRealm.roleAttributeNames = "role"
+ casRealm.permissionAttributeNames = "permission"
+ CasToken casToken = new CasToken('$=defaultId|role=aRole|permission=aPermission');
+ AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
+ def principals = authcInfo.principals
+ assertEquals "defaultId", principals.primaryPrincipal
+ def attributes = principals.oneByType(Map)
+ assertEquals 2, attributes.size()
+ assertEquals "aRole", attributes['role']
+ assertEquals "aPermission", attributes['permission']
+ AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
+ assertTrue authzInfo.roles.contains("aRole")
+ assertTrue authzInfo.stringPermissions.contains("aPermission")
+ }
+
+ void testRolesAndPermissions() {
+ CasRealm casRealm = createCasRealm();
+ casRealm.setRoleAttributeNames("role1 , role2");
+ casRealm.setPermissionAttributeNames("permission1,permission2");
+ CasToken casToken = new CasToken(
+ '$=defaultId|role1=role11 , role12|role2=role21,role22|permission1=permission11, permission12|permission2=permission21 ,permission22');
+ AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
+ def principals = authcInfo.principals
+ assertEquals "defaultId", principals.primaryPrincipal
+ def attributes = principals.oneByType(Map)
+ assertEquals "role11 , role12", attributes['role1']
+ assertEquals "role21,role22", attributes['role2']
+ assertEquals "permission11, permission12", attributes['permission1']
+ assertEquals "permission21 ,permission22", attributes['permission2']
+ AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
+ assertEquals 4, authzInfo.roles.size()
+ assertTrue authzInfo.roles.contains("role11")
+ assertTrue authzInfo.roles.contains("role12")
+ assertTrue authzInfo.roles.contains("role21")
+ assertTrue authzInfo.roles.contains("role22")
+ assertTrue authzInfo.stringPermissions.contains("permission11")
+ assertTrue authzInfo.stringPermissions.contains("permission12")
+ assertTrue authzInfo.stringPermissions.contains("permission21")
+ assertTrue authzInfo.stringPermissions.contains("permission22")
+ }
+
+ void testNotRememberMe() {
+ CasRealm casRealm = createCasRealm();
+ CasToken casToken = new CasToken("\$=defaultId|$CasRealm.DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME=false");
+ AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
+ def principals = authcInfo.principals
+ assertEquals "defaultId", principals.primaryPrincipal
+ def attributes = principals.oneByType(Map)
+ assertEquals "false", attributes[CasRealm.DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME]
+ assertFalse casToken.rememberMe
+ AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
+ assertNull authzInfo.stringPermissions
+ assertNull authzInfo.roles
+ }
+
+ void testRememberMe() {
+ CasRealm casRealm = createCasRealm();
+ CasToken casToken = new CasToken("\$=defaultId|$CasRealm.DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME=true");
+ AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
+ def principals = authcInfo.principals
+ assertEquals "defaultId", principals.primaryPrincipal
+ def attributes = principals.oneByType(Map)
+ assertEquals "true", attributes[CasRealm.DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME]
+ assertTrue casToken.rememberMe
+ AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
+ assertNull authzInfo.stringPermissions
+ assertNull authzInfo.roles
+ }
+
+ void testRememberMeNewAttributeName() {
+ CasRealm casRealm = createCasRealm();
+ casRealm.rememberMeAttributeName = "rme"
+ CasToken casToken = new CasToken('$=defaultId|rme=true');
+ AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
+ def principals = authcInfo.principals
+ assertEquals "defaultId", principals.primaryPrincipal
+ def attributes = principals.oneByType(Map)
+ assertEquals "true", attributes[casRealm.rememberMeAttributeName]
+ assertTrue casToken.rememberMe
+ AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
+ assertNull authzInfo.stringPermissions
+ assertNull authzInfo.roles
+ }
+
+}
Added: shiro/trunk/support/cas/src/test/groovy/org/apache/shiro/cas/CasTokenTest.groovy
URL: http://svn.apache.org/viewvc/shiro/trunk/support/cas/src/test/groovy/org/apache/shiro/cas/CasTokenTest.groovy?rev=1226983&view=auto
==============================================================================
--- shiro/trunk/support/cas/src/test/groovy/org/apache/shiro/cas/CasTokenTest.groovy (added)
+++ shiro/trunk/support/cas/src/test/groovy/org/apache/shiro/cas/CasTokenTest.groovy Tue Jan 3 23:12:49 2012
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cas
+
+/**
+ * Unit tests for the {@link CasToken} implementation.
+ *
+ * @since 1.2
+ */
+class CasTokenTest extends GroovyTestCase {
+
+ void testPrincipal() {
+ CasToken casToken = new CasToken("fakeTicket")
+ assertNull casToken.principal
+ casToken.userId = "myUserId"
+ assertEquals "myUserId", casToken.principal
+ }
+
+ void testCredentials() {
+ CasToken casToken = new CasToken("fakeTicket")
+ assertEquals "fakeTicket", casToken.credentials
+ }
+
+ void testRememberMe() {
+ CasToken casToken = new CasToken("fakeTicket")
+ assertFalse casToken.rememberMe
+ casToken.rememberMe = true
+ assertTrue casToken.rememberMe
+ }
+}
Added: shiro/trunk/support/cas/src/test/groovy/org/apache/shiro/cas/MockServiceTicketValidator.groovy
URL: http://svn.apache.org/viewvc/shiro/trunk/support/cas/src/test/groovy/org/apache/shiro/cas/MockServiceTicketValidator.groovy?rev=1226983&view=auto
==============================================================================
--- shiro/trunk/support/cas/src/test/groovy/org/apache/shiro/cas/MockServiceTicketValidator.groovy (added)
+++ shiro/trunk/support/cas/src/test/groovy/org/apache/shiro/cas/MockServiceTicketValidator.groovy Tue Jan 3 23:12:49 2012
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.cas
+
+import org.apache.shiro.util.StringUtils
+import org.jasig.cas.client.authentication.AttributePrincipalImpl
+import org.jasig.cas.client.validation.Assertion
+import org.jasig.cas.client.validation.AssertionImpl
+import org.jasig.cas.client.validation.TicketValidationException
+import org.jasig.cas.client.validation.TicketValidator
+
+/**
+ * @since 1.2
+ */
+class MockServiceTicketValidator implements TicketValidator {
+
+ /**
+ * Returns different assertions according to the ticket input. The format of the mock ticket must be :
+ * key1=value1,key2=value2,...,keyN=valueN. If keyX is $, valueX is considered to be the name of the principal, otherwise (keyX, valueX)
+ * is considered to be an attribute of the principal.
+ */
+ public Assertion validate(String ticket, String service) throws TicketValidationException {
+ String name = null;
+ def attributes = [:]
+ String[] elements = StringUtils.split(ticket, '|' as char);
+ int length = elements.length;
+ for (int i = 0; i < length; i++) {
+ String[] pair = StringUtils.split(elements[i], '=' as char);
+ String key = pair[0].trim();
+ String value = pair[1].trim();
+ if ('$'.equals(key)) {
+ name = value;
+ } else {
+ attributes.put(key, value);
+ }
+ }
+ AttributePrincipalImpl attributePrincipalImpl = new AttributePrincipalImpl(name, attributes);
+ return new AssertionImpl(attributePrincipalImpl, [:]);
+
+ }
+}
Modified: shiro/trunk/support/pom.xml
URL: http://svn.apache.org/viewvc/shiro/trunk/support/pom.xml?rev=1226983&r1=1226982&r2=1226983&view=diff
==============================================================================
--- shiro/trunk/support/pom.xml (original)
+++ shiro/trunk/support/pom.xml Tue Jan 3 23:12:49 2012
@@ -39,6 +39,7 @@
<module>guice</module>
<module>openid4j</module>
<module>features</module>
+ <module>cas</module>
</modules>
</project>