2 * ============LICENSE_START====================================================
4 * ===========================================================================
5 * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
6 * ===========================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END====================================================
22 package org.onap.aaf.cadi;
25 import java.io.ByteArrayInputStream;
26 import java.io.IOException;
27 import java.security.Principal;
28 import java.util.ArrayList;
29 import java.util.List;
31 import java.util.Timer;
32 import java.util.TimerTask;
33 import java.util.TreeMap;
34 import java.util.concurrent.ConcurrentHashMap;
36 import org.onap.aaf.cadi.Access.Level;
37 import org.onap.aaf.cadi.CachedPrincipal.Resp;
38 import org.onap.aaf.cadi.principal.CachedBasicPrincipal;
41 * Implement Fast lookup and Cache for Local User Info
43 * Include ability to add and remove Users
45 * Also includes a Timer Thread (when necessary) to invoke cleanup on expiring Credentials
50 public abstract class AbsUserCache<PERM extends Permission> {
51 // Need an obvious key for when there is no Authentication Cred
52 private static final String NO_CRED = "NoCred";
53 static final int MIN_INTERVAL = 1000*60; // Min 1 min
54 static final int MAX_INTERVAL = 1000*60*60*4; // 4 hour max
55 private static Timer timer;
56 // Map of userName to User
57 private final Map<String, User<PERM>> userMap;
58 private static final Map<String, Miss> missMap = new TreeMap<String,Miss>();
59 private final Symm missEncrypt;
62 protected Access access;
63 // private final static Permission teaser = new LocalPermission("***NoPERM****");
65 protected AbsUserCache(Access access, long cleanInterval, int highCount, int usageCount) {
69 byte[] gennedKey = Symm.keygen();
70 s = Symm.obtain(new ByteArrayInputStream(gennedKey));
71 } catch (IOException e) {
73 s = Symm.base64noSplit;
77 userMap = new ConcurrentHashMap<String, User<PERM>>();
81 cleanInterval = Math.max(MIN_INTERVAL, cleanInterval);
82 synchronized(AbsUserCache.class) { // Lazy instantiate.. in case there is no cleanup needed
84 timer = new Timer("CADI Cleanup Timer",true);
87 timer.schedule(clean = new Clean(access, cleanInterval, highCount, usageCount), cleanInterval, cleanInterval);
88 access.log(Access.Level.INIT, "Cleaning Thread initialized with interval of",cleanInterval, "ms and max objects of", highCount);
93 @SuppressWarnings("unchecked")
94 public AbsUserCache(AbsUserCache<PERM> cache) {
95 this.access = cache.access;
96 userMap = cache.userMap;
97 missEncrypt = cache.missEncrypt;
99 synchronized(AbsUserCache.class) {
100 if(cache.clean!=null && cache.clean.lur==null && this instanceof CachingLur) {
101 cache.clean.lur=(CachingLur<PERM>)this;
106 protected void setLur(CachingLur<PERM> lur) {
107 if(clean!=null)clean.lur = lur;
111 protected void addUser(User<PERM> user) {
112 Principal p = user.principal;
115 if(p instanceof GetCred) {
116 key = missKey(p.getName(), ((GetCred)p).getCred());
119 if((cred=user.getCred())==null) {
120 key = user.name + NO_CRED;
122 key = missKey(user.name,cred);
125 } catch (IOException e) {
129 userMap.put(key, user);
132 // Useful for looking up by WebToken, etc.
133 protected void addUser(String key, User<PERM> user) {
134 userMap.put(key, user);
138 * Add miss to missMap. If Miss exists, or too many tries, returns false.
140 * otherwise, returns true to allow another attempt.
145 * @throws IOException
147 protected synchronized boolean addMiss(String key, byte[] bs) {
150 mkey = missKey(key,bs);
151 } catch (IOException e) {
155 Miss miss = missMap.get(mkey);
157 missMap.put(mkey, new Miss(bs,clean==null?MIN_INTERVAL:clean.timeInterval));
160 return miss.mayContinue();
163 protected Miss missed(String key, byte[] bs) throws IOException {
164 return missMap.get(missKey(key,bs));
167 protected User<PERM> getUser(Principal principal) {
169 if(principal instanceof GetCred) {
170 GetCred gc = (GetCred)principal;
172 key = missKey(principal.getName(), gc.getCred());
173 } catch (IOException e) {
174 access.log(e, "Error getting key from Principal");
175 key = principal.getName();
178 key = principal.getName()+NO_CRED;
180 User<PERM> u = userMap.get(key);
187 protected User<PERM> getUser(CachedBasicPrincipal cbp) {
188 return getUser(cbp.getName(), cbp.getCred());
191 protected User<PERM> getUser(String user, byte[] cred) {
195 key =missKey(user,cred);
196 } catch (IOException e) {
200 u = userMap.get(key);
202 if(u.permExpired()) {
213 * Removes User from the Cache
216 protected void remove(User<PERM> user) {
217 userMap.remove(user.principal.getName());
221 * Removes user from the Cache
225 public void remove(String user) {
226 Object o = userMap.remove(user);
228 access.log(Level.INFO, user,"removed from Client Cache by Request");
233 * Clear all Users from the Client Cache
235 public void clearAll() {
239 public final List<DumpInfo> dumpInfo() {
240 List<DumpInfo> rv = new ArrayList<DumpInfo>();
241 for(User<PERM> user : userMap.values()) {
242 rv.add(new DumpInfo(user));
248 * The default behavior of a LUR is to not handle something exclusively.
250 public boolean handlesExclusively(Permission pond) {
255 * Container calls when cleaning up...
257 * If overloading in Derived class, be sure to call "super.destroy()"
259 public void destroy() {
268 // Simple map of Group name to a set of User Names
269 // private Map<String, Set<String>> groupMap = new HashMap<String, Set<String>>();
272 * Class to hold a small subset of the data, because we don't want to expose actual Permission or User Objects
274 public final class DumpInfo {
276 public List<String> perms;
278 public DumpInfo(User<PERM> user) {
279 this.user = user.principal.getName();
280 perms = new ArrayList<String>(user.perms.keySet());
285 * Clean will examine resources, and remove those that have expired.
287 * If "highs" have been exceeded, then we'll expire 10% more the next time. This will adjust after each run
288 * without checking contents more than once, making a good average "high" in the minimum speed.
293 private final class Clean extends TimerTask {
294 private final Access access;
295 private CachingLur<PERM> lur;
297 // The idea here is to not be too restrictive on a high, but to Expire more items by
298 // shortening the time to expire. This is done by judiciously incrementing "advance"
299 // when the "highs" are exceeded. This effectively reduces numbers of cached items quickly.
300 private final int high;
301 private long advance;
302 private final long timeInterval;
303 private final int usageTriggerCount;
305 public Clean(Access access, long cleanInterval, int highCount, int usageTriggerCount) {
306 this.access = access;
309 timeInterval = cleanInterval;
311 this.usageTriggerCount=usageTriggerCount;
318 // look at now. If we need to expire more by increasing "now" by "advance"
319 ArrayList<User<PERM>> al = new ArrayList<User<PERM>>(userMap.values().size());
320 al.addAll(0, userMap.values());
321 long now = System.currentTimeMillis() + advance;
322 for(User<PERM> user : al) {
324 if(user.count>usageTriggerCount) {
325 // access.log(Level.AUDIT, "Checking Thread", new Date(now));
326 boolean touched = false, removed=false;
327 if(user.principal instanceof CachedPrincipal) {
328 CachedPrincipal cp = (CachedPrincipal)user.principal;
329 if(cp.expires() < now) {
330 switch(cp.revalidate(null)) {
332 access.log(Level.AUDIT, "AAF Inaccessible. Keeping credentials");
336 // access.log(Level.AUDIT, "CACHE revalidated credentials");
349 // access.log(Level.AUDIT, "User Perm Expires", new Date(user.permExpires));
350 if(!removed && lur!=null && user.permExpires<= now ) {
351 // access.log(Level.AUDIT, "Reloading");
352 if(lur.reload(user).equals(Resp.REVALIDATED)) {
354 access.log(Level.DEBUG, "Reloaded Perms for",user);
364 if(user.permExpired()) {
372 int missTotal = missMap.keySet().size();
375 ArrayList<String> keys = new ArrayList<String>(missTotal);
376 keys.addAll(missMap.keySet());
377 for(String key : keys) {
378 Miss m = missMap.get(key);
379 if(m!=null && m.timestamp<System.currentTimeMillis()) {
380 synchronized(missMap) {
383 access.log(Level.INFO, key, "has been removed from Missed Credential Map (" + m.tries + " invalid tries)");
389 if(count+renewed+miss>0) {
390 access.log(Level.INFO, (lur==null?"Cache":lur.getClass().getSimpleName()), "removed",count,
391 "and renewed",renewed,"expired Permissions out of", total,"and removed", miss, "password misses out of",missTotal);
394 // If High (total) is reached during this period, increase the number of expired services removed for next time.
395 // There's no point doing it again here, as there should have been cleaned items.
397 // advance cleanup by 10%, without getting greater than timeInterval.
398 advance = Math.min(timeInterval, advance+(timeInterval/10));
400 // reduce advance by 10%, without getting lower than 0.
401 advance = Math.max(0, advance-(timeInterval/10));
403 } catch (Exception e) {
404 access.log(Level.ERROR,e.getMessage());
410 private String missKey(String name, byte[] bs) throws IOException {
411 return name + Hash.toHex(missEncrypt.encode(bs));
414 protected static class Miss {
415 private static final int MAX_TRIES = 3;
419 private long timetolive;
423 public Miss(byte[] first, long timeInterval) {
424 timestamp = System.currentTimeMillis() + timeInterval;
425 this.timetolive = timeInterval;
430 public synchronized boolean mayContinue() {
431 long ts = System.currentTimeMillis();
434 timestamp = ts + timetolive;
435 } else if(MAX_TRIES <= ++tries) {
445 public String toString() {
446 return getClass().getSimpleName() +
447 " Cache:\n Users Cached: " +
449 "\n Misses Saved: " +
455 public void clear(Principal p, StringBuilder sb) {
456 sb.append(toString());
459 access.log(Level.AUDIT, p.getName(),"has cleared User Cache in",getClass().getSimpleName());
460 sb.append("Now cleared\n");