diff options
author | Instrumental <jcgmisc@stl.gathman.org> | 2018-03-26 13:51:48 -0700 |
---|---|---|
committer | Instrumental <jcgmisc@stl.gathman.org> | 2018-03-26 13:52:07 -0700 |
commit | 71037c39a37d3549dcfe31926832a657744fbe05 (patch) | |
tree | 78911b2b5e86e4e44228f7a27b3a8cd954b7f3e2 /auth/auth-core/src/main | |
parent | a20accc73189d8e5454cd26049c0e6fae75da16f (diff) |
AT&T 2.0.19 Code drop, stage 3
Issue-ID: AAF-197
Change-Id: I8b02cb073ccba318ccaf6ea0276446bdce88fb82
Signed-off-by: Instrumental <jcgmisc@stl.gathman.org>
Diffstat (limited to 'auth/auth-core/src/main')
41 files changed, 6284 insertions, 0 deletions
diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/cache/Cache.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/cache/Cache.java new file mode 100644 index 00000000..17368031 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/cache/Cache.java @@ -0,0 +1,200 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.cache; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; + +import org.onap.aaf.misc.env.Env; +import org.onap.aaf.misc.env.Trans; + +/** + * Create and maintain a Map of Maps used for Caching + * + * @author Jonathan + * + * @param <TRANS> + * @param <DATA> + */ +public class Cache<TRANS extends Trans, DATA> { + private static Clean clean; + private static Timer cleanseTimer; + + public static final String CACHE_HIGH_COUNT = "CACHE_HIGH_COUNT"; + public static final String CACHE_CLEAN_INTERVAL = "CACHE_CLEAN_INTERVAL"; +// public static final String CACHE_MIN_REFRESH_INTERVAL = "CACHE_MIN_REFRESH_INTERVAL"; + + private static final Map<String,Map<String,Dated>> cacheMap; + + static { + cacheMap = new HashMap<String,Map<String,Dated>>(); + } + + /** + * Dated Class - store any Data with timestamp + * + * @author Jonathan + * + */ + public final static class Dated { + public Date timestamp; + public List<?> data; + private long expireIn; + + public Dated(List<?> data, long expireIn) { + timestamp = new Date(System.currentTimeMillis()+expireIn); + this.data = data; + this.expireIn = expireIn; + } + + public <T> Dated(T t, long expireIn) { + timestamp = new Date(System.currentTimeMillis()+expireIn); + ArrayList<T> al = new ArrayList<T>(1); + al.add(t); + data = al; + this.expireIn = expireIn; + } + + public void touch() { + timestamp = new Date(System.currentTimeMillis()+expireIn); + } + } + + public static Map<String,Dated> obtain(String key) { + Map<String, Dated> m = cacheMap.get(key); + if(m==null) { + m = new ConcurrentHashMap<String, Dated>(); + synchronized(cacheMap) { + cacheMap.put(key, m); + } + } + return m; + } + + /** + * Clean will examine resources, and remove those that have expired. + * + * If "highs" have been exceeded, then we'll expire 10% more the next time. This will adjust after each run + * without checking contents more than once, making a good average "high" in the minimum speed. + * + * @author Jonathan + * + */ + private final static class Clean extends TimerTask { + private final Env env; + private Set<String> set; + + // The idea here is to not be too restrictive on a high, but to Expire more items by + // shortening the time to expire. This is done by judiciously incrementing "advance" + // when the "highs" are exceeded. This effectively reduces numbers of cached items quickly. + private final int high; + private long advance; + private final long timeInterval; + + public Clean(Env env, long cleanInterval, int highCount) { + this.env = env; + high = highCount; + timeInterval = cleanInterval; + advance = 0; + set = new HashSet<String>(); + } + + public synchronized void add(String key) { + set.add(key); + } + + public void run() { + int count = 0; + int total = 0; + // look at now. If we need to expire more by increasing "now" by "advance" + Date now = new Date(System.currentTimeMillis() + advance); + + + for(String name : set) { + Map<String,Dated> map = cacheMap.get(name); + if(map!=null) for(Map.Entry<String,Dated> me : map.entrySet()) { + ++total; + if(me.getValue().timestamp.before(now)) { + map.remove(me.getKey()); + ++count; + } + } +// if(count>0) { +// env.info().log(Level.INFO, "Cache removed",count,"expired",name,"Elements"); +// } + } + + if(count>0) { + env.info().log(Level.INFO, "Cache removed",count,"expired Cached Elements out of", total); + } + + // If High (total) is reached during this period, increase the number of expired services removed for next time. + // There's no point doing it again here, as there should have been cleaned items. + if(total>high) { + // advance cleanup by 10%, without getting greater than timeInterval. + advance = Math.min(timeInterval, advance+(timeInterval/10)); + } else { + // reduce advance by 10%, without getting lower than 0. + advance = Math.max(0, advance-(timeInterval/10)); + } + } + } + + public static synchronized void startCleansing(Env env, String ... keys) { + if(cleanseTimer==null) { + cleanseTimer = new Timer("Cache Cleanup Timer"); + int cleanInterval = Integer.parseInt(env.getProperty(CACHE_CLEAN_INTERVAL,"60000")); // 1 minute clean cycles + int highCount = Integer.parseInt(env.getProperty(CACHE_HIGH_COUNT,"5000")); + cleanseTimer.schedule(clean = new Clean(env, cleanInterval, highCount), cleanInterval, cleanInterval); + } + + for(String key : keys) { + clean.add(key); + } + } + + public static void stopTimer() { + if(cleanseTimer!=null) { + cleanseTimer.cancel(); + cleanseTimer = null; + } + } + + public static void addShutdownHook() { + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + Cache.stopTimer(); + } + }); + } + +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/common/Define.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/common/Define.java new file mode 100644 index 00000000..6f0ea084 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/common/Define.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * ============LICENSE_START==================================================== + * * org.onap.aaf + * * =========================================================================== + * * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * * =========================================================================== + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * * ============LICENSE_END==================================================== + * * + * * + ******************************************************************************/ +package org.onap.aaf.auth.common; + +import java.util.Map.Entry; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.config.Config; + +public class Define { + private static String ROOT_NS = null; + private static String ROOT_COMPANY = null; + + private final static String MSG = ".set(Access access) must be called before use"; + public static final CharSequence ROOT_NS_TAG = "AAF_NS"; // use for certain Replacements in Location + private static final String ROOT_NS_TAG_DOT = ROOT_NS_TAG +"."; + + public static String ROOT_NS() { + if(ROOT_NS==null) { + throw new RuntimeException(Define.class.getName() + MSG); + } + return ROOT_NS; + } + + public static String ROOT_COMPANY() { + if(ROOT_NS==null) { + throw new RuntimeException(Define.class.getName() + MSG); + } + return ROOT_COMPANY; + } + + public static void set(Access access) throws CadiException { + ROOT_NS = access.getProperty(Config.AAF_ROOT_NS,"org.onap.aaf"); + ROOT_COMPANY = access.getProperty(Config.AAF_ROOT_COMPANY,null); + if(ROOT_COMPANY==null) { + int last = ROOT_NS.lastIndexOf('.'); + if(last>=0) { + ROOT_COMPANY = ROOT_NS.substring(0, last); + } else { + throw new CadiException(Config.AAF_ROOT_COMPANY + " or " + Config.AAF_ROOT_NS + " property with 3 positions is required."); + } + } + + for( Entry<Object, Object> es : access.getProperties().entrySet()) { + if(es.getKey().toString().startsWith(ROOT_NS_TAG_DOT)) { + access.getProperties().setProperty(es.getKey().toString(),varReplace(es.getValue().toString())); + } + } + + access.printf(Level.INIT,"AAF Root NS is %s, and AAF Company Root is %s",ROOT_NS,ROOT_COMPANY); + } + + public static String varReplace(final String potential) { + if(potential.startsWith(ROOT_NS_TAG_DOT)) { + return ROOT_NS + potential.substring(6); + } else { + return potential; + } + } + +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/env/AuthzEnv.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/env/AuthzEnv.java new file mode 100644 index 00000000..0bbe079e --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/env/AuthzEnv.java @@ -0,0 +1,310 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.env; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.PropAccess; +import org.onap.aaf.cadi.Symm; +import org.onap.aaf.cadi.PropAccess.LogIt; +import org.onap.aaf.cadi.config.Config; +import org.onap.aaf.misc.env.APIException; +import org.onap.aaf.misc.env.Decryptor; +import org.onap.aaf.misc.env.Encryptor; +import org.onap.aaf.misc.env.impl.Log4JLogTarget; +import org.onap.aaf.misc.env.log4j.LogFileNamer; +import org.onap.aaf.misc.rosetta.env.RosettaEnv; + + +/** + * AuthzEnv is the Env tailored to Authz Service + * + * Most of it is derived from RosettaEnv, but it also implements Access, which + * is an Interface that Allows CADI to interact with Container Logging + * + * @author Jonathan + * + */ +public class AuthzEnv extends RosettaEnv implements Access { + private long[] times = new long[20]; + private int idx = 0; + private PropAccess access; + + public AuthzEnv() { + super(); + _init(new PropAccess()); + } + + public AuthzEnv(String ... args) { + super(); + _init(new PropAccess(args)); + } + + public AuthzEnv(Properties props) { + super(); + _init(new PropAccess(props)); + } + + + public AuthzEnv(PropAccess pa) { + super(); + _init(pa); + } + + private final void _init(PropAccess pa) { + access = pa; + times = new long[20]; + idx = 0; + } + + private class Log4JLogit implements LogIt { + + @Override + public void push(Level level, Object... elements) { + switch(level) { + case AUDIT: + audit.log(elements); + break; + case DEBUG: + debug.log(elements); + break; + case ERROR: + error.log(elements); + break; + case INFO: + info.log(elements); + break; + case INIT: + init.log(elements); + break; + case NONE: + break; + case WARN: + warn.log(elements); + break; + } + + } + + } + + @Override + public AuthzTransImpl newTrans() { + synchronized(this) { + times[idx]=System.currentTimeMillis(); + if(++idx>=times.length)idx=0; + } + return new AuthzTransImpl(this); + } + + /** + * Create a Trans, but do not include in Weighted Average + * @return + */ + public AuthzTrans newTransNoAvg() { + return new AuthzTransImpl(this); + } + + public long transRate() { + int count = 0; + long pot = 0; + long prev = 0; + for(int i=idx;i<times.length;++i) { + if(times[i]>0) { + if(prev>0) { + ++count; + pot += times[i]-prev; + } + prev = times[i]; + } + } + for(int i=0;i<idx;++i) { + if(times[i]>0) { + if(prev>0) { + ++count; + pot += times[i]-prev; + } + prev = times[i]; + } + } + + return count==0?300000L:pot/count; // Return Weighted Avg, or 5 mins, if none avail. + } + + @Override + public ClassLoader classLoader() { + return getClass().getClassLoader(); + } + + @Override + public void load(InputStream is) throws IOException { + access.load(is); + } + + @Override + public void log(Level lvl, Object... msgs) { + access.log(lvl, msgs); + } + + @Override + public void log(Exception e, Object... msgs) { + access.log(e,msgs); + } + + @Override + public void printf(Level level, String fmt, Object... elements) { + access.printf(level, fmt, elements); + } + + /* (non-Javadoc) + * @see org.onap.aaf.cadi.Access#willLog(org.onap.aaf.cadi.Access.Level) + */ + @Override + public boolean willLog(Level level) { + return access.willLog(level); + } + + @Override + public void setLogLevel(Level level) { + access.setLogLevel(level); + } + + public void setLog4JNames(String path, String root, String _service, String _audit, String _init, String _trace) throws APIException { + LogFileNamer lfn = new LogFileNamer(root); + if(_service==null) { + throw new APIException("AuthzEnv.setLog4JNames \"_service\" required (as default). Others can be null"); + } + String service=_service=lfn.setAppender(_service); // when name is split, i.e. authz|service, the Appender is "authz", and "service" + String audit=_audit==null?service:lfn.setAppender(_audit); // is part of the log-file name + String init=_init==null?service:lfn.setAppender(_init); + String trace=_trace==null?service:lfn.setAppender(_trace); + //TODO Validate path on Classpath + lfn.configure(path); + super.fatal = new Log4JLogTarget(service,org.apache.log4j.Level.FATAL); + super.error = new Log4JLogTarget(service,org.apache.log4j.Level.ERROR); + super.warn = new Log4JLogTarget(service,org.apache.log4j.Level.WARN); + super.audit = new Log4JLogTarget(audit,org.apache.log4j.Level.WARN); + super.init = new Log4JLogTarget(init,org.apache.log4j.Level.WARN); + super.info = new Log4JLogTarget(service,org.apache.log4j.Level.INFO); + super.debug = new Log4JLogTarget(service,org.apache.log4j.Level.DEBUG); + super.trace = new Log4JLogTarget(trace,org.apache.log4j.Level.TRACE); + + access.set(new Log4JLogit()); + } + + private static final byte[] ENC="enc:".getBytes(); + public String decrypt(String encrypted, final boolean anytext) throws IOException { + if(encrypted==null) { + throw new IOException("Password to be decrypted is null"); + } + if(anytext || encrypted.startsWith("enc:")) { + if(decryptor.equals(Decryptor.NULL) && getProperty(Config.CADI_KEYFILE)!=null) { + final Symm s; + try { + s = Symm.obtain(this); + } catch (CadiException e1) { + throw new IOException(e1); + } + decryptor = new Decryptor() { + private Symm symm = s; + @Override + public String decrypt(String encrypted) { + try { + return (encrypted!=null && (anytext || encrypted.startsWith(Symm.ENC))) + ? symm.depass(encrypted) + : encrypted; + } catch (IOException e) { + return ""; + } + } + }; + encryptor = new Encryptor() { + @Override + public String encrypt(String data) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + baos.write(ENC); + return "enc:"+s.enpass(data); + } catch (IOException e) { + return ""; + } + } + + }; + } + return decryptor.decrypt(encrypted); + } else { + return encrypted; + } + } + + /* (non-Javadoc) + * @see org.onap.aaf.misc.env.impl.BasicEnv#getProperty(java.lang.String) + */ + @Override + public String getProperty(String key) { + return access.getProperty(key); + } + + /* (non-Javadoc) + * @see org.onap.aaf.misc.env.impl.BasicEnv#getProperties(java.lang.String[]) + */ + @Override + public Properties getProperties(String... filter) { + return access.getProperties(); + } + + /* (non-Javadoc) + * @see org.onap.aaf.misc.env.impl.BasicEnv#getProperty(java.lang.String, java.lang.String) + */ + @Override + public String getProperty(String key, String defaultValue) { + return access.getProperty(key, defaultValue); + } + + /* (non-Javadoc) + * @see org.onap.aaf.misc.env.impl.BasicEnv#setProperty(java.lang.String, java.lang.String) + */ + @Override + public String setProperty(String key, String value) { + access.setProperty(key, value); + return value; + } + + public PropAccess access() { + return access; + } + + /* (non-Javadoc) + * @see org.onap.aaf.cadi.Access#getProperties() + */ + @Override + public Properties getProperties() { + return access.getProperties(); + }; + +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/env/AuthzTrans.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/env/AuthzTrans.java new file mode 100644 index 00000000..a38a3e20 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/env/AuthzTrans.java @@ -0,0 +1,78 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.env; + +import java.util.Date; + +import javax.servlet.http.HttpServletRequest; + +import org.onap.aaf.auth.org.Organization; +import org.onap.aaf.cadi.Lur; +import org.onap.aaf.cadi.Permission; +import org.onap.aaf.cadi.principal.TaggedPrincipal; +import org.onap.aaf.misc.env.LogTarget; +import org.onap.aaf.misc.env.TransStore; + +public interface AuthzTrans extends TransStore { + public enum REQD_TYPE {future(1),force(2),move(4),ns(8); + public final int bit; + + REQD_TYPE(int bit) { + this.bit = bit; + } + }; + + public abstract AuthzTrans set(HttpServletRequest req); + + public abstract String user(); + + public abstract void setUser(TaggedPrincipal p); + + public abstract TaggedPrincipal getUserPrincipal(); + + public abstract String ip(); + + public abstract int port(); + + public abstract String meth(); + + public abstract String path(); + + public abstract String agent(); + + public abstract AuthzEnv env(); + + public abstract void setLur(Lur lur); + + public abstract boolean fish(Permission p); + + public abstract Organization org(); + + public abstract boolean requested(REQD_TYPE requested); + + public void requested(REQD_TYPE requested, boolean b); + + public abstract void logAuditTrail(LogTarget lt); + + public abstract Date now(); + +}
\ No newline at end of file diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/env/AuthzTransFilter.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/env/AuthzTransFilter.java new file mode 100644 index 00000000..a25c5f31 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/env/AuthzTransFilter.java @@ -0,0 +1,181 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.env; + +import java.security.Principal; + +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; + +import org.onap.aaf.auth.rserv.TransFilter; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.Connector; +import org.onap.aaf.cadi.TrustChecker; +import org.onap.aaf.cadi.principal.TaggedPrincipal; +import org.onap.aaf.cadi.principal.TrustPrincipal; +import org.onap.aaf.misc.env.Env; +import org.onap.aaf.misc.env.LogTarget; +import org.onap.aaf.misc.env.Slot; +import org.onap.aaf.misc.env.TimeTaken; +import org.onap.aaf.misc.env.Trans.Metric; + +public class AuthzTransFilter extends TransFilter<AuthzTrans> { + private AuthzEnv env; + public Metric serviceMetric; + public static Slot transIDslot,specialLogSlot; + + public static final String TRANS_ID_SLOT = "TRANS_ID_SLOT"; + public static final String SPECIAL_LOG_SLOT = "SPECIAL_LOG_SLOT"; + + public static final int BUCKETSIZE = 2; + + public AuthzTransFilter(AuthzEnv env, Connector con, TrustChecker tc, Object ... additionalTafLurs) throws CadiException { + super(env.access(),con, tc, additionalTafLurs); + this.env = env; + serviceMetric = new Metric(); + serviceMetric.buckets = new float[BUCKETSIZE]; + if(transIDslot==null) { + transIDslot = env.slot(TRANS_ID_SLOT); + } + if(specialLogSlot==null) { + specialLogSlot = env.slot(SPECIAL_LOG_SLOT); + } + } + + @Override + protected AuthzTrans newTrans() { + AuthzTrans at = env.newTrans(); + at.setLur(getLur()); + return at; + } + + @Override + protected TimeTaken start(AuthzTrans trans, ServletRequest request) { + trans.set((HttpServletRequest)request); + return trans.start("Trans " + //(context==null?"n/a":context.toString()) + + " IP: " + trans.ip() + + " Port: " + trans.port() + , Env.SUB); + } + + @Override + protected void authenticated(AuthzTrans trans, Principal p) { + trans.setUser((TaggedPrincipal)p); // We only work with TaggedPrincipals in Authz + } + + @Override + protected void tallyHo(AuthzTrans trans) { + Boolean b = trans.get(specialLogSlot, false); + LogTarget lt = b?trans.warn():trans.info(); + + if(lt.isLoggable()) { + // Transaction is done, now post full Audit Trail + StringBuilder sb = new StringBuilder("AuditTrail\n"); + // We'll grabAct sub-metrics for Remote Calls and JSON + // IMPORTANT!!! if you add more entries here, change "BUCKETSIZE"!!! + Metric m = trans.auditTrail(lt,1, sb, Env.REMOTE,Env.JSON); + + // Add current Metrics to total metrics + serviceMetric.total+= m.total; + for(int i=0;i<serviceMetric.buckets.length;++i) { + serviceMetric.buckets[i]+=m.buckets[i]; + } + + Long tsi; + if((tsi=trans.get(transIDslot, null))!=null) { + sb.append(" TraceID="); + sb.append(Long.toHexString(tsi)); + sb.append('\n'); + } + // Log current info + sb.append(" Total: "); + sb.append(m.total); + sb.append(" Remote: "); + sb.append(m.buckets[0]); + sb.append(" JSON: "); + sb.append(m.buckets[1]); + lt.log(sb); + } else { + // Single Line entry + // IMPORTANT!!! if you add more entries here, change "BUCKETSIZE"!!! + StringBuilder content = new StringBuilder(); + Metric m = trans.auditTrail(lt,1, content, Env.REMOTE,Env.JSON); + // Add current Metrics to total metrics + serviceMetric.total+= m.total; + for(int i=0;i<serviceMetric.buckets.length;++i) { + serviceMetric.buckets[i]+=m.buckets[i]; + } + + StringBuilder sb = new StringBuilder(); + sb.append("user="); + Principal p = trans.getUserPrincipal(); + if(p==null) { + sb.append("n/a"); + } else { + sb.append(p.getName()); + if(p instanceof TrustPrincipal) { + sb.append('('); + sb.append(((TrustPrincipal)p).personalName()); // UserChain + sb.append(')'); + } else { + sb.append('['); + if(p instanceof TaggedPrincipal) { + sb.append(((TaggedPrincipal)p).tag()); + } else { + sb.append(p.getClass().getSimpleName()); + } + sb.append(']'); + } + } + sb.append(",ip="); + sb.append(trans.ip()); + sb.append(",port="); + sb.append(trans.port()); +// Current code won't ever get here... Always does a Full Audit Trail +// Long tsi; +// if((tsi=trans.get(transIDslot, null))!=null) { +// sb.append(",TraceID="); +// sb.append(Long.toHexString(tsi)); +// } + sb.append(",ms="); + sb.append(m.total); + sb.append(",meth="); + sb.append(trans.meth()); + sb.append(",path="); + sb.append(trans.path()); + + if(content.length()>0) { + sb.append(",msg=\""); + int start = content.lastIndexOf(",msg=\""); + if(start>=0) { + sb.append(content,start+6,content.length()-1); + } else { + sb.append(content); + } + sb.append('"'); + } + + trans.warn().log(sb); + } + } + +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/env/AuthzTransImpl.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/env/AuthzTransImpl.java new file mode 100644 index 00000000..2ca8dfd7 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/env/AuthzTransImpl.java @@ -0,0 +1,216 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.env; + +import java.util.Date; + +import javax.servlet.http.HttpServletRequest; + +import org.onap.aaf.auth.org.Organization; +import org.onap.aaf.auth.org.OrganizationFactory; +import org.onap.aaf.cadi.Lur; +import org.onap.aaf.cadi.Permission; +import org.onap.aaf.cadi.principal.TaggedPrincipal; +import org.onap.aaf.misc.env.LogTarget; +import org.onap.aaf.misc.env.impl.BasicTrans; + +public class AuthzTransImpl extends BasicTrans implements AuthzTrans { + private TaggedPrincipal user; + private String ip,agent,meth,path; + private int port; + private Lur lur; + private Organization org; + private int mask; + private Date now; + public AuthzTransImpl(AuthzEnv env) { + super(env); + ip="n/a"; + org=null; + mask=0; + } + + /** + * @see org.onap.aaf.auth.env.test.AuthTrans#set(javax.servlet.http.HttpServletRequest) + */ + @Override + public AuthzTrans set(HttpServletRequest req) { + user = (TaggedPrincipal)req.getUserPrincipal(); + ip = req.getRemoteAddr(); + port = req.getRemotePort(); + agent = req.getHeader("User-Agent"); + meth = req.getMethod(); + path = req.getPathInfo(); + + for(REQD_TYPE rt : REQD_TYPE.values()) { + requested(rt,req); + } + // Handle alternate "request" for "future" + String request = req.getParameter("request"); + if(request!=null) { + requested(REQD_TYPE.future,(request.length()==0 || "true".equalsIgnoreCase(request))); + } + + org=null; + return this; + } + + @Override + public void setUser(TaggedPrincipal p) { + user = p; + } + + /** + * @see org.onap.aaf.auth.env.test.AuthTrans#user() + */ + @Override + public String user() { + return user==null?"n/a":user.getName(); + } + + /** + * @see org.onap.aaf.auth.env.test.AuthTrans#getUserPrincipal() + */ + @Override + public TaggedPrincipal getUserPrincipal() { + return user; + } + + /** + * @see org.onap.aaf.auth.env.test.AuthTrans#ip() + */ + @Override + public String ip() { + return ip; + } + + /** + * @see org.onap.aaf.auth.env.test.AuthTrans#port() + */ + @Override + public int port() { + return port; + } + + + /* (non-Javadoc) + * @see org.onap.aaf.auth.env.test.AuthzTrans#meth() + */ + @Override + public String meth() { + return meth; + } + + /* (non-Javadoc) + * @see org.onap.aaf.auth.env.test.AuthzTrans#path() + */ + @Override + public String path() { + return path; + } + + /** + * @see org.onap.aaf.auth.env.test.AuthTrans#agent() + */ + @Override + public String agent() { + return agent; + } + + @Override + public AuthzEnv env() { + return (AuthzEnv)delegate; + } + + @Override + public boolean requested(REQD_TYPE requested) { + return (mask&requested.bit)==requested.bit; + } + + public void requested(REQD_TYPE requested, boolean b) { + if(b) { + mask|=requested.bit; + } else { + mask&=~requested.bit; + } + } + + private void requested(REQD_TYPE reqtype, HttpServletRequest req) { + String p = req.getParameter(reqtype.name()); + if(p!=null) { + requested(reqtype,p.length()==0 || "true".equalsIgnoreCase(p)); + } + } + + @Override + public void setLur(Lur lur) { + this.lur = lur; + } + + @Override + public boolean fish(Permission p) { + if(lur!=null) { + return lur.fish(user, p); + } + return false; + } + + /* (non-Javadoc) + * @see org.onap.aaf.auth.env.test.AuthzTrans#org() + */ + @Override + public Organization org() { + if(org==null) { + try { + if((org = OrganizationFactory.obtain(env(), user()))==null) { + org = Organization.NULL; + } + } catch (Exception e) { + + org = Organization.NULL; + } + } + return org; + } + + /* (non-Javadoc) + * @see org.onap.aaf.auth.env.test.AuthzTrans#logAuditTrailOnly(com.att.inno.env.LogTarget) + */ + @Override + public void logAuditTrail(LogTarget lt) { + if(lt.isLoggable()) { + StringBuilder sb = new StringBuilder(); + auditTrail(1, sb); + lt.log(sb); + } + } + + /* (non-Javadoc) + * @see org.onap.aaf.auth.env.test.AuthzTrans#now() + */ + @Override + public Date now() { + if(now==null) { + now = new Date(); + } + return now; + } +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/env/AuthzTransOnlyFilter.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/env/AuthzTransOnlyFilter.java new file mode 100644 index 00000000..2488cc7e --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/env/AuthzTransOnlyFilter.java @@ -0,0 +1,86 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.env; + +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; + +import org.onap.aaf.auth.rserv.TransOnlyFilter; +import org.onap.aaf.cadi.principal.TaggedPrincipal; +import org.onap.aaf.misc.env.Env; +import org.onap.aaf.misc.env.TimeTaken; +import org.onap.aaf.misc.env.Trans.Metric; + +public class AuthzTransOnlyFilter extends TransOnlyFilter<AuthzTrans> { + private AuthzEnv env; + public Metric serviceMetric; + + public static final int BUCKETSIZE = 2; + + public AuthzTransOnlyFilter(AuthzEnv env) { + this.env = env; + serviceMetric = new Metric(); + serviceMetric.buckets = new float[BUCKETSIZE]; + } + + @Override + protected AuthzTrans newTrans() { + return env.newTrans(); + } + + @Override + protected TimeTaken start(AuthzTrans trans, ServletRequest request) { + trans.set((HttpServletRequest)request); + return trans.start("Trans " + //(context==null?"n/a":context.toString()) + + " IP: " + trans.ip() + + " Port: " + trans.port() + , Env.SUB); + } + + @Override + protected void authenticated(AuthzTrans trans, TaggedPrincipal p) { + trans.setUser(p); + } + + @Override + protected void tallyHo(AuthzTrans trans) { + // Transaction is done, now post + StringBuilder sb = new StringBuilder("AuditTrail\n"); + // We'll grab sub-metrics for Remote Calls and JSON + // IMPORTANT!!! if you add more entries here, change "BUCKETSIZE"!!! + Metric m = trans.auditTrail(1, sb, Env.REMOTE,Env.JSON); + // Add current Metrics to total metrics + serviceMetric.total+= m.total; + for(int i=0;i<serviceMetric.buckets.length;++i) { + serviceMetric.buckets[i]+=m.buckets[i]; + } + // Log current info + sb.append(" Total: "); + sb.append(m.total); + sb.append(" Remote: "); + sb.append(m.buckets[0]); + sb.append(" JSON: "); + sb.append(m.buckets[1]); + trans.info().log(sb); + } + +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/env/NullTrans.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/env/NullTrans.java new file mode 100644 index 00000000..13f6551b --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/env/NullTrans.java @@ -0,0 +1,234 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.env; + +import java.util.Date; + +import javax.servlet.http.HttpServletRequest; + +import org.onap.aaf.auth.org.Organization; +import org.onap.aaf.cadi.Lur; +import org.onap.aaf.cadi.Permission; +import org.onap.aaf.cadi.principal.TaggedPrincipal; +import org.onap.aaf.misc.env.Decryptor; +import org.onap.aaf.misc.env.Encryptor; +import org.onap.aaf.misc.env.LogTarget; +import org.onap.aaf.misc.env.Slot; +import org.onap.aaf.misc.env.StaticSlot; +import org.onap.aaf.misc.env.TimeTaken; + +/** + * A NULL implementation of AuthzTrans, for use in DirectAAF Taf/Lurs + */ +public class NullTrans implements AuthzTrans { + private static final AuthzTrans singleton = new NullTrans(); + + public static final AuthzTrans singleton() { + return singleton; + } + + private Date now; + + public void checkpoint(String text) {} + public void checkpoint(String text, int additionalFlag) {} + public Metric auditTrail(int indent, StringBuilder sb, int... flag) {return null;} + + @Override + public Metric auditTrail(LogTarget lt, int indent, StringBuilder sb, int... flag) { + return null; + } + + public LogTarget fatal() { + return LogTarget.NULL; + } + + public LogTarget error() { + return LogTarget.NULL; + } + + public LogTarget audit() { + return LogTarget.NULL; + } + + /* (non-Javadoc) + * @see com.att.env.Env#init() + */ + @Override + public LogTarget init() { + return LogTarget.NULL; + } + + public LogTarget warn() { + return LogTarget.NULL; + } + + public LogTarget info() { + return LogTarget.NULL; + } + + public LogTarget debug() { + return LogTarget.NULL; + } + + public LogTarget trace() { + return LogTarget.NULL; + } + + public TimeTaken start(String name, int flag) { + return new TimeTaken(name,flag) { + public void output(StringBuilder sb) { + sb.append(name); + sb.append(' '); + sb.append(millis()); + sb.append("ms"); + } + }; + } + + @Override + public String setProperty(String tag, String value) { + return value; + } + + @Override + public String getProperty(String tag) { + return tag; + } + + @Override + public String getProperty(String tag, String deflt) { + return deflt; + } + + @Override + public Decryptor decryptor() { + return null; + } + + @Override + public Encryptor encryptor() { + return null; + } + @Override + public AuthzTrans set(HttpServletRequest req) { + return null; + } + + @Override + public String user() { + return null; + } + + @Override + public TaggedPrincipal getUserPrincipal() { + return null; + } + + @Override + public void setUser(TaggedPrincipal p) { + } + + @Override + public String ip() { + return null; + } + + @Override + public int port() { + return 0; + } + @Override + public String meth() { + return null; + } + + @Override + public String path() { + return null; + } + + @Override + public void put(Slot slot, Object value) { + } + @Override + public <T> T get(Slot slot, T deflt) { + return null; + } + @Override + public <T> T get(StaticSlot slot, T dflt) { + return null; + } + @Override + public Slot slot(String name) { + return null; + } + @Override + public AuthzEnv env() { + return null; + } + @Override + public String agent() { + return null; + } + + @Override + public void setLur(Lur lur) { + } + + @Override + public boolean fish(Permission p) { + return false; + } + + @Override + public Organization org() { + return Organization.NULL; + } + + @Override + public void logAuditTrail(LogTarget lt) { + } + + /* (non-Javadoc) + * @see org.onap.aaf.auth.env.test.AuthzTrans#requested(org.onap.aaf.auth.env.test.AuthzTrans.REQD_TYPE) + */ + @Override + public boolean requested(REQD_TYPE requested) { + return false; + } + + /* (non-Javadoc) + * @see org.onap.aaf.auth.env.test.AuthzTrans#requested(org.onap.aaf.auth.env.test.AuthzTrans.REQD_TYPE, boolean) + */ + @Override + public void requested(REQD_TYPE requested, boolean b) { + } + + @Override + public Date now() { + if(now==null) { + now = new Date(); + } + return now; + } +} + diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/layer/DirectIntrospectImpl.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/layer/DirectIntrospectImpl.java new file mode 100644 index 00000000..41f0e74a --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/layer/DirectIntrospectImpl.java @@ -0,0 +1,26 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.layer; + +public class DirectIntrospectImpl { + +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/layer/FacadeImpl.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/layer/FacadeImpl.java new file mode 100644 index 00000000..81fc1e26 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/layer/FacadeImpl.java @@ -0,0 +1,42 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.layer; + +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.misc.env.Data; +import org.onap.aaf.misc.env.Data.TYPE; + + + +public abstract class FacadeImpl { + protected static final String IN = "in"; + + protected void setContentType(HttpServletResponse response, TYPE type) { + response.setContentType(type==Data.TYPE.JSON?"application/json":"text.xml"); + } + + protected void setCacheControlOff(HttpServletResponse response) { + response.setHeader("Cache-Control", "no-store"); + response.setHeader("Pragma", "no-cache"); + } +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/layer/Result.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/layer/Result.java new file mode 100644 index 00000000..e61cf2e8 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/layer/Result.java @@ -0,0 +1,328 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.layer; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + + +/** + * It would be nice if Java Enums were extensible, but they're not. + * + * @author Jonathan + * + */ +public class Result<RV> { + private static final String SUCCESS = "Success"; + public static final String[] EMPTY_VARS = new String[0]; + + public final static int OK=0, + ERR_Security = 1, + ERR_Denied = 2, + ERR_Policy = 3, + ERR_BadData = 4, + ERR_NotImplemented = 5, + ERR_NotFound = 6, + ERR_ConflictAlreadyExists = 7, + ERR_ActionNotCompleted = 8, + ERR_Backend = 9, + ERR_General = 20; + + public final RV value; + public final int status; + public final String details; + public final String[] variables; + + protected Result(RV value, int status, String details, String[] variables) { + this.value = value; + if(value==null) { + specialCondition|=EMPTY_LIST; + } + this.status = status; + this.details = details; + if(variables==null) { + this.variables = EMPTY_VARS; + } else { + this.variables=variables; + } + } + + /** + * Create a Result class with "OK" status and "Success" for details + * + * This is the easiest to use + * + * @param value + * @param status + * @return + */ + public static<R> Result<R> ok(R value) { + return new Result<R>(value,OK,SUCCESS,null); + } + + /** + * Accept Arrays and mark as empty or not + * @param value + * @return + */ + public static<R> Result<R[]> ok(R value[]) { + return new Result<R[]>(value,OK,SUCCESS,null).emptyList(value.length==0); + } + + /** + * Accept Sets and mark as empty or not + * @param value + * @return + */ + public static<R> Result<Set<R>> ok(Set<R> value) { + return new Result<Set<R>>(value,OK,SUCCESS,null).emptyList(value.size()==0); + } + + /** + * Accept Lists and mark as empty or not + * @param value + * @return + */ + public static<R> Result<List<R>> ok(List<R> value) { + return new Result<List<R>>(value,OK,SUCCESS,null).emptyList(value.size()==0); + } + + /** + * Accept Collections and mark as empty or not + * @param value + * @return + */ + public static<R> Result<Collection<R>> ok(Collection<R> value) { + return new Result<Collection<R>>(value,OK,SUCCESS,null).emptyList(value.size()==0); + } + + + /** + * Special Case for Void Type + * @return + */ + public static Result<Void> ok() { + return new Result<Void>(null,OK,SUCCESS,null); + } + + /** + * Create a Status (usually non OK, with a details statement + * @param value + * @param status + * @param details + * @return + */ +// public static<R> Result<R> err(int status, String details) { +// return new Result<R>(null,status,details,null); +// } + + /** + * Create a Status (usually non OK, with a details statement and variables supported + * @param status + * @param details + * @param variables + * @return + */ + public static<R> Result<R> err(int status, String details, String ... variables) { + return new Result<R>(null,status,details,variables); + } + + /** + * Create Error from status and Details of previous Result (and not data) + * @param pdr + * @return + */ + public static<R> Result<R> err(Result<?> pdr) { + return new Result<R>(null,pdr.status,pdr.details,pdr.variables); + } + + /** + * Create General Error from Exception + * @param e + * @return + */ + public static<R> Result<R> err(Exception e) { + return new Result<R>(null,ERR_General,e.getMessage(),EMPTY_VARS); + } + + /** + * Create a Status (usually non OK, with a details statement + * @param value + * @param status + * @param details + * @return + */ + public static<R> Result<R> create(R value, int status, String details, String ... vars) { + return new Result<R>(value,status,details,vars); + } + + /** + * Create a Status from a previous status' result/details + * @param value + * @param status + * @param details + * @return + */ + public static<R> Result<R> create(R value, Result<?> result) { + return new Result<R>(value,result.status,result.details,result.variables); + } + + private static final int PARTIAL_CONTENT = 0x001; + private static final int EMPTY_LIST = 0x002; + + /** + * AAF Specific problems, etc + * + * @author Jonathan + * + */ + + /** + * specialCondition is a bit field to enable multiple conditions, e.g. PARTIAL_CONTENT + */ + private int specialCondition = 0; + + + /** + * Is result set only partial results, i.e. the DAO clipped the real result set to a smaller number. + * @return true iff result returned PARTIAL_CONTENT + */ + public boolean partialContent() { + return (specialCondition & PARTIAL_CONTENT) == PARTIAL_CONTENT; + } + + /** + * Set fact that result set only returned partial results, i.e. the DAO clipped the real result set to a smaller number. + * @param hasPartialContent set true iff result returned PARTIAL_CONTENT + * @return this Result object, so you can chain calls, in builder style + */ + public Result<RV> partialContent(boolean hasPartialContent) { + if (hasPartialContent) { + specialCondition |= PARTIAL_CONTENT; + } else { + specialCondition &= (~PARTIAL_CONTENT); + } + return this; + } + + /** + * When Result is a List, you can check here to see if it's empty instead of looping + * + * @return + */ + public boolean isEmpty() { + return (specialCondition & EMPTY_LIST) == EMPTY_LIST; + } + + /** + * A common occurrence is that data comes back, but list is empty. If set, you can skip looking + * at list at the outset. + * + * @param emptyList + * @return + */ + public Result<RV> emptyList(boolean emptyList) { + if (emptyList) { + specialCondition |= EMPTY_LIST; + } else { + specialCondition &= (~EMPTY_LIST); + } + return this; + } + + + /** + * Convenience function. Checks OK, and also if List is not Empty + * Not valid if Data is not a List + * @return + */ + public boolean isOK() { + return status == OK; + } + + /** + * Convenience function. Checks OK, and also if List is not Empty + * Not valid if Data is not a List + * @return + */ + public boolean notOK() { + return status != OK; + } + + /** + * Convenience function. Checks OK, and also if List is not Empty + * Not valid if Data is not a List + * @return + */ + public boolean isOKhasData() { + return status == OK && (specialCondition & EMPTY_LIST) != EMPTY_LIST; + } + + + /** + * Convenience function. Checks OK, and also if List is not Empty + * Not valid if Data is not a List + * @return + */ + public boolean notOKorIsEmpty() { + return status != OK || (specialCondition & EMPTY_LIST) == EMPTY_LIST; + } + + @Override + public String toString() { + if(status==0) { + return details; + } else { + StringBuilder sb = new StringBuilder(); + sb.append(status); + sb.append(':'); + sb.append(String.format(details,((Object[])variables))); + if(isEmpty()) { + sb.append("{empty}"); + } + if(value!=null) { + sb.append('-'); + sb.append(value.toString()); + } + return sb.toString(); + } + } + + public String errorString() { + StringBuilder sb = new StringBuilder(); + switch(status) { + case 1: sb.append("Security"); break; + case 2: sb.append("Denied"); break; + case 3: sb.append("Policy"); break; + case 4: sb.append("BadData"); break; + case 5: sb.append("NotImplemented"); break; + case 6: sb.append("NotFound"); break; + case 7: sb.append("AlreadyExists"); break; + case 8: sb.append("ActionNotComplete"); break; + default: sb.append("Error"); + } + sb.append(" - "); + sb.append(String.format(details, (Object[])variables)); + return sb.toString(); + } +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/local/AbsData.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/local/AbsData.java new file mode 100644 index 00000000..40e0b22c --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/local/AbsData.java @@ -0,0 +1,206 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.local; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Iterator; + +import org.onap.aaf.auth.env.AuthzTrans; +import org.onap.aaf.auth.local.DataFile.Token; +import org.onap.aaf.auth.local.DataFile.Token.Field; +import org.onap.aaf.misc.env.Env; +import org.onap.aaf.misc.env.TimeTaken; + +public abstract class AbsData implements Iterable<String> { + protected DataFile data; + protected TextIndex ti; + private File dataf,idxf,lockf; + private String name; + private char delim; + private int maxLineSize; + private int fieldOffset; + private int skipLines; + + public AbsData(File dataf,char sepChar, int maxLineSize, int fieldOffset) { + File dir = dataf.getParentFile(); + int dot = dataf.getName().lastIndexOf('.'); + name = dataf.getName().substring(0,dot); + + this.dataf=dataf; + this.delim = sepChar; + this.maxLineSize = maxLineSize; + this.fieldOffset = fieldOffset; + idxf = new File(dir,name.concat(".idx")); + lockf = new File(dir,name.concat(".lock")); + + + data = new DataFile(dataf,"r"); + ti = new TextIndex(idxf); + skipLines=0; + } + + public void skipLines(int lines) { + skipLines=lines; + } + + public String name() { + return name; + } + + public void open(AuthzTrans trans, long timeout) throws IOException { + TimeTaken tt = trans.start("Open Data File", Env.SUB); + boolean opened = false, first = true; + try { + if(!dataf.exists()) { + throw new FileNotFoundException("Data File Missing:" + dataf.getCanonicalPath()); + } + long begin = System.currentTimeMillis(); + long end = begin+timeout; + boolean exists; + while((exists=lockf.exists()) && begin<end) { + if(first) { + trans.warn().log("Waiting for",lockf.getCanonicalPath(),"to close"); + first = false; + } + try { + Thread.sleep(200); + } catch (InterruptedException e) { + break; + } + begin = System.currentTimeMillis(); + } + if(exists) { + throw new IOException(lockf.getCanonicalPath() + "exists. May not open Datafile"); + } + data.open(); + try { + ensureIdxGood(trans); + } catch (IOException e) { + data.close(); + throw e; + } + ti.open(); + opened = true; + + } finally { + tt.done(); + } + if(!opened) { + throw new IOException("DataFile pair for " + name + " was not able to be opened in " + timeout + "ms"); + } + } + + private synchronized void ensureIdxGood(AuthzTrans trans) throws IOException { + if(!idxf.exists() || idxf.length()==0 || dataf.lastModified()>idxf.lastModified()) { + trans.warn().log(idxf.getAbsolutePath(),"is missing, empty or out of date, creating"); + RandomAccessFile raf = new RandomAccessFile(lockf, "rw"); + try { + ti.create(trans, data, maxLineSize, delim, fieldOffset, skipLines); + if(!idxf.exists() || (idxf.length()==0 && dataf.length()!=0)) { + throw new IOException("Data Index File did not create correctly"); + } + } finally { + raf.close(); + lockf.delete(); + } + } + } + + public void close(AuthzTrans trans) throws IOException { + ti.close(); + data.close(); + } + + public class Reuse { + public Token tokenData; + private Field fieldData; + + private Reuse(int size,char delim) { + tokenData = data.new Token(size); + fieldData = tokenData.new Field(delim); + } + + public void reset() { + getFieldData().reset(); + } + + public void pos(int rec) { + getFieldData().reset(); + tokenData.pos(rec); + } + + public String next() { + return getFieldData().next(); + } + + public String at(int field) { + return getFieldData().at(field); + } + + public String atToEnd(int field) { + return getFieldData().atToEnd(field); + } + + public Field getFieldData() { + return fieldData; + } + } + + public Reuse reuse() { + return new Reuse(maxLineSize,delim); + } + + public Iter iterator() { + return new Iter(); + } + + public class Iter implements Iterator<String> { + private Reuse reuse; + private org.onap.aaf.auth.local.TextIndex.Iter tii; + + public Iter() { + reuse = reuse(); + tii = ti.new Iter(); + } + + @Override + public boolean hasNext() { + return tii.hasNext(); + } + + @Override + public String next() { + reuse.reset(); + int rec = tii.next(); + reuse.pos(rec); + return reuse.at(0); + } + + @Override + public void remove() { + // read only + } + } +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/local/DataFile.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/local/DataFile.java new file mode 100644 index 00000000..bb9fb1fd --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/local/DataFile.java @@ -0,0 +1,190 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.local; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; + +public class DataFile { + private RandomAccessFile rafile; + private FileChannel channel; + public MappedByteBuffer mapBuff; + private final File file; + private final String access; + + public DataFile(File file, String access) { + this.file = file; + this.access = access; + } + public void open() throws IOException { + if(!file.exists()) throw new FileNotFoundException(); + rafile = new RandomAccessFile(file,access); + channel = rafile.getChannel(); + mapBuff = channel.map("r".equals(access)?MapMode.READ_ONLY:MapMode.READ_WRITE,0,channel.size()); + } + public boolean isOpened() { + return mapBuff!=null; + } + public void close() throws IOException { + if(channel!=null){ + channel.close(); + } + if(rafile!=null) { + rafile.close(); + } + mapBuff = null; + } + + public long size() throws IOException { + return channel==null?0:channel.size(); + } + + private synchronized int load(Token t) { + int len = Math.min(mapBuff.limit()-t.next,t.buff.length); + if(len>0) { + mapBuff.position(t.next); + mapBuff.get(t.buff,0,len); + } + return len<0?0:len; + } + + public class Token { + private byte[] buff; + int pos, next, end; + + public Token(int size) { + buff = new byte[size]; + pos = next = end = 0; + } + + public boolean pos(int to) { + pos = next = to; + return (end=load(this))>0; + } + + public boolean nextLine() { + end = load(this); + pos = next; + for(int i=0;i<end;++i) { + if(buff[i]=='\n') { + end = i; + next += i+1; + return true; + } + } + return false; + } + + public IntBuffer getIntBuffer() { + return ByteBuffer.wrap(buff).asIntBuffer(); + } + + public String toString() { + return new String(buff,0,end); + } + + public class Field { + char delim; + int idx; + ByteBuffer bb; + + public Field(char delimiter) { + delim = delimiter; + idx = 0; + bb = null; + } + + public Field reset() { + idx = 0; + return this; + } + + public String next() { + if(idx>=end)return null; + int start = idx; + byte c=0; + int endStr = -1; + while(idx<end && idx<buff.length && (c=buff[idx])!=delim && c!='\n') { // for DOS + if(c=='\r')endStr=idx; + ++idx; + } + + if(endStr<0) { + endStr=idx-start; + } else { + endStr=endStr-start; + } + ++idx; + return new String(buff,start,endStr); + } + + public String at(int fieldOffset) { + int start; + byte c=0; + for(int count = idx = start = 0; idx<end && idx<buff.length; ++idx) { + if((c=buff[idx])==delim || c=='\n') { + if(count++ == fieldOffset) { + break; + } + start = idx+1; + } + } + return new String(buff,start,(idx-start-(c=='\r'?1:0))); + } + + public String atToEnd(int fieldOffset) { + int start; + byte c=0; + for(int count = idx = start = 0; idx<end && idx<buff.length; ++idx) { + if((c=buff[idx])==delim || c=='\n') { + if(count++ == fieldOffset) { + break; + } + start = idx+1; + } + } + + for(; idx<end && idx<buff.length && (c=buff[idx])!='\n'; ++idx) { + ++idx; + } + return new String(buff,start,(idx-start-((c=='\r' || idx>=end)?1:0))); + } + + } + + public int pos() { + return pos; + } + } + + public File file() { + return file; + } + +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/local/TextIndex.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/local/TextIndex.java new file mode 100644 index 00000000..5169cf88 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/local/TextIndex.java @@ -0,0 +1,256 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.local; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import org.onap.aaf.auth.local.DataFile.Token; +import org.onap.aaf.auth.local.DataFile.Token.Field; +import org.onap.aaf.misc.env.Env; +import org.onap.aaf.misc.env.TimeTaken; +import org.onap.aaf.misc.env.Trans; + +public class TextIndex { + private static final int REC_SIZE=8; + + private File file; + private DataFile dataFile=null; + + public TextIndex(File theFile) { + file = theFile; + } + + public void open() throws IOException { + dataFile = new DataFile(file,"r"); + dataFile.open(); + } + + public void close() throws IOException { + if(dataFile!=null) { + dataFile.close(); + dataFile=null; + } + } + + public int find(Object key, AbsData.Reuse reuse, int offset) throws IOException { + return find(key,reuse.tokenData,reuse.getFieldData(),offset); + } + + public int find(Object key, DataFile.Token dtok, Field df, int offset) throws IOException { + if(dataFile==null) { + throw new IOException("File not opened"); + } + long hash = hashToLong(key.hashCode()); + int min=0, max = (int)(dataFile.size()/REC_SIZE); + Token ttok = dataFile.new Token(REC_SIZE); + IntBuffer tib = ttok.getIntBuffer(); + long lhash; + int curr; + while((max-min)>100) { + ttok.pos((curr=(min+(max-min)/2))*REC_SIZE); + tib.rewind(); + lhash = hashToLong(tib.get()); + if(lhash<hash) { + min=curr+1; + } else if(lhash>hash) { + max=curr-1; + } else { + min=curr-40; + max=curr+40; + break; + } + } + + List<Integer> entries = new ArrayList<Integer>(); + for(int i=min;i<=max;++i) { + ttok.pos(i*REC_SIZE); + tib.rewind(); + lhash = hashToLong(tib.get()); + if(lhash==hash) { + entries.add(tib.get()); + } else if(lhash>hash) { + break; + } + } + + for(Integer i : entries) { + dtok.pos(i); + if(df.at(offset).equals(key)) { + return i; + } + } + return -1; + } + + + /* + * Have to change Bytes into a Long, to avoid the inevitable signs in the Hash + */ + private static long hashToLong(int hash) { + long rv; + if(hash<0) { + rv = 0xFFFFFFFFL & hash; + } else { + rv = hash; + } + return rv; + } + + public void create(final Trans trans,final DataFile data, int maxLine, char delim, int fieldOffset, int skipLines) throws IOException { + RandomAccessFile raf; + FileChannel fos; + + List<Idx> list = new LinkedList<Idx>(); // Some hashcodes will double... DO NOT make a set + TimeTaken tt2 = trans.start("Open Files", Env.SUB); + try { + raf = new RandomAccessFile(file,"rw"); + raf.setLength(0L); + fos = raf.getChannel(); + } finally { + tt2.done(); + } + + try { + + Token t = data.new Token(maxLine); + Field f = t.new Field(delim); + + int count = 0; + if(skipLines>0) { + trans.info().log("Skipping",skipLines,"line"+(skipLines==1?" in":"s in"),data.file().getName()); + } + for(int i=0;i<skipLines;++i) { + t.nextLine(); + } + tt2 = trans.start("Read", Env.SUB); + try { + while(t.nextLine()) { + list.add(new Idx(f.at(fieldOffset),t.pos())); + ++count; + } + } finally { + tt2.done(); + } + trans.checkpoint(" Read " + count + " records"); + tt2 = trans.start("Sort List", Env.SUB); + Collections.sort(list); + tt2.done(); + tt2 = trans.start("Write Idx", Env.SUB); + try { + ByteBuffer bb = ByteBuffer.allocate(8*1024); + IntBuffer ib = bb.asIntBuffer(); + for(Idx idx : list) { + if(!ib.hasRemaining()) { + fos.write(bb); + ib.clear(); + bb.rewind(); + } + ib.put(idx.hash); + ib.put(idx.pos); + } + bb.limit(4*ib.position()); + fos.write(bb); + } finally { + tt2.done(); + } + } finally { + fos.close(); + raf.close(); + } + } + + public class Iter { + private int idx; + private Token t; + private long end; + private IntBuffer ib; + + + public Iter() { + try { + idx = 0; + end = dataFile.size(); + t = dataFile.new Token(REC_SIZE); + ib = t.getIntBuffer(); + + } catch (IOException e) { + end = -1L; + } + } + + public int next() { + t.pos(idx); + ib.clear(); + ib.get(); + int rec = ib.get(); + idx += REC_SIZE; + return rec; + } + + public boolean hasNext() { + return idx<end; + } + } + + private static class Idx implements Comparable<Idx> { + public int hash, pos; + public Idx(Object obj, int pos) { + hash = obj.hashCode(); + this.pos = pos; + } + + @Override + public int compareTo(Idx ib) { + long a = hashToLong(hash); + long b = hashToLong(ib.hash); + return a>b?1:a<b?-1:0; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object o) { + if(o!=null && o instanceof Idx) { + return hash == ((Idx)o).hash; + } + return false; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return hash; + } + } +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/org/EmailWarnings.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/org/EmailWarnings.java new file mode 100644 index 00000000..8360ffcb --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/org/EmailWarnings.java @@ -0,0 +1,33 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.org; + +public interface EmailWarnings +{ + public long credExpirationWarning(); + public long roleExpirationWarning(); + public long credEmailInterval(); + public long roleEmailInterval(); + public long apprEmailInterval(); + public long emailUrgentWarning(); + +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/org/Executor.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/org/Executor.java new file mode 100644 index 00000000..a839ae73 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/org/Executor.java @@ -0,0 +1,34 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.org; + +public interface Executor { + // remove User from user/Role + // remove user from Admins + // if # of Owners > 1, remove User from Owner + // if # of Owners = 1, changeOwner to X Remove Owner???? + boolean hasPermission(String user, String ns, String type, String instance, String action); + boolean inRole(String name); + + public String namespace() throws Exception; + public String id(); +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/org/Organization.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/org/Organization.java new file mode 100644 index 00000000..6d7a3586 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/org/Organization.java @@ -0,0 +1,515 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.org; + +import java.util.ArrayList; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.onap.aaf.auth.env.AuthzTrans; + +/** + * Organization + * + * There is Organizational specific information required which we have extracted to a plugin + * + * It supports using Company Specific User Directory lookups, as well as supporting an + * Approval/Validation Process to simplify control of Roles and Permissions for large organizations + * in lieu of direct manipulation by a set of Admins. + * + * @author Jonathan + * + */ +public interface Organization { + public static final String N_A = "n/a"; + + public interface Identity { + public String id(); + public String fullID() throws OrganizationException; // Fully Qualified ID (includes Domain of Organization) + public String type(); // Must be one of "IdentityTypes", see below + public Identity responsibleTo() throws OrganizationException; // Chain of Command, or Application ID Sponsor + public List<String> delegate(); // Someone who has authority to act on behalf of Identity + public String email(); + public String fullName(); + public String firstName(); + /** + * If Responsible entity, then String returned is "null" meaning "no Objection". + * If String exists, it is the Policy objection text setup by the entity. + * @return + */ + public String mayOwn(); // Is id passed belong to a person suitable to be Responsible for content Management + public boolean isFound(); // Is Identity found in Identity stores + public boolean isPerson(); // Whether a Person or a Machine (App) + public Organization org(); // Organization of Identity + + } + + + /** + * Name of Organization, suitable for Logging + * @return + */ + public String getName(); + + /** + * Realm, for use in distinguishing IDs from different systems/Companies + * @return + */ + public String getRealm(); + + String getDomain(); + + /** + * Get Identity information based on userID + * + * @param id + * @return + */ + public Identity getIdentity(AuthzTrans trans, String id) throws OrganizationException; + + + /** + * Does the ID pass Organization Standards + * + * Return a Blank (empty) String if empty, otherwise, return a "\n" separated list of + * reasons why it fails + * + * @param id + * @return + */ + public String isValidID(AuthzTrans trans, String id); + + /** + * Return a Blank (empty) String if empty, otherwise, return a "\n" separated list of + * reasons why it fails + * + * Identity is passed in to allow policies regarding passwords that are the same as user ID + * + * any entries for "prev" imply a reset + * + * @param id + * @param password + * @return + */ + public String isValidPassword(final AuthzTrans trans, final String id, final String password, final String ... prev); + + /** + * Return a list of Strings denoting Organization Password Rules, suitable for posting on a WebPage with <p> + */ + public String[] getPasswordRules(); + + /** + * + * @param id + * @return + */ + public boolean isValidCred(final AuthzTrans trans, final String id); + + /** + * If response is Null, then it is valid. Otherwise, the Organization specific reason is returned. + * + * @param trans + * @param policy + * @param executor + * @param vars + * @return + * @throws OrganizationException + */ + public String validate(AuthzTrans trans, Policy policy, Executor executor, String ... vars) throws OrganizationException; + + /** + * Does your Company distinguish essential permission structures by kind of Identity? + * i.e. Employee, Contractor, Vendor + * @return + */ + public Set<String> getIdentityTypes(); + + public enum Notify { + Approval(1), + PasswordExpiration(2), + RoleExpiration(3); + + final int id; + Notify(int id) {this.id = id;} + public int getValue() {return id;} + public static Notify from(int type) { + for(Notify t : Notify.values()) { + if(t.id==type) { + return t; + } + } + return null; + } + } + + public enum Response{ + OK, + ERR_NotImplemented, + ERR_UserNotExist, + ERR_NotificationFailure, + }; + + public enum Expiration { + Password, + TempPassword, + Future, + UserInRole, + UserDelegate, + ExtendPassword + } + + public enum Policy { + CHANGE_JOB, + LEFT_COMPANY, + CREATE_MECHID, + CREATE_MECHID_BY_PERM_ONLY, + OWNS_MECHID, + AS_RESPONSIBLE, + MAY_EXTEND_CRED_EXPIRES, + MAY_APPLY_DEFAULT_REALM + } + + /** + * Notify a User of Action or Info + * + * @param type + * @param url + * @param users (separated by commas) + * @param ccs (separated by commas) + * @param summary + */ + + public Response notify(AuthzTrans trans, Notify type, String url, String ids[], String ccs[], String summary, Boolean urgent); + + /** + * (more) generic way to send an email + * + * @param toList + * @param ccList + * @param subject + * @param body + * @param urgent + */ + + public int sendEmail(AuthzTrans trans, List<String> toList, List<String> ccList, String subject, String body, Boolean urgent) throws OrganizationException; + + /** + * whenToValidate + * + * Authz support services will ask the Organization Object at startup when it should + * kickoff Validation processes given particular types. + * + * This allows the Organization to express Policy + * + * Turn off Validation behavior by returning "null" + * + */ + public Date whenToValidate(Notify type, Date lastValidated); + + + /** + * Expiration + * + * Given a Calendar item of Start (or now), set the Expiration Date based on the Policy + * based on type. + * + * For instance, "Passwords expire in 3 months" + * + * The Extra Parameter is used by certain Orgs. + * + * For Password, the extra is UserID, so it can check the User Type + * + * @param gc + * @param exp + * @return + */ + public GregorianCalendar expiration(GregorianCalendar gc, Expiration exp, String ... extra); + + /** + * Get Email Warning timing policies + * @return + */ + public EmailWarnings emailWarningPolicy(); + + /** + * + * @param trans + * @param user + * @return + */ + public List<Identity> getApprovers(AuthzTrans trans, String user) throws OrganizationException ; + + /* + * + * @param user + * @param type + * @param users + * @return + public Response notifyRequest(AuthzTrans trans, String user, Approval type, List<User> approvers); + */ + + /** + * + * @return + */ + public String getApproverType(); + + /* + * startOfDay - define for company what hour of day business starts (specifically for password and other expiration which + * were set by Date only.) + * + * @return + */ + public int startOfDay(); + + /** + * implement this method to support any IDs that can have multiple entries in the cred table + * NOTE: the combination of ID/expiration date/(encryption type when implemented) must be unique. + * Since expiration date is based on startOfDay for your company, you cannot create many + * creds for the same ID in the same day. + * @param id + * @return + */ + public boolean canHaveMultipleCreds(String id); + + boolean isTestEnv(); + + public void setTestMode(boolean dryRun); + + public static final Organization NULL = new Organization() + { + private final GregorianCalendar gc = new GregorianCalendar(1900, 1, 1); + private final List<Identity> nullList = new ArrayList<Identity>(); + private final Set<String> nullStringSet = new HashSet<String>(); + private String[] nullStringArray = new String[0]; + private final Identity nullIdentity = new Identity() { + List<String> nullUser = new ArrayList<String>(); + @Override + public String type() { + return N_A; + } + + @Override + public String mayOwn() { + return N_A; // negative case + } + + @Override + public boolean isFound() { + return false; + } + + @Override + public String id() { + return N_A; + } + + @Override + public String fullID() { + return N_A; + } + + @Override + public String email() { + return N_A; + } + + @Override + public List<String> delegate() { + return nullUser; + } + @Override + public String fullName() { + return N_A; + } + @Override + public Organization org() { + return NULL; + } + @Override + public String firstName() { + return N_A; + } + @Override + public boolean isPerson() { + return false; + } + + @Override + public Identity responsibleTo() { + return null; + } + }; + @Override + public String getName() { + return N_A; + } + + @Override + public String getRealm() { + return N_A; + } + + @Override + public String getDomain() { + return N_A; + } + + @Override + public Identity getIdentity(AuthzTrans trans, String id) { + return nullIdentity; + } + + @Override + public String isValidID(final AuthzTrans trans, String id) { + return N_A; + } + + @Override + public String isValidPassword(final AuthzTrans trans, final String user, final String password, final String... prev) { + return N_A; + } + + @Override + public Set<String> getIdentityTypes() { + return nullStringSet; + } + + @Override + public Response notify(AuthzTrans trans, Notify type, String url, + String[] users, String[] ccs, String summary, Boolean urgent) { + return Response.ERR_NotImplemented; + } + + @Override + public int sendEmail(AuthzTrans trans, List<String> toList, List<String> ccList, + String subject, String body, Boolean urgent) throws OrganizationException { + return 0; + } + + @Override + public Date whenToValidate(Notify type, Date lastValidated) { + return gc.getTime(); + } + + @Override + public GregorianCalendar expiration(GregorianCalendar gc, + Expiration exp, String... extra) { + return gc; + } + + @Override + public List<Identity> getApprovers(AuthzTrans trans, String user) + throws OrganizationException { + return nullList; + } + + @Override + public String getApproverType() { + return ""; + } + + @Override + public int startOfDay() { + return 0; + } + + @Override + public boolean canHaveMultipleCreds(String id) { + return false; + } + + @Override + public boolean isValidCred(final AuthzTrans trans, final String id) { + return false; + } + + @Override + public String validate(AuthzTrans trans, Policy policy, Executor executor, String ... vars) + throws OrganizationException { + return "Null Organization rejects all Policies"; + } + + @Override + public boolean isTestEnv() { + return false; + } + + @Override + public void setTestMode(boolean dryRun) { + } + + @Override + public EmailWarnings emailWarningPolicy() { + return new EmailWarnings() { + + @Override + public long credEmailInterval() + { + return 604800000L; // 7 days in millis 1000 * 86400 * 7 + } + + @Override + public long roleEmailInterval() + { + return 604800000L; // 7 days in millis 1000 * 86400 * 7 + } + + @Override + public long apprEmailInterval() { + return 259200000L; // 3 days in millis 1000 * 86400 * 3 + } + + @Override + public long credExpirationWarning() + { + return( 2592000000L ); // One month, in milliseconds 1000 * 86400 * 30 in milliseconds + } + + @Override + public long roleExpirationWarning() + { + return( 2592000000L ); // One month, in milliseconds 1000 * 86400 * 30 in milliseconds + } + + @Override + public long emailUrgentWarning() + { + return( 1209600000L ); // Two weeks, in milliseconds 1000 * 86400 * 14 in milliseconds + } + + }; + } + + @Override + public String[] getPasswordRules() { + return nullStringArray; + } + + }; + +} + + diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/org/OrganizationException.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/org/OrganizationException.java new file mode 100644 index 00000000..ed1d398b --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/org/OrganizationException.java @@ -0,0 +1,52 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.org; + +public class OrganizationException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public OrganizationException() { + super(); + } + + public OrganizationException(String message) { + super(message); + } + + public OrganizationException(Throwable cause) { + super(cause); + } + + public OrganizationException(String message, Throwable cause) { + super(message, cause); + } + + public OrganizationException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/org/OrganizationFactory.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/org/OrganizationFactory.java new file mode 100644 index 00000000..36efb5dc --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/org/OrganizationFactory.java @@ -0,0 +1,125 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.org; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +import org.onap.aaf.auth.env.AuthzTrans; +import org.onap.aaf.cadi.util.FQI; +import org.onap.aaf.misc.env.Env; +import org.onap.aaf.misc.env.impl.BasicEnv; + +/** + * Organization Plugin Mechanism + * + * Define a NameSpace for the company (i.e. com.att), and put in Properties as + * "Organization.[your NS" and assign the supporting Class. + * + * Example: + * Organization.com.att=org.onap.aaf.auth.org.test.att.ATT + * + * @author Pavani, Jonathan + * + */ +public class OrganizationFactory { + private static final String ORGANIZATION_DOT = "Organization."; + private static Organization defaultOrg = null; + private static Map<String,Organization> orgs = new ConcurrentHashMap<String,Organization>(); + public static Organization init(BasicEnv env) throws OrganizationException { + int idx = ORGANIZATION_DOT.length(); + Organization org,firstOrg = null; + + for(Entry<Object, Object> es : env.getProperties().entrySet()) { + String key = es.getKey().toString(); + if(key.startsWith(ORGANIZATION_DOT)) { + org = obtain(env,key.substring(idx)); + if(firstOrg==null) { + firstOrg = org; + } + } + } + if(defaultOrg == null) { + defaultOrg = firstOrg; + } + return defaultOrg; + } + public static Organization obtain(Env env,final String theNS) throws OrganizationException { + String orgNS; + if(theNS.indexOf('@')>=0) { + orgNS=FQI.reverseDomain(theNS); + } else { + orgNS=theNS; + } + Organization org = orgs.get(orgNS); + if(org == null) { + String orgClass = env.getProperty(ORGANIZATION_DOT+orgNS); + if(orgClass == null) { + env.warn().log("There is no Organization." + orgNS + " property"); + } else { + for(Organization o : orgs.values()) { + if(orgClass.equals(o.getClass().getName())) { + org = o; + } + } + if(org==null) { + try { + @SuppressWarnings("unchecked") + Class<Organization> cls = (Class<Organization>) Class.forName(orgClass); + Constructor<Organization> cnst = cls.getConstructor(Env.class,String.class); + org = cnst.newInstance(env,orgNS); + } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | + InstantiationException | IllegalAccessException | IllegalArgumentException | + InvocationTargetException e) { + env.error().log(e, "Error on Organization Construction"); + throw new OrganizationException(e); + } + } + orgs.put(orgNS, org); + if("true".equalsIgnoreCase(env.getProperty(orgNS+".default"))) { + defaultOrg = org; + } + + } + if(org==null) { + if(defaultOrg!=null) { + org=defaultOrg; + orgs.put(orgNS, org); + } + } + } + + return org; + } + + public static Organization get(AuthzTrans trans) throws OrganizationException { + String domain = FQI.reverseDomain(trans.user()); + Organization org = orgs.get(domain); + if(org==null) { + org = defaultOrg; // can be null, btw, unless set. + } + return org; + } +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Acceptor.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Acceptor.java new file mode 100644 index 00000000..1953694b --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Acceptor.java @@ -0,0 +1,169 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.rserv; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.onap.aaf.misc.env.Trans; + +/** + * Find Acceptable Paths and place them where TypeCode can evaluate. + * + * If there are more than one, TypeCode will choose based on "q" value + * @author Jonathan + * + * @param <TRANS> + */ +class Acceptor<TRANS extends Trans> { + private List<Pair<String, Pair<HttpCode<TRANS,?>, List<Pair<String, Object>>>>> types; + List<Pair<String, Pair<HttpCode<TRANS,?>, List<Pair<String, Object>>>>> acceptable; + + public Acceptor(List<Pair<String, Pair<HttpCode<TRANS,?>, List<Pair<String, Object>>>>> types) { + this.types = types; + acceptable = new ArrayList<Pair<String, Pair<HttpCode<TRANS,?>, List<Pair<String, Object>>>>>(); + } + + private boolean eval(HttpCode<TRANS,?> code, String str, List<String> props) { +// int plus = str.indexOf('+'); +// if(plus<0) { + boolean ok = false; + boolean any = false; + for(Pair<String, Pair<HttpCode<TRANS,?>, List<Pair<String, Object>>>> type : types) { + ok = true; + if(type.x.equals(str)) { + for(Iterator<String> iter = props.iterator();ok && iter.hasNext();) { + ok = props(type,iter.next(),iter.next()); + } + if(ok) { + any = true; + acceptable.add(type); + } + } + } +// } else { // Handle Accepts with "+" as in application/xaml+xml +// int prev = str.indexOf('/')+1; +// String first = str.substring(0,prev); +// String nstr; +// while(prev!=0) { +// nstr = first + (plus<0?str.substring(prev):str.substring(prev,plus)); +// +// for(Pair<String, Pair<HttpCode<TRANS,?>, List<Pair<String, Object>>>> type : types) { +// if(type.x.equals(nstr)) { +// acceptable.add(type); +// return type; +// } +// } +// prev = plus+1; +// plus=str.indexOf('+', prev); +// }; +// } + return any; + } + + /** + * Evaluate Properties + * @param type + * @param tag + * @param value + * @return + */ + private boolean props(Pair<String, Pair<HttpCode<TRANS,?>, List<Pair<String, Object>>>> type, String tag, String value) { + boolean rv = false; + if(type.y!=null) { + for(Pair<String,Object> prop : type.y.y){ + if(tag.equals(prop.x)) { + if(tag.equals("charset")) { + return prop.x==null?false:prop.y.equals(value.toLowerCase()); // return True if Matched + } else if(tag.equals("version")) { + return prop.y.equals(new Version(value)); // Note: Version Class knows Minor Version encoding + } else if(tag.equals(Content.Q)) { // replace Q value + try { + type.y.y.get(0).y=Float.parseFloat(value); + } catch (NumberFormatException e) { + rv=false; // need to do something to make Sonar happy. But nothing to do. + } + return true; + } else { + return value.equals(prop.y); + } + } + } + } + return rv; + } + + /** + * parse + * + * Note: I'm processing by index to avoid lots of memory creation, which speeds things + * up for this time critical section of code. + * @param code + * @param cntnt + * @return + */ + protected boolean parse(HttpCode<TRANS, ?> code, String cntnt) { + byte bytes[] = cntnt.getBytes(); + + int cis,cie=-1,cend; + int sis,sie,send; + String name; + ArrayList<String> props = new ArrayList<String>(); + do { + // Clear these in case more than one Semi + props.clear(); // on loop, do not want mixed properties + name=null; + + cis = cie+1; // find comma start + while(cis<bytes.length && Character.isSpaceChar(bytes[cis]))++cis; + cie = cntnt.indexOf(',',cis); // find comma end + cend = cie<0?bytes.length:cie; // If no comma, set comma end to full length, else cie + while(cend>cis && Character.isSpaceChar(bytes[cend-1]))--cend; + // Start SEMIS + sie=cis-1; + do { + sis = sie+1; // semi start is one after previous end + while(sis<bytes.length && Character.isSpaceChar(bytes[sis]))++sis; + sie = cntnt.indexOf(';',sis); + send = sie>cend || sie<0?cend:sie; // if the Semicolon is after the comma, or non-existent, use comma end, else keep + while(send>sis && Character.isSpaceChar(bytes[send-1]))--send; + if(name==null) { // first entry in Comma set is the name, not a property + name = new String(bytes,sis,send-sis); + } else { // We've looped past the first Semi, now process as properties + // If there are additional elements (more entities within Semi Colons) + // apply Properties + int eq = cntnt.indexOf('=',sis); + if(eq>sis && eq<send) { + props.add(new String(bytes,sis,eq-sis)); + props.add(new String(bytes,eq+1,send-(eq+1))); + } + } + // End Property + } while(sie<=cend && sie>=cis); // End SEMI processing + // Now evaluate Comma set and return if true + if(eval(code,name,props))return true; // else loop again to check next comma + } while(cie>=0); // loop to next comma + return false; // didn't get even one match + } + +}
\ No newline at end of file diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/CachingFileAccess.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/CachingFileAccess.java new file mode 100644 index 00000000..7bb276a2 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/CachingFileAccess.java @@ -0,0 +1,564 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.rserv; + + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.misc.env.Env; +import org.onap.aaf.misc.env.EnvJAXB; +import org.onap.aaf.misc.env.LogTarget; +import org.onap.aaf.misc.env.Store; +import org.onap.aaf.misc.env.TimeTaken; +import org.onap.aaf.misc.env.Trans; +/* + * CachingFileAccess + * + * Author: Jonathan Gathman, Gathsys 2010 + * + */ +public class CachingFileAccess<TRANS extends Trans> extends HttpCode<TRANS, Void> { + public static void setEnv(Store store, String[] args) { + for(int i=0;i<args.length-1;i+=2) { // cover two parms required for each + if(CFA_WEB_PATH.equals(args[i])) { + store.put(store.staticSlot(CFA_WEB_PATH), args[i+1]); + } else if(CFA_CACHE_CHECK_INTERVAL.equals(args[i])) { + store.put(store.staticSlot(CFA_CACHE_CHECK_INTERVAL), Long.parseLong(args[i+1])); + } else if(CFA_MAX_SIZE.equals(args[i])) { + store.put(store.staticSlot(CFA_MAX_SIZE), Integer.parseInt(args[i+1])); + } + } + } + + private static String MAX_AGE = "max-age=3600"; // 1 hour Caching + private final Map<String,String> typeMap; + private final NavigableMap<String,Content> content; + private final Set<String> attachOnly; + public final static String CFA_WEB_PATH = "aaf_cfa_web_path"; + // when to re-validate from file + // Re validating means comparing the Timestamp on the disk, and seeing it has changed. Cache is not marked + // dirty unless file has changed, but it still makes File IO, which for some kinds of cached data, i.e. + // deployed GUI elements is unnecessary, and wastes time. + // This parameter exists to cover the cases where data can be more volatile, so the user can choose how often the + // File IO will be accessed, based on probability of change. "0", of course, means, check every time. + private final static String CFA_CACHE_CHECK_INTERVAL = "aaf_cfa_cache_check_interval"; + private final static String CFA_MAX_SIZE = "aaf_cfa_max_size"; // Cache size limit + private final static String CFA_CLEAR_COMMAND = "aaf_cfa_clear_command"; + + // Note: can be null without a problem, but included + // to tie in with existing Logging. + public LogTarget logT = null; + public long checkInterval; // = 600000L; // only check if not hit in 10 mins by default + public int maxItemSize; // = 512000; // max file 500k + private Timer timer; + private String web_path; + // A command key is set in the Properties, preferably changed on deployment. + // it is compared at the beginning of the path, and if so, it is assumed to issue certain commands + // It's purpose is to protect, to some degree the command, even though it is HTTP, allowing + // local batch files to, for instance, clear caches on resetting of files. + private String clear_command; + + public CachingFileAccess(EnvJAXB env, String ... args) throws IOException { + super(null,"Caching File Access"); + setEnv(env,args); + content = new ConcurrentSkipListMap<String,Content>(); // multi-thread changes possible + + attachOnly = new HashSet<String>(); // short, unchanged + + typeMap = new TreeMap<String,String>(); // Structure unchanged after Construction + typeMap.put("ico","image/icon"); + typeMap.put("html","text/html"); + typeMap.put("css","text/css"); + typeMap.put("js","text/javascript"); + typeMap.put("txt","text/plain"); + typeMap.put("xml","text/xml"); + typeMap.put("xsd","text/xml"); + attachOnly.add("xsd"); + typeMap.put("crl", "application/x-pkcs7-crl"); + typeMap.put("appcache","text/cache-manifest"); + + typeMap.put("json","text/json"); + typeMap.put("ogg", "audio/ogg"); + typeMap.put("jpg","image/jpeg"); + typeMap.put("gif","image/gif"); + typeMap.put("png","image/png"); + typeMap.put("svg","image/svg+xml"); + typeMap.put("jar","application/x-java-applet"); + typeMap.put("jnlp", "application/x-java-jnlp-file"); + typeMap.put("class", "application/java"); + typeMap.put("props", "text/plain"); + typeMap.put("jks", "application/octet-stream"); + + timer = new Timer("Caching Cleanup",true); + timer.schedule(new Cleanup(content,500),60000,60000); + + // Property params + web_path = env.get(env.staticSlot(CFA_WEB_PATH)); + env.init().log("CachingFileAccess path: " + new File(web_path).getCanonicalPath()); + Object obj; + obj = env.get(env.staticSlot(CFA_CACHE_CHECK_INTERVAL),600000L); // Default is 10 mins + if(obj instanceof Long) {checkInterval=(Long)obj; + } else {checkInterval=Long.parseLong((String)obj);} + + obj = env.get(env.staticSlot(CFA_MAX_SIZE), 512000); // Default is max file 500k + if(obj instanceof Integer) {maxItemSize=(Integer)obj; + } else {maxItemSize =Integer.parseInt((String)obj);} + + clear_command = env.getProperty(CFA_CLEAR_COMMAND,null); + } + + + + @Override + public void handle(TRANS trans, HttpServletRequest req, HttpServletResponse resp) throws IOException { + String key = pathParam(req, ":key"); + String cmd = pathParam(req,":cmd"); + System.out.print(key + clear_command); + if(key.equals(clear_command)) { + resp.setHeader("Content-Type",typeMap.get("txt")); + if("clear".equals(cmd)) { + content.clear(); + resp.setStatus(200/*HttpStatus.OK_200*/); + } else { + resp.setStatus(400/*HttpStatus.BAD_REQUEST_400 */); + } + return; + } + Content c = load(logT , web_path,cmd!=null && cmd.length()>0?key+'/'+cmd:key, null, checkInterval); + if(c.attachmentOnly) { + resp.setHeader("Content-disposition", "attachment"); + } + c.setHeader(resp); + c.write(resp.getOutputStream()); + trans.checkpoint(req.getPathInfo()); + } + + + public String webPath() { + return web_path; + } + + /** + * Reset the Cleanup size and interval + * + * The size and interval when started are 500 items (memory size unknown) checked every minute in a background thread. + * + * @param size + * @param interval + */ + public void cleanupParams(int size, long interval) { + timer.cancel(); + timer = new Timer(); + timer.schedule(new Cleanup(content,size), interval, interval); + } + + + + /** + * Load a file, first checking cache + * + * + * @param logTarget - logTarget can be null (won't log) + * @param dataRoot - data root storage directory + * @param key - relative File Path + * @param mediaType - what kind of file is it. If null, will check via file extension + * @param timeCheck - "-1" will take system default - Otherwise, will compare "now" + timeCheck(Millis) before looking at File mod + * @return + * @throws IOException + */ + public Content load(LogTarget logTarget, String dataRoot, String key, String mediaType, long _timeCheck) throws IOException { + long timeCheck = _timeCheck; + if(timeCheck<0) { + timeCheck=checkInterval; // if time < 0, then use default + } + boolean isRoot; + String fileName; + if("-".equals(key)) { + fileName = dataRoot; + isRoot = true; + } else { + fileName=dataRoot + '/' + key; + isRoot = false; + } + Content c = content.get(key); + long systime = System.currentTimeMillis(); + File f=null; + if(c!=null) { + // Don't check every hit... only after certain time value + if(c.date < systime + timeCheck) { + f = new File(fileName); + if(f.lastModified()>c.date) { + c=null; + } + } + } + if(c==null) { + if(logTarget!=null) { + logTarget.log("File Read: ",key); + } + + if(f==null){ + f = new File(fileName); + } + boolean cacheMe; + if(f.exists()) { + if(f.isDirectory()) { + cacheMe = false; + c = new DirectoryContent(f,isRoot); + } else { + if(f.length() > maxItemSize) { + c = new DirectFileContent(f); + cacheMe = false; + } else { + c = new CachedContent(f); + cacheMe = checkInterval>0; + } + + if(mediaType==null) { // determine from file Ending + int idx = key.lastIndexOf('.'); + String subkey = key.substring(++idx); + if((c.contentType = idx<0?null:typeMap.get(subkey))==null) { + // if nothing else, just set to default type... + c.contentType = "application/octet-stream"; + } + c.attachmentOnly = attachOnly.contains(subkey); + } else { + c.contentType=mediaType; + c.attachmentOnly = false; + } + + c.date = f.lastModified(); + + if(cacheMe) { + content.put(key, c); + } + } + } else { + c=NULL; + } + } else { + if(logTarget!=null)logTarget.log("Cache Read: ",key); + } + + // refresh hit time + c.access = systime; + return c; + } + + public Content loadOrDefault(Trans trans, String targetDir, String targetFileName, String sourcePath, String mediaType) throws IOException { + try { + return load(trans.info(),targetDir,targetFileName,mediaType,0); + } catch(FileNotFoundException e) { + String targetPath = targetDir + '/' + targetFileName; + TimeTaken tt = trans.start("File doesn't exist; copy " + sourcePath + " to " + targetPath, Env.SUB); + try { + FileInputStream sourceFIS = new FileInputStream(sourcePath); + FileChannel sourceFC = sourceFIS.getChannel(); + File targetFile = new File(targetPath); + targetFile.getParentFile().mkdirs(); // ensure directory exists + FileOutputStream targetFOS = new FileOutputStream(targetFile); + try { + ByteBuffer bb = ByteBuffer.allocate((int)sourceFC.size()); + sourceFC.read(bb); + bb.flip(); // ready for reading + targetFOS.getChannel().write(bb); + } finally { + sourceFIS.close(); + targetFOS.close(); + } + } finally { + tt.done(); + } + return load(trans.info(),targetDir,targetFileName,mediaType,0); + } + } + + public void invalidate(String key) { + content.remove(key); + } + + private static final Content NULL=new Content() { + + @Override + public void setHeader(HttpServletResponse resp) { + resp.setStatus(404/*NOT_FOUND_404*/); + resp.setHeader("Content-type","text/plain"); + } + + @Override + public void write(Writer writer) throws IOException { + } + + @Override + public void write(OutputStream os) throws IOException { + } + + }; + + private static abstract class Content { + private long date; // date of the actual artifact (i.e. File modified date) + private long access; // last accessed + + protected String contentType; + protected boolean attachmentOnly; + + public void setHeader(HttpServletResponse resp) { + resp.setStatus(200/*OK_200*/); + resp.setHeader("Content-Type",contentType); + resp.setHeader("Cache-Control", MAX_AGE); + } + + public abstract void write(Writer writer) throws IOException; + public abstract void write(OutputStream os) throws IOException; + + } + + private static class DirectFileContent extends Content { + private File file; + public DirectFileContent(File f) { + file = f; + } + + public String toString() { + return file.getName(); + } + + public void write(Writer writer) throws IOException { + FileReader fr = new FileReader(file); + char[] buff = new char[1024]; + try { + int read; + while((read = fr.read(buff,0,1024))>=0) { + writer.write(buff,0,read); + } + } finally { + fr.close(); + } + } + + public void write(OutputStream os) throws IOException { + FileInputStream fis = new FileInputStream(file); + byte[] buff = new byte[1024]; + try { + int read; + while((read = fis.read(buff,0,1024))>=0) { + os.write(buff,0,read); + } + } finally { + fis.close(); + } + } + + } + private static class DirectoryContent extends Content { + private static final Pattern A_NUMBER = Pattern.compile("\\d"); + private static final String H1 = "<html><head><title>AAF Fileserver</title></head><body><h1>AAF Fileserver</h1><h2>"; + private static final String H2 = "</h2><ul>\n"; + private static final String F = "\n</ul></body></html>"; + private File[] files; + private String name; + private boolean notRoot; + + public DirectoryContent(File directory, boolean isRoot) { + notRoot = !isRoot; + + files = directory.listFiles(); + Arrays.sort(files,new Comparator<File>() { + @Override + public int compare(File f1, File f2) { + // See if there are Numbers in the name + Matcher m1 = A_NUMBER.matcher(f1.getName()); + Matcher m2 = A_NUMBER.matcher(f2.getName()); + if(m1.find() && m2.find()) { + // if numbers, are the numbers in the same start position + int i1 = m1.start(); + int i2 = m2.start(); + + // If same start position and the text is the same, then reverse sort + if(i1==i2 && f1.getName().startsWith(f2.getName().substring(0,i1))) { + // reverse sort files that start similarly, but have numbers in them + return f2.compareTo(f1); + } + } + return f1.compareTo(f2); + } + + }); + name = directory.getName(); + attachmentOnly = false; + contentType = "text/html"; + } + + + @Override + public void write(Writer w) throws IOException { + w.append(H1); + w.append(name); + w.append(H2); + for (File f : files) { + w.append("<li><a href=\""); + if(notRoot) { + w.append(name); + w.append('/'); + } + w.append(f.getName()); + w.append("\">"); + w.append(f.getName()); + w.append("</a></li>\n"); + } + w.append(F); + w.flush(); + } + + @Override + public void write(OutputStream os) throws IOException { + write(new OutputStreamWriter(os)); + } + + } + + private static class CachedContent extends Content { + private byte[] data; + private int end; + private char[] cdata; + + public CachedContent(File f) throws IOException { + // Read and Cache + ByteBuffer bb = ByteBuffer.allocate((int)f.length()); + FileInputStream fis = new FileInputStream(f); + try { + fis.getChannel().read(bb); + } finally { + fis.close(); + } + + data = bb.array(); + end = bb.position(); + cdata=null; + } + + public String toString() { + return data.toString(); + } + + public void write(Writer writer) throws IOException { + synchronized(this) { + // do the String Transformation once, and only if actually used + if(cdata==null) { + cdata = new char[end]; + new String(data).getChars(0, end, cdata, 0); + } + } + writer.write(cdata,0,end); + } + public void write(OutputStream os) throws IOException { + os.write(data,0,end); + } + + } + + public void setEnv(LogTarget env) { + logT = env; + } + + /** + * Cleanup thread to remove older items if max Cache is reached. + * @author Jonathan + * + */ + private static class Cleanup extends TimerTask { + private int maxSize; + private NavigableMap<String, Content> content; + + public Cleanup(NavigableMap<String, Content> content, int size) { + maxSize = size; + this.content = content; + } + + private class Comp implements Comparable<Comp> { + public Map.Entry<String, Content> entry; + + public Comp(Map.Entry<String, Content> en) { + entry = en; + } + + @Override + public int compareTo(Comp o) { + return (int)(entry.getValue().access-o.entry.getValue().access); + } + + } + @SuppressWarnings("unchecked") + @Override + public void run() { + int size = content.size(); + if(size>maxSize) { + ArrayList<Comp> scont = new ArrayList<Comp>(size); + Object[] entries = content.entrySet().toArray(); + for(int i=0;i<size;++i) { + scont.add(i, new Comp((Map.Entry<String,Content>)entries[i])); + } + Collections.sort(scont); + int end = size - ((maxSize/4)*3); // reduce to 3/4 of max size + System.out.println("------ Cleanup Cycle ------ " + new Date().toString() + " -------"); + for(int i=0;i<end;++i) { + Entry<String, Content> entry = scont.get(i).entry; + content.remove(entry.getKey()); + System.out.println("removed Cache Item " + entry.getKey() + "/" + new Date(entry.getValue().access).toString()); + } + for(int i=end;i<size;++i) { + Entry<String, Content> entry = scont.get(i).entry; + System.out.println("remaining Cache Item " + entry.getKey() + "/" + new Date(entry.getValue().access).toString()); + } + } + } + } +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/CodeSetter.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/CodeSetter.java new file mode 100644 index 00000000..6ea8880b --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/CodeSetter.java @@ -0,0 +1,52 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.rserv; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.misc.env.Trans; + +// Package on purpose. only want between RServlet and Routes +class CodeSetter<TRANS extends Trans> { + private HttpCode<TRANS,?> code; + private TRANS trans; + private HttpServletRequest req; + private HttpServletResponse resp; + public CodeSetter(TRANS trans, HttpServletRequest req, HttpServletResponse resp) { + this.trans = trans; + this.req = req; + this.resp = resp; + + } + public boolean matches(Route<TRANS> route) throws IOException, ServletException { + // Find best Code in Route based on "Accepts (Get) or Content-Type" (if exists) + return (code = route.getCode(trans, req, resp))!=null; + } + + public HttpCode<TRANS,?> code() { + return code; + } +}
\ No newline at end of file diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Content.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Content.java new file mode 100644 index 00000000..ae329ce2 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Content.java @@ -0,0 +1,115 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.rserv; + +import java.util.List; + +import org.onap.aaf.misc.env.Trans; + + + +/** + * A Class to hold Service "ContentTypes", and to match incoming "Accept" types from HTTP. + * + * This is a multi-use class built to use the same Parser for ContentTypes and Accept. + * + * Thus, you would create and use "Content.Type" within your service, and use it to match + * Accept Strings. What is returned is an Integer (for faster processing), which can be + * used in a switch statement to act on match different Actions. The server should + * know which behaviors match. + * + * "bestMatch" returns an integer for the best match, or -1 if no matches. + * + * @author Jonathan + * + */ +public abstract class Content<TRANS extends Trans> { + public static final String Q = "q"; + protected abstract Pair<String,Pair<HttpCode<TRANS,?>,List<Pair<String,Object>>>> types(HttpCode<TRANS,?> code, String str); + protected abstract boolean props(Pair<String, Pair<HttpCode<TRANS,?>,List<Pair<String,Object>>>> type, String tag, String value); + + /** + * Parse a Content-Type/Accept. As found, call "types" and "props", which do different + * things depending on if it's a Content-Type or Accepts. + * + * For Content-Type, it builds a tree suitable for Comparison + * For Accepts, it compares against the tree, and builds an acceptable type list + * + * Since this parse code is used for every incoming HTTP transaction, I have removed the implementation + * that uses String.split, and replaced with integers evaluating the Byte array. This results + * in only the necessary strings created, resulting in 1/3 better speed, and less + * Garbage collection. + * + * @param trans + * @param code + * @param cntnt + * @return + */ + protected boolean parse(HttpCode<TRANS,?> code, String cntnt) { + byte bytes[] = cntnt.getBytes(); + boolean contType=false,contProp=true; + int cis,cie=-1,cend; + int sis,sie,send; + do { + cis = cie+1; + cie = cntnt.indexOf(',',cis); + cend = cie<0?bytes.length:cie; + // Start SEMIS + sie=cis-1; + Pair<String, Pair<HttpCode<TRANS,?>, List<Pair<String, Object>>>> me = null; + do { + sis = sie+1; + sie = cntnt.indexOf(';',sis); + send = sie>cend || sie<0?cend:sie; + if(me==null) { + String semi = new String(bytes,sis,send-sis); + // trans.checkpoint(semi); + // Look at first entity within comma group + // Is this an acceptable Type? + me=types(code, semi); + if(me==null) { + sie=-1; // skip the rest of the processing... not a type + } else { + contType=true; + } + } else { // We've looped past the first Semi, now process as properties + // If there are additional elements (more entities within Semi Colons) + // apply Propertys + int eq = cntnt.indexOf('=',sis); + if(eq>sis && eq<send) { + String tag = new String(bytes,sis,eq-sis); + String value = new String(bytes,eq+1,send-(eq+1)); + // trans.checkpoint(" Prop " + tag + "=" + value); + boolean bool = props(me,tag,value); + if(!bool) { + contProp=false; + } + } + } + // End Property + } while(sie<=cend && sie>=cis); + // End SEMIS + } while(cie>=0); + return contType && contProp; // for use in finds, True if a type found AND all props matched + } + +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/HttpCode.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/HttpCode.java new file mode 100644 index 00000000..0bfe310a --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/HttpCode.java @@ -0,0 +1,118 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.rserv; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.misc.env.Trans; + +/** + * HTTP Code element, which responds to the essential "handle Method". + * + * Use Native HttpServletRe[quest|sponse] calls for questions like QueryParameters (getParameter, etc) + * + * Use local "pathParam" method to obtain in an optimized manner the path parameter, which must be interpreted by originating string + * + * i.e. my/path/:id/:other/* + * + * @author Jonathan + * + * @param <TRANS> + * @param <T> + */ +public abstract class HttpCode<TRANS extends Trans, CONTEXT> { + protected CONTEXT context; + private String desc; + protected String [] roles; + private boolean all; + + // Package by design... Set by Route when linked + Match match; + + public HttpCode(CONTEXT context, String description, String ... roles) { + this.context = context; + desc = description; + + // Evaluate for "*" once... + all = false; + for(String srole : roles) { + if("*".equals(srole)) { + all = true; + break; + } + } + this.roles = all?null:roles; + } + + public abstract void handle(TRANS trans, HttpServletRequest req, HttpServletResponse resp) throws Exception; + + public String desc() { + return desc; + } + + /** + * Get the variable element out of the Path Parameter, as set by initial Code + * + * @param req + * @param key + * @return + */ + public String pathParam(HttpServletRequest req, String key) { + String rv = match.param(req.getPathInfo(), key); + if(rv!=null) { + rv = rv.trim(); + if(rv.endsWith("/")) { + rv = rv.substring(0, rv.length()-1); + } + } + return rv; + } + + // Note: get Query Params from Request + + /** + * Check for Authorization when set. + * + * If no Roles set, then accepts all users + * + * @param req + * @return + */ + public boolean isAuthorized(HttpServletRequest req) { + if(all)return true; + if(roles!=null) { + for(String srole : roles) { + if(req.isUserInRole(srole)) return true; + } + } + return false; + } + + public boolean no_cache() { + return false; + } + + public String toString() { + return desc; + } +}
\ No newline at end of file diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/HttpMethods.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/HttpMethods.java new file mode 100644 index 00000000..4dbaf17b --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/HttpMethods.java @@ -0,0 +1,29 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.rserv; + +public enum HttpMethods { + POST, + GET, + PUT, + DELETE +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Match.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Match.java new file mode 100644 index 00000000..ac8b31c1 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Match.java @@ -0,0 +1,211 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.rserv; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * This path matching algorithm avoids using split strings during the critical transactional run-time. By pre-analyzing the + * content at "set Param" time, and storing data in an array-index model which presumably is done once and at the beginning, + * we can match in much less time when it actually counts. + * + * @author Jonathan + * + */ +public class Match { + private Map<String, Integer> params; + private byte[] values[]; + private Integer vars[]; + private boolean wildcard; + + + /* + * These two methods are pairs of searching performance for variables Spark Style. + * setParams evaluates the target path, and sets a HashMap that will return an Integer. + * the Keys are both :key and key so that there will be no string operations during + * a transaction + * + * For the Integer, if the High Order is 0, then it is just one value. If High Order >0, then it is + * a multi-field option, i.e. ending with a wild-card. + */ + public Match(String path) { + // IF DEBUG: System.out.print("\n[" + path + "]"); + params = new HashMap<String,Integer>(); + if(path!=null) { + String[] pa = path.split("/"); + values = new byte[pa.length][]; + vars = new Integer[pa.length]; + + int val = 0; + String key; + for(int i=0;i<pa.length && !wildcard;++i) { + if(pa[i].startsWith(":")) { + if(pa[i].endsWith("*")) { + val = i | pa.length<<16; // load end value in high order bits + key = pa[i].substring(0, pa[i].length()-1);// remove * + wildcard = true; + } else { + val = i; + key = pa[i]; + } + params.put(key,val); //put in :key + params.put(key.substring(1,key.length()), val); // put in just key, better than adding a missing one, like Spark + // values[i]=null; // null stands for Variable + vars[i]=val; + } else { + values[i]=pa[i].getBytes(); + if(pa[i].endsWith("*")) { + wildcard = true; + if(pa[i].length()>1) { + /* remove * from value */ + int newlength = values[i].length-1; + byte[] real = new byte[newlength]; + System.arraycopy(values[i],0,real,0,newlength); + values[i]=real; + } else { + vars[i]=0; // this is actually a variable, if it only contains a "*" + } + } + // vars[i]=null; + } + } + } + } + + /* + * This is the second of the param evaluation functions. First, we look up to see if there is + * any reference by key in the params Map created by the above. + * + * The resulting Integer, if not null, is split high/low order into start and end. + * We evaluate the string for '/', rather than splitting into String[] to avoid the time/mem needed + * We traverse to the proper field number for slash, evaluate the end (whether wild card or no), + * and return the substring. + * + * The result is something less than .003 milliseconds per evaluation + * + */ + public String param(String path,String key) { + Integer val = params.get(key); // :key or key + if(val!=null) { + int start = val & 0xFFFF; + int end = (val >> 16) & 0xFFFF; + int idx = -1; + int i; + for(i=0;i<start;++i) { + idx = path.indexOf('/',idx+1); + if(idx<0)break; + } + if(i==start) { + ++idx; + if(end==0) { + end = path.indexOf('/',idx); + if(end<0)end=path.length(); + } else { + end=path.length(); + } + return path.substring(idx,end); + } else if(i==start-1) { // if last spot was left blank, i.e. :key* + return ""; + } + } + return null; + } + + public boolean match(String path) { + if(path==null|| path.length()==0 || "/".equals(path) ) { + if(values==null)return true; + switch(values.length) { + case 0: return true; + case 1: return values[0].length==0; + default: return false; + } + } + boolean rv = true; + byte[] pabytes = path.getBytes(); + int field=0; + int fieldIdx = 0; + + int lastField = values.length; + int lastByte = pabytes.length; + boolean fieldMatched = false; // = lastByte>0?(pabytes[0]=='/'):false; + // IF DEBUG: System.out.println("\n -- " + path + " --"); + for(int i=0;rv && i<lastByte;++i) { + if(field>=lastField) { // checking here allows there to be a non-functional ending / + rv = false; + break; + } + if(values[field]==null) { // it's a variable, just look for /s + if(wildcard && field==lastField-1) return true;// we've made it this far. We accept all remaining characters + Integer val = vars[field]; + int start = val & 0xFFFF; + int end = (val >> 16) & 0xFFFF; + if(end==0)end=start+1; + int k = i; + for(int j=start; j<end && k<lastByte; ++k) { + // IF DEBUG: System.out.print((char)pabytes[k]); + if(pabytes[k]=='/') { + ++field; + ++j; + } + } + + if(k==lastByte && pabytes[k-1]!='/')++field; + if(k>i)i=k-1; // if we've incremented, have to accommodate the outer for loop incrementing as well + fieldMatched = false; // reset + fieldIdx = 0; + } else { + // IF DEBUG: System.out.print((char)pabytes[i]); + if(pabytes[i]=='/') { // end of field, eval if Field is matched + // if double slash, check if supposed to be empty + if(fieldIdx==0 && values[field].length==0) { + fieldMatched = true; + } + rv = fieldMatched && ++field<lastField; + // reset + fieldMatched = false; + fieldIdx = 0; + } else if(values[field].length==0) { + // double slash in path, but content in field. We check specially here to avoid + // Array out of bounds issues. + rv = false; + } else { + if(fieldMatched) { + rv =false; // field is already matched, now there's too many bytes + } else { + rv = pabytes[i]==values[field][fieldIdx++]; // compare expected (pabytes[i]) with value for particular field + fieldMatched=values[field].length==fieldIdx; // are all the bytes match in the field? + if(fieldMatched && (i==lastByte-1 || (wildcard && field==lastField-1))) + return true; // last field info + } + } + } + } + if(field!=lastField || pabytes.length!=lastByte) rv = false; // have we matched all the fields and all the bytes? + return rv; + } + + public Set<String> getParamNames() { + return params.keySet(); + } +}
\ No newline at end of file diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Pair.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Pair.java new file mode 100644 index 00000000..810f9129 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Pair.java @@ -0,0 +1,44 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.rserv; + +/** + * A pair of generic Objects. + * + * @author Jonathan + * + * @param <X> + * @param <Y> + */ +public class Pair<X,Y> { + public X x; + public Y y; + + public Pair(X x, Y y) { + this.x = x; + this.y = y; + } + + public String toString() { + return "X: " + x.toString() + "-->" + y.toString(); + } +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/RServlet.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/RServlet.java new file mode 100644 index 00000000..4ae0f882 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/RServlet.java @@ -0,0 +1,154 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.rserv; + +import java.io.IOException; +import java.util.List; + +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.misc.env.Env; +import org.onap.aaf.misc.env.TimeTaken; +import org.onap.aaf.misc.env.Trans; + +public abstract class RServlet<TRANS extends Trans> implements Servlet { + private Routes<TRANS> routes = new Routes<TRANS>(); + + private ServletConfig config; + + @Override + public void init(ServletConfig config) throws ServletException { + this.config = config; + } + + @Override + public ServletConfig getServletConfig() { + return config; + } + + public void route(Env env, HttpMethods meth, String path, HttpCode<TRANS, ?> code, String ... moreTypes) { + Route<TRANS> r = routes.findOrCreate(meth,path); + r.add(code,moreTypes); + env.init().log(r.report(code),code); + } + + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { + HttpServletRequest request = (HttpServletRequest)req; + HttpServletResponse response = (HttpServletResponse)res; + + @SuppressWarnings("unchecked") + TRANS trans = (TRANS)req.getAttribute(TransFilter.TRANS_TAG); + if(trans==null) { + response.setStatus(404); // Not Found, because it didn't go through TransFilter + return; + } + + Route<TRANS> route; + HttpCode<TRANS,?> code=null; + String ct = req.getContentType(); + TimeTaken tt = trans.start("Resolve to Code", Env.SUB); + try { + // routes have multiple code sets. This object picks the best code set + // based on Accept or Content-Type + CodeSetter<TRANS> codesetter = new CodeSetter<TRANS>(trans,request,response); + // Find declared route + route = routes.derive(request, codesetter); + if(route==null) { + String method = request.getMethod(); + trans.checkpoint("No Route matches "+ method + ' ' + request.getPathInfo()); + response.setStatus(404); // Not Found + } else { + // Find best Code in Route based on "Accepts (Get) or Content-Type" (if exists) + code = codesetter.code();// route.getCode(trans, request, response); + } + } finally { + tt.done(); + } + + if(route!=null && code!=null) { + StringBuilder sb = new StringBuilder(72); + sb.append(route.auditText); + sb.append(','); + sb.append(code.desc()); + if(ct!=null) { + sb.append(", ContentType: "); + sb.append(ct); + } + tt = trans.start(sb.toString(),Env.SUB); + try { + /*obj = */ + code.handle(trans, request, response); + response.flushBuffer(); + } catch (ServletException e) { + trans.error().log(e); + throw e; + } catch (Exception e) { + trans.error().log(e,request.getMethod(),request.getPathInfo()); + throw new ServletException(e); + } finally { + tt.done(); + } + } + } + + @Override + public String getServletInfo() { + return "RServlet for Jetty"; + } + + @Override + public void destroy() { + } + + public String applicationJSON(Class<?> cls, String version) { + StringBuilder sb = new StringBuilder(); + sb.append("application/"); + sb.append(cls.getSimpleName()); + sb.append("+json"); + sb.append(";charset=utf-8"); + sb.append(";version="); + sb.append(version); + return sb.toString(); + } + + public String applicationXML(Class<?> cls, String version) { + StringBuilder sb = new StringBuilder(); + sb.append("application/"); + sb.append(cls.getSimpleName()); + sb.append("+xml"); + sb.append(";charset=utf-8"); + sb.append(";version="); + sb.append(version); + return sb.toString(); + } + + public List<RouteReport> routeReport() { + return routes.routeReport(); + } +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Route.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Route.java new file mode 100644 index 00000000..9ae202a2 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Route.java @@ -0,0 +1,141 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.rserv; + +import java.io.IOException; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.misc.env.Env; +import org.onap.aaf.misc.env.TimeTaken; +import org.onap.aaf.misc.env.Trans; + +public class Route<TRANS extends Trans> { + public final String auditText; + public final HttpMethods meth; + public final String path; + + private Match match; + // package on purpose + private final TypedCode<TRANS> content; + private final boolean isGet; + + public Route(HttpMethods meth, String path) { + this.path = path; + auditText = meth.name() + ' ' + path; + this.meth = meth; // Note: Using Spark def for now. + isGet = meth.compareTo(HttpMethods.GET) == 0; + match = new Match(path); + content = new TypedCode<TRANS>(); + } + + public void add(HttpCode<TRANS,?> code, String ... others) { + code.match = match; + content.add(code, others); + } + +// public void add(HttpCode<TRANS,?> code, Class<?> cls, String version, String ... others) { +// code.match = match; +// content.add(code, cls, version, others); +// } +// + public HttpCode<TRANS,?> getCode(TRANS trans, HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { + // Type is associated with Accept for GET (since it is what is being returned + // We associate the rest with ContentType. + // FYI, thought about this a long time before implementing this way. + String compare; +// String special[]; // todo, expose Charset (in special) to outside + if(isGet) { + compare = req.getHeader("Accept"); // Accept is used for read, as we want to agree on what caller is ready to handle + } else { + compare = req.getContentType(); // Content type used to declare what data is being created, updated or deleted (might be used for key) + } + + Pair<String, Pair<HttpCode<TRANS, ?>, List<Pair<String, Object>>>> hl = content.prep(trans, compare); + if(hl==null) { + resp.setStatus(406); // NOT_ACCEPTABLE + } else { + if(isGet) { // Set Content Type to expected content + if("*".equals(hl.x) || "*/*".equals(hl.x)) {// if wild-card, then choose first kind of type + resp.setContentType(content.first()); + } else { + resp.setContentType(hl.x); + } + } + return hl.y.x; + } + return null; + } + + public Route<TRANS> matches(String method, String path) { + return meth.name().equalsIgnoreCase(method) && match.match(path)?this:null; + } + + public TimeTaken start(Trans trans, String auditText, HttpCode<TRANS,?> code, String type) { + StringBuilder sb = new StringBuilder(auditText); + sb.append(", "); + sb.append(code.desc()); + sb.append(", Content: "); + sb.append(type); + return trans.start(sb.toString(), Env.SUB); + } + + // Package on purpose.. for "find/Create" routes only + boolean resolvesTo(HttpMethods hm, String p) { + return(path.equals(p) && hm.equals(meth)); + } + + public String toString() { + return auditText + ' ' + content; + } + + public String report(HttpCode<TRANS, ?> code) { + StringBuilder sb = new StringBuilder(); + sb.append(auditText); + sb.append(' '); + content.relatedTo(code, sb); + return sb.toString(); + } + + public RouteReport api() { + RouteReport tr = new RouteReport(); + tr.meth = meth; + tr.path = path; + content.api(tr); + return tr; + } + + + /** + * contentRelatedTo (For reporting) list routes that will end up at a specific Code + * @return + */ + public String contentRelatedTo(HttpCode<TRANS, ?> code) { + StringBuilder sb = new StringBuilder(path); + sb.append(' '); + content.relatedTo(code, sb); + return sb.toString(); + } +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/RouteReport.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/RouteReport.java new file mode 100644 index 00000000..5de2ebe3 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/RouteReport.java @@ -0,0 +1,33 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.rserv; + +import java.util.ArrayList; +import java.util.List; + +public class RouteReport { + public HttpMethods meth; + public String path; + public String desc; + public final List<String> contextTypes = new ArrayList<String>(); + +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Routes.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Routes.java new file mode 100644 index 00000000..fefb8f3c --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Routes.java @@ -0,0 +1,89 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.rserv; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + +import org.onap.aaf.misc.env.Trans; + + +public class Routes<TRANS extends Trans> { + // Since this must be very, very fast, and only needs one creation, we'll use just an array. + private Route<TRANS>[] routes; + private int end; + + + @SuppressWarnings("unchecked") + public Routes() { + routes = new Route[10]; + end = 0; + } + + // This method for setup of Routes only... + // Package on purpose + synchronized Route<TRANS> findOrCreate(HttpMethods meth, String path) { + Route<TRANS> rv = null; + for(int i=0;i<end;++i) { + if(routes[i].resolvesTo(meth,path))rv = routes[i]; + } + + if(rv==null) { + if(end>=routes.length) { + @SuppressWarnings("unchecked") + Route<TRANS>[] temp = new Route[end+10]; + System.arraycopy(routes, 0, temp, 0, routes.length); + routes = temp; + } + + routes[end++]=rv=new Route<TRANS>(meth,path); + } + return rv; + } + + public Route<TRANS> derive(HttpServletRequest req, CodeSetter<TRANS> codeSetter) throws IOException, ServletException { + Route<TRANS> rv = null; + String path = req.getPathInfo(); + String meth = req.getMethod(); + //TODO a TREE would be better + for(int i=0;rv==null && i<end; ++i) { + rv = routes[i].matches(meth,path); + if(rv!=null && !codeSetter.matches(rv)) { // potential match, check if has Code + rv = null; // not quite, keep going + } + } + //TODO a Default? + return rv; + } + + public List<RouteReport> routeReport() { + ArrayList<RouteReport> ltr = new ArrayList<RouteReport>(); + for(int i=0;i<end;++i) { + ltr.add(routes[i].api()); + } + return ltr; + } +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/TransFilter.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/TransFilter.java new file mode 100644 index 00000000..1011767a --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/TransFilter.java @@ -0,0 +1,156 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.rserv; + +import java.io.IOException; +import java.security.Principal; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.CadiWrap; +import org.onap.aaf.cadi.Connector; +import org.onap.aaf.cadi.Lur; +import org.onap.aaf.cadi.TrustChecker; +import org.onap.aaf.cadi.config.Config; +import org.onap.aaf.cadi.filter.CadiHTTPManip; +import org.onap.aaf.cadi.taf.TafResp; +import org.onap.aaf.cadi.taf.TafResp.RESP; +import org.onap.aaf.misc.env.Env; +import org.onap.aaf.misc.env.TimeTaken; +import org.onap.aaf.misc.env.TransStore; +import org.onap.aaf.misc.env.util.Split; + +/** + * Create a new Transaction Object for each and every incoming Transaction + * + * Attach to Request. User "FilterHolder" mechanism to retain single instance. + * + * TransFilter includes CADIFilter as part of the package, so that it can + * set User Data, etc, as necessary. + * + * @author Jonathan + * + */ +public abstract class TransFilter<TRANS extends TransStore> implements Filter { + public static final String TRANS_TAG = "__TRANS__"; + + private CadiHTTPManip cadi; + + private final String[] no_authn; + + public TransFilter(Access access, Connector con, TrustChecker tc, Object ... additionalTafLurs) throws CadiException { + cadi = new CadiHTTPManip(access, con, tc, additionalTafLurs); + String no = access.getProperty(Config.CADI_NOAUTHN, null); + if(no!=null) { + no_authn = Split.split(':', no); + } else { + no_authn=null; + } + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + protected Lur getLur() { + return cadi.getLur(); + } + + protected abstract TRANS newTrans(); + protected abstract TimeTaken start(TRANS trans, ServletRequest request); + protected abstract void authenticated(TRANS trans, Principal p); + protected abstract void tallyHo(TRANS trans); + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + TRANS trans = newTrans(); + + TimeTaken overall = start(trans,request); + try { + request.setAttribute(TRANS_TAG, trans); + + HttpServletRequest req = (HttpServletRequest)request; + HttpServletResponse res = (HttpServletResponse)response; + + if(no_authn!=null) { + for(String prefix : no_authn) { + if(req.getPathInfo().startsWith(prefix)) { + chain.doFilter(request, response); + return; + } + } + } + + TimeTaken security = trans.start("CADI Security", Env.SUB); + TafResp resp; + RESP r; + CadiWrap cw = null; + try { + resp = cadi.validate(req,res,trans); + switch(r=resp.isAuthenticated()) { + case IS_AUTHENTICATED: + cw = new CadiWrap(req,resp,cadi.getLur()); + authenticated(trans, cw.getUserPrincipal()); + break; + default: + break; + } + } finally { + security.done(); + } + + if(r==RESP.IS_AUTHENTICATED) { + trans.checkpoint(resp.desc()); + if(cadi.notCadi(cw, res)) { + chain.doFilter(cw, response); + } + } else { + //TODO this is a good place to check if too many checks recently + // Would need Cached Counter objects that are cleaned up on + // use + trans.checkpoint(resp.desc(),Env.ALWAYS); + if(resp.isFailedAttempt()) + trans.audit().log(resp.desc()); + } + } catch(Exception e) { + trans.error().log(e); + trans.checkpoint("Error: " + e.getClass().getSimpleName() + ": " + e.getMessage()); + throw new ServletException(e); + } finally { + overall.done(); + tallyHo(trans); + } + } + + @Override + public void destroy() { + }; +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/TransOnlyFilter.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/TransOnlyFilter.java new file mode 100644 index 00000000..e0f7512d --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/TransOnlyFilter.java @@ -0,0 +1,77 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.rserv; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.onap.aaf.cadi.principal.TaggedPrincipal; +import org.onap.aaf.misc.env.TimeTaken; +import org.onap.aaf.misc.env.TransStore; + +/** + * Create a new Transaction Object for each and every incoming Transaction + * + * Attach to Request. User "FilterHolder" mechanism to retain single instance. + * + * TransFilter includes CADIFilter as part of the package, so that it can + * set User Data, etc, as necessary. + * + * @author Jonathan + * + */ +public abstract class TransOnlyFilter<TRANS extends TransStore> implements Filter { + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + + + protected abstract TRANS newTrans(); + protected abstract TimeTaken start(TRANS trans, ServletRequest request); + protected abstract void authenticated(TRANS trans, TaggedPrincipal p); + protected abstract void tallyHo(TRANS trans); + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + TRANS trans = newTrans(); + + TimeTaken overall = start(trans,request); + try { + request.setAttribute(TransFilter.TRANS_TAG, trans); + chain.doFilter(request, response); + } finally { + overall.done(); + } + tallyHo(trans); + } + + @Override + public void destroy() { + }; +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/TypedCode.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/TypedCode.java new file mode 100644 index 00000000..82b291c7 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/TypedCode.java @@ -0,0 +1,269 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.rserv; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import javax.servlet.ServletException; + +import org.onap.aaf.misc.env.Env; +import org.onap.aaf.misc.env.TimeTaken; +import org.onap.aaf.misc.env.Trans; + + +/** + * TypedCode organizes implementation code based on the Type and Version of code it works with so that it can + * be located quickly at runtime based on the "Accept" HTTP Header. + * + * FYI: For those in the future wondering why I would create a specialized set of "Pair" for the data content: + * 1) TypeCode is used in Route, and this code is used for every transaction... it needs to be blazingly fast + * 2) The actual number of objects accessed is quite small and built at startup. Arrays are best + * 3) I needed a small, well defined tree where each level is a different Type. Using a "Pair" Generic definitions, + * I created type-safety at each level, which you can't get from a TreeSet, etc. + * 4) Chaining through the Network is simply object dereferencing, which is as fast as Java can go. + * 5) The drawback is that in your code is that all the variables are named "x" and "y", which can be a bit hard to + * read both in code, and in the debugger. However, TypeSafety allows your IDE (Eclipse) to help you make the + * choices. Also, make sure you have a good "toString()" method on each object so you can see what's happening + * in the IDE Debugger. + * + * Empirically, this method of obtaining routes proved to be much faster than the HashSet implementations available in otherwise + * competent Open Source. + * + * @author Jonathan + * + * @param <TRANS> + */ +public class TypedCode<TRANS extends Trans> extends Content<TRANS> { + private List<Pair<String, Pair<HttpCode<TRANS,?>,List<Pair<String, Object>>>>> types; + + public TypedCode() { + types = new ArrayList<Pair<String,Pair<HttpCode<TRANS,?>,List<Pair<String,Object>>>>>(); + } + + /** + * Construct Typed Code based on ContentType parameters passed in + * + * @param code + * @param others + * @return + */ + public TypedCode<TRANS> add(HttpCode<TRANS,?> code, String ... others) { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for(String str : others) { + if(first) { + first = false; + } else { + sb.append(','); + } + sb.append(str); + } + parse(code, sb.toString()); + + return this; + } + + @Override + protected Pair<String, Pair<HttpCode<TRANS,?>, List<Pair<String, Object>>>> types(HttpCode<TRANS,?> code, String str) { + Pair<String, Pair<HttpCode<TRANS,?>,List<Pair<String, Object>>>> type = null; + ArrayList<Pair<String, Object>> props = new ArrayList<Pair<String,Object>>(); + // Want Q percentage is to be first in the array everytime. If not listed, 1.0 is default + props.add(new Pair<String,Object>(Q,1f)); + Pair<HttpCode<TRANS,?>, List<Pair<String,Object>>> cl = new Pair<HttpCode<TRANS,?>, List<Pair<String,Object>>>(code, props); +// // breakup "plus" stuff, i.e. application/xaml+xml +// int plus = str.indexOf('+'); +// if(plus<0) { + type = new Pair<String, Pair<HttpCode<TRANS,?>,List<Pair<String,Object>>>>(str, cl); + types.add(type); + return type; +// } else { +// int prev = str.indexOf('/')+1; +// String first = str.substring(0,prev); +// String nstr; +// while(prev!=0) { +// nstr = first + (plus>-1?str.substring(prev,plus):str.substring(prev)); +// type = new Pair<String, Pair<HttpCode<TRANS,?>,List<Pair<String,Object>>>>(nstr, cl); +// types.add(type); +// prev = plus+1; +// plus = str.indexOf('+',prev); +// } +// return type; +// } + } + + @Override + protected boolean props(Pair<String, Pair<HttpCode<TRANS,?>, List<Pair<String, Object>>>> type, String tag, String value) { + if(tag.equals(Q)) { // reset the Q value (first in array) + boolean rv = true; + try { + type.y.y.get(0).y=Float.parseFloat(value); + return rv; + } catch (NumberFormatException e) { + rv=false; // Note: this awkward syntax forced by Sonar, which doesn't like doing nothing with Exception + // which is what should happen + } + } + return type.y.y.add(new Pair<String,Object>(tag,"version".equals(tag)?new Version(value):value)); + } + + public Pair<String, Pair<HttpCode<TRANS, ?>, List<Pair<String, Object>>>> prep(TRANS trans, String compare) throws IOException, ServletException { + Pair<String, Pair<HttpCode<TRANS,?>, List<Pair<String, Object>>>> c,rv=null; + if(types.size()==1 && "".equals((c=types.get(0)).x)) { // if there are no checks for type, skip + rv = c; + } else { + if(compare==null || compare.length()==0) { + rv = types.get(0); // first code is used + } else { + Acceptor<TRANS> acc = new Acceptor<TRANS>(types); + boolean accepted; + TimeTaken tt = trans.start(compare, Env.SUB); + try { + accepted = acc.parse(null, compare); + } finally { + tt.done(); + } + if(accepted) { + switch(acc.acceptable.size()) { + case 0: +// // TODO best Status Code? +// resp.setStatus(HttpStatus.NOT_ACCEPTABLE_406); + break; + case 1: + rv = acc.acceptable.get(0); + break; + default: // compare Q values to get Best Match + float bestQ = -1.0f; + Pair<String, Pair<HttpCode<TRANS,?>, List<Pair<String, Object>>>> bestT = null; + for(Pair<String, Pair<HttpCode<TRANS,?>, List<Pair<String, Object>>>> type : acc.acceptable) { + Float f = (Float)type.y.y.get(0).y; // first property is always Q + if(f>bestQ) { + bestQ=f; + bestT = type; + } + } + if(bestT!=null) { + // When it is a GET, the matched type is what is returned, so set ContentType +// if(isGet)resp.setContentType(bestT.x); // set ContentType of Code<TRANS,?> +// rv = bestT.y.x; + rv = bestT; + } + } + } else { + trans.checkpoint("No Match found for Accept"); + } + } + } + return rv; + } + + /** + * Print on String Builder content related to specific Code + * + * This is for Reporting and Debugging purposes, so the content is not cached. + * + * If code is "null", then all content is matched + * + * @param code + * @return + */ + public StringBuilder relatedTo(HttpCode<TRANS, ?> code, StringBuilder sb) { + boolean first = true; + for(Pair<String, Pair<HttpCode<TRANS, ?>, List<Pair<String, Object>>>> pair : types) { + if(code==null || pair.y.x == code) { + if(first) { + first = false; + } else { + sb.append(','); + } + sb.append(pair.x); + for(Pair<String,Object> prop : pair.y.y) { + // Don't print "Q". it's there for internal use, but it is only meaningful for "Accepts" + if(!prop.x.equals(Q) || !prop.y.equals(1f) ) { + sb.append(';'); + sb.append(prop.x); + sb.append('='); + sb.append(prop.y); + } + } + } + } + return sb; + } + + public List<Pair<String, Object>> getContent(HttpCode<TRANS,?> code) { + for(Pair<String, Pair<HttpCode<TRANS, ?>, List<Pair<String, Object>>>> pair : types) { + if(pair.y.x == code) { + return pair.y.y; + } + } + return null; + } + + public String toString() { + return relatedTo(null,new StringBuilder()).toString(); + } + + public void api(RouteReport tr) { + // Need to build up a map, because Prop entries can be in several places. + HashMap<HttpCode<?,?>,StringBuilder> psb = new HashMap<HttpCode<?,?>,StringBuilder>(); + StringBuilder temp; + tr.desc = null; + + // Read through Code/TypeCode trees for all accepted Typecodes + for(Pair<String, Pair<HttpCode<TRANS, ?>, List<Pair<String, Object>>>> tc : types) { + // If new, then it's new Code set, create prefix content + if((temp=psb.get(tc.y.x))==null) { + psb.put(tc.y.x,temp=new StringBuilder()); + if(tr.desc==null) { + tr.desc = tc.y.x.desc(); + } + } else { + temp.append(','); + } + temp.append(tc.x); + + // add all properties + for(Pair<String, Object> props : tc.y.y) { + temp.append(';'); + temp.append(props.x); + temp.append('='); + temp.append(props.y); + } + } + // Gather all ContentType possibilities for the same code together + + for(StringBuilder sb : psb.values()) { + tr.contextTypes.add(sb.toString()); + } + } + + public String first() { + if(types.size()>0) { + return types.get(0).x; + } + return null; + } + + }
\ No newline at end of file diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Version.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Version.java new file mode 100644 index 00000000..ce0981fe --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/Version.java @@ -0,0 +1,93 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.rserv; + + +/** + * Analyze and hold Version information for Code + * + * @author Jonathan + * + */ +public class Version { + private Object[] parts; + + public Version(String v) { + String sparts[] = v.split("\\."); + parts = new Object[sparts.length]; + System.arraycopy(sparts, 0, parts, 0, sparts.length); + if(parts.length>1) { // has at least a minor + try { + parts[1]=Integer.decode(sparts[1]); // minor elements need to be converted to Integer for comparison + } catch (NumberFormatException e) { + // it's ok, leave it as a string + parts[1]=sparts[1]; // This useless piece of code forced by Sonar which calls empty Exceptions "Blockers". + } + } + } + + public boolean equals(Object obj) { + if(obj instanceof Version) { + Version ver = (Version)obj; + int length = Math.min(parts.length, ver.parts.length); + for(int i=0;i<length;++i) { // match on declared parts + if(i==1) { + if(parts[1] instanceof Integer && ver.parts[1] instanceof Integer) { + // Match on Minor version if this Version is less than Version to be checked + if(((Integer)parts[1])<((Integer)ver.parts[1])) { + return false; + } + continue; // don't match next line + } + } + if(!parts[i].equals(ver.parts[i])) { + return false; // other spots exact match + } + } + return true; + } + return false; + } + + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return super.hashCode(); + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for(Object obj : parts) { + if(first) { + first = false; + } else { + sb.append('.'); + } + sb.append(obj.toString()); + } + return sb.toString(); + } +}
\ No newline at end of file diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/doc/ApiDoc.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/doc/ApiDoc.java new file mode 100644 index 00000000..e2914752 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/doc/ApiDoc.java @@ -0,0 +1,40 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ +package org.onap.aaf.auth.rserv.doc; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.onap.aaf.auth.rserv.HttpMethods; +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface ApiDoc { + HttpMethods method(); + String path(); + int expectedCode(); + int[] errorCodes(); + String[] text(); + /** Format with name|type|[true|false] */ + String[] params(); + +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/server/AbsService.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/server/AbsService.java new file mode 100644 index 00000000..e1c01718 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/server/AbsService.java @@ -0,0 +1,156 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.server; + +import java.security.NoSuchAlgorithmException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.servlet.Filter; + +import org.onap.aaf.auth.common.Define; +import org.onap.aaf.auth.rserv.RServlet; +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.LocatorException; +import org.onap.aaf.cadi.aaf.v2_0.AAFConHttp; +import org.onap.aaf.cadi.client.Rcli; +import org.onap.aaf.cadi.client.Retryable; +import org.onap.aaf.cadi.config.Config; +import org.onap.aaf.cadi.http.HTransferSS; +import org.onap.aaf.cadi.principal.TaggedPrincipal; +import org.onap.aaf.cadi.register.Registrant; +import org.onap.aaf.cadi.util.Split; +import org.onap.aaf.misc.env.APIException; +import org.onap.aaf.misc.env.Trans; +import org.onap.aaf.misc.env.impl.BasicEnv; + +public abstract class AbsService<ENV extends BasicEnv, TRANS extends Trans> extends RServlet<TRANS> { + public final Access access; + public final ENV env; + private AAFConHttp aafCon; + + public final String app_name; + public final String app_version; + public final String app_interface_version; + public final String ROOT_NS; + + public AbsService(final Access access, final ENV env) throws CadiException { + Define.set(access); + ROOT_NS = Define.ROOT_NS(); + this.access = access; + this.env = env; + + String component = access.getProperty(Config.AAF_COMPONENT, null); + final String[] locator_deploy; + + if(component == null) { + locator_deploy = null; + } else { + locator_deploy = Split.splitTrim(':', component); + } + + if(component == null || locator_deploy==null || locator_deploy.length<2) { + throw new CadiException("AAF Component must include the " + Config.AAF_COMPONENT + " property, <fully qualified service name>:<full deployed version (i.e. 2.1.3.13)"); + } + final String[] version = Split.splitTrim('.', locator_deploy[1]); + if(version==null || version.length<2) { + throw new CadiException("AAF Component Version must have at least Major.Minor version"); + } + app_name = Define.varReplace(locator_deploy[0]); + app_version = locator_deploy[1]; + app_interface_version = version[0]+'.'+version[1]; + + // Print Cipher Suites Available + if(access.willLog(Level.DEBUG)) { + SSLContext context; + try { + context = SSLContext.getDefault(); + } catch (NoSuchAlgorithmException e) { + throw new CadiException("SSLContext issue",e); + } + SSLSocketFactory sf = context.getSocketFactory(); + StringBuilder sb = new StringBuilder("Available Cipher Suites: "); + boolean first = true; + int count=0; + for( String cs : sf.getSupportedCipherSuites()) { + if(first)first = false; + else sb.append(','); + sb.append(cs); + if(++count%4==0){sb.append('\n');} + } + access.log(Level.DEBUG,sb); + } + } + + public abstract Filter[] filters() throws CadiException, LocatorException; + + + public abstract Registrant<ENV>[] registrants(final int port) throws CadiException, LocatorException; + + // Lazy Instantiation + public synchronized AAFConHttp aafCon() throws CadiException, LocatorException { + if(aafCon==null) { + if(access.getProperty(Config.AAF_URL,null)!=null) { + aafCon = _newAAFConHttp(); + } else { + throw new CadiException("AAFCon cannot be constructed without " + Config.AAF_URL); + } + } + return aafCon; + } + + /** + * Allow to be over ridden for special cases + * @return + * @throws LocatorException + */ + protected synchronized AAFConHttp _newAAFConHttp() throws CadiException, LocatorException { + try { + if(aafCon==null) { + aafCon = new AAFConHttp(access); + } + return aafCon; + } catch (APIException e) { + throw new CadiException(e); + } + } + + // This is a method, so we can overload for AAFAPI + public String aaf_url() { + return access.getProperty(Config.AAF_URL, null); + } + + public Rcli<?> client() throws CadiException { + return aafCon.client(Config.AAF_DEFAULT_VERSION); + } + + public Rcli<?> clientAsUser(TaggedPrincipal p) throws CadiException { + return aafCon.client(Config.AAF_DEFAULT_VERSION).forUser( + new HTransferSS(p,app_name, aafCon.securityInfo())); + } + + public<RET> RET clientAsUser(TaggedPrincipal p,Retryable<RET> retryable) throws APIException, LocatorException, CadiException { + return aafCon.hman().best(new HTransferSS(p,app_name, aafCon.securityInfo()), retryable); + } +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/server/AbsServiceStarter.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/server/AbsServiceStarter.java new file mode 100644 index 00000000..1a6c54d7 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/server/AbsServiceStarter.java @@ -0,0 +1,95 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ +package org.onap.aaf.auth.server; +import org.onap.aaf.auth.org.OrganizationException; +import org.onap.aaf.auth.org.OrganizationFactory; +import org.onap.aaf.auth.rserv.RServlet; +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.register.Registrant; +import org.onap.aaf.cadi.register.Registrar; +import org.onap.aaf.misc.env.Trans; +import org.onap.aaf.misc.rosetta.env.RosettaEnv; + +public abstract class AbsServiceStarter<ENV extends RosettaEnv, TRANS extends Trans> implements ServiceStarter { + private Registrar<ENV> registrar; + private boolean do_register; + protected AbsService<ENV,TRANS> service; + + + public AbsServiceStarter(final AbsService<ENV,TRANS> service) { + this.service = service; + try { + OrganizationFactory.init(service.env); + } catch (OrganizationException e) { + service.access.log(e, "Missing defined Organzation Plugins"); + System.exit(3); + } + // do_register - this is used for specialty Debug Situations. Developer can create an Instance for a remote system + // for Debugging purposes without fear that real clients will start to call your debug instance + do_register = !"TRUE".equalsIgnoreCase(access().getProperty("aaf_locate_no_register",null)); + _propertyAdjustment(); + } + + public abstract void _start(RServlet<TRANS> rserv) throws Exception; + public abstract void _propertyAdjustment(); + + public ENV env() { + return service.env; + } + + public Access access() { + return service.access; + } + + @Override + public final void start() throws Exception { + _start(service); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + shutdown(); + } + }); + } + + @SafeVarargs + public final synchronized void register(final Registrant<ENV> ... registrants) { + if(do_register) { + if(registrar==null) { + registrar = new Registrar<ENV>(env(),false); + } + for(Registrant<ENV> r : registrants) { + registrar.register(r); + } + } + } + + @Override + public void shutdown() { + if(registrar!=null) { + registrar.close(env()); + registrar=null; + } + if(service!=null) { + service.destroy(); + } + } +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/server/JettyServiceStarter.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/server/JettyServiceStarter.java new file mode 100644 index 00000000..dbf24cc6 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/server/JettyServiceStarter.java @@ -0,0 +1,255 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ +package org.onap.aaf.auth.server; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.Properties; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.onap.aaf.auth.org.OrganizationException; +import org.onap.aaf.auth.rserv.RServlet; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.LocatorException; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.config.Config; +import org.onap.aaf.cadi.config.SecurityInfo; +import org.onap.aaf.misc.env.Trans; +import org.onap.aaf.misc.env.util.Split; +import org.onap.aaf.misc.rosetta.env.RosettaEnv; + + +public class JettyServiceStarter<ENV extends RosettaEnv, TRANS extends Trans> extends AbsServiceStarter<ENV,TRANS> { + + private boolean secure; + + public JettyServiceStarter(final AbsService<ENV,TRANS> service) throws OrganizationException { + super(service); + secure = true; + } + + /** + * Specifically set this Service starter to Insecure (HTTP) Mode. + * @return + */ + public JettyServiceStarter<ENV,TRANS> insecure() { + secure = false; + return this; + } + +// @Override +// public void _propertyAdjustment() { +// Properties props = access().getProperties(); +// Object temp = null; +// // Critical - if no Security Protocols set, then set it. We'll just get messed up if not +// if((temp=props.get(Config.CADI_PROTOCOLS))==null) { +// if((temp=props.get(Config.HTTPS_PROTOCOLS))==null) { +// props.put(Config.CADI_PROTOCOLS, SecurityInfo.HTTPS_PROTOCOLS_DEFAULT); +// } else { +// props.put(Config.CADI_PROTOCOLS, temp); +// } +// } +// +// if("1.7".equals(System.getProperty("java.specification.version"))) { +// System.setProperty(Config.HTTPS_CIPHER_SUITES, Config.HTTPS_CIPHER_SUITES_DEFAULT); +// } +// System.setProperty(Config.HTTPS_CIPHER_SUITES, temp.toString()); +// } + + @Override + public void _propertyAdjustment() { +// System.setProperty("com.sun.management.jmxremote.port", "8081"); + Properties props = access().getProperties(); + Object httpproto = null; + // Critical - if no Security Protocols set, then set it. We'll just get messed up if not + if((httpproto=props.get(Config.CADI_PROTOCOLS))==null) { + if((httpproto=props.get(Config.HTTPS_PROTOCOLS))==null) { + props.put(Config.CADI_PROTOCOLS, (httpproto=SecurityInfo.HTTPS_PROTOCOLS_DEFAULT)); + } else { + props.put(Config.CADI_PROTOCOLS, httpproto); + } + } + + if("1.7".equals(System.getProperty("java.specification.version")) && (httpproto==null || (httpproto instanceof String && ((String)httpproto).contains("TLSv1.2")))) { + System.setProperty(Config.HTTPS_CIPHER_SUITES, Config.HTTPS_CIPHER_SUITES_DEFAULT); + } + } + + @Override + public void _start(RServlet<TRANS> rserv) throws Exception { + final String hostname = access().getProperty(Config.HOSTNAME, "localhost"); + final int port = Integer.parseInt(access().getProperty("port","0")); + final String keystore = access().getProperty(Config.CADI_KEYSTORE, null); + final int IDLE_TIMEOUT = Integer.parseInt(access().getProperty(Config.AAF_CONN_IDLE_TIMEOUT, Config.AAF_CONN_IDLE_TIMEOUT_DEF)); + Server server = new Server(); + + ServerConnector conn; + String protocol; + if(!secure || keystore==null) { + conn = new ServerConnector(server); + protocol = "http"; + } else { + protocol = "https"; + + String keystorePassword = access().getProperty(Config.CADI_KEYSTORE_PASSWORD, null); + if(keystorePassword==null) { + throw new CadiException("No Keystore Password configured for " + keystore); + } + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setKeyStorePath(keystore); + String temp; + sslContextFactory.setKeyStorePassword(temp=access().decrypt(keystorePassword, true)); // don't allow unencrypted + sslContextFactory.setKeyManagerPassword(temp); + temp=null; // don't leave lying around + + String truststore = access().getProperty(Config.CADI_TRUSTSTORE, null); + if(truststore!=null) { + String truststorePassword = access().getProperty(Config.CADI_TRUSTSTORE_PASSWORD, null); + if(truststorePassword==null) { + throw new CadiException("No Truststore Password configured for " + truststore); + } + sslContextFactory.setTrustStorePath(truststore); + sslContextFactory.setTrustStorePassword(access().decrypt(truststorePassword, true)); + } + // Be able to accept only certain protocols, i.e. TLSv1.1+ + final String[] protocols = Split.splitTrim(',', access().getProperty(Config.CADI_PROTOCOLS, SecurityInfo.HTTPS_PROTOCOLS_DEFAULT)); + sslContextFactory.setIncludeProtocols(protocols); + + // Want to use Client Certificates, if they exist. + sslContextFactory.setWantClientAuth(true); + + // Optional future checks. + // sslContextFactory.setValidateCerts(true); + // sslContextFactory.setValidatePeerCerts(true); + // sslContextFactory.setEnableCRLDP(false); + // sslContextFactory.setEnableOCSP(false); + String certAlias = access().getProperty(Config.CADI_ALIAS, null); + if(certAlias!=null) { + sslContextFactory.setCertAlias(certAlias); + } + + HttpConfiguration httpConfig = new HttpConfiguration(); + httpConfig.setSecureScheme(protocol); + httpConfig.setSecurePort(port); + httpConfig.addCustomizer(new SecureRequestCustomizer()); + // httpConfig.setOutputBufferSize(32768); Not sure why take this setting + + conn = new ServerConnector(server, + new SslConnectionFactory(sslContextFactory,HttpVersion.HTTP_1_1.asString()), + new HttpConnectionFactory(httpConfig) + ); + } + + // Setup JMX + // TODO trying to figure out how to set up/log ports +// MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); +// MBeanContainer mbContainer=new MBeanContainer(mbeanServer); +// server.addEventListener(mbContainer); +// server.addBean(mbContainer); + + // Add loggers MBean to server (will be picked up by MBeanContainer above) +// server.addBean(Log.getLog()); + + conn.setHost(hostname); + conn.setPort(port); + conn.setIdleTimeout(IDLE_TIMEOUT); + server.addConnector(conn); + + server.setHandler(new AbstractHandler() { + private FilterChain fc = buildFilterChain(service,new FilterChain() { + @Override + public void doFilter(ServletRequest req, ServletResponse resp) throws IOException, ServletException { + rserv.service(req, resp); + } + }); + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest hreq, HttpServletResponse hresp) throws IOException, ServletException { + try { + fc.doFilter(hreq,hresp); + } catch (Exception e) { + service.access.log(e, "Error Processing " + target); + hresp.setStatus(500 /* Service Error */); + } + baseRequest.setHandled(true); + } + } + ); + + try { + access().printf(Level.INIT, "Starting service on %s:%d (%s)",hostname,port,InetAddress.getLocalHost().getHostAddress()); + server.start(); + access().log(Level.INIT,server.dump()); + } catch (Exception e) { + access().log(e,"Error starting " + service.app_name); + System.exit(1); + } + try { + register(service.registrants(port)); + access().printf(Level.INIT, "Starting Jetty Service for %s, version %s, on %s://%s:%d", service.app_name,service.app_version,protocol,hostname,port); + } catch(Exception e) { + access().log(e,"Error registering " + service.app_name); + // Question: Should Registered Services terminate? + } + server.join(); + } + + private FilterChain buildFilterChain(final AbsService<?,?> as, final FilterChain doLast) throws CadiException, LocatorException { + Filter[] filters = as.filters(); + FilterChain fc = doLast; + for(int i=filters.length-1;i>=0;--i) { + fc = new FCImpl(filters[i],fc); + } + return fc; + } + + private class FCImpl implements FilterChain { + private Filter f; + private FilterChain next; + + public FCImpl(final Filter f, final FilterChain fc) { + this.f=f; + next = fc; + + } + @Override + public void doFilter(ServletRequest req, ServletResponse resp) throws IOException, ServletException { + f.doFilter(req,resp, next); + } + } +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/server/ServiceStarter.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/server/ServiceStarter.java new file mode 100644 index 00000000..529d2d35 --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/server/ServiceStarter.java @@ -0,0 +1,26 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ +package org.onap.aaf.auth.server; + +public interface ServiceStarter { + public void start() throws Exception; + public void shutdown(); +} diff --git a/auth/auth-core/src/main/java/org/onap/aaf/auth/validation/Validator.java b/auth/auth-core/src/main/java/org/onap/aaf/auth/validation/Validator.java new file mode 100644 index 00000000..16c0d3ba --- /dev/null +++ b/auth/auth-core/src/main/java/org/onap/aaf/auth/validation/Validator.java @@ -0,0 +1,204 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.validation; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import org.onap.aaf.auth.layer.Result; + + +public class Validator { + private static final String ESSENTIAL = "\\x25\\x28\\x29\\x2C-\\x2E\\x30-\\x39\\x3D\\x40-\\x5A\\x5F\\x61-\\x7A"; + private static final Pattern ESSENTIAL_CHARS = Pattern.compile("["+ESSENTIAL+"]+"); + public static final Pattern ACTION_CHARS = Pattern.compile( + "["+ESSENTIAL+"]+" + // All AlphaNumeric+ + "|\\*" // Just Star + ); + public static final Pattern INST_CHARS = Pattern.compile( + "["+ESSENTIAL+"]+[\\*]*" + // All AlphaNumeric+ possibly ending with * + "|\\*" + // Just Star + "|(([:/]\\*)|([:/][!]{0,1}["+ESSENTIAL+"]+[\\*]*[:/]*))+" // Key :asdf:*:sdf*:sdk + ); + public static final Pattern ID_CHARS = Pattern.compile("[\\w.-]+@[\\w.-]+"); + public static final Pattern NAME_CHARS = Pattern.compile("[\\w.-]+"); + public static final Pattern DESC_CHAR = Pattern.compile("["+ESSENTIAL+"\\x20]+"); + public static List<String> nsKeywords; + protected final Pattern actionChars; + protected final Pattern instChars; + private StringBuilder msgs; + + static { + nsKeywords = new ArrayList<String>(); + nsKeywords.add(".access"); + nsKeywords.add(".owner"); + nsKeywords.add(".admin"); + nsKeywords.add(".member"); + nsKeywords.add(".perm"); + nsKeywords.add(".role"); + nsKeywords.add(".ns"); + nsKeywords.add(".cred"); + } + + public Validator() { + actionChars = ACTION_CHARS; + instChars = INST_CHARS; + } + + public final String errs() { + return msgs.toString(); + } + + public final Validator nullOrBlank(String name, String str) { + if(str==null) { + msg(name + " is null."); + } else if(str.length()==0) { + msg(name + " is blank."); + } + return this; + } + + public final Validator isNull(String name, Object o) { + if(o==null) { + msg(name + " is null."); + } + return this; + } + + protected final boolean noMatch(String str, Pattern p) { + return !p.matcher(str).matches(); + } + protected final boolean nob(String str, Pattern p) { + return str==null || !p.matcher(str).matches(); + } + + protected final void msg(String ... strs) { + if(msgs==null) { + msgs=new StringBuilder(); + } + for(String str : strs) { + msgs.append(str); + } + msgs.append('\n'); + } + + public final boolean err() { + return msgs!=null; + } + + public final Validator notOK(Result<?> res) { + if(res==null) { + msgs.append("Result object is blank"); + } else if(res.notOK()) { + msgs.append(res.getClass().getSimpleName() + " is not OK"); + } + return this; + } + + protected Validator intRange(String text, int target, int start, int end) { + if(target<start || target>end) { + msg(text + " is out of range (" + start + '-' + end + ')'); + } + return this; + } + + protected Validator floatRange(String text, float target, float start, float end) { + if(target<start || target>end) { + msg(text + " is out of range (" + start + '-' + end + ')'); + } + return this; + } + + protected Validator description(String type, String description) { + if(description!=null) { + if(noMatch(description, DESC_CHAR)) { + msg(type + " Description is invalid."); + } + } + return this; + } + + public final Validator permType(String type) { + if(nob(type,NAME_CHARS)) { + msg("Perm Type [" +type + "] is invalid."); + } + return this; + } + + public final Validator permType(String type, String ns) { + if(nob(type,NAME_CHARS)) { + msg("Perm Type [" + (ns==null?"":ns+(type.length()==0?"":'.'))+type + "] is invalid."); + } + return this; + } + + public final Validator permInstance(String instance) { + if(nob(instance,instChars)) { + msg("Perm Instance [" + instance + "] is invalid."); + } + return this; + } + + public final Validator permAction(String action) { + // TODO check for correct Splits? Type|Instance|Action ? + if(nob(action, actionChars)) { + msg("Perm Action [" + action + "] is invalid."); + } + return this; + } + + public final Validator role(String role) { + if(nob(role, NAME_CHARS)) { + msg("Role [" + role + "] is invalid."); + } + return this; + } + + public final Validator ns(String ns) { + if(nob(ns,NAME_CHARS)){ + msg("NS [" + ns + "] is invalid."); + } + for(String s : nsKeywords) { + if(ns.endsWith(s)) { + msg("NS [" + ns + "] may not be named with NS keywords"); + break; + } + } + return this; + } + + public final Validator key(String key) { + if(nob(key,NAME_CHARS)) { + msg("NS Prop Key [" + key + "] is invalid"); + } + return this; + } + + public final Validator value(String value) { + if(nob(value,ESSENTIAL_CHARS)) { + msg("NS Prop value [" + value + "] is invalid"); + } + return this; + } + +} |