shiro-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bdem...@apache.org
Subject [4/8] shiro git commit: Adding shiro-cas back to master
Date Mon, 07 Nov 2016 22:31:46 GMT
Adding shiro-cas back to master

Still deprecated, but we cannot remove it until 2.0


Project: http://git-wip-us.apache.org/repos/asf/shiro/repo
Commit: http://git-wip-us.apache.org/repos/asf/shiro/commit/056d7cc2
Tree: http://git-wip-us.apache.org/repos/asf/shiro/tree/056d7cc2
Diff: http://git-wip-us.apache.org/repos/asf/shiro/diff/056d7cc2

Branch: refs/heads/master
Commit: 056d7cc27b462d94f8dc91490154dbca784d857c
Parents: 3fccc75
Author: Brian Demers <bdemers@apache.org>
Authored: Mon Nov 7 15:16:10 2016 -0500
Committer: Brian Demers <bdemers@apache.org>
Committed: Mon Nov 7 15:16:10 2016 -0500

----------------------------------------------------------------------
 support/cas/pom.xml                             |  89 ++++++
 .../shiro/cas/CasAuthenticationException.java   |  46 +++
 .../java/org/apache/shiro/cas/CasFilter.java    | 156 +++++++++
 .../java/org/apache/shiro/cas/CasRealm.java     | 313 +++++++++++++++++++
 .../org/apache/shiro/cas/CasSubjectFactory.java |  59 ++++
 .../java/org/apache/shiro/cas/CasToken.java     |  67 ++++
 .../org/apache/shiro/cas/CasRealmTest.groovy    | 176 +++++++++++
 .../org/apache/shiro/cas/CasTokenTest.groovy    |  49 +++
 .../shiro/cas/MockServiceTicketValidator.groovy |  60 ++++
 support/pom.xml                                 |   1 +
 test-coverage/pom.xml                           |   4 +
 11 files changed, 1020 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/shiro/blob/056d7cc2/support/cas/pom.xml
----------------------------------------------------------------------
diff --git a/support/cas/pom.xml b/support/cas/pom.xml
new file mode 100644
index 0000000..387f018
--- /dev/null
+++ b/support/cas/pom.xml
@@ -0,0 +1,89 @@
+<?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.4.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.2</version>
+        </dependency>
+        <dependency>
+            <!-- for Optional SAML ticket validation: -->
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <!-- for Optional SAML ticket validation: -->
+            <groupId>org.opensaml</groupId>
+            <artifactId>opensaml</artifactId>
+            <version>1.1</version>
+            <scope>runtime</scope>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <!-- for Optional SAML ticket validation: -->
+            <groupId>org.apache.santuario</groupId>
+            <artifactId>xmlsec</artifactId>
+            <version>1.4.3</version>
+            <scope>runtime</scope>
+            <optional>true</optional>
+        </dependency>
+    </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.client*;version="[3.2, 4)",
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

http://git-wip-us.apache.org/repos/asf/shiro/blob/056d7cc2/support/cas/src/main/java/org/apache/shiro/cas/CasAuthenticationException.java
----------------------------------------------------------------------
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasAuthenticationException.java b/support/cas/src/main/java/org/apache/shiro/cas/CasAuthenticationException.java
new file mode 100644
index 0000000..e3add40
--- /dev/null
+++ b/support/cas/src/main/java/org/apache/shiro/cas/CasAuthenticationException.java
@@ -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;
+
+import org.apache.shiro.authc.AuthenticationException;
+
+/**
+ * @since 1.2
+ * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
+ * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
+ */
+@Deprecated
+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);
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/056d7cc2/support/cas/src/main/java/org/apache/shiro/cas/CasFilter.java
----------------------------------------------------------------------
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasFilter.java b/support/cas/src/main/java/org/apache/shiro/cas/CasFilter.java
new file mode 100644
index 0000000..88262a8
--- /dev/null
+++ b/support/cas/src/main/java/org/apache/shiro/cas/CasFilter.java
@@ -0,0 +1,156 @@
+/*
+ * 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
+ * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
+ * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
+ */
+@Deprecated
+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) {
+        if (logger.isDebugEnabled()) {
+            logger.debug( "Authentication exception", ae );
+        }
+        // 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;
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/056d7cc2/support/cas/src/main/java/org/apache/shiro/cas/CasRealm.java
----------------------------------------------------------------------
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasRealm.java b/support/cas/src/main/java/org/apache/shiro/cas/CasRealm.java
new file mode 100644
index 0000000..791674a
--- /dev/null
+++ b/support/cas/src/main/java/org/apache/shiro/cas/CasRealm.java
@@ -0,0 +1,313 @@
+/*
+ * 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
+ * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
+ * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
+ */
+@Deprecated
+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);
+    }
+
+    @Override
+    protected void onInit() {
+        super.onInit();
+        ensureTicketValidator();
+    }
+
+    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;
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/056d7cc2/support/cas/src/main/java/org/apache/shiro/cas/CasSubjectFactory.java
----------------------------------------------------------------------
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasSubjectFactory.java b/support/cas/src/main/java/org/apache/shiro/cas/CasSubjectFactory.java
new file mode 100644
index 0000000..51a774e
--- /dev/null
+++ b/support/cas/src/main/java/org/apache/shiro/cas/CasSubjectFactory.java
@@ -0,0 +1,59 @@
+/*
+ * 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
+ * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
+ * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
+ */
+@Deprecated
+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);
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/056d7cc2/support/cas/src/main/java/org/apache/shiro/cas/CasToken.java
----------------------------------------------------------------------
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasToken.java b/support/cas/src/main/java/org/apache/shiro/cas/CasToken.java
new file mode 100644
index 0000000..221d1cb
--- /dev/null
+++ b/support/cas/src/main/java/org/apache/shiro/cas/CasToken.java
@@ -0,0 +1,67 @@
+/*
+ * 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.RememberMeAuthenticationToken;
+
+/**
+ * This class represents a token for a CAS authentication (service ticket + user id + remember me).
+ *
+ * @since 1.2
+ * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
+ * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
+ */
+@Deprecated
+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;
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/056d7cc2/support/cas/src/test/groovy/org/apache/shiro/cas/CasRealmTest.groovy
----------------------------------------------------------------------
diff --git a/support/cas/src/test/groovy/org/apache/shiro/cas/CasRealmTest.groovy b/support/cas/src/test/groovy/org/apache/shiro/cas/CasRealmTest.groovy
new file mode 100644
index 0000000..baf2a57
--- /dev/null
+++ b/support/cas/src/test/groovy/org/apache/shiro/cas/CasRealmTest.groovy
@@ -0,0 +1,176 @@
+/*
+ * 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
+ * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
+ * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
+ */
+@Deprecated
+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
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/056d7cc2/support/cas/src/test/groovy/org/apache/shiro/cas/CasTokenTest.groovy
----------------------------------------------------------------------
diff --git a/support/cas/src/test/groovy/org/apache/shiro/cas/CasTokenTest.groovy b/support/cas/src/test/groovy/org/apache/shiro/cas/CasTokenTest.groovy
new file mode 100644
index 0000000..ae86eee
--- /dev/null
+++ b/support/cas/src/test/groovy/org/apache/shiro/cas/CasTokenTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * 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
+ * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
+ * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
+ */
+@Deprecated
+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
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/056d7cc2/support/cas/src/test/groovy/org/apache/shiro/cas/MockServiceTicketValidator.groovy
----------------------------------------------------------------------
diff --git a/support/cas/src/test/groovy/org/apache/shiro/cas/MockServiceTicketValidator.groovy b/support/cas/src/test/groovy/org/apache/shiro/cas/MockServiceTicketValidator.groovy
new file mode 100644
index 0000000..fc46ab2
--- /dev/null
+++ b/support/cas/src/test/groovy/org/apache/shiro/cas/MockServiceTicketValidator.groovy
@@ -0,0 +1,60 @@
+/*
+ * 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
+ * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
+ * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
+ */
+@Deprecated
+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, [:]);
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/056d7cc2/support/pom.xml
----------------------------------------------------------------------
diff --git a/support/pom.xml b/support/pom.xml
index e753d60..fd7f801 100644
--- a/support/pom.xml
+++ b/support/pom.xml
@@ -40,6 +40,7 @@
         <module>guice</module>
         <module>openid4j</module>
         <module>features</module>
+        <module>cas</module>
         <module>spring-boot</module>
         <module>servlet-plugin</module>
         <module>jaxrs</module>

http://git-wip-us.apache.org/repos/asf/shiro/blob/056d7cc2/test-coverage/pom.xml
----------------------------------------------------------------------
diff --git a/test-coverage/pom.xml b/test-coverage/pom.xml
index 8705d61..f088f32 100644
--- a/test-coverage/pom.xml
+++ b/test-coverage/pom.xml
@@ -46,6 +46,10 @@
         </dependency>
         <dependency>
             <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-cas</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
             <artifactId>shiro-ehcache</artifactId>
         </dependency>
         <dependency>


Mime
View raw message