View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.shiro.cas;
20  
21  import org.apache.shiro.authc.AuthenticationException;
22  import org.apache.shiro.authc.AuthenticationInfo;
23  import org.apache.shiro.authc.AuthenticationToken;
24  import org.apache.shiro.authc.SimpleAuthenticationInfo;
25  import org.apache.shiro.authz.AuthorizationInfo;
26  import org.apache.shiro.authz.SimpleAuthorizationInfo;
27  import org.apache.shiro.realm.AuthorizingRealm;
28  import org.apache.shiro.subject.PrincipalCollection;
29  import org.apache.shiro.subject.SimplePrincipalCollection;
30  import org.apache.shiro.util.CollectionUtils;
31  import org.apache.shiro.util.StringUtils;
32  import org.jasig.cas.client.authentication.AttributePrincipal;
33  import org.jasig.cas.client.validation.*;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  import java.util.ArrayList;
38  import java.util.List;
39  import java.util.Map;
40  
41  /**
42   * This realm implementation acts as a CAS client to a CAS server for authentication and basic authorization.
43   * <p/>
44   * This realm functions by inspecting a submitted {@link org.apache.shiro.cas.CasToken CasToken} (which essentially 
45   * wraps a CAS service ticket) and validates it against the CAS server using a configured CAS
46   * {@link org.jasig.cas.client.validation.TicketValidator TicketValidator}.
47   * <p/>
48   * The {@link #getValidationProtocol() validationProtocol} is {@code CAS} by default, which indicates that a
49   * a {@link org.jasig.cas.client.validation.Cas20ServiceTicketValidator Cas20ServiceTicketValidator}
50   * will be used for ticket validation.  You can alternatively set
51   * or {@link org.jasig.cas.client.validation.Saml11TicketValidator Saml11TicketValidator} of CAS client. It is based on
52   * {@link AuthorizingRealm AuthorizingRealm} for both authentication and authorization. User id and attributes are retrieved from the CAS
53   * service ticket validation response during authentication phase. Roles and permissions are computed during authorization phase (according
54   * to the attributes previously retrieved).
55   *
56   * @since 1.2
57   * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
58   * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
59   */
60  @Deprecated
61  public class CasRealm extends AuthorizingRealm {
62  
63      // default name of the CAS attribute for remember me authentication (CAS 3.4.10+)
64      public static final String DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME = "longTermAuthenticationRequestTokenUsed";
65      public static final String DEFAULT_VALIDATION_PROTOCOL = "CAS";
66      
67      private static Logger log = LoggerFactory.getLogger(CasRealm.class);
68      
69      // this is the url of the CAS server (example : http://host:port/cas)
70      private String casServerUrlPrefix;
71      
72      // this is the CAS service url of the application (example : http://host:port/mycontextpath/shiro-cas)
73      private String casService;
74      
75      /* CAS protocol to use for ticket validation : CAS (default) or SAML :
76         - 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)
77         - 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
78      */
79      private String validationProtocol = DEFAULT_VALIDATION_PROTOCOL;
80      
81      // default name of the CAS attribute for remember me authentication (CAS 3.4.10+)
82      private String rememberMeAttributeName = DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME;
83      
84      // this class from the CAS client is used to validate a service ticket on CAS server
85      private TicketValidator ticketValidator;
86      
87      // default roles to applied to authenticated user
88      private String defaultRoles;
89      
90      // default permissions to applied to authenticated user
91      private String defaultPermissions;
92      
93      // names of attributes containing roles
94      private String roleAttributeNames;
95      
96      // names of attributes containing permissions
97      private String permissionAttributeNames;
98      
99      public CasRealm() {
100         setAuthenticationTokenClass(CasToken.class);
101     }
102 
103     @Override
104     protected void onInit() {
105         super.onInit();
106         ensureTicketValidator();
107     }
108 
109     protected TicketValidator ensureTicketValidator() {
110         if (this.ticketValidator == null) {
111             this.ticketValidator = createTicketValidator();
112         }
113         return this.ticketValidator;
114     }
115     
116     protected TicketValidator createTicketValidator() {
117         String urlPrefix = getCasServerUrlPrefix();
118         if ("saml".equalsIgnoreCase(getValidationProtocol())) {
119             return new Saml11TicketValidator(urlPrefix);
120         }
121         return new Cas20ServiceTicketValidator(urlPrefix);
122     }
123     
124     /**
125      * Authenticates a user and retrieves its information.
126      * 
127      * @param token the authentication token
128      * @throws AuthenticationException if there is an error during authentication.
129      */
130     @Override
131     @SuppressWarnings("unchecked")
132     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
133         CasToken casToken = (CasToken) token;
134         if (token == null) {
135             return null;
136         }
137         
138         String ticket = (String)casToken.getCredentials();
139         if (!StringUtils.hasText(ticket)) {
140             return null;
141         }
142         
143         TicketValidator ticketValidator = ensureTicketValidator();
144 
145         try {
146             // contact CAS server to validate service ticket
147             Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
148             // get principal, user id and attributes
149             AttributePrincipal casPrincipal = casAssertion.getPrincipal();
150             String userId = casPrincipal.getName();
151             log.debug("Validate ticket : {} in CAS server : {} to retrieve user : {}", new Object[]{
152                     ticket, getCasServerUrlPrefix(), userId
153             });
154 
155             Map<String, Object> attributes = casPrincipal.getAttributes();
156             // refresh authentication token (user id + remember me)
157             casToken.setUserId(userId);
158             String rememberMeAttributeName = getRememberMeAttributeName();
159             String rememberMeStringValue = (String)attributes.get(rememberMeAttributeName);
160             boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);
161             if (isRemembered) {
162                 casToken.setRememberMe(true);
163             }
164             // create simple authentication info
165             List<Object> principals = CollectionUtils.asList(userId, attributes);
166             PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());
167             return new SimpleAuthenticationInfo(principalCollection, ticket);
168         } catch (TicketValidationException e) { 
169             throw new CasAuthenticationException("Unable to validate ticket [" + ticket + "]", e);
170         }
171     }
172     
173     /**
174      * Retrieves the AuthorizationInfo for the given principals (the CAS previously authenticated user : id + attributes).
175      * 
176      * @param principals the primary identifying principals of the AuthorizationInfo that should be retrieved.
177      * @return the AuthorizationInfo associated with this principals.
178      */
179     @Override
180     @SuppressWarnings("unchecked")
181     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
182         // retrieve user information
183         SimplePrincipalCollection principalCollection = (SimplePrincipalCollection) principals;
184         List<Object> listPrincipals = principalCollection.asList();
185         Map<String, String> attributes = (Map<String, String>) listPrincipals.get(1);
186         // create simple authorization info
187         SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
188         // add default roles
189         addRoles(simpleAuthorizationInfo, split(defaultRoles));
190         // add default permissions
191         addPermissions(simpleAuthorizationInfo, split(defaultPermissions));
192         // get roles from attributes
193         List<String> attributeNames = split(roleAttributeNames);
194         for (String attributeName : attributeNames) {
195             String value = attributes.get(attributeName);
196             addRoles(simpleAuthorizationInfo, split(value));
197         }
198         // get permissions from attributes
199         attributeNames = split(permissionAttributeNames);
200         for (String attributeName : attributeNames) {
201             String value = attributes.get(attributeName);
202             addPermissions(simpleAuthorizationInfo, split(value));
203         }
204         return simpleAuthorizationInfo;
205     }
206     
207     /**
208      * Split a string into a list of not empty and trimmed strings, delimiter is a comma.
209      * 
210      * @param s the input string
211      * @return the list of not empty and trimmed strings
212      */
213     private List<String> split(String s) {
214         List<String> list = new ArrayList<String>();
215         String[] elements = StringUtils.split(s, ',');
216         if (elements != null && elements.length > 0) {
217             for (String element : elements) {
218                 if (StringUtils.hasText(element)) {
219                     list.add(element.trim());
220                 }
221             }
222         }
223         return list;
224     }
225     
226     /**
227      * Add roles to the simple authorization info.
228      * 
229      * @param simpleAuthorizationInfo
230      * @param roles the list of roles to add
231      */
232     private void addRoles(SimpleAuthorizationInfo simpleAuthorizationInfo, List<String> roles) {
233         for (String role : roles) {
234             simpleAuthorizationInfo.addRole(role);
235         }
236     }
237     
238     /**
239      * Add permissions to the simple authorization info.
240      * 
241      * @param simpleAuthorizationInfo
242      * @param permissions the list of permissions to add
243      */
244     private void addPermissions(SimpleAuthorizationInfo simpleAuthorizationInfo, List<String> permissions) {
245         for (String permission : permissions) {
246             simpleAuthorizationInfo.addStringPermission(permission);
247         }
248     }
249 
250     public String getCasServerUrlPrefix() {
251         return casServerUrlPrefix;
252     }
253 
254     public void setCasServerUrlPrefix(String casServerUrlPrefix) {
255         this.casServerUrlPrefix = casServerUrlPrefix;
256     }
257 
258     public String getCasService() {
259         return casService;
260     }
261 
262     public void setCasService(String casService) {
263         this.casService = casService;
264     }
265 
266     public String getValidationProtocol() {
267         return validationProtocol;
268     }
269 
270     public void setValidationProtocol(String validationProtocol) {
271         this.validationProtocol = validationProtocol;
272     }
273 
274     public String getRememberMeAttributeName() {
275         return rememberMeAttributeName;
276     }
277 
278     public void setRememberMeAttributeName(String rememberMeAttributeName) {
279         this.rememberMeAttributeName = rememberMeAttributeName;
280     }
281 
282     public String getDefaultRoles() {
283         return defaultRoles;
284     }
285 
286     public void setDefaultRoles(String defaultRoles) {
287         this.defaultRoles = defaultRoles;
288     }
289 
290     public String getDefaultPermissions() {
291         return defaultPermissions;
292     }
293 
294     public void setDefaultPermissions(String defaultPermissions) {
295         this.defaultPermissions = defaultPermissions;
296     }
297 
298     public String getRoleAttributeNames() {
299         return roleAttributeNames;
300     }
301 
302     public void setRoleAttributeNames(String roleAttributeNames) {
303         this.roleAttributeNames = roleAttributeNames;
304     }
305 
306     public String getPermissionAttributeNames() {
307         return permissionAttributeNames;
308     }
309 
310     public void setPermissionAttributeNames(String permissionAttributeNames) {
311         this.permissionAttributeNames = permissionAttributeNames;
312     }
313 }