1 /*******************************************************************************
\r
2 * ============LICENSE_START====================================================
\r
4 * * ===========================================================================
\r
5 * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
\r
6 * * ===========================================================================
\r
7 * * Licensed under the Apache License, Version 2.0 (the "License");
\r
8 * * you may not use this file except in compliance with the License.
\r
9 * * You may obtain a copy of the License at
\r
11 * * http://www.apache.org/licenses/LICENSE-2.0
\r
13 * * Unless required by applicable law or agreed to in writing, software
\r
14 * * distributed under the License is distributed on an "AS IS" BASIS,
\r
15 * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
16 * * See the License for the specific language governing permissions and
\r
17 * * limitations under the License.
\r
18 * * ============LICENSE_END====================================================
\r
20 * * ECOMP is a trademark and service mark of AT&T Intellectual Property.
\r
22 ******************************************************************************/
\r
23 package org.onap.aaf.cadi;
\r
26 import java.security.Principal;
\r
27 import java.util.ArrayList;
\r
28 import java.util.List;
\r
29 import java.util.Map;
\r
30 import java.util.Timer;
\r
31 import java.util.TimerTask;
\r
32 import java.util.TreeMap;
\r
33 import java.util.concurrent.ConcurrentHashMap;
\r
35 import org.onap.aaf.cadi.Access.Level;
\r
36 import org.onap.aaf.cadi.CachedPrincipal.Resp;
\r
39 * Implement Fast lookup and Cache for Local User Info
\r
41 * Include ability to add and remove Users
\r
43 * Also includes a Timer Thread (when necessary) to invoke cleanup on expiring Credentials
\r
47 public abstract class AbsUserCache<PERM extends Permission> {
\r
48 static final int MIN_INTERVAL = 15000;
\r
49 static final int MAX_INTERVAL = 1000*60*5; // 5 mins
\r
50 private static Timer timer;
\r
51 // Map of userName to User
\r
52 private final Map<String, User<PERM>> userMap;
\r
53 private final Map<String, Miss> missMap;
\r
54 private Clean clean;
\r
55 protected Access access;
\r
56 // private final static Permission teaser = new LocalPermission("***NoPERM****");
\r
58 protected AbsUserCache(Access access, long cleanInterval, int highCount, int usageCount) {
\r
59 this.access = access;
\r
60 userMap = new ConcurrentHashMap<String, User<PERM>>();
\r
61 missMap = new TreeMap<String,Miss>();
\r
62 if(cleanInterval>0) {
\r
63 cleanInterval = Math.max(MIN_INTERVAL, cleanInterval);
\r
64 synchronized(AbsUserCache.class) { // Lazy instantiate.. in case there is no cleanup needed
\r
66 timer = new Timer("CADI Cleanup Timer",true);
\r
69 timer.schedule(clean = new Clean(access, cleanInterval, highCount, usageCount), cleanInterval, cleanInterval);
\r
70 access.log(Access.Level.INIT, "Cleaning Thread initialized with interval of",cleanInterval, "ms and max objects of", highCount);
\r
75 @SuppressWarnings("unchecked")
\r
76 public AbsUserCache(AbsUserCache<PERM> cache) {
\r
77 this.access = cache.access;
\r
78 userMap = cache.userMap;
\r
79 missMap = cache.missMap;
\r
80 synchronized(AbsUserCache.class) {
\r
81 if(cache.clean!=null && cache.clean.lur==null && this instanceof CachingLur) {
\r
82 cache.clean.lur=(CachingLur<PERM>)this;
\r
87 protected void setLur(CachingLur<PERM> lur) {
\r
88 if(clean!=null)clean.lur = lur;
\r
92 protected void addUser(User<PERM> user) {
\r
93 userMap.put(user.principal.getName(), user);
\r
96 // Useful for looking up by WebToken, etc.
\r
97 protected void addUser(String key, User<PERM> user) {
\r
98 userMap.put(key, user);
\r
102 * Add miss to missMap. If Miss exists, or too many tries, returns false.
\r
104 * otherwise, returns true to allow another attempt.
\r
110 protected boolean addMiss(String key, byte[] bs) {
\r
111 Miss miss = missMap.get(key);
\r
113 synchronized(missMap) {
\r
114 missMap.put(key, new Miss(bs,clean==null?MIN_INTERVAL:clean.timeInterval));
\r
118 return miss.add(bs);
\r
121 protected Miss missed(String key) {
\r
122 return missMap.get(key);
\r
125 protected User<PERM> getUser(String userName) {
\r
126 User<PERM> u = userMap.get(userName);
\r
133 protected User<PERM> getUser(Principal principal) {
\r
134 return getUser(principal.getName());
\r
138 * Removes User from the Cache
\r
141 protected void remove(User<PERM> user) {
\r
142 userMap.remove(user.principal.getName());
\r
146 * Removes user from the Cache
\r
150 public void remove(String user) {
\r
151 Object o = userMap.remove(user);
\r
153 access.log(Level.INFO, user,"removed from Client Cache by Request");
\r
158 * Clear all users from the Client Cache
\r
160 public void clearAll() {
\r
164 public final List<DumpInfo> dumpInfo() {
\r
165 List<DumpInfo> rv = new ArrayList<DumpInfo>();
\r
166 for(User<PERM> user : userMap.values()) {
\r
167 rv.add(new DumpInfo(user));
\r
173 * The default behavior of a LUR is to not handle something exclusively.
\r
175 public boolean handlesExclusively(Permission pond) {
\r
180 * Container calls when cleaning up...
\r
182 * If overloading in Derived class, be sure to call "super.destroy()"
\r
184 public void destroy() {
\r
193 // Simple map of Group name to a set of User Names
\r
194 // private Map<String, Set<String>> groupMap = new HashMap<String, Set<String>>();
\r
197 * Class to hold a small subset of the data, because we don't want to expose actual Permission or User Objects
\r
199 public final class DumpInfo {
\r
200 public String user;
\r
201 public List<String> perms;
\r
203 public DumpInfo(User<PERM> user) {
\r
204 this.user = user.principal.getName();
\r
205 perms = new ArrayList<String>(user.perms.keySet());
\r
210 * Clean will examine resources, and remove those that have expired.
\r
212 * If "highs" have been exceeded, then we'll expire 10% more the next time. This will adjust after each run
\r
213 * without checking contents more than once, making a good average "high" in the minimum speed.
\r
217 private final class Clean extends TimerTask {
\r
218 private final Access access;
\r
219 private CachingLur<PERM> lur;
\r
221 // The idea here is to not be too restrictive on a high, but to Expire more items by
\r
222 // shortening the time to expire. This is done by judiciously incrementing "advance"
\r
223 // when the "highs" are exceeded. This effectively reduces numbers of cached items quickly.
\r
224 private final int high;
\r
225 private long advance;
\r
226 private final long timeInterval;
\r
227 private final int usageTriggerCount;
\r
229 public Clean(Access access, long cleanInterval, int highCount, int usageTriggerCount) {
\r
230 this.access = access;
\r
233 timeInterval = cleanInterval;
\r
235 this.usageTriggerCount=usageTriggerCount;
\r
237 public void run() {
\r
242 // look at now. If we need to expire more by increasing "now" by "advance"
\r
243 ArrayList<User<PERM>> al = new ArrayList<User<PERM>>(userMap.values().size());
\r
244 al.addAll(0, userMap.values());
\r
245 long now = System.currentTimeMillis() + advance;
\r
246 for(User<PERM> user : al) {
\r
248 if(user.count>usageTriggerCount) {
\r
249 // access.log(Level.AUDIT, "Checking Thread", new Date(now));
\r
250 boolean touched = false, removed=false;
\r
251 if(user.principal instanceof CachedPrincipal) {
\r
252 CachedPrincipal cp = (CachedPrincipal)user.principal;
\r
253 if(cp.expires() < now) {
\r
254 switch(cp.revalidate()) {
\r
256 access.log(Level.AUDIT, "AAF Inaccessible. Keeping credentials");
\r
260 // access.log(Level.AUDIT, "CACHE revalidated credentials");
\r
273 // access.log(Level.AUDIT, "User Perm Expires", new Date(user.permExpires));
\r
274 if(!removed && lur!=null && user.permExpires<= now ) {
\r
275 // access.log(Level.AUDIT, "Reloading");
\r
276 if(lur.reload(user).equals(Resp.REVALIDATED)) {
\r
278 access.log(Level.DEBUG, "Reloaded Perms for",user);
\r
288 if(user.permExpired()) {
\r
295 // Clean out Misses
\r
296 int missTotal = missMap.keySet().size();
\r
299 ArrayList<String> keys = new ArrayList<String>(missTotal);
\r
300 keys.addAll(missMap.keySet());
\r
301 for(String key : keys) {
\r
302 Miss m = missMap.get(key);
\r
303 if(m!=null && m.timestamp<System.currentTimeMillis()) {
\r
304 synchronized(missMap) {
\r
305 missMap.remove(key);
\r
307 access.log(Level.INFO, key, "has been removed from Missed Credential Map (" + m.tries + " invalid tries)");
\r
313 if(count+renewed+miss>0) {
\r
314 access.log(Level.INFO, (lur==null?"Cache":lur.getClass().getSimpleName()), "removed",count,
\r
315 "and renewed",renewed,"expired Permissions out of", total,"and removed", miss, "password misses out of",missTotal);
\r
318 // If High (total) is reached during this period, increase the number of expired services removed for next time.
\r
319 // There's no point doing it again here, as there should have been cleaned items.
\r
321 // advance cleanup by 10%, without getting greater than timeInterval.
\r
322 advance = Math.min(timeInterval, advance+(timeInterval/10));
\r
324 // reduce advance by 10%, without getting lower than 0.
\r
325 advance = Math.max(0, advance-(timeInterval/10));
\r
327 } catch (Exception e) {
\r
328 access.log(Level.ERROR,e.getMessage());
\r
333 public static class Miss {
\r
334 private static final int MAX_TRIES = 3;
\r
339 private long timetolive;
\r
343 public Miss(byte[] first, long timeInterval) {
\r
344 array = new byte[MAX_TRIES][];
\r
346 timestamp = System.currentTimeMillis() + timeInterval;
\r
347 this.timetolive = timeInterval;
\r
351 public boolean mayContinue(byte[] bs) {
\r
352 if(++tries > MAX_TRIES) return false;
\r
353 for(byte[] a : array) {
\r
354 if(a==null)return true;
\r
362 public synchronized boolean add(byte[] bc) {
\r
363 if(++tries>MAX_TRIES)return false;
\r
364 timestamp = System.currentTimeMillis()+timetolive;
\r
365 for(int i=0;i<MAX_TRIES;++i) {
\r
366 if(array[i]==null) {
\r
368 return true; // add to array, and allow more tries
\r
369 } else if(equals(array[i],bc)) {
\r
373 return false; // no more tries until cache cleared.
\r
376 private boolean equals(byte[] src, byte[] target) {
\r
377 if(target.length==src.length) {
\r
378 for(int j=0;j<src.length;++j) {
\r
379 if(src[j]!=target[j]) return false;
\r
381 return true; // same length and same chars
\r
390 public String toString() {
\r
391 return getClass().getSimpleName() +
\r
392 " Cache:\n Users Cached: " +
\r
394 "\n Misses Saved: " +
\r
400 public void clear(Principal p, StringBuilder sb) {
\r
401 sb.append(toString());
\r
404 access.log(Level.AUDIT, p.getName(),"has cleared User Cache in",getClass().getSimpleName());
\r
405 sb.append("Now cleared\n");
\r