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 module hunt.shiro.realm.CachingRealm;
20 
21 import hunt.shiro.realm.Realm;
22 
23 import hunt.shiro.authc.LogoutAware;
24 import hunt.shiro.cache.CacheManager;
25 import hunt.shiro.cache.CacheManagerAware;
26 import hunt.shiro.subject.PrincipalCollection;
27 import hunt.shiro.util.CollectionUtils;
28 import hunt.shiro.util.Common;
29 import hunt.logging.Logger;
30 
31 import hunt.collection;
32 
33 import core.atomic;
34 import std.array;
35 import std.conv;
36 
37 /**
38  * A very basic abstract extension point for the {@link Realm} interface that provides caching support for subclasses.
39  * <p/>
40  * It also provides a convenience method,
41  * {@link #getAvailablePrincipal(hunt.shiro.subject.PrincipalCollection)}, which is useful across all
42  * realm subclasses for obtaining a realm-specific principal/identity.
43  * <p/>
44  * All actual Realm method implementations are left to subclasses.
45  *
46  * @see #clearCache(hunt.shiro.subject.PrincipalCollection)
47  * @see #onLogout(hunt.shiro.subject.PrincipalCollection)
48  * @see #getAvailablePrincipal(hunt.shiro.subject.PrincipalCollection)
49  */
50 abstract class CachingRealm : Realm, Nameable, CacheManagerAware, LogoutAware {
51 
52     private static shared int INSTANCE_COUNT = 0; //
53 
54     /*--------------------------------------------
55     |    I N S T A N C E   V A R I A B L E S    |
56     ============================================*/
57     private string name;
58     private bool cachingEnabled;
59     private CacheManager cacheManager;
60 
61     /**
62      * Default no-argument constructor that defaults
63      * {@link #isCachingEnabled() cachingEnabled} (for general caching) to {@code true} and sets a
64      * default {@link #getName() name} based on the class name.
65      * <p/>
66      * Note that while in general, caching may be enabled by default, subclasses have control over
67      * if specific caching is enabled.
68      */
69      this() {
70         this.cachingEnabled = true;
71         this.name = typeid(this).name ~ "_" ~ INSTANCE_COUNT.to!string();
72         atomicOp!("+=")(INSTANCE_COUNT, 1);
73     }
74 
75     /**
76      * Returns the <tt>CacheManager</tt> used for data caching to reduce EIS round trips, or <tt>null</tt> if
77      * caching is disabled.
78      *
79      * @return the <tt>CacheManager</tt> used for data caching to reduce EIS round trips, or <tt>null</tt> if
80      *         caching is disabled.
81      */
82      CacheManager getCacheManager() {
83         return this.cacheManager;
84     }
85 
86     /**
87      * Sets the <tt>CacheManager</tt> to be used for data caching to reduce EIS round trips.
88      * <p/>
89      * This property is <tt>null</tt> by default, indicating that caching is turned off.
90      *
91      * @param cacheManager the <tt>CacheManager</tt> to use for data caching, or <tt>null</tt> to disable caching.
92      */
93      void setCacheManager(CacheManager cacheManager) {
94         this.cacheManager = cacheManager;
95         afterCacheManagerSet();
96     }
97 
98     /**
99      * Returns {@code true} if caching should be used if a {@link CacheManager} has been
100      * {@link #setCacheManager(hunt.shiro.cache.CacheManager) configured}, {@code false} otherwise.
101      * <p/>
102      * The default value is {@code true} since the large majority of Realms will benefit from caching if a CacheManager
103      * has been configured.  However, memory-only realms should set this value to {@code false} since they would
104      * manage account data in memory already lookups would already be as efficient as possible.
105      *
106      * @return {@code true} if caching will be globally enabled if a {@link CacheManager} has been
107      *         configured, {@code false} otherwise
108      */
109      bool isCachingEnabled() {
110         return cachingEnabled;
111     }
112 
113     /**
114      * Sets whether or not caching should be used if a {@link CacheManager} has been
115      * {@link #setCacheManager(hunt.shiro.cache.CacheManager) configured}.
116      *
117      * @param cachingEnabled whether or not to globally enable caching for this realm.
118      */
119      void setCachingEnabled(bool cachingEnabled) {
120         this.cachingEnabled = cachingEnabled;
121     }
122 
123      string getName() {
124         return name;
125     }
126 
127      void setName(string name) {
128         this.name = name;
129     }
130 
131     /**
132      * Template method that may be implemented by subclasses should they wish to react to a
133      * {@link CacheManager} instance being set on the realm instance via the
134      * {@link #setCacheManager(hunt.shiro.cache.CacheManager)} mutator.
135      */
136     protected void afterCacheManagerSet() {
137     }
138 
139     /**
140      * If caching is enabled, this will clear any cached data associated with the specified account identity.
141      * Subclasses are free to override for additional behavior, but be sure to call {@code super.onLogout} first.
142      * <p/>
143      * This default implementation merely calls {@link #clearCache(hunt.shiro.subject.PrincipalCollection)}.
144      *
145      * @param principals the application-specific Subject/user identifier that is logging out.
146      * @see #clearCache(hunt.shiro.subject.PrincipalCollection)
147      * @see #getAvailablePrincipal(hunt.shiro.subject.PrincipalCollection)
148      */
149      void onLogout(PrincipalCollection principals) {
150         clearCache(principals);
151     }
152 
153     private static bool isEmpty(PrincipalCollection pc) {
154         return pc  is null || pc.isEmpty();
155     }
156 
157     /**
158      * Clears out any cached data associated with the specified account identity/identities.
159      * <p/>
160      * This implementation will return quietly if the principals argument is null or empty.  Otherwise it delegates
161      * to {@link #doClearCache(hunt.shiro.subject.PrincipalCollection)}.
162      *
163      * @param principals the principals of the account for which to clear any cached data.
164      */
165     protected void clearCache(PrincipalCollection principals) {
166         if (!isEmpty(principals)) {
167             doClearCache(principals);
168             version(HUNT_SHIRO_DEBUG) tracef("Cleared cache entries for account with principals [%s]", principals);
169         }
170     }
171 
172     /**
173      * This implementation does nothing - it is a template to be overridden by subclasses if necessary.
174      *
175      * @param principals principals the principals of the account for which to clear any cached data.
176      */
177     protected void doClearCache(PrincipalCollection principals) {
178     }
179 
180     /**
181      * A utility method for subclasses that returns the first available principal of interest to this particular realm.
182      * The heuristic used to acquire the principal is as follows:
183      * <ul>
184      * <li>Attempt to get <em>this particular Realm's</em> 'primary' principal in the {@code PrincipalCollection} via a
185      * <code>principals.{@link PrincipalCollection#fromRealm(string) fromRealm}({@link #getName() getName()})</code>
186      * call.</li>
187      * <li>If the previous call does not result in any principals, attempt to get the overall 'primary' principal
188      * from the PrincipalCollection via {@link hunt.shiro.subject.PrincipalCollection#getPrimaryPrincipal()}.</li>
189      * <li>If there are no principals from that call (or the PrincipalCollection argument was null to begin with),
190      * return {@code null}</li>
191      * </ul>
192      *
193      * @param principals the PrincipalCollection holding all principals (from all realms) associated with a single Subject.
194      * @return the 'primary' principal attributed to this particular realm, or the fallback 'master' principal if it
195      *         exists, or if not {@code null}.
196      */
197     protected Object getAvailablePrincipal(PrincipalCollection principals) {
198         Object primary = null;
199         if (!isEmpty(principals)) {
200             Object[] thisPrincipals = principals.fromRealm(getName());
201             if (!thisPrincipals.empty()) {
202                 primary = thisPrincipals[0];
203             } else {
204                 //no principals attributed to this particular realm.  Fall back to the 'master' primary:
205                 primary = principals.getPrimaryPrincipal();
206             }
207         }
208 
209         return primary;
210     }
211 }