Coverage Report - org.apache.shiro.mgt.DefaultSubjectDAO
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultSubjectDAO
96%
54/56
88%
30/34
2.9
 
 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.mgt;
 20  
 
 21  
 import org.apache.shiro.session.Session;
 22  
 import org.apache.shiro.subject.PrincipalCollection;
 23  
 import org.apache.shiro.subject.Subject;
 24  
 import org.apache.shiro.subject.support.DefaultSubjectContext;
 25  
 import org.apache.shiro.subject.support.DelegatingSubject;
 26  
 import org.apache.shiro.util.CollectionUtils;
 27  
 import org.slf4j.Logger;
 28  
 import org.slf4j.LoggerFactory;
 29  
 
 30  
 import java.lang.reflect.Field;
 31  
 
 32  
 /**
 33  
  * Default {@code SubjectDAO} implementation that stores Subject state in the Subject's Session by default (but this
 34  
  * can be disabled - see below).  The Subject instance
 35  
  * can be re-created at a later time by first acquiring the associated Session (typically from a
 36  
  * {@link org.apache.shiro.session.mgt.SessionManager SessionManager}) via a session ID or session key and then
 37  
  * building a {@code Subject} instance from {@code Session} attributes.
 38  
  * <h2>Controlling how Sessions are used</h2>
 39  
  * Whether or not a {@code Subject}'s {@code Session} is used or not to persist its own state is controlled on a
 40  
  * <em>per-Subject</em> basis as determined by the configured
 41  
  * {@link #setSessionStorageEvaluator(SessionStorageEvaluator) sessionStorageEvaluator}.
 42  
  * The default {@code Evaluator} is a {@link DefaultSessionStorageEvaluator}, which supports enabling or disabling
 43  
  * session usage for Subject persistence at a global level for all subjects (and defaults to allowing sessions to be
 44  
  * used).
 45  
  * <h3>Disabling Session Persistence Entirely</h3>
 46  
  * Because the default {@code SessionStorageEvaluator} instance is a {@link DefaultSessionStorageEvaluator}, you
 47  
  * can disable Session usage for Subject state entirely by configuring that instance directly, e.g.:
 48  
  * <pre>
 49  
  *     ((DefaultSessionStorageEvaluator)sessionDAO.getSessionStorageEvaluator()).setSessionStorageEnabled(false);
 50  
  * </pre>
 51  
  * or, for example, in {@code shiro.ini}:
 52  
  * <pre>
 53  
  *     securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false
 54  
  * </pre>
 55  
  * but <b>note:</b> ONLY do this your
 56  
  * application is 100% stateless and you <em>DO NOT</em> need subjects to be remembered across remote
 57  
  * invocations, or in a web environment across HTTP requests.
 58  
  * <h3>Supporting Both Stateful and Stateless Subject paradigms</h3>
 59  
  * Perhaps your application needs to support a hybrid approach of both stateful and stateless Subjects:
 60  
  * <ul>
 61  
  * <li>Stateful: Stateful subjects might represent web end-users that need their identity and authentication
 62  
  * state to be remembered from page to page.</li>
 63  
  * <li>Stateless: Stateless subjects might represent API clients (e.g. REST clients) that authenticate on every
 64  
  * request, and therefore don't need authentication state to be stored across requests in a session.</li>
 65  
  * </ul>
 66  
  * To support the hybrid <em>per-Subject</em> approach, you will need to create your own implementation of the
 67  
  * {@link SessionStorageEvaluator} interface and configure it via the
 68  
  * {@link #setSessionStorageEvaluator(SessionStorageEvaluator)} method, or, with {@code shiro.ini}:
 69  
  * <pre>
 70  
  *     myEvaluator = com.my.CustomSessionStorageEvaluator
 71  
  *     securityManager.subjectDAO.sessionStorageEvaluator = $myEvaluator
 72  
  * </pre>
 73  
  * <p/>
 74  
  * Unless overridden, the default evaluator is a {@link DefaultSessionStorageEvaluator}, which enables session usage for
 75  
  * Subject state by default.
 76  
  *
 77  
  * @see #isSessionStorageEnabled(org.apache.shiro.subject.Subject)
 78  
  * @see SessionStorageEvaluator
 79  
  * @see DefaultSessionStorageEvaluator
 80  
  * @since 1.2
 81  
  */
 82  
 public class DefaultSubjectDAO implements SubjectDAO {
 83  
 
 84  1
     private static final Logger log = LoggerFactory.getLogger(DefaultSubjectDAO.class);
 85  
 
 86  
     /**
 87  
      * Evaluator that determines if a Subject's session may be used to store the Subject's own state.
 88  
      */
 89  
     private SessionStorageEvaluator sessionStorageEvaluator;
 90  
 
 91  54
     public DefaultSubjectDAO() {
 92  
         //default implementation allows enabling/disabling session usages at a global level for all subjects:
 93  54
         this.sessionStorageEvaluator = new DefaultSessionStorageEvaluator();
 94  54
     }
 95  
 
 96  
     /**
 97  
      * Determines if the subject's session will be used to persist subject state or not.  This implementation
 98  
      * merely delegates to the internal {@link SessionStorageEvaluator} (a
 99  
      * {@code DefaultSessionStorageEvaluator} by default).
 100  
      *
 101  
      * @param subject the subject to inspect to determine if the subject's session will be used to persist subject
 102  
      *                state or not.
 103  
      * @return {@code true} if the subject's session will be used to persist subject state, {@code false} otherwise.
 104  
      * @see #setSessionStorageEvaluator(SessionStorageEvaluator)
 105  
      * @see DefaultSessionStorageEvaluator
 106  
      */
 107  
     protected boolean isSessionStorageEnabled(Subject subject) {
 108  51
         return getSessionStorageEvaluator().isSessionStorageEnabled(subject);
 109  
     }
 110  
 
 111  
     /**
 112  
      * Returns the {@code SessionStorageEvaluator} that will determine if a {@code Subject}'s state may be persisted in
 113  
      * the Subject's session.  The default instance is a {@link DefaultSessionStorageEvaluator}.
 114  
      *
 115  
      * @return the {@code SessionStorageEvaluator} that will determine if a {@code Subject}'s state may be persisted in
 116  
      *         the Subject's session.
 117  
      * @see DefaultSessionStorageEvaluator
 118  
      */
 119  
     public SessionStorageEvaluator getSessionStorageEvaluator() {
 120  53
         return sessionStorageEvaluator;
 121  
     }
 122  
 
 123  
     /**
 124  
      * Sets the {@code SessionStorageEvaluator} that will determine if a {@code Subject}'s state may be persisted in
 125  
      * the Subject's session. The default instance is a {@link DefaultSessionStorageEvaluator}.
 126  
      *
 127  
      * @param sessionStorageEvaluator the {@code SessionStorageEvaluator} that will determine if a {@code Subject}'s
 128  
      *                                state may be persisted in the Subject's session.
 129  
      * @see DefaultSessionStorageEvaluator
 130  
      */
 131  
     public void setSessionStorageEvaluator(SessionStorageEvaluator sessionStorageEvaluator) {
 132  1
         this.sessionStorageEvaluator = sessionStorageEvaluator;
 133  1
     }
 134  
 
 135  
     /**
 136  
      * Saves the subject's state to the subject's {@link org.apache.shiro.subject.Subject#getSession() session} only
 137  
      * if {@link #isSessionStorageEnabled(Subject) sessionStorageEnabled(subject)}.  If session storage is not enabled
 138  
      * for the specific {@code Subject}, this method does nothing.
 139  
      * <p/>
 140  
      * In either case, the argument {@code Subject} is returned directly (a new Subject instance is not created).
 141  
      *
 142  
      * @param subject the Subject instance for which its state will be created or updated.
 143  
      * @return the same {@code Subject} passed in (a new Subject instance is not created).
 144  
      */
 145  
     public Subject save(Subject subject) {
 146  48
         if (isSessionStorageEnabled(subject)) {
 147  47
             saveToSession(subject);
 148  
         } else {
 149  1
             log.trace("Session storage of subject state for Subject [{}] has been disabled: identity and " +
 150  
                     "authentication state are expected to be initialized on every request or invocation.", subject);
 151  
         }
 152  
 
 153  48
         return subject;
 154  
     }
 155  
 
 156  
     /**
 157  
      * Saves the subject's state (it's principals and authentication state) to its
 158  
      * {@link org.apache.shiro.subject.Subject#getSession() session}.  The session can be retrieved at a later time
 159  
      * (typically from a {@link org.apache.shiro.session.mgt.SessionManager SessionManager} to be used to recreate
 160  
      * the {@code Subject} instance.
 161  
      *
 162  
      * @param subject the subject for which state will be persisted to its session.
 163  
      */
 164  
     protected void saveToSession(Subject subject) {
 165  
         //performs merge logic, only updating the Subject's session if it does not match the current state:
 166  47
         mergePrincipals(subject);
 167  47
         mergeAuthenticationState(subject);
 168  47
     }
 169  
 
 170  
     /**
 171  
      * Merges the Subject's current {@link org.apache.shiro.subject.Subject#getPrincipals()} with whatever may be in
 172  
      * any available session.  Only updates the Subject's session if the session does not match the current principals
 173  
      * state.
 174  
      *
 175  
      * @param subject the Subject for which principals will potentially be merged into the Subject's session.
 176  
      */
 177  
     protected void mergePrincipals(Subject subject) {
 178  
         //merge PrincipalCollection state:
 179  
 
 180  53
         PrincipalCollection currentPrincipals = null;
 181  
 
 182  
         //SHIRO-380: added if/else block - need to retain original (source) principals
 183  
         //This technique (reflection) is only temporary - a proper long term solution needs to be found,
 184  
         //but this technique allowed an immediate fix that is API point-version forwards and backwards compatible
 185  
         //
 186  
         //A more comprehensive review / cleaning of runAs should be performed for Shiro 1.3 / 2.0 +
 187  53
         if (subject.isRunAs() && subject instanceof DelegatingSubject) {
 188  
             try {
 189  1
                 Field field = DelegatingSubject.class.getDeclaredField("principals");
 190  1
                 field.setAccessible(true);
 191  1
                 currentPrincipals = (PrincipalCollection)field.get(subject);
 192  0
             } catch (Exception e) {
 193  0
                 throw new IllegalStateException("Unable to access DelegatingSubject principals property.", e);
 194  1
             }
 195  
         }
 196  53
         if (currentPrincipals == null || currentPrincipals.isEmpty()) {
 197  52
             currentPrincipals = subject.getPrincipals();
 198  
         }
 199  
 
 200  53
         Session session = subject.getSession(false);
 201  
 
 202  53
         if (session == null) {
 203  48
             if (!CollectionUtils.isEmpty(currentPrincipals)) {
 204  18
                 session = subject.getSession();
 205  18
                 session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
 206  
             }
 207  
             //otherwise no session and no principals - nothing to save
 208  
         } else {
 209  5
             PrincipalCollection existingPrincipals =
 210  
                     (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
 211  
 
 212  5
             if (CollectionUtils.isEmpty(currentPrincipals)) {
 213  2
                 if (!CollectionUtils.isEmpty(existingPrincipals)) {
 214  1
                     session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
 215  
                 }
 216  
                 //otherwise both are null or empty - no need to update the session
 217  
             } else {
 218  3
                 if (!currentPrincipals.equals(existingPrincipals)) {
 219  3
                     session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
 220  
                 }
 221  
                 //otherwise they're the same - no need to update the session
 222  
             }
 223  
         }
 224  53
     }
 225  
 
 226  
     /**
 227  
      * Merges the Subject's current authentication state with whatever may be in
 228  
      * any available session.  Only updates the Subject's session if the session does not match the current
 229  
      * authentication state.
 230  
      *
 231  
      * @param subject the Subject for which principals will potentially be merged into the Subject's session.
 232  
      */
 233  
     protected void mergeAuthenticationState(Subject subject) {
 234  
 
 235  52
         Session session = subject.getSession(false);
 236  
 
 237  52
         if (session == null) {
 238  31
             if (subject.isAuthenticated()) {
 239  1
                 session = subject.getSession();
 240  1
                 session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE);
 241  
             }
 242  
             //otherwise no session and not authenticated - nothing to save
 243  
         } else {
 244  21
             Boolean existingAuthc = (Boolean) session.getAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
 245  
 
 246  21
             if (subject.isAuthenticated()) {
 247  19
                 if (existingAuthc == null || !existingAuthc) {
 248  19
                     session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE);
 249  
                 }
 250  
                 //otherwise authc state matches - no need to update the session
 251  
             } else {
 252  2
                 if (existingAuthc != null) {
 253  
                     //existing doesn't match the current state - remove it:
 254  1
                     session.removeAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
 255  
                 }
 256  
                 //otherwise not in the session and not authenticated - no need to update the session
 257  
             }
 258  
         }
 259  52
     }
 260  
 
 261  
     /**
 262  
      * Removes any existing subject state from the Subject's session (if the session exists).  If the session
 263  
      * does not exist, this method does not do anything.
 264  
      *
 265  
      * @param subject the subject for which any existing subject state will be removed from its session.
 266  
      */
 267  
     protected void removeFromSession(Subject subject) {
 268  11
         Session session = subject.getSession(false);
 269  11
         if (session != null) {
 270  10
             session.removeAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
 271  10
             session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
 272  
         }
 273  11
     }
 274  
 
 275  
     /**
 276  
      * Removes any existing subject state from the subject's session (if the session exists).
 277  
      *
 278  
      * @param subject the Subject instance for which any persistent state should be deleted.
 279  
      */
 280  
     public void delete(Subject subject) {
 281  11
         removeFromSession(subject);
 282  11
     }
 283  
 }