1 /*******************************************************************************
\r
2 * ============LICENSE_START====================================================
\r
4 * * ===========================================================================
\r
5 * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
\r
6 * * Copyright © 2017 Amdocs
\r
7 * * ===========================================================================
\r
8 * * Licensed under the Apache License, Version 2.0 (the "License");
\r
9 * * you may not use this file except in compliance with the License.
\r
10 * * You may obtain a copy of the License at
\r
12 * * http://www.apache.org/licenses/LICENSE-2.0
\r
14 * * Unless required by applicable law or agreed to in writing, software
\r
15 * * distributed under the License is distributed on an "AS IS" BASIS,
\r
16 * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
17 * * See the License for the specific language governing permissions and
\r
18 * * limitations under the License.
\r
19 * * ============LICENSE_END====================================================
\r
21 * * ECOMP is a trademark and service mark of AT&T Intellectual Property.
\r
23 ******************************************************************************/
\r
24 package com.att.cadi;
\r
27 import java.security.Principal;
\r
28 import java.util.ArrayList;
\r
29 import java.util.List;
\r
30 import java.util.Map;
\r
31 import java.util.Timer;
\r
32 import java.util.TimerTask;
\r
33 import java.util.TreeMap;
\r
34 import java.util.concurrent.ConcurrentHashMap;
\r
36 import com.att.cadi.Access.Level;
\r
37 import com.att.cadi.CachedPrincipal.Resp;
\r
40 * Implement Fast lookup and Cache for Local User Info
\r
42 * Include ability to add and remove Users
\r
44 * Also includes a Timer Thread (when necessary) to invoke cleanup on expiring Credentials
\r
48 public abstract class AbsUserCache<PERM extends Permission> {
\r
49 static final int MIN_INTERVAL = 15000;
\r
50 static final int MAX_INTERVAL = 1000*60*5; // 5 mins
\r
51 private static Timer timer;
\r
52 // Map of userName to User
\r
53 private final Map<String, User<PERM>> userMap;
\r
54 private final Map<String, Miss> missMap;
\r
55 private Clean clean;
\r
56 protected Access access;
\r
57 // private final static Permission teaser = new LocalPermission("***NoPERM****");
\r
59 protected AbsUserCache(Access access, long cleanInterval, int highCount, int usageCount) {
\r
60 this.access = access;
\r
61 userMap = new ConcurrentHashMap<String, User<PERM>>();
\r
62 missMap = new TreeMap<String,Miss>();
\r
63 if(cleanInterval>0) {
\r
64 cleanInterval = Math.max(MIN_INTERVAL, cleanInterval);
\r
65 synchronized(AbsUserCache.class) { // Lazy instantiate.. in case there is no cleanup needed
\r
67 timer = new Timer("CADI Cleanup Timer",true);
\r
70 timer.schedule(clean = new Clean(access, cleanInterval, highCount, usageCount), cleanInterval, cleanInterval);
\r
71 access.log(Access.Level.INIT, "Cleaning Thread initialized with interval of",cleanInterval, "ms and max objects of", highCount);
\r
76 @SuppressWarnings("unchecked")
\r
77 public AbsUserCache(AbsUserCache<PERM> cache) {
\r
78 this.access = cache.access;
\r
79 userMap = cache.userMap;
\r
80 missMap = cache.missMap;
\r
81 synchronized(AbsUserCache.class) {
\r
82 if(cache.clean!=null && cache.clean.lur==null && this instanceof CachingLur) {
\r
83 cache.clean.lur=(CachingLur<PERM>)this;
\r
88 protected void setLur(CachingLur<PERM> lur) {
\r
89 if(clean!=null)clean.lur = lur;
\r
93 protected void addUser(User<PERM> user) {
\r
94 userMap.put(user.principal.getName(), user);
\r
97 // Useful for looking up by WebToken, etc.
\r
98 protected void addUser(String key, User<PERM> user) {
\r
99 userMap.put(key, user);
\r
103 * Add miss to missMap. If Miss exists, or too many tries, returns false.
\r
105 * otherwise, returns true to allow another attempt.
\r
111 protected boolean addMiss(String key, byte[] bs) {
\r
112 Miss miss = missMap.get(key);
\r
114 synchronized(missMap) {
\r
115 missMap.put(key, new Miss(bs,clean==null?MIN_INTERVAL:clean.timeInterval));
\r
119 return miss.add(bs);
\r
122 protected Miss missed(String key) {
\r
123 return missMap.get(key);
\r
126 protected User<PERM> getUser(String userName) {
\r
127 User<PERM> u = userMap.get(userName);
\r
134 protected User<PERM> getUser(Principal principal) {
\r
135 return getUser(principal.getName());
\r
139 * Removes User from the Cache
\r
142 protected void remove(User<PERM> user) {
\r
143 userMap.remove(user.principal.getName());
\r
147 * Removes user from the Cache
\r
151 public void remove(String user) {
\r
152 Object o = userMap.remove(user);
\r
154 access.log(Level.INFO, user,"removed from Client Cache by Request");
\r
159 * Clear all users from the Client Cache
\r
161 public void clearAll() {
\r
165 public final List<DumpInfo> dumpInfo() {
\r
166 List<DumpInfo> rv = new ArrayList<DumpInfo>();
\r
167 for(User<PERM> user : userMap.values()) {
\r
168 rv.add(new DumpInfo(user));
\r
174 * The default behavior of a LUR is to not handle something exclusively.
\r
176 public boolean handlesExclusively(Permission pond) {
\r
181 * Container calls when cleaning up...
\r
183 * If overloading in Derived class, be sure to call "super.destroy()"
\r
185 public void destroy() {
\r
194 // Simple map of Group name to a set of User Names
\r
195 // private Map<String, Set<String>> groupMap = new HashMap<String, Set<String>>();
\r
198 * Class to hold a small subset of the data, because we don't want to expose actual Permission or User Objects
\r
200 public final class DumpInfo {
\r
201 public String user;
\r
202 public List<String> perms;
\r
204 public DumpInfo(User<PERM> user) {
\r
205 this.user = user.principal.getName();
\r
206 perms = new ArrayList<String>(user.perms.keySet());
\r
211 * Clean will examine resources, and remove those that have expired.
\r
213 * If "highs" have been exceeded, then we'll expire 10% more the next time. This will adjust after each run
\r
214 * without checking contents more than once, making a good average "high" in the minimum speed.
\r
218 private final class Clean extends TimerTask {
\r
219 private final Access access;
\r
220 private CachingLur<PERM> lur;
\r
222 // The idea here is to not be too restrictive on a high, but to Expire more items by
\r
223 // shortening the time to expire. This is done by judiciously incrementing "advance"
\r
224 // when the "highs" are exceeded. This effectively reduces numbers of cached items quickly.
\r
225 private final int high;
\r
226 private long advance;
\r
227 private final long timeInterval;
\r
228 private final int usageTriggerCount;
\r
230 public Clean(Access access, long cleanInterval, int highCount, int usageTriggerCount) {
\r
231 this.access = access;
\r
234 timeInterval = cleanInterval;
\r
236 this.usageTriggerCount=usageTriggerCount;
\r
238 public void run() {
\r
243 // look at now. If we need to expire more by increasing "now" by "advance"
\r
244 ArrayList<User<PERM>> al = new ArrayList<User<PERM>>(userMap.values().size());
\r
245 al.addAll(0, userMap.values());
\r
246 long now = System.currentTimeMillis() + advance;
\r
247 for(User<PERM> user : al) {
\r
249 if(user.count>usageTriggerCount) {
\r
250 // access.log(Level.AUDIT, "Checking Thread", new Date(now));
\r
251 boolean touched = false, removed=false;
\r
252 if(user.principal instanceof CachedPrincipal) {
\r
253 CachedPrincipal cp = (CachedPrincipal)user.principal;
\r
254 if(cp.expires() < now) {
\r
255 switch(cp.revalidate()) {
\r
257 access.log(Level.AUDIT, "AAF Inaccessible. Keeping credentials");
\r
261 // access.log(Level.AUDIT, "CACHE revalidated credentials");
\r
274 // access.log(Level.AUDIT, "User Perm Expires", new Date(user.permExpires));
\r
275 if(!removed && lur!=null && user.permExpires<= now ) {
\r
276 // access.log(Level.AUDIT, "Reloading");
\r
277 if(lur.reload(user).equals(Resp.REVALIDATED)) {
\r
279 access.log(Level.DEBUG, "Reloaded Perms for",user);
\r
289 if(user.permExpired()) {
\r
296 // Clean out Misses
\r
297 int missTotal = missMap.keySet().size();
\r
300 ArrayList<String> keys = new ArrayList<String>(missTotal);
\r
301 keys.addAll(missMap.keySet());
\r
302 for(String key : keys) {
\r
303 Miss m = missMap.get(key);
\r
304 if(m!=null && m.timestamp<System.currentTimeMillis()) {
\r
305 synchronized(missMap) {
\r
306 missMap.remove(key);
\r
308 access.log(Level.INFO, key, "has been removed from Missed Credential Map (" + m.tries + " invalid tries)");
\r
314 if(count+renewed+miss>0) {
\r
315 access.log(Level.INFO, (lur==null?"Cache":lur.getClass().getSimpleName()), "removed",count,
\r
316 "and renewed",renewed,"expired Permissions out of", total,"and removed", miss, "password misses out of",missTotal);
\r
319 // If High (total) is reached during this period, increase the number of expired services removed for next time.
\r
320 // There's no point doing it again here, as there should have been cleaned items.
\r
322 // advance cleanup by 10%, without getting greater than timeInterval.
\r
323 advance = Math.min(timeInterval, advance+(timeInterval/10));
\r
325 // reduce advance by 10%, without getting lower than 0.
\r
326 advance = Math.max(0, advance-(timeInterval/10));
\r
328 } catch (Exception e) {
\r
329 access.log(Level.ERROR,e.getMessage());
\r
334 public static class Miss {
\r
335 private static final int MAX_TRIES = 3;
\r
340 private long timetolive;
\r
344 public Miss(byte[] first, long timeInterval) {
\r
345 array = new byte[MAX_TRIES][];
\r
347 timestamp = System.currentTimeMillis() + timeInterval;
\r
348 this.timetolive = timeInterval;
\r
352 public boolean mayContinue(byte[] bs) {
\r
353 if(++tries > MAX_TRIES) return false;
\r
354 for(byte[] a : array) {
\r
355 if(a==null)return true;
\r
363 public synchronized boolean add(byte[] bc) {
\r
364 if(++tries>MAX_TRIES)return false;
\r
365 timestamp = System.currentTimeMillis()+timetolive;
\r
366 for(int i=0;i<MAX_TRIES;++i) {
\r
367 if(array[i]==null) {
\r
369 return true; // add to array, and allow more tries
\r
370 } else if(equals(array[i],bc)) {
\r
374 return false; // no more tries until cache cleared.
\r
377 private boolean equals(byte[] src, byte[] target) {
\r
378 if(target.length==src.length) {
\r
379 for(int j=0;j<src.length;++j) {
\r
380 if(src[j]!=target[j]) return false;
\r
382 return true; // same length and same chars
\r
391 public String toString() {
\r
392 return getClass().getSimpleName() +
\r
393 " Cache:\n Users Cached: " +
\r
395 "\n Misses Saved: " +
\r
401 public void clear(Principal p, StringBuilder sb) {
\r
402 sb.append(toString());
\r
405 access.log(Level.AUDIT, p.getName(),"has cleared User Cache in",getClass().getSimpleName());
\r
406 sb.append("Now cleared\n");
\r