diff options
author | Instrumental <jcgmisc@stl.gathman.org> | 2018-03-26 13:49:56 -0700 |
---|---|---|
committer | Instrumental <jcgmisc@stl.gathman.org> | 2018-03-26 13:50:07 -0700 |
commit | a20accc73189d8e5454cd26049c0e6fae75da16f (patch) | |
tree | 3526110a1f65591354eff6400383512df4828903 /cadi/core/src | |
parent | ac1e1ec76e9125206be91a2f32c7104c9392dc9a (diff) |
AT&T 2.0.19 Code drop, stage 2
Issue-ID: AAF-197
Change-Id: Ifc93308f52c10d6ad82e99cd3ff5ddb900bf219a
Signed-off-by: Instrumental <jcgmisc@stl.gathman.org>
Diffstat (limited to 'cadi/core/src')
141 files changed, 16572 insertions, 0 deletions
diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/AES.java b/cadi/core/src/main/java/org/onap/aaf/cadi/AES.java new file mode 100644 index 00000000..3ef3355a --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/AES.java @@ -0,0 +1,131 @@ +/** + * ============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.cadi; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import org.onap.aaf.cadi.Symm.Encryption; +import org.onap.aaf.cadi.util.Chmod; + + +/** + * AES Class wraps Cipher AES, 128 + * NOTE: While not explicitly stated in JavaDocs, Ciphers AND SecretKeySpecs are NOT ThreadSafe + * Ciphers take time to create, therefore, we have pooled them. + * + * @author Jonathan + * + */ +public class AES implements Encryption { + public static final String AES = AES.class.getSimpleName(); + public static final int AES_KEY_SIZE = 128; // 256 isn't supported on all JDKs. + + private SecretKeySpec aeskeySpec; + + public static SecretKey newKey() throws NoSuchAlgorithmException { + KeyGenerator kgen = KeyGenerator.getInstance(AES); + kgen.init(AES_KEY_SIZE); + return kgen.generateKey(); + } + + public AES(byte[] aeskey, int offset, int len) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException { + aeskeySpec = new SecretKeySpec(aeskey,offset,len,AES); + } + + public byte[] encrypt(byte[] in) throws CadiException { + try { + Cipher c = Cipher.getInstance(AES); + c.init(Cipher.ENCRYPT_MODE,aeskeySpec); + return c.doFinal(in); + } catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new CadiException(e); + } + } + + public byte[] decrypt(byte[] in) throws CadiException { + try { + Cipher c = Cipher.getInstance(AES); + c.init(Cipher.DECRYPT_MODE,aeskeySpec); + return c.doFinal(in); + } catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new CadiException(e); + } + } + + public void save(File keyfile) throws IOException { + FileOutputStream fis = new FileOutputStream(keyfile); + try { + fis.write(aeskeySpec.getEncoded()); + } finally { + fis.close(); + } + Chmod.to400.chmod(keyfile); + } + + public CipherOutputStream outputStream(OutputStream os, boolean encrypt) { + try { + Cipher c = Cipher.getInstance(AES); + if(encrypt) { + c.init(Cipher.ENCRYPT_MODE,aeskeySpec); + } else { + c.init(Cipher.DECRYPT_MODE,aeskeySpec); + } + return new CipherOutputStream(os,c); + } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException e) { + // Cannot add Exception to this API. throw Runtime + System.err.println("Error creating Aes CipherOutputStream"); + return null; // should never get here. + } + } + + public CipherInputStream inputStream(InputStream is, boolean encrypt) { + try { + Cipher c = Cipher.getInstance(AES); + if(encrypt) { + c.init(Cipher.ENCRYPT_MODE,aeskeySpec); + } else { + c.init(Cipher.DECRYPT_MODE,aeskeySpec); + } + return new CipherInputStream(is,c); + } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException e) { + // Cannot add Exception to this API. throw Runtime + System.err.println("Error creating Aes CipherInputStream"); + return null; // should never get here. + } + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/AbsUserCache.java b/cadi/core/src/main/java/org/onap/aaf/cadi/AbsUserCache.java new file mode 100644 index 00000000..be1e739b --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/AbsUserCache.java @@ -0,0 +1,463 @@ +/** + * ============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.cadi; + + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; + +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.CachedPrincipal.Resp; +import org.onap.aaf.cadi.principal.CachedBasicPrincipal; + +/** + * Implement Fast lookup and Cache for Local User Info + * + * Include ability to add and remove Users + * + * Also includes a Timer Thread (when necessary) to invoke cleanup on expiring Credentials + * + * @author Jonathan + * + */ +public abstract class AbsUserCache<PERM extends Permission> { + // Need an obvious key for when there is no Authentication Cred + private static final String NO_CRED = "NoCred"; + static final int MIN_INTERVAL = 1000*60; // Min 1 min + static final int MAX_INTERVAL = 1000*60*60*4; // 4 hour max + private static Timer timer; + // Map of userName to User + private final Map<String, User<PERM>> userMap; + private static final Map<String, Miss> missMap = new TreeMap<String,Miss>(); + private final Symm missEncrypt; + + private Clean clean; + protected Access access; +// private final static Permission teaser = new LocalPermission("***NoPERM****"); + + protected AbsUserCache(Access access, long cleanInterval, int highCount, int usageCount) { + this.access = access; + Symm s; + try { + byte[] gennedKey = Symm.keygen(); + s = Symm.obtain(new ByteArrayInputStream(gennedKey)); + } catch (IOException e) { + access.log(e); + s = Symm.base64noSplit; + } + missEncrypt = s; + + userMap = new ConcurrentHashMap<String, User<PERM>>(); + + + if(cleanInterval>0) { + cleanInterval = Math.max(MIN_INTERVAL, cleanInterval); + synchronized(AbsUserCache.class) { // Lazy instantiate.. in case there is no cleanup needed + if(timer==null) { + timer = new Timer("CADI Cleanup Timer",true); + } + + timer.schedule(clean = new Clean(access, cleanInterval, highCount, usageCount), cleanInterval, cleanInterval); + access.log(Access.Level.INIT, "Cleaning Thread initialized with interval of",cleanInterval, "ms and max objects of", highCount); + } + } + } + + @SuppressWarnings("unchecked") + public AbsUserCache(AbsUserCache<PERM> cache) { + this.access = cache.access; + userMap = cache.userMap; + missEncrypt = cache.missEncrypt; + + synchronized(AbsUserCache.class) { + if(cache.clean!=null && cache.clean.lur==null && this instanceof CachingLur) { + cache.clean.lur=(CachingLur<PERM>)this; + } + } + } + + protected void setLur(CachingLur<PERM> lur) { + if(clean!=null)clean.lur = lur; + + } + + protected void addUser(User<PERM> user) { + Principal p = user.principal; + String key; + try { + if(p instanceof GetCred) { + key = missKey(p.getName(), ((GetCred)p).getCred()); + } else { + byte[] cred; + if((cred=user.getCred())==null) { + key = user.name + NO_CRED; + } else { + key = missKey(user.name,cred); + } + } + } catch (IOException e) { + access.log(e); + return; + } + userMap.put(key, user); + } + + // Useful for looking up by WebToken, etc. + protected void addUser(String key, User<PERM> user) { + userMap.put(key, user); + } + + /** + * Add miss to missMap. If Miss exists, or too many tries, returns false. + * + * otherwise, returns true to allow another attempt. + * + * @param key + * @param bs + * @return + * @throws IOException + */ + protected synchronized boolean addMiss(String key, byte[] bs) { + String mkey; + try { + mkey = missKey(key,bs); + } catch (IOException e) { + access.log(e); + return false; + } + Miss miss = missMap.get(mkey); + if(miss==null) { + missMap.put(mkey, new Miss(bs,clean==null?MIN_INTERVAL:clean.timeInterval)); + return true; + } + return miss.mayContinue(); + } + + protected Miss missed(String key, byte[] bs) throws IOException { + return missMap.get(missKey(key,bs)); + } + + protected User<PERM> getUser(Principal principal) { + String key; + if(principal instanceof GetCred) { + GetCred gc = (GetCred)principal; + try { + key = missKey(principal.getName(), gc.getCred()); + } catch (IOException e) { + access.log(e, "Error getting key from Principal"); + key = principal.getName(); + } + } else { + key = principal.getName()+NO_CRED; + } + User<PERM> u = userMap.get(key); + if(u!=null) { + u.incCount(); + } + return u; + } + + protected User<PERM> getUser(CachedBasicPrincipal cbp) { + return getUser(cbp.getName(), cbp.getCred()); + } + + protected User<PERM> getUser(String user, byte[] cred) { + User<PERM> u; + String key=null; + try { + key =missKey(user,cred); + } catch (IOException e) { + access.log(e); + return null; + } + u = userMap.get(key); + if(u!=null) { + if(u.permExpired()) { + userMap.remove(key); + u=null; + } else { + u.incCount(); + } + } + return u; + } + + /** + * Removes User from the Cache + * @param user + */ + protected void remove(User<PERM> user) { + userMap.remove(user.principal.getName()); + } + + /** + * Removes user from the Cache + * + * @param user + */ + public void remove(String user) { + Object o = userMap.remove(user); + if(o!=null) { + access.log(Level.INFO, user,"removed from Client Cache by Request"); + } + } + + /** + * Clear all Users from the Client Cache + */ + public void clearAll() { + userMap.clear(); + } + + public final List<DumpInfo> dumpInfo() { + List<DumpInfo> rv = new ArrayList<DumpInfo>(); + for(User<PERM> user : userMap.values()) { + rv.add(new DumpInfo(user)); + } + return rv; + } + + /** + * The default behavior of a LUR is to not handle something exclusively. + */ + public boolean handlesExclusively(Permission pond) { + return false; + } + + /** + * Container calls when cleaning up... + * + * If overloading in Derived class, be sure to call "super.destroy()" + */ + public void destroy() { + if(timer!=null) { + timer.purge(); + timer.cancel(); + } + } + + + + // Simple map of Group name to a set of User Names + // private Map<String, Set<String>> groupMap = new HashMap<String, Set<String>>(); + + /** + * Class to hold a small subset of the data, because we don't want to expose actual Permission or User Objects + */ + public final class DumpInfo { + public String user; + public List<String> perms; + + public DumpInfo(User<PERM> user) { + this.user = user.principal.getName(); + perms = new ArrayList<String>(user.perms.keySet()); + } + } + + /** + * 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 class Clean extends TimerTask { + private final Access access; + private CachingLur<PERM> lur; + + // 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; + private final int usageTriggerCount; + + public Clean(Access access, long cleanInterval, int highCount, int usageTriggerCount) { + this.access = access; + lur = null; + high = highCount; + timeInterval = cleanInterval; + advance = 0; + this.usageTriggerCount=usageTriggerCount; + } + public void run() { + int renewed = 0; + int count = 0; + int total = 0; + try { + // look at now. If we need to expire more by increasing "now" by "advance" + ArrayList<User<PERM>> al = new ArrayList<User<PERM>>(userMap.values().size()); + al.addAll(0, userMap.values()); + long now = System.currentTimeMillis() + advance; + for(User<PERM> user : al) { + ++total; + if(user.count>usageTriggerCount) { + // access.log(Level.AUDIT, "Checking Thread", new Date(now)); + boolean touched = false, removed=false; + if(user.principal instanceof CachedPrincipal) { + CachedPrincipal cp = (CachedPrincipal)user.principal; + if(cp.expires() < now) { + switch(cp.revalidate(null)) { + case INACCESSIBLE: + access.log(Level.AUDIT, "AAF Inaccessible. Keeping credentials"); + break; + case REVALIDATED: + user.resetCount(); + // access.log(Level.AUDIT, "CACHE revalidated credentials"); + touched = true; + break; + default: + user.resetCount(); + remove(user); + ++count; + removed = true; + break; + } + } + } + + // access.log(Level.AUDIT, "User Perm Expires", new Date(user.permExpires)); + if(!removed && lur!=null && user.permExpires<= now ) { + // access.log(Level.AUDIT, "Reloading"); + if(lur.reload(user).equals(Resp.REVALIDATED)) { + user.renewPerm(); + access.log(Level.DEBUG, "Reloaded Perms for",user); + touched = true; + } + } + user.resetCount(); + if(touched) { + ++renewed; + } + + } else { + if(user.permExpired()) { + remove(user); + ++count; + } + } + } + + // Clean out Misses + int missTotal = missMap.keySet().size(); + int miss = 0; + if(missTotal>0) { + ArrayList<String> keys = new ArrayList<String>(missTotal); + keys.addAll(missMap.keySet()); + for(String key : keys) { + Miss m = missMap.get(key); + if(m!=null && m.timestamp<System.currentTimeMillis()) { + synchronized(missMap) { + missMap.remove(key); + } + access.log(Level.INFO, key, "has been removed from Missed Credential Map (" + m.tries + " invalid tries)"); + ++miss; + } + } + } + + if(count+renewed+miss>0) { + access.log(Level.INFO, (lur==null?"Cache":lur.getClass().getSimpleName()), "removed",count, + "and renewed",renewed,"expired Permissions out of", total,"and removed", miss, "password misses out of",missTotal); + } + + // 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)); + } + } catch (Exception e) { + access.log(Level.ERROR,e.getMessage()); + } + } + } + + + private String missKey(String name, byte[] bs) throws IOException { + return name + Hash.toHex(missEncrypt.encode(bs)); + } + + protected static class Miss { + private static final int MAX_TRIES = 3; + + long timestamp; + + private long timetolive; + + private long tries; + + public Miss(byte[] first, long timeInterval) { + timestamp = System.currentTimeMillis() + timeInterval; + this.timetolive = timeInterval; + tries = 0L; + } + + + public synchronized boolean mayContinue() { + long ts = System.currentTimeMillis(); + if(ts>timestamp) { + tries = 0; + timestamp = ts + timetolive; + } else if(MAX_TRIES <= ++tries) { + return false; + } + return true; + } + } + + /** + * Report on state + */ + public String toString() { + return getClass().getSimpleName() + + " Cache:\n Users Cached: " + + userMap.size() + + "\n Misses Saved: " + + missMap.size() + + '\n'; + + } + + public void clear(Principal p, StringBuilder sb) { + sb.append(toString()); + userMap.clear(); + missMap.clear(); + access.log(Level.AUDIT, p.getName(),"has cleared User Cache in",getClass().getSimpleName()); + sb.append("Now cleared\n"); + } + +}
\ No newline at end of file diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/Access.java b/cadi/core/src/main/java/org/onap/aaf/cadi/Access.java new file mode 100644 index 00000000..7a9e63be --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/Access.java @@ -0,0 +1,180 @@ +/** + * ============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.cadi; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * Various Environments require different logging mechanisms, or at least allow + * for different ones. We need the Framework to be able to hook into any particular instance of logging + * mechanism, whether it be a Logging Object within a Servlet Context, or a direct library like log4j. + * This interface, therefore, allows maximum pluggability in a variety of different app styles. + * + * @author Jonathan + * + */ +public interface Access { + // levels to use + public enum Level { + DEBUG(0x1), INFO(0x10), AUDIT(0x100), WARN(0x2000), ERROR(0x4000), INIT(0x8000),NONE(0XFFFF); + private final int bit; + + Level(int ord) { + bit = ord; + } + + public boolean inMask(int mask) { + return (mask & bit) == bit; + } + + public int addToMask(int mask) { + return mask | bit; + } + + public int delFromMask(int mask) { + return mask & ~bit; + } + + public int toggle(int mask) { + if(inMask(mask)) { + return delFromMask(mask); + } else { + return addToMask(mask); + } + } + + + public int maskOf() { + int mask=0; + for(Level l : values()) { + if(ordinal()<=l.ordinal() && l!=NONE) { + mask|=l.bit; + } + } + return mask; + } + } + + /** + * Write a variable list of Object's text via the toString() method with appropriate space, etc. + * @param elements + */ + public void log(Level level, Object ... elements); + + /** + * Printf mechanism for Access + * @param level + * @param fmt + * @param elements + */ + public void printf(Level level, String fmt, Object ... elements); + + /** + * Check if message will log before constructing + * @param level + * @return + */ + public boolean willLog(Level level); + + /** + * Write the contents of an exception, followed by a variable list of Object's text via the + * toString() method with appropriate space, etc. + * + * The Loglevel is always "ERROR" + * + * @param elements + */ + public void log(Exception e, Object ... elements); + + /** + * Set the Level to compare logging too + */ + public void setLogLevel(Level level); + + /** + * It is important in some cases to create a class from within the same Classloader that created + * Security Objects. Specifically, it's pretty typical for Web Containers to separate classloaders + * so as to allow Apps with different dependencies. + * @return + */ + public ClassLoader classLoader(); + + public String getProperty(String string, String def); + + public Properties getProperties(); + + public void load(InputStream is) throws IOException; + + /** + * if "anytext" is true, then decryption will always be attempted. Otherwise, only if starts with + * Symm.ENC + * @param encrypted + * @param anytext + * @return + * @throws IOException + */ + public String decrypt(String encrypted, boolean anytext) throws IOException; + + public static final Access NULL = new Access() { + public void log(Level level, Object... elements) { + } + + @Override + public void printf(Level level, String fmt, Object... elements) { + } + + public void log(Exception e, Object... elements) { + } + + public ClassLoader classLoader() { + return ClassLoader.getSystemClassLoader(); + } + + public String getProperty(String string, String def) { + return null; + } + + public void load(InputStream is) throws IOException { + } + + public void setLogLevel(Level level) { + } + + public String decrypt(String encrypted, boolean anytext) throws IOException { + return encrypted; + } + + @Override + public boolean willLog(Level level) { + return false; + } + + @Override + public Properties getProperties() { + return new Properties(); + } + }; + + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/BasicCred.java b/cadi/core/src/main/java/org/onap/aaf/cadi/BasicCred.java new file mode 100644 index 00000000..b80cda89 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/BasicCred.java @@ -0,0 +1,36 @@ +/** + * ============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.cadi; + +/** + * An Interface for testing on Requests to see if we can get a User and Password + * It works for CadiWrap, but also, Container Specific Wraps (aka Tomcat) should also + * implement. + * + * @author Jonathan + * + */ +public interface BasicCred extends GetCred { + public void setUser(String user); + public void setCred(byte[] passwd); + public String getUser(); +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/BufferedServletInputStream.java b/cadi/core/src/main/java/org/onap/aaf/cadi/BufferedServletInputStream.java new file mode 100644 index 00000000..2df01cda --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/BufferedServletInputStream.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.cadi; + +import java.io.IOException; +import java.io.InputStream; + +import javax.servlet.ServletInputStream; + +/** + * BufferedServletInputStream + * + * There are cases in brain-dead middleware (SOAP) where they store routing information in the content. + * + * In HTTP, this requires reading the content from the InputStream which, of course, cannot be re-read. + * + * BufferedInputStream exists to implement the "Mark" protocols for Streaming, which will enable being + * re-read. Unfortunately, J2EE chose to require a "ServletInputStream" as an abstract class, rather than + * an interface, which requires we create a delegating pattern, rather than the preferred inheriting pattern. + * + * Unfortunately, the standard "BufferedInputStream" cannot be used, because it simply creates a byte array + * in the "mark(int)" method of that size. This is not appropriate for this application, because the Header + * can be potentially huge, and if a buffer was allocated to accommodate all possibilities, the cost of memory + * allocation would be too large for high performance transactions. + * + * + * @author Jonathan + * + */ +public class BufferedServletInputStream extends ServletInputStream { + private static final int NONE = 0; + private static final int STORE = 1; + private static final int READ = 2; + + private InputStream is; + private int state = NONE; + private Capacitor capacitor; + + public BufferedServletInputStream(InputStream is) { + this.is = is; + capacitor = null; + } + + + public int read() throws IOException { + int value=-1; + if(capacitor==null) { + value=is.read(); + } else { + switch(state) { + case STORE: + value = is.read(); + if(value>=0) { + capacitor.put((byte)value); + } + break; + case READ: + value = capacitor.read(); + if(value<0) { + capacitor.done(); + capacitor=null; // all done with buffer + value = is.read(); + } + } + } + return value; + } + + public int read(byte[] b) throws IOException { + return read(b,0,b.length); + } + + + public int read(byte[] b, int off, int len) throws IOException { + int count = -1; + if(capacitor==null) { + count = is.read(b,off,len); + } else { + switch(state) { + case STORE: + count = is.read(b, off, len); + if(count>0) { + capacitor.put(b, off, count); + } + break; + case READ: + count = capacitor.read(b, off, len); + if(count<=0) { + capacitor.done(); + capacitor=null; // all done with buffer + } + if(count<len) { + int temp = is.read(b, count, len-count); + if(temp>0) { // watch for -1 + count+=temp; + } else if(count<=0) { + count = temp; // must account for Stream coming back -1 + } + } + break; + } + } + return count; + } + + public long skip(long n) throws IOException { + long skipped = capacitor.skip(n); + if(skipped<n) { + skipped += is.skip(n-skipped); + } + return skipped; + } + + + public int available() throws IOException { + int count = is.available(); + if(capacitor!=null)count+=capacitor.available(); + return count; + } + + /** + * Return just amount buffered (for debugging purposes, mostly) + * @return + */ + public int buffered() { + return capacitor.available(); + } + + + public void close() throws IOException { + if(capacitor!=null) { + capacitor.done(); + capacitor=null; + } + is.close(); + } + + + /** + * Note: Readlimit is ignored in this implementation, because the need was for unknown buffer size which wouldn't + * require allocating and dumping huge chunks of memory every use, or risk overflow. + */ + public synchronized void mark(int readlimit) { + switch(state) { + case NONE: + capacitor = new Capacitor(); + break; + case READ: + capacitor.done(); + break; + } + state = STORE; + } + + + /** + * Reset Stream + * + * Calling this twice is not supported in typical Stream situations, but it is allowed in this service. The caveat is that it can only reset + * the data read in since Mark has been called. The data integrity is only valid if you have not continued to read past what is stored. + * + */ + public synchronized void reset() throws IOException { + switch(state) { + case STORE: + capacitor.setForRead(); + state = READ; + break; + case READ: + capacitor.reset(); + break; + case NONE: + throw new IOException("InputStream has not been marked"); + } + } + + + public boolean markSupported() { + return true; + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/CachedPrincipal.java b/cadi/core/src/main/java/org/onap/aaf/cadi/CachedPrincipal.java new file mode 100644 index 00000000..2bb3db32 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/CachedPrincipal.java @@ -0,0 +1,47 @@ +/** + * ============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.cadi; + +import java.security.Principal; + +/** + * Cached Principals need to be able to revalidate in the background. + * + * @author Jonathan + * + */ +public interface CachedPrincipal extends Principal { + public enum Resp {NOT_MINE,UNVALIDATED,REVALIDATED,INACCESSIBLE,DENIED}; + + /** + * Re-validate with Creator + * + * @return + */ + public abstract Resp revalidate(Object state); + + /** + * Store when last updated. + * @return + */ + public abstract long expires(); +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/CachingLur.java b/cadi/core/src/main/java/org/onap/aaf/cadi/CachingLur.java new file mode 100644 index 00000000..e083f4ed --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/CachingLur.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.cadi; + +import java.security.Principal; + +import org.onap.aaf.cadi.CachedPrincipal.Resp; + + +public interface CachingLur<PERM extends Permission> extends Lur { + public abstract void remove(String user); + public abstract Resp reload(User<PERM> user); + public abstract void setDebug(String commaDelimIDsOrNull); + public abstract void clear(Principal p, StringBuilder sb); +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/CadiException.java b/cadi/core/src/main/java/org/onap/aaf/cadi/CadiException.java new file mode 100644 index 00000000..0f250b36 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/CadiException.java @@ -0,0 +1,50 @@ +/** + * ============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.cadi; + +/** + * CADI Specific Exception + * @author Jonathan + */ +public class CadiException extends Exception { + /** + * Generated ID + */ + private static final long serialVersionUID = -4180145363107742619L; + + public CadiException() { + super(); + } + + public CadiException(String message) { + super(message); + } + + public CadiException(Throwable cause) { + super(cause); + } + + public CadiException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/CadiWrap.java b/cadi/core/src/main/java/org/onap/aaf/cadi/CadiWrap.java new file mode 100644 index 00000000..49572f4c --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/CadiWrap.java @@ -0,0 +1,198 @@ +/** + * ============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.cadi; + +import java.security.Principal; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.filter.NullPermConverter; +import org.onap.aaf.cadi.filter.PermConverter; +import org.onap.aaf.cadi.lur.EpiLur; +import org.onap.aaf.cadi.principal.TaggedPrincipal; +import org.onap.aaf.cadi.taf.TafResp; + + + +/** + * Inherit the HttpServletRequestWrapper, which calls methods of delegate it's created with, but + * overload the key security mechanisms with CADI mechanisms + * + * This works with mechanisms working strictly with HttpServletRequest (i.e. Servlet Filters) + * + * Specialty cases, i.e. Tomcat, which for their containers utilize their own mechanisms and Wrappers, you may + * need something similar. See AppServer specific code (i.e. tomcat) for these. + * + * @author Jonathan + * + */ +public class CadiWrap extends HttpServletRequestWrapper implements HttpServletRequest, BasicCred { + private TaggedPrincipal principal; + private Lur lur; + private String user; // used to set user/pass from brain-dead protocols like WSSE + private byte[] password; + private PermConverter pconv; + private Access access; + + /** + * Standard Wrapper constructor for Delegate pattern + * @param request + */ + public CadiWrap(HttpServletRequest request, TafResp tafResp, Lur lur) { + super(request); + principal = tafResp.getPrincipal(); + access = tafResp.getAccess(); + this.lur = lur; + pconv = NullPermConverter.singleton(); + } + + /** + * Standard Wrapper constructor for Delegate pattern, with PermConverter + * @param request + */ + public CadiWrap(HttpServletRequest request, TafResp tafResp, Lur lur, PermConverter pc) { + super(request); + principal = tafResp.getPrincipal(); + access = tafResp.getAccess(); + this.lur = lur; + pconv = pc; + } + + + /** + * Part of the HTTP Security API. Declare the User associated with this HTTP Transaction. + * CADI does this by reporting the name associated with the Principal obtained, if any. + */ + @Override + public String getRemoteUser() { + return principal==null?null:principal.getName(); + } + + /** + * Part of the HTTP Security API. Return the User Principal associated with this HTTP + * Transaction. + */ + @Override + public Principal getUserPrincipal() { + return principal; + } + + /** + * This is the key API call for AUTHZ in J2EE. Given a Role (String passed in), is the user + * associated with this HTTP Transaction allowed to function in this Role? + * + * For CADI, we pass the responsibility for determining this to the "LUR", which may be + * determined by the Enterprise. + * + * Note: Role check is also done in "CadiRealm" in certain cases... + * + * + */ + @Override + public boolean isUserInRole(String perm) { + return perm==null?false:checkPerm(access,"(HttpRequest)",principal,pconv,lur,perm); + } + + public static boolean checkPerm(Access access, String caller, Principal principal, PermConverter pconv, Lur lur, String perm) { + if(principal== null) { + access.log(Level.AUDIT,caller, "No Principal in Transaction"); + return false; + } else { + perm = pconv.convert(perm); + if(lur.fish(principal,lur.createPerm(perm))) { + access.log(Level.DEBUG,caller, principal.getName(), "has", perm); + return true; + } else { + access.log(Level.DEBUG,caller, principal.getName(), "does not have", perm); + return false; + } + } + + } + + /** + * CADI Function (Non J2EE standard). GetPermissions will read the Permissions from AAF (if configured) and Roles from Local Lur, etc + * as implemented with lur.fishAll + * + * To utilize, the Request must be a "CadiWrap" object, then call. + */ + public List<Permission> getPermissions(Principal p) { + List<Permission> perms = new ArrayList<Permission>(); + lur.fishAll(p, perms); + return perms; + } + /** + * Allow setting of tafResp and lur after construction + * + * This can happen if the CadiWrap is constructed in a Valve other than CadiValve + */ + public void set(TafResp tafResp, Lur lur) { + principal = tafResp.getPrincipal(); + access = tafResp.getAccess(); + this.lur = lur; + } + + public String getUser() { + if(user==null && principal!=null) { + user = principal.getName(); + } + return user; + } + + public byte[] getCred() { + return password; + } + + public void setUser(String user) { + this.user = user; + } + + public void setCred(byte[] passwd) { + password = passwd; + } + + public CadiWrap setPermConverter(PermConverter pc) { + pconv = pc; + return this; + } + + // Add a feature + public void invalidate(String id) { + if(lur instanceof EpiLur) { + ((EpiLur)lur).remove(id); + } else if(lur instanceof CachingLur) { + ((CachingLur<?>)lur).remove(id); + } + } + + public Lur getLur() { + return lur; + } + + public Access access() { + return access; + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/Capacitor.java b/cadi/core/src/main/java/org/onap/aaf/cadi/Capacitor.java new file mode 100644 index 00000000..00383851 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/Capacitor.java @@ -0,0 +1,241 @@ +/** + * ============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.cadi; + +import java.nio.ByteBuffer; +import java.util.ArrayList; + +/** + * Capacitor + * + * Storage mechanism for read data, specifically designed for InputStreams. + * + * The Standard BufferedInputStream requires a limit to be set for buffered reading, which is + * impractical for reading SOAP headers, which can be quite large. + * @author Jonathan + * + */ +public class Capacitor { + private static final int DEFAULT_CHUNK = 256; + private ArrayList<ByteBuffer> bbs = new ArrayList<ByteBuffer>(); + private ByteBuffer curr = null; + private int idx; + + // Maintain a private RingBuffer for Memory, for efficiency + private static ByteBuffer[] ring = new ByteBuffer[16]; + private static int start, end; + + + public void put(byte b) { + if(curr == null || curr.remaining()==0) { // ensure we have a "curr" buffer ready for data + curr = ringGet(); + bbs.add(curr); + } + curr.put(b); + } + + public int read() { + if(curr!=null) { + if(curr.remaining()>0) { // have a buffer, use it! + return curr.get(); + } else if(idx<bbs.size()){ // Buffer not enough, get next one from array + curr=bbs.get(idx++); + return curr.get(); + } + } // if no curr buffer, treat as end of stream + return -1; + } + + /** + * read into an array like Streams + * + * @param array + * @param offset + * @param length + * @return + */ + public int read(byte[] array, int offset, int length) { + if(curr==null)return -1; + int len; + int count=0; + while(length>0) { // loop through while there's data needed + if((len=curr.remaining())>length) { // if enough data in curr buffer, use this code + curr.get(array,offset,length); + count+=length; + length=0; + } else { // get data from curr, mark how much is needed to fulfil, and loop for next curr. + curr.get(array,offset,len); + count+=len; + offset+=len; + length-=len; + if(idx<bbs.size()) { + curr=bbs.get(idx++); + } else { + length=0; // stop, and return the count of how many we were able to load + } + } + } + return count; + } + + /** + * Put an array of data into Capacitor + * + * @param array + * @param offset + * @param length + */ + public void put(byte[] array, int offset, int length) { + if(curr == null || curr.remaining()==0) { + curr = ringGet(); + bbs.add(curr); + } + + int len; + while(length>0) { + if((len=curr.remaining())>length) { + curr.put(array,offset,length); + length=0; + } else { +// System.out.println(new String(array)); + curr.put(array,offset,len); + length-=len; + offset+=len; + curr = ringGet(); + bbs.add(curr); + } + } + } + + /** + * Move state from Storage mode into Read mode, changing all internal buffers to read mode, etc + */ + public void setForRead() { + for(ByteBuffer bb : bbs) { + bb.flip(); + } + if(bbs.isEmpty()) { + curr = null; + idx = 0; + } else { + curr=bbs.get(0); + idx=1; + } + } + + /** + * reuse all the buffers + */ + public void done() { + for(ByteBuffer bb : bbs) { + ringPut(bb); + } + bbs.clear(); + curr = null; + } + + /** + * Declare amount of data available to be read at once. + * + * @return + */ + public int available() { + int count = 0; + for(ByteBuffer bb : bbs) { + count+=bb.remaining(); + } + return count; + } + + /** + * Returns how many are left that were not skipped + * @param n + * @return + */ + public long skip(long n) { + long skipped=0L; + int skip; + if(curr==null) { + return 0; + } + while(n>0) { + if(n<(skip=curr.remaining())) { + curr.position(curr.position()+(int)n); + skipped+=skip; + n=0; + } else { + curr.position(curr.limit()); + + skipped-=skip; + if(idx<bbs.size()) { + curr=bbs.get(idx++); + n-=skip; + } else { + n=0; + } + } + } + return skipped > 0 ? skipped : 0; + } + /** + * Be able to re-read data that is stored that has already been re-read. This is not a standard Stream behavior, but can be useful + * in a standalone mode. + */ + public void reset() { + for(ByteBuffer bb : bbs) { + bb.position(0); + } + if(bbs.isEmpty()) { + curr = null; + idx = 0; + } else { + curr=bbs.get(0); + idx=1; + } + } + + /* + * Ring Functions. Reuse allocated memory + */ + private ByteBuffer ringGet() { + ByteBuffer bb = null; + synchronized(ring) { + bb=ring[start]; + ring[start]=null; + if(bb!=null && ++start>15)start=0; + } + if(bb==null) { + bb=ByteBuffer.allocate(DEFAULT_CHUNK); + } else { + bb.clear();// refresh reused buffer + } + return bb; + } + + private void ringPut(ByteBuffer bb) { + synchronized(ring) { + ring[end]=bb; // if null or not, BB will just be Garbage collected + if(++end>15)end=0; + } + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/CmdLine.java b/cadi/core/src/main/java/org/onap/aaf/cadi/CmdLine.java new file mode 100644 index 00000000..90f14405 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/CmdLine.java @@ -0,0 +1,361 @@ +/** + * ============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.cadi; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.NoSuchAlgorithmException; + +import org.onap.aaf.cadi.util.Chmod; +import org.onap.aaf.cadi.util.JsonOutputStream; + + + +/** + * A Class to run on command line to determine suitability of environment for certain TAFs. + * + * For instance, CSP supports services only in certain domains, and while dynamic host + * lookups on the machine work in most cases, sometimes, names and IPs are unexpected (and + * invalid) for CSP because of multiple NetworkInterfaces, etc + * + * @author Jonathan + * + */ +public class CmdLine { + + public static Access access; + + /** + * @param args + */ + public static void main(String[] args) { + if(args.length>0) { + if("digest".equalsIgnoreCase(args[0]) && (args.length>2 || (args.length>1 && System.console()!=null))) { + String keyfile; + String password; + if(args.length>2) { + password = args[1]; + keyfile = args[2]; + if("-i".equals(password)) { + int c; + StringBuilder sb = new StringBuilder(); + try { + while((c=System.in.read())>=0) { + sb.append((char)c); + } + } catch (IOException e) { + e.printStackTrace(); + } + password = sb.toString(); + } + } else { + keyfile = args[1]; + password = new String(System.console().readPassword("Type here (keystrokes hidden): ")); + } + + try { + Symm symm; + FileInputStream fis = new FileInputStream(keyfile); + try { + symm = Symm.obtain(fis); + } finally { + fis.close(); + } + symm.enpass(password, System.out); + System.out.println(); + System.out.flush(); + return; + /* testing code... don't want it exposed + System.out.println(" ******** Testing *********"); + for(int i=0;i<100000;++i) { + System.out.println(args[1]); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + b64.enpass(args[1], baos); + String pass; + System.out.println(pass=new String(baos.toByteArray())); + ByteArrayOutputStream reconstituted = new ByteArrayOutputStream(); + b64.depass(pass, reconstituted); + String r = reconstituted.toString(); + System.out.println(r); + if(!r.equals(args[1])) { + System.err.println("!!!!! STOP - ERROR !!!!!"); + return; + } + System.out.println(); + } + System.out.flush(); + */ + + } catch (IOException e) { + System.err.println("Cannot digest password"); + System.err.println(" \""+ e.getMessage() + '"'); + } +// DO NOT LEAVE THIS METHOD Compiled IN CODE... Do not want looking at passwords on disk too easy +// Jonathan. Oh, well, Deployment services need this behavior. I will put this code in, but leave it undocumented. +// One still needs access to the keyfile to read. +// July 2016 - thought of a tool "CMPass" to regurgitate from properties, but only if allowed. + } else if("regurgitate".equalsIgnoreCase(args[0]) && args.length>2) { + try { + Symm symm; + FileInputStream fis = new FileInputStream(args[2]); + try { + symm = Symm.obtain(fis); + } finally { + fis.close(); + } + boolean isFile = false; + if("-i".equals(args[1]) || (isFile="-f".equals(args[1]))) { + BufferedReader br; + if(isFile) { + if(args.length<4) { + System.err.println("Filename in 4th position"); + return; + } + br = new BufferedReader(new FileReader(args[3])); + } else { + br = new BufferedReader(new InputStreamReader(System.in)); + } + try { + String line; + boolean cont = false; + StringBuffer sb = new StringBuffer(); + JsonOutputStream jw = new JsonOutputStream(System.out); + while((line=br.readLine())!=null) { + if(cont) { + int end; + if((end=line.indexOf('"'))>=0) { + sb.append(line,0,end); + cont=false; + } else { + sb.append(line); + } + } else { + int idx; + if((idx = line.indexOf(' '))>=0 + && (idx = line.indexOf(' ',++idx))>0 + && (idx = line.indexOf('=',++idx))>0 + && (idx = line.indexOf('=',++idx))>0 + ) { + System.out.println(line.substring(0, idx-5)); + int start = idx+2; + int end; + if((end=line.indexOf('"',start))<0) { + end = line.length(); + cont = true; + } + sb.append(line,start,end); + } + } + if(sb.length()>0) { + symm.depass(sb.toString(),jw); + if(!cont) { + System.out.println(); + } + } + System.out.flush(); + sb.setLength(0); + if(!cont) { + jw.resetIndent(); + } + } + } finally { + if(isFile) { + br.close(); + } + } + } else { + symm.depass(args[1], System.out); + } + System.out.println(); + System.out.flush(); + return; + } catch (IOException e) { + System.err.println("Cannot regurgitate password"); + System.err.println(" \""+ e.getMessage() + '"'); + } + } else if("encode64".equalsIgnoreCase(args[0]) && args.length>1) { + try { + Symm.base64.encode(args[1], System.out); + System.out.println(); + System.out.flush(); + return; + } catch (IOException e) { + System.err.println("Cannot encode Base64 with " + args[1]); + System.err.println(" \""+ e.getMessage() + '"'); + } + } else if("decode64".equalsIgnoreCase(args[0]) && args.length>1) { + try { + Symm.base64.decode(args[1], System.out); + System.out.println(); + System.out.flush(); + return; + } catch (IOException e) { + System.err.println("Cannot decode Base64 text from " + args[1]); + System.err.println(" \""+ e.getMessage() + '"'); + } + } else if("encode64url".equalsIgnoreCase(args[0]) && args.length>1) { + try { + Symm.base64url.encode(args[1], System.out); + System.out.println(); + System.out.flush(); + return; + } catch (IOException e) { + System.err.println("Cannot encode Base64url with " + args[1]); + System.err.println(" \""+ e.getMessage() + '"'); + } + } else if("decode64url".equalsIgnoreCase(args[0]) && args.length>1) { + try { + Symm.base64url.decode(args[1], System.out); + System.out.println(); + System.out.flush(); + return; + } catch (IOException e) { + System.err.println("Cannot decode Base64url text from " + args[1]); + System.err.println(" \""+ e.getMessage() + '"'); + } + } else if("md5".equalsIgnoreCase(args[0]) && args.length>1) { + try { + System.out.println(Hash.hashMD5asStringHex(args[1])); + System.out.flush(); + } catch (NoSuchAlgorithmException e) { + System.err.println("Cannot hash MD5 from " + args[1]); + System.err.println(" \""+ e.getMessage() + '"'); + } + return; + } else if("sha256".equalsIgnoreCase(args[0]) && args.length>1) { + try { + if(args.length>2) { + int max = args.length>7?7:args.length; + for(int i=2;i<max;++i) { + int salt = Integer.parseInt(args[i]); + System.out.println(Hash.hashSHA256asStringHex(args[1],salt)); + } + } else { + System.out.println(Hash.hashSHA256asStringHex(args[1])); + } + } catch (NoSuchAlgorithmException e) { + System.err.println("Cannot hash SHA256 text from " + args[1]); + System.err.println(" \""+ e.getMessage() + '"'); + } + System.out.flush(); + return; + } else if("keygen".equalsIgnoreCase(args[0])) { + try { + if(args.length>1) { + File f = new File(args[1]); + FileOutputStream fos = new FileOutputStream(f); + try { + fos.write(Symm.keygen()); + fos.flush(); + } finally { + fos.close(); + Chmod.to400.chmod(f); + } + } else { + // create a Symmetric Key out of same characters found in base64 + System.out.write(Symm.keygen()); + System.out.flush(); + } + return; + } catch (IOException e) { + System.err.println("Cannot create a key " + args[0]); + System.err.println(" \""+ e.getMessage() + '"'); + } + + } else if("passgen".equalsIgnoreCase(args[0])) { + int numDigits; + if(args.length <= 1) { + numDigits = 24; + } else { + numDigits = Integer.parseInt(args[1]); + if(numDigits<8)numDigits = 8; + } + String pass; + boolean noLower,noUpper,noDigits,noSpecial,repeatingChars,missingChars; + do { + pass = Symm.randomGen(numDigits); + missingChars=noLower=noUpper=noDigits=noSpecial=true; + repeatingChars=false; + int c=-1,last; + for(int i=0;i<numDigits;++i) { + last = c; + c = pass.charAt(i); + if(c==last) { + repeatingChars=true; + break; + } + if(noLower) { + noLower=!(c>=0x61 && c<=0x7A); + } + if(noUpper) { + noUpper=!(c>=0x41 && c<=0x5A); + } + if(noDigits) { + noDigits=!(c>=0x30 && c<=0x39); + } + if(noSpecial) { + noSpecial = "+!@#$%^&*(){}[]?:;,.".indexOf(c)<0; + } + + missingChars = (noLower || noUpper || noDigits || noSpecial); + } + } while(missingChars || repeatingChars); + System.out.println(pass.substring(0,numDigits)); + } else if("urlgen".equalsIgnoreCase(args[0])) { + int numDigits; + if(args.length <= 1) { + numDigits = 24; + } else { + numDigits = Integer.parseInt(args[1]); + } + System.out.println(Symm.randomGen(Symm.base64url.codeset, numDigits).substring(0,numDigits)); + } + } else { + System.out.println("Usage: java -jar <this jar> ..."); + System.out.println(" keygen [<keyfile>] (Generates Key on file, or Std Out)"); + System.out.println(" digest [<passwd>|-i|] <keyfile> (Encrypts Password with \"keyfile\""); + System.out.println(" if passwd = -i, will read StdIin"); + System.out.println(" if passwd is blank, will ask securely)"); + System.out.println(" passgen <digits> (Generate Password of given size)"); + System.out.println(" urlgen <digits> (Generate URL field of given size)"); + System.out.println(" csptest (Tests for CSP compatibility)"); + System.out.println(" encode64 <your text> (Encodes to Base64)"); + System.out.println(" decode64 <base64 encoded text> (Decodes from Base64)"); + System.out.println(" encode64url <your text> (Encodes to Base64 URL charset)"); + System.out.println(" decode64url <base64url encoded text> (Decodes from Base64 URL charset)"); + System.out.println(" sha256 <text> <salts(s)> (Digest String into SHA256 Hash)"); + System.out.println(" md5 <text> (Digest String into MD5 Hash)"); + } + String forceExit = access.getProperty("force_exit", null); + if (forceExit == null) { + System.exit(1); + } + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/Connector.java b/cadi/core/src/main/java/org/onap/aaf/cadi/Connector.java new file mode 100644 index 00000000..7f47ce78 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/Connector.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.cadi; + +public interface Connector { + public Lur newLur() throws CadiException; +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/CredVal.java b/cadi/core/src/main/java/org/onap/aaf/cadi/CredVal.java new file mode 100644 index 00000000..4e42a5fb --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/CredVal.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.cadi; + + +/** + * UserPass + * + * The essential interface required by BasicAuth to determine if a given User/Password combination is + * valid. This is done as an interface. + * + * @author Jonathan + */ +public interface CredVal { + public enum Type{PASSWORD}; + /** + * Validate if the User/Password combination matches records + * @param user + * @param pass + * @return + */ + public boolean validate(String user, Type type, byte[] cred, Object state); +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/GetCred.java b/cadi/core/src/main/java/org/onap/aaf/cadi/GetCred.java new file mode 100644 index 00000000..e64f0dd4 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/GetCred.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.cadi; + +public interface GetCred { + byte[] getCred(); +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/Hash.java b/cadi/core/src/main/java/org/onap/aaf/cadi/Hash.java new file mode 100644 index 00000000..6babb4c9 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/Hash.java @@ -0,0 +1,258 @@ +/** + * ============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.cadi; + +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class Hash { + private static char hexDigit[] = "0123456789abcdef".toCharArray(); + +///////////////////////////////// +// MD5 +///////////////////////////////// + /** + * Encrypt MD5 from Byte Array to Byte Array + * @param input + * @return + * @throws NoSuchAlgorithmException + */ + public static byte[] hashMD5 (byte[] input) throws NoSuchAlgorithmException { + // Note: Protect against Multi-thread issues with new MessageDigest + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(input); + return md.digest(); + } + + /** + * Encrypt MD5 from Byte Array to Byte Array + * @param input + * @return + * @throws NoSuchAlgorithmException + */ + public static byte[] hashMD5 (byte[] input, int offset, int length) throws NoSuchAlgorithmException { + // Note: Protect against Multi-thread issues with new MessageDigest + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(input,offset,length); + return md.digest(); + } + + + + /** + * Convenience Function: Encrypt MD5 from String to String Hex representation + * + * @param input + * @return + * @throws NoSuchAlgorithmException + */ + public static String hashMD5asStringHex(String input) throws NoSuchAlgorithmException { + byte[] output = hashMD5(input.getBytes()); + StringBuilder sb = new StringBuilder("0x"); + for (byte b : output) { + sb.append(hexDigit[(b >> 4) & 0x0f]); + sb.append(hexDigit[b & 0x0f]); + } + return sb.toString(); + } + +///////////////////////////////// +// SHA256 +///////////////////////////////// + /** + * SHA256 Hashing + */ + public static byte[] hashSHA256(byte[] input) throws NoSuchAlgorithmException { + // Note: Protect against Multi-thread issues with new MessageDigest + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(input); + return md.digest(); + } + + /** + * SHA256 Hashing + */ + public static byte[] hashSHA256(byte[] input, int offset, int length) throws NoSuchAlgorithmException { + // Note: Protect against Multi-thread issues with new MessageDigest + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(input,offset,length); + return md.digest(); + } + + /** + * Convenience Function: Hash from String to String Hex representation + * + * @param input + * @return + * @throws NoSuchAlgorithmException + */ + public static String hashSHA256asStringHex(String input) throws NoSuchAlgorithmException { + return toHex(hashSHA256(input.getBytes())); + } + + /** + * Convenience Function: Hash from String to String Hex representation + * + * @param input + * @return + * @throws NoSuchAlgorithmException + */ + public static String hashSHA256asStringHex(String input, int salt) throws NoSuchAlgorithmException { + byte[] in = input.getBytes(); + ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE + in.length); + bb.putInt(salt); + bb.put(input.getBytes()); + return toHex(Hash.hashSHA256(bb.array())); + } + + /** + * Compare two byte arrays for equivalency + * @param ba1 + * @param ba2 + * @return + */ + public static boolean isEqual(byte ba1[], byte ba2[]) { + if(ba1.length!=ba2.length)return false; + for(int i = 0;i<ba1.length; ++i) { + if(ba1[i]!=ba2[i])return false; + } + return true; + } + + public static int compareTo(byte[] a, byte[] b) { + int end = Math.min(a.length, b.length); + int compare = 0; + for(int i=0;compare == 0 && i<end;++i) { + compare = a[i]-b[i]; + } + if(compare==0)compare=a.length-b.length; + return compare; + } + + public static String toHexNo0x(byte[] ba) { + StringBuilder sb = new StringBuilder(); + for (byte b : ba) { + sb.append(hexDigit[(b >> 4) & 0x0f]); + sb.append(hexDigit[b & 0x0f]); + } + return sb.toString(); + } + + public static String toHex(byte[] ba) { + StringBuilder sb = new StringBuilder("0x"); + for (byte b : ba) { + sb.append(hexDigit[(b >> 4) & 0x0f]); + sb.append(hexDigit[b & 0x0f]); + } + return sb.toString(); + } + + public static String toHex(byte[] ba, int start, int length) { + StringBuilder sb = new StringBuilder("0x"); + for (int i=start;i<length;++i) { + sb.append(hexDigit[(ba[i] >> 4) & 0x0f]); + sb.append(hexDigit[ba[i] & 0x0f]); + } + return sb.toString(); + } + + + public static byte[] fromHex(String s) throws CadiException{ + if(!s.startsWith("0x")) { + throw new CadiException("HexString must start with \"0x\""); + } + boolean high = true; + int c; + byte b; + byte[] ba = new byte[(s.length()-2)/2]; + int idx; + for(int i=2;i<s.length();++i) { + c = s.charAt(i); + if(c>=0x30 && c<=0x39) { + b=(byte)(c-0x30); + } else if(c>=0x61 && c<=0x66) { + b=(byte)(c-0x57); // account for "A" + } else if(c>=0x41 && c<=0x46) { + b=(byte)(c-0x37); + } else { + throw new CadiException("Invalid char '" + c + "' in HexString"); + } + idx = (i-2)/2; + if(high) { + ba[idx]=(byte)(b<<4); + high = false; + } else { + ba[idx]|=b; + high = true; + } + } + return ba; + } + + /** + * Does not expect to start with "0x" + * if Any Character doesn't match, it returns null; + * + * @param s + * @return + */ + public static byte[] fromHexNo0x(String s) { + int c; + byte b; + byte[] ba; + boolean high; + int start; + if(s.length()%2==0) { + ba = new byte[s.length()/2]; + high=true; + start=0; + } else { + ba = new byte[(s.length()/2)+1]; + high = false; + start=1; + } + int idx; + for(int i=start;i<s.length();++i) { + c = s.charAt((i-start)); + if(c>=0x30 && c<=0x39) { + b=(byte)(c-0x30); + } else if(c>=0x61 && c<=0x66) { + b=(byte)(c-0x57); // account for "A" + } else if(c>=0x41 && c<=0x46) { + b=(byte)(c-0x37); + } else { + return null; + } + idx = i/2; + if(high) { + ba[idx]=(byte)(b<<4); + high = false; + } else { + ba[idx]|=b; + high = true; + } + } + return ba; + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/Locator.java b/cadi/core/src/main/java/org/onap/aaf/cadi/Locator.java new file mode 100644 index 00000000..22258d12 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/Locator.java @@ -0,0 +1,36 @@ +/** + * ============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.cadi; + +public interface Locator<T> { + public T get(Locator.Item item) throws LocatorException; + public boolean hasItems(); + public void invalidate(Locator.Item item) throws LocatorException; + public Locator.Item best() throws LocatorException; + public Item first() throws LocatorException; + public Item next(Item item) throws LocatorException; + public boolean refresh(); + public void destroy(); + + public interface Item {} + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/LocatorException.java b/cadi/core/src/main/java/org/onap/aaf/cadi/LocatorException.java new file mode 100644 index 00000000..f14fba70 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/LocatorException.java @@ -0,0 +1,46 @@ +/** + * ============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.cadi; + +public class LocatorException extends Exception { + /** + * + */ + private static final long serialVersionUID = -4267929804321134469L; + + public LocatorException(String arg0) { + super(arg0); + } + + public LocatorException(Throwable arg0) { + super(arg0); + } + + public LocatorException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + + public LocatorException(CharSequence cs) { + super(cs.toString()); + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/Lur.java b/cadi/core/src/main/java/org/onap/aaf/cadi/Lur.java new file mode 100644 index 00000000..fd73d00b --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/Lur.java @@ -0,0 +1,92 @@ +/** + * ============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.cadi; + +import java.security.Principal; +import java.util.List; + + + +/** + * LUR: Local User Registry + * + * Concept by Robert Garskof, Implementation by Jonathan Gathman + * + * Where we can keep local copies of users and roles for faster Authorization when asked. + * + * Note: Author cannot resist the mental image of using a Fishing Lure to this LUR pattern + * + * @author Jonathan + * + */ +public interface Lur { + /** + * Allow the Lur, which has correct Permission access, to create and hand back. + */ + public Permission createPerm(String p); + + /** + * Fish for Principals in a Pond + * + * or more boringly, is the User identified within a named collection representing permission. + * + * @param principalName + * @return + */ + public boolean fish(Principal bait, Permission pond); + + /** + * Fish all the Principals out a Pond + * + * For additional humor, pronounce the following with a Southern Drawl, "FishOil" + * + * or more boringly, load the List with Permissions found for Principal + * + * @param principalName + * @return + */ + public void fishAll(Principal bait, List<Permission> permissions); + + /** + * Allow implementations to disconnect, or cleanup resources if unneeded + */ + public void destroy(); + + /** + * Does this LUR handle this pond exclusively? Important for EpiLUR to determine whether + * to try another (more expensive) LUR + * @param pond + * @return + */ + public boolean handlesExclusively(Permission pond); + + /** + * Does the LUR support a particular kind of Principal + * This can be used to check name's domain, like above, or Principal type + */ + public boolean handles(Principal principal); + + /** + * Clear: Clear any Caching, if exists + */ + public void clear(Principal p, StringBuilder report); +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/Permission.java b/cadi/core/src/main/java/org/onap/aaf/cadi/Permission.java new file mode 100644 index 00000000..f8061290 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/Permission.java @@ -0,0 +1,28 @@ +/** + * ============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.cadi; + +public interface Permission { + public String permType(); + public String getKey(); + public boolean match(Permission p); +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/PropAccess.java b/cadi/core/src/main/java/org/onap/aaf/cadi/PropAccess.java new file mode 100644 index 00000000..07e9b592 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/PropAccess.java @@ -0,0 +1,387 @@ +/** + * ============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.cadi; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map.Entry; + +import org.onap.aaf.cadi.config.Config; +import org.onap.aaf.cadi.config.SecurityInfo; + +import java.util.Properties; + +public class PropAccess implements Access { + private static final SimpleDateFormat iso8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + + public static Level DEFAULT = Level.AUDIT; + + private Symm symm; + private int level; + private Properties props; + private List<String> recursionProtection = null; + private LogIt logIt; + private String name; + + public PropAccess() { + logIt = new StreamLogIt(System.out); + init(null); + } + + /** + * This Constructor soly exists to instantiate Servlet Context Based Logging that will call "init" later. + * @param sc + */ + protected PropAccess(Object o) { + logIt = new StreamLogIt(System.out); + props = new Properties(); + } + + public PropAccess(String ... args) { + this(System.out,args); + } + + public PropAccess(PrintStream ps, String[] args) { + logIt = new StreamLogIt(ps==null?System.out:ps); + Properties nprops=new Properties(); + int eq; + for(String arg : args) { + if((eq=arg.indexOf('='))>0) { + nprops.setProperty(arg.substring(0, eq),arg.substring(eq+1)); + } + } + init(nprops); + } + + public PropAccess(LogIt logit, String[] args) { + logIt = logit; + } + + public PropAccess(Properties p) { + this(System.out,p); + } + + public PropAccess(PrintStream ps, Properties p) { + logIt = new StreamLogIt(ps==null?System.out:ps); + init(p); + } + + protected void init(Properties p) { + // Make sure these two are set before any changes in Logging + name = "cadi"; + level=DEFAULT.maskOf(); + + props = new Properties(); + // First, load related System Properties + for(Entry<Object,Object> es : System.getProperties().entrySet()) { + String key = es.getKey().toString(); + for(String start : new String[] {"cadi_","aaf_","cm_"}) { + if(key.startsWith(start)) { + props.put(key, es.getValue()); + } + } + } + // Second, overlay or fill in with Passed in Props + if(p!=null) { + props.putAll(p); + } + + // Third, load any Chained Property Files + load(props.getProperty(Config.CADI_PROP_FILES)); + + String sLevel = props.getProperty(Config.CADI_LOGLEVEL); + if(sLevel!=null) { + level=Level.valueOf(sLevel).maskOf(); + } + // Setup local Symmetrical key encryption + if(symm==null) { + try { + symm = Symm.obtain(this); + } catch (CadiException e) { + System.err.append("FATAL ERROR: Cannot obtain Key Information."); + e.printStackTrace(System.err); + System.exit(1); + } + } + + name = props.getProperty(Config.CADI_LOGNAME, name); + + specialConversions(); + } + + private void specialConversions() { + // Critical - if no Security Protocols set, then set it. We'll just get messed up if not + if(props.get(Config.CADI_PROTOCOLS)==null) { + props.setProperty(Config.CADI_PROTOCOLS, SecurityInfo.HTTPS_PROTOCOLS_DEFAULT); + } + + Object temp; + temp=props.get(Config.CADI_PROTOCOLS); + if(props.get(Config.HTTPS_PROTOCOLS)==null && temp!=null) { + props.put(Config.HTTPS_PROTOCOLS, temp); + } + + if(temp!=null) { + if("1.7".equals(System.getProperty("java.specification.version")) + && (temp==null || (temp instanceof String && ((String)temp).contains("TLSv1.2")))) { + System.setProperty(Config.HTTPS_CIPHER_SUITES, Config.HTTPS_CIPHER_SUITES_DEFAULT); + } + } + } + + private void load(String cadi_prop_files) { + if(cadi_prop_files==null) { + return; + } + String prevKeyFile = props.getProperty(Config.CADI_KEYFILE); + int prev = 0, end = cadi_prop_files.length(); + int idx; + String filename; + while(prev<end) { + idx = cadi_prop_files.indexOf(File.pathSeparatorChar,prev); + if(idx<0) { + idx = end; + } + File file = new File(filename=cadi_prop_files.substring(prev,idx)); + if(file.exists()) { + printf(Level.INIT,"Loading CADI Properties from %s",file.getAbsolutePath()); + try { + FileInputStream fis = new FileInputStream(file); + try { + props.load(fis); + // Recursively Load + String chainProp = props.getProperty(Config.CADI_PROP_FILES); + if(chainProp!=null) { + if(recursionProtection==null) { + recursionProtection = new ArrayList<String>(); + recursionProtection.add(cadi_prop_files); + } + if(!recursionProtection.contains(chainProp)) { + recursionProtection.add(chainProp); + load(chainProp); // recurse + } + } + } finally { + fis.close(); + } + } catch (Exception e) { + log(e,filename,"cannot be opened"); + } + } else { + printf(Level.WARN,"Warning: recursive CADI Property %s does not exist",file.getAbsolutePath()); + } + prev = idx+1; + } + + // Trim + for(Entry<Object, Object> es : props.entrySet()) { + Object value = es.getValue(); + if(value instanceof String) { + String trim = ((String)value).trim(); + if(trim!=value) { // Yes, I want OBJECT equals + props.setProperty((String)es.getKey(), trim); + } + } + } + // Reset Symm if Keyfile Changes: + String newKeyFile = props.getProperty(Config.CADI_KEYFILE); + if((prevKeyFile!=null && newKeyFile!=null) || (newKeyFile!=null && !newKeyFile.equals(prevKeyFile))) { + try { + symm = Symm.obtain(this); + } catch (CadiException e) { + System.err.append("FATAL ERROR: Cannot obtain Key Information."); + e.printStackTrace(System.err); + System.exit(1); + } + + prevKeyFile=newKeyFile; + } + + String loglevel = props.getProperty(Config.CADI_LOGLEVEL); + if(loglevel!=null) { + try { + level=Level.valueOf(loglevel).maskOf(); + } catch (IllegalArgumentException e) { + printf(Level.ERROR,"%s=%s is an Invalid Log Level",Config.CADI_LOGLEVEL,loglevel); + } + } + + specialConversions(); + } + + @Override + public void load(InputStream is) throws IOException { + props.load(is); + load(props.getProperty(Config.CADI_PROP_FILES)); + } + + @Override + public void log(Level level, Object ... elements) { + if(willLog(level)) { + logIt.push(level,elements); + } + } + + protected StringBuilder buildMsg(Level level, Object[] elements) { + StringBuilder sb = new StringBuilder(iso8601.format(new Date())); + sb.append(' '); + sb.append(level.name()); + sb.append(" ["); + sb.append(name); + + int end = elements.length; + if(end<=0) { + sb.append("] "); + } else { + int idx = 0; + if(elements[idx] instanceof Integer) { + sb.append('-'); + sb.append(elements[idx]); + ++idx; + } + sb.append("] "); + String s; + boolean first = true; + for(Object o : elements) { + if(o!=null) { + s=o.toString(); + if(first) { + first = false; + } else { + int l = s.length(); + if(l>0) { + switch(s.charAt(l-1)) { + case ' ': + break; + default: + sb.append(' '); + } + } + } + sb.append(s); + } + } + } + return sb; + } + + @Override + public void log(Exception e, Object... elements) { + log(Level.ERROR,e.getMessage(),elements); + e.printStackTrace(System.err); + } + + @Override + public void printf(Level level, String fmt, Object... elements) { + if(willLog(level)) { + log(level,String.format(fmt, elements)); + } + } + + @Override + public void setLogLevel(Level level) { + this.level = level.maskOf(); + } + + @Override + public boolean willLog(Level level) { + return level.inMask(this.level); + } + + @Override + public ClassLoader classLoader() { + return ClassLoader.getSystemClassLoader(); + } + + @Override + public String getProperty(String tag, String def) { + return props.getProperty(tag,def); + } + + @Override + public String decrypt(String encrypted, boolean anytext) throws IOException { + return (encrypted!=null && (anytext==true || encrypted.startsWith(Symm.ENC))) + ? symm.depass(encrypted) + : encrypted; + } + + public String encrypt(String unencrypted) throws IOException { + return Symm.ENC+symm.enpass(unencrypted); + } + + ////////////////// + // Additional + ////////////////// + public String getProperty(String tag) { + return props.getProperty(tag); + } + + + public Properties getProperties() { + return props; + } + + public void setProperty(String tag, String value) { + if(value!=null) { + props.put(tag, value); + if(Config.CADI_KEYFILE.equals(tag)) { + // reset decryption too + try { + symm = Symm.obtain(this); + } catch (CadiException e) { + System.err.append("FATAL ERROR: Cannot obtain Key Information."); + e.printStackTrace(System.err); + System.exit(1); + } + } + } + } + + public interface LogIt { + public void push(Level level, Object ... elements) ; + } + + private class StreamLogIt implements LogIt { + private PrintStream ps; + + public StreamLogIt(PrintStream ps) { + this.ps = ps; + } + @Override + public void push(Level level, Object ... elements) { + ps.println(buildMsg(level,elements)); + ps.flush(); + } + + } + + public void set(LogIt logit) { + logIt = logit; + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/Revalidator.java b/cadi/core/src/main/java/org/onap/aaf/cadi/Revalidator.java new file mode 100644 index 00000000..125ac24c --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/Revalidator.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.cadi; + + +public interface Revalidator<TRANS> { + /** + * Re-Validate Credential + * + * @param prin + * @return + */ + public CachedPrincipal.Resp revalidate(TRANS trans, CachedPrincipal prin); + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/SecuritySetter.java b/cadi/core/src/main/java/org/onap/aaf/cadi/SecuritySetter.java new file mode 100644 index 00000000..31563017 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/SecuritySetter.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.cadi; + + +/** + * Apply any particular security mechanism + * + * This allows the definition of various mechanisms involved outside of DRcli jars + * + * @author Jonathan + * + */ +public interface SecuritySetter<CT> { + public String getID(); + + public void setSecurity(CT client) throws CadiException; + + /** + * Returns number of bad logins registered + * @param respCode + * @return + */ + public int setLastResponse(int respCode); +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/ServletContextAccess.java b/cadi/core/src/main/java/org/onap/aaf/cadi/ServletContextAccess.java new file mode 100644 index 00000000..38a01a09 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/ServletContextAccess.java @@ -0,0 +1,67 @@ +/** + * ============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.cadi; + +import java.util.Enumeration; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; + +public class ServletContextAccess extends PropAccess { + + private ServletContext context; + + public ServletContextAccess(FilterConfig filterConfig) { + super(filterConfig); // protected constructor... does not have "init" called. + context = filterConfig.getServletContext(); + + for(Enumeration<?> en = filterConfig.getInitParameterNames();en.hasMoreElements();) { + String name = (String)en.nextElement(); + setProperty(name, filterConfig.getInitParameter(name)); + } + init(getProperties()); + } + + /* (non-Javadoc) + * @see org.onap.aaf.cadi.PropAccess#log(org.onap.aaf.cadi.Access.Level, java.lang.Object[]) + */ + @Override + public void log(Level level, Object... elements) { + if(willLog(level)) { + StringBuilder sb = buildMsg(level, elements); + context.log(sb.toString()); + } + } + + /* (non-Javadoc) + * @see org.onap.aaf.cadi.PropAccess#log(java.lang.Exception, java.lang.Object[]) + */ + @Override + public void log(Exception e, Object... elements) { + StringBuilder sb = buildMsg(Level.ERROR, elements); + context.log(sb.toString(),e); + } + + public ServletContext context() { + return context; + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/Symm.java b/cadi/core/src/main/java/org/onap/aaf/cadi/Symm.java new file mode 100644 index 00000000..095f6e97 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/Symm.java @@ -0,0 +1,854 @@ +/** + * ============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.cadi; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Random; + +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; + +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.config.Config; + +/** + * Key Conversion, primarily "Base64" + * + * Base64 is required for "Basic Authorization", which is an important part of the overall CADI Package. + * + * Note: This author found that there is not a "standard" library for Base64 conversion within Java. + * The source code implementations available elsewhere were surprisingly inefficient, requiring, for + * instance, multiple string creation, on a transaction pass. Integrating other packages that might be + * efficient enough would put undue Jar File Dependencies given this Framework should have none-but-Java + * dependencies. + * + * The essential algorithm is good for a symmetrical key system, as Base64 is really just + * a symmetrical key that everyone knows the values. + * + * This code is quite fast, taking about .016 ms for encrypting, decrypting and even .08 for key + * generation. The speed quality, especially of key generation makes this a candidate for a short term token + * used for identity. + * + * It may be used to easily avoid placing Clear-Text passwords in configurations, etc. and contains + * supporting functions such as 2048 keyfile generation (see keygen). This keyfile should, of course, + * be set to "400" (Unix) and protected as any other mechanism requires. + * + * However, this algorithm has not been tested against hackers. Until such a time, utilize more tested + * packages to protect Data, especially sensitive data at rest (long term). + * + * @author Jonathan + * + */ +public class Symm { + private static final byte[] DOUBLE_EQ = new byte[] {'=','='}; + public static final String ENC = "enc:"; + private static final SecureRandom random = new SecureRandom(); + + public final char[] codeset; + private final int splitLinesAt; + private final String encoding; + private final Convert convert; + private final boolean endEquals; + private byte[] keyBytes = null; + //Note: AES Encryption is not Thread Safe. It is Synchronized + //private AES aes = null; // only initialized from File, and only if needed for Passwords + + /** + * This is the standard base64 Key Set. + * RFC 2045 + */ + public static final Symm base64 = new Symm( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray() + ,76, Config.UTF_8,true); + + public static final Symm base64noSplit = new Symm( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray() + ,Integer.MAX_VALUE, Config.UTF_8,true); + + /** + * This is the standard base64 set suitable for URLs and Filenames + * RFC 4648 + */ + public static final Symm base64url = new Symm( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".toCharArray() + ,76, Config.UTF_8,true); + + /** + * A Password set, using US-ASCII + * RFC 4648 + */ + public static final Symm encrypt = new Symm(base64url.codeset,1024, "US-ASCII", false); + private static final byte[] EMPTY = new byte[0]; + + /** + * A typical set of Password Chars + * Note, this is too large to fit into the algorithm. Only use with PassGen + */ + private static char passChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+!@#$%^&*(){}[]?:;,.".toCharArray(); + + + + /** + * Use this to create special case Case Sets and/or Line breaks + * + * If you don't know why you need this, use the Singleton Method + * + * @param codeset + * @param split + */ + public Symm(char[] codeset, int split, String charset, boolean useEndEquals) { + this.codeset = codeset; + splitLinesAt = split; + encoding = charset; + endEquals = useEndEquals; + char prev = 0, curr=0, first = 0; + int offset=Integer.SIZE; // something that's out of range for integer array + + // There can be time efficiencies gained when the underlying keyset consists mainly of ordered + // data (i.e. abcde...). Therefore, we'll quickly analyze the keyset. If it proves to have + // too much entropy, the "Unordered" algorithm, which is faster in such cases is used. + ArrayList<int[]> la = new ArrayList<int[]>(); + for(int i=0;i<codeset.length;++i) { + curr = codeset[i]; + if(prev+1==curr) { // is next character in set + prev = curr; + } else { + if(offset!=Integer.SIZE) { // add previous range + la.add(new int[]{first,prev,offset}); + } + first = prev = curr; + offset = curr-i; + } + } + la.add(new int[]{first,curr,offset}); + if(la.size()>codeset.length/3) { + convert = new Unordered(codeset); + } else { // too random to get speed enhancement from range algorithm + int[][] range = new int[la.size()][]; + la.toArray(range); + convert = new Ordered(range); + } + } + + public Symm copy(int lines) { + return new Symm(codeset,lines,encoding,endEquals); + } + + // Only used by keygen, which is intentionally randomized. Therefore, always use unordered + private Symm(char[] codeset, Symm parent) { + this.codeset = codeset; + splitLinesAt = parent.splitLinesAt; + endEquals = parent.endEquals; + encoding = parent.encoding; + convert = new Unordered(codeset); + } + + /** + * Obtain the base64() behavior of this class, for use in standard BASIC AUTH mechanism, etc. + * @return + */ + @Deprecated + public static final Symm base64() { + return base64; + } + + /** + * Obtain the base64() behavior of this class, for use in standard BASIC AUTH mechanism, etc. + * No Line Splitting + * @return + */ + @Deprecated + public static final Symm base64noSplit() { + return base64noSplit; + } + + /** + * Obtain the base64 "URL" behavior of this class, for use in File Names, etc. (no "/") + */ + @Deprecated + public static final Symm base64url() { + return base64url; + } + + /** + * Obtain a special ASCII version for Scripting, with base set of base64url use in File Names, etc. (no "/") + */ + public static final Symm baseCrypt() { + return encrypt; + } + + public <T> T exec(SyncExec<T> exec) throws Exception { + synchronized(ENC) { + if(keyBytes == null) { + keyBytes = new byte[AES.AES_KEY_SIZE/8]; + int offset = (Math.abs(codeset[0])+47)%(codeset.length-keyBytes.length); + for(int i=0;i<keyBytes.length;++i) { + keyBytes[i] = (byte)codeset[i+offset]; + } + } + } + return exec.exec(new AES(keyBytes,0,keyBytes.length)); + } + + public interface Encryption { + public CipherOutputStream outputStream(OutputStream os, boolean encrypt); + public CipherInputStream inputStream(InputStream is, boolean encrypt); + } + + public static interface SyncExec<T> { + public T exec(Encryption enc) throws IOException, Exception; + } + + public byte[] encode(byte[] toEncrypt) throws IOException { + if(toEncrypt==null) { + return EMPTY; + } else { + ByteArrayOutputStream baos = new ByteArrayOutputStream((int)(toEncrypt.length*1.25)); + encode(new ByteArrayInputStream(toEncrypt),baos); + return baos.toByteArray(); + } + } + + public byte[] decode(byte[] encrypted) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream((int)(encrypted.length*1.25)); + decode(new ByteArrayInputStream(encrypted),baos); + return baos.toByteArray(); + } + + /** + * Helper function for String API of "Encode" + * use "getBytes" with appropriate char encoding, etc. + * + * @param str + * @return + * @throws IOException + */ + public String encode(String str) throws IOException { + byte[] array; + boolean useDefaultEncoding = false; + try { + array = str.getBytes(encoding); + } catch (IOException e) { + array = str.getBytes(); // take default + useDefaultEncoding = true; + } + // Calculate expected size to avoid any buffer expansion copies within the ByteArrayOutput code + ByteArrayOutputStream baos = new ByteArrayOutputStream((int)(array.length*1.363)); // account for 4 bytes for 3 and a byte or two more + + encode(new ByteArrayInputStream(array),baos); + if (useDefaultEncoding) { + return baos.toString(); + } + return baos.toString(encoding); + } + + /** + * Helper function for the String API of "Decode" + * use "getBytes" with appropriate char encoding, etc. + * @param str + * @return + * @throws IOException + */ + public String decode(String str) throws IOException { + byte[] array; + boolean useDefaultEncoding = false; + try { + array = str.getBytes(encoding); + } catch (IOException e) { + array = str.getBytes(); // take default + useDefaultEncoding = true; + } + // Calculate expected size to avoid any buffer expansion copies within the ByteArrayOutput code + ByteArrayOutputStream baos = new ByteArrayOutputStream((int)(array.length*.76)); // Decoding is 3 bytes for 4. Allocate slightly more than 3/4s + decode(new ByteArrayInputStream(array), baos); + if (useDefaultEncoding) { + return baos.toString(); + } + return baos.toString(encoding); + } + + /** + * Convenience Function + * + * encode String into InputStream and call encode(InputStream, OutputStream) + * + * @param string + * @param out + * @throws IOException + */ + public void encode(String string, OutputStream out) throws IOException { + encode(new ByteArrayInputStream(string.getBytes()),out); + } + + /** + * Convenience Function + * + * encode String into InputStream and call decode(InputStream, OutputStream) + * + * @param string + * @param out + * @throws IOException + */ + public void decode(String string, OutputStream out) throws IOException { + decode(new ByteArrayInputStream(string.getBytes()),out); + } + + public void encode(InputStream is, OutputStream os, byte[] prefix) throws IOException { + os.write(prefix); + encode(is,os); + } + + /** + * encode InputStream onto Output Stream + * + * @param is + * @param estimate + * @return + * @throws IOException + */ + public void encode(InputStream is, OutputStream os) throws IOException { + // StringBuilder sb = new StringBuilder((int)(estimate*1.255)); // try to get the right size of StringBuilder from start.. slightly more than 1.25 times + int prev=0; + int read, idx=0, line=0; + boolean go; + do { + read = is.read(); + if(go = read>=0) { + if(line>=splitLinesAt) { + os.write('\n'); + line = 0; + } + switch(++idx) { // 1 based reading, slightly faster ++ + case 1: // ptr is the first 6 bits of read + os.write(codeset[read>>2]); + prev = read; + break; + case 2: // ptr is the last 2 bits of prev followed by the first 4 bits of read + os.write(codeset[((prev & 0x03)<<4) | (read>>4)]); + prev = read; + break; + default: //(3+) + // Char 1 is last 4 bits of prev plus the first 2 bits of read + // Char 2 is the last 6 bits of read + os.write(codeset[(((prev & 0xF)<<2) | (read>>6))]); + if(line==splitLinesAt) { // deal with line splitting for two characters + os.write('\n'); + line=0; + } + os.write(codeset[(read & 0x3F)]); + ++line; + idx = 0; + prev = 0; + } + ++line; + } else { // deal with any remaining bits from Prev, then pad + switch(idx) { + case 1: // just the last 2 bits of prev + os.write(codeset[(prev & 0x03)<<4]); + if(endEquals)os.write(DOUBLE_EQ); + break; + case 2: // just the last 4 bits of prev + os.write(codeset[(prev & 0xF)<<2]); + if(endEquals)os.write('='); + break; + } + idx = 0; + } + + } while(go); + } + + public void decode(InputStream is, OutputStream os, int skip) throws IOException { + is.skip(skip); + decode(is,os); + } + + /** + * Decode InputStream onto OutputStream + * @param is + * @param os + * @throws IOException + */ + public void decode(InputStream is, OutputStream os) throws IOException { + int read, idx=0; + int prev=0, index; + while((read = is.read())>=0) { + index = convert.convert(read); + if(index>=0) { + switch(++idx) { // 1 based cases, slightly faster ++ + case 1: // index goes into first 6 bits of prev + prev = index<<2; + break; + case 2: // write second 2 bits of into prev, write byte, last 4 bits go into prev + os.write((byte)(prev|(index>>4))); + prev = index<<4; + break; + case 3: // first 4 bits of index goes into prev, write byte, last 2 bits go into prev + os.write((byte)(prev|(index>>2))); + prev = index<<6; + break; + default: // (3+) | prev and last six of index + os.write((byte)(prev|(index&0x3F))); + idx = prev = 0; + } + } + }; + os.flush(); + } + + /** + * Interface to allow this class to choose which algorithm to find index of character in Key + * @author Jonathan + * + */ + private interface Convert { + public int convert(int read) throws IOException; + } + + /** + * Ordered uses a range of orders to compare against, rather than requiring the investigation + * of every character needed. + * @author Jonathan + * + */ + private static final class Ordered implements Convert { + private int[][] range; + public Ordered(int[][] range) { + this.range = range; + } + public int convert(int read) throws IOException { + switch(read) { + case -1: + case '=': + case '\n': + return -1; + } + for(int i=0;i<range.length;++i) { + if(read >= range[i][0] && read<=range[i][1]) { + return read-range[i][2]; + } + } + throw new IOException("Unacceptable Character in Stream"); + } + } + + /** + * Unordered, i.e. the key is purposely randomized, simply has to investigate each character + * until we find a match. + * @author Jonathan + * + */ + private static final class Unordered implements Convert { + private char[] codec; + public Unordered(char[] codec) { + this.codec = codec; + } + public int convert(int read) throws IOException { + switch(read) { + case -1: + case '=': + case '\n': + return -1; + } + for(int i=0;i<codec.length;++i) { + if(codec[i]==read)return i; + } + // don't give clue in Encryption mode + throw new IOException("Unacceptable Character in Stream"); + } + } + + /** + * Generate a 2048 based Key from which we extract our code base + * + * @return + * @throws IOException + */ + public static byte[] keygen() throws IOException { + byte inkey[] = new byte[0x600]; + new SecureRandom().nextBytes(inkey); + ByteArrayOutputStream baos = new ByteArrayOutputStream(0x800); + base64url.encode(new ByteArrayInputStream(inkey), baos); + return baos.toByteArray(); + } + + // A class allowing us to be less predictable about significant digits (i.e. not picking them up from the + // beginning, and not picking them up in an ordered row. Gives a nice 2048 with no visible patterns. + private class Obtain { + private int last; + private int skip; + private int length; + private byte[] key; + + private Obtain(Symm b64, byte[] key) { + skip = Math.abs(key[key.length-13]%key.length); + if((key.length&0x1) == (skip&0x1)) { // if both are odd or both are even + ++skip; + } + length = b64.codeset.length; + last = 17+length%59; // never start at beginning + this.key = key; + } + + private int next() { + return Math.abs(key[(++last*skip)%key.length])%length; + } + }; + + /** + * Obtain a Symm from "keyfile" (Config.KEYFILE) property + * + * @param acesss + * @return + * @throws IOException + * @throws CadiException + */ + public static Symm obtain(Access access) throws CadiException { + Symm symm = Symm.baseCrypt(); + + String keyfile = access.getProperty(Config.CADI_KEYFILE,null); + if(keyfile!=null) { + File file = new File(keyfile); + try { + access.log(Level.INIT, Config.CADI_KEYFILE,"points to",file.getCanonicalPath()); + } catch (IOException e1) { + access.log(Level.INIT, Config.CADI_KEYFILE,"points to",file.getAbsolutePath()); + } + if(file.exists()) { + try { + FileInputStream fis = new FileInputStream(file); + try { + symm = Symm.obtain(fis); + } finally { + try { + fis.close(); + } catch (IOException e) { + } + } + } catch (IOException e) { + access.log(e, "Cannot load keyfile"); + } + } else { + String filename; + try { + filename = file.getCanonicalPath(); + } catch (IOException e) { + filename = file.getAbsolutePath(); + } + throw new CadiException("ERROR: " + filename + " does not exist!"); + } + } + return symm; + } + /** + * Create a new random key + */ + public Symm obtain() throws IOException { + byte inkey[] = new byte[0x800]; + new SecureRandom().nextBytes(inkey); + return obtain(inkey); + } + + /** + * Obtain a Symm from 2048 key from a String + * + * @param key + * @return + * @throws IOException + */ + public static Symm obtain(String key) throws IOException { + return obtain(new ByteArrayInputStream(key.getBytes())); + } + + /** + * Obtain a Symm from 2048 key from a Stream + * + * @param is + * @return + * @throws IOException + */ + public static Symm obtain(InputStream is) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + base64url.decode(is, baos); + } catch (IOException e) { + // don't give clue + throw new IOException("Invalid Key"); + } + byte[] bkey = baos.toByteArray(); + if(bkey.length<0x88) { // 2048 bit key + throw new IOException("Invalid key"); + } + return baseCrypt().obtain(bkey); + } + + /** + * Convenience for picking up Keyfile + * + * @param f + * @return + * @throws IOException + */ + public static Symm obtain(File f) throws IOException { + FileInputStream fis = new FileInputStream(f); + try { + return obtain(fis); + } finally { + fis.close(); + } + } + /** + * Decrypt into a String + * + * Convenience method + * + * @param password + * @return + * @throws IOException + */ + public String enpass(String password) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + enpass(password,baos); + return new String(baos.toByteArray()); + } + + /** + * Create an encrypted password, making sure that even short passwords have a minimum length. + * + * @param password + * @param os + * @throws IOException + */ + public void enpass(final String password, final OutputStream os) throws IOException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + byte[] bytes = password.getBytes(); + if(this.getClass().getSimpleName().startsWith("base64")) { // don't expose randomization + dos.write(bytes); + } else { + + Random r = new SecureRandom(); + int start = 0; + byte b; + for(int i=0;i<3;++i) { + dos.writeByte(b=(byte)r.nextInt()); + start+=Math.abs(b); + } + start%=0x7; + for(int i=0;i<start;++i) { + dos.writeByte(r.nextInt()); + } + dos.writeInt((int)System.currentTimeMillis()); + int minlength = Math.min(0x9,bytes.length); + dos.writeByte(minlength); // expect truncation + if(bytes.length<0x9) { + for(int i=0;i<bytes.length;++i) { + dos.writeByte(r.nextInt()); + dos.writeByte(bytes[i]); + } + // make sure it's long enough + for(int i=bytes.length;i<0x9;++i) { + dos.writeByte(r.nextInt()); + } + } else { + dos.write(bytes); + } + } + + // 7/21/2016 Jonathan add AES Encryption to the mix + try { + exec(new SyncExec<Void>() { + @Override + public Void exec(Encryption enc) throws Exception { + CipherInputStream cis = enc.inputStream(new ByteArrayInputStream(baos.toByteArray()), true); + try { + encode(cis,os); + } finally { + os.flush(); + cis.close(); + } + return null; + } + }); + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new IOException(e); + } + } + + /** + * Decrypt a password into a String + * + * Convenience method + * + * @param password + * @return + * @throws IOException + */ + public String depass(String password) throws IOException { + if(password==null)return null; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + depass(password,baos); + return new String(baos.toByteArray()); + } + + /** + * Decrypt a password + * + * Skip Symm.ENC + * + * @param password + * @param os + * @return + * @throws IOException + */ + public long depass(final String password, final OutputStream os) throws IOException { + int offset = password.startsWith(ENC)?4:0; + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final ByteArrayInputStream bais = new ByteArrayInputStream(password.getBytes(),offset,password.length()-offset); + try { + exec(new SyncExec<Void>() { + @Override + public Void exec(Encryption enc) throws IOException { + CipherOutputStream cos = enc.outputStream(baos, false); + decode(bais,cos); + cos.close(); // flush + return null; + } + }); + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new IOException(e); + } + + byte[] bytes = baos.toByteArray(); + DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes)); + long time; + if(this.getClass().getSimpleName().startsWith("base64")) { // don't expose randomization + os.write(bytes); + time = 0L; + } else { + int start=0; + for(int i=0;i<3;++i) { + start+=Math.abs(dis.readByte()); + } + start%=0x7; + for(int i=0;i<start;++i) { + dis.readByte(); + } + time = (dis.readInt() & 0xFFFF)|(System.currentTimeMillis()&0xFFFF0000); + int minlength = dis.readByte(); + if(minlength<0x9){ + DataOutputStream dos = new DataOutputStream(os); + for(int i=0;i<minlength;++i) { + dis.readByte(); + dos.writeByte(dis.readByte()); + } + } else { + int pre =((Byte.SIZE*3+Integer.SIZE+Byte.SIZE)/Byte.SIZE)+start; + os.write(bytes, pre, bytes.length-pre); + } + } + return time; + } + + public static String randomGen(int numBytes) { + return randomGen(passChars,numBytes); + } + + public static String randomGen(char[] chars ,int numBytes) { + int rint; + StringBuilder sb = new StringBuilder(numBytes); + for(int i=0;i<numBytes;++i) { + rint = random.nextInt(chars.length); + sb.append(chars[rint]); + } + return sb.toString(); + } + // Internal mechanism for helping to randomize placement of characters within a Symm codeset + // Based on an incoming data stream (originally created randomly, but can be recreated within + // 2048 key), go after a particular place in the new codeset. If that codeset spot is used, then move + // right or left (depending on iteration) to find the next available slot. In this way, key generation + // is speeded up by only enacting N iterations, but adds a spreading effect of the random number stream, so that keyset is also + // shuffled for a good spread. It is, however, repeatable, given the same number set, allowing for + // quick recreation when the official stream is actually obtained. + public Symm obtain(byte[] key) throws IOException { + int filled = codeset.length; + char[] seq = new char[filled]; + int end = filled--; + + boolean right = true; + int index; + Obtain o = new Obtain(this,key); + + while(filled>=0) { + index = o.next(); + if(index<0 || index>=codeset.length) { + System.out.println("uh, oh"); + } + if(right) { // alternate going left or right to find the next open slot (keeps it from taking too long to hit something) + for(int j=index;j<end;++j) { + if(seq[j]==0) { + seq[j]=codeset[filled]; + --filled; + break; + } + } + right = false; + } else { + for(int j=index;j>=0;--j) { + if(seq[j]==0) { + seq[j]=codeset[filled]; + --filled; + break; + } + } + right = true; + } + } + Symm newSymm = new Symm(seq,this); + // Set the KeyBytes + try { + newSymm.keyBytes = new byte[AES.AES_KEY_SIZE/8]; + int offset = (Math.abs(key[(47%key.length)])+137)%(key.length-newSymm.keyBytes.length); + for(int i=0;i<newSymm.keyBytes.length;++i) { + newSymm.keyBytes[i] = key[i+offset]; + } + } catch (Exception e) { + throw new IOException(e); + } + + return newSymm; + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/Taf.java b/cadi/core/src/main/java/org/onap/aaf/cadi/Taf.java new file mode 100644 index 00000000..1767258c --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/Taf.java @@ -0,0 +1,57 @@ +/** + * ============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.cadi; + +import org.onap.aaf.cadi.taf.TafResp; + + +/** + * TAF - Transmutative Assertion Framework. + * + * This main Interface embodies the essential of the assertion, where a number of different TAFs might be used to authenticate + * and that authentication to be recognized through other elements. + * + * Concept by Robert Garskof. Implemented by Jonathan Gathman + * + * @author Jonathan + * + */ +public interface Taf { + enum LifeForm {CBLF, SBLF, LFN}; + /** + * The lifeForm param is a humorous way of describing whether the interaction is proceeding from direct Human Interaction via a browser + * or App which can directly query a memorized password, key sequence, bio-feedback, from that user, or a machine mechanism for which identity + * can more easily be determined by Certificate, Mechanical ID/Password etc. Popularized in modern culture and Science Fiction (especially + * Star Trek), we (starting with Robert Garskof) use the terms "Carbon Based Life Form" (CBLF) for mechanisms with people at the end of them, or + * "Silicon Based Life Forms" (SBLF) to indicate machine only interactions. I have added "LFN" for (Life-Form Neutral) to aid identifying + * processes for which it doesn't matter whether there is a human at the immediate end of the chain, or cannot be determined mechanically. + * + * The variable parameter is not necessarily ideal, but with too many unknown Tafs to be created, flexibility, + * is unfortunately required at this point. Future versions could lock this down more. Jonathan 10/18/2012 + * + * @param lifeForm + * @param info + * @return + */ + public TafResp validate(LifeForm reading, String ... info); + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/Transmutate.java b/cadi/core/src/main/java/org/onap/aaf/cadi/Transmutate.java new file mode 100644 index 00000000..63722253 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/Transmutate.java @@ -0,0 +1,45 @@ +/** + * ============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.cadi; + +import java.security.Principal; + +/** + * The unique element of TAF is that we establish the relationship/mechanism to mutate the Principal derived from + * one Authentication mechanism into a trustable Principal of another. The mechanism needs to be decided by system + * trusting. + * + * The Generic "T" is used so that the code used will be very specific for the implementation, enforced by Compiler + * + * This interface will allow differences of trusting Transmutation of Authentication + * @author Jonathan + * + */ +public interface Transmutate<T> { + /** + * Mutate the (assumed validated) Principal into the expected Principal name to be used to construct + * + * @param p + * @return + */ + public T mutate(Principal p); +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/TrustChecker.java b/cadi/core/src/main/java/org/onap/aaf/cadi/TrustChecker.java new file mode 100644 index 00000000..fabec0b0 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/TrustChecker.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.cadi; + +import javax.servlet.http.HttpServletRequest; + +import org.onap.aaf.cadi.taf.TafResp; + +/** + * Change to another Principal based on Trust of caller and User Chain (if desired) + * + * @author Jonathan + * + */ +public interface TrustChecker { + public TafResp mayTrust(TafResp tresp, HttpServletRequest req); + + /** + * A class that trusts no-one else, so just return same TResp + */ + public static TrustChecker NOTRUST = new TrustChecker() { + @Override + public TafResp mayTrust(TafResp tresp, HttpServletRequest req) { + return tresp; + } + + @Override + public void setLur(Lur lur) { + } + }; + + public void setLur(Lur lur); +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/User.java b/cadi/core/src/main/java/org/onap/aaf/cadi/User.java new file mode 100644 index 00000000..5e9f8a58 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/User.java @@ -0,0 +1,177 @@ +/** + * ============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.cadi; + +import java.security.Principal; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.onap.aaf.cadi.lur.LocalPermission; + +/** + * Class to hold info from the User Perspective. + * + * @author Jonathan + * + */ +public final class User<PERM extends Permission> { + private static final Map<String,Permission> NULL_MAP = new HashMap<String,Permission>(); + public String name; + private byte[] cred; + public Principal principal; + Map<String, Permission> perms ; + long permExpires; + private final long interval; + int count; + + // Note: This should only be used for Local RBAC (in memory) + public User(Principal principal) { + this.principal = principal; + name = principal.getName(); + perms = NULL_MAP; + permExpires = Long.MAX_VALUE; // Never. Well, until 64 bits of millis since 1970 expires... + interval = 0L; + count = 0; + } + + public User(String name, byte[] cred) { + this.principal = null; + this.name = name; + this.cred = cred; + perms = NULL_MAP; + permExpires = Long.MAX_VALUE; // Never. Well, until 64 bits of millis since 1970 expires... + interval = 0L; + count = 0; + } + + public User(Principal principal, long expireInterval) { + this.principal = principal; + this.name = principal.getName(); + perms = NULL_MAP; + expireInterval = Math.max(expireInterval, 0); // avoid < 1 + interval = Math.max(AbsUserCache.MIN_INTERVAL,Math.min(expireInterval,AbsUserCache.MAX_INTERVAL)); + count = 0; + renewPerm(); + renewPerm(); + } + + public User(String name, byte[] cred, long expireInterval) { + this.principal = null; + this.name = name; + this.cred = cred; + perms = NULL_MAP; + expireInterval = Math.max(expireInterval, 0); // avoid < 1 + interval = Math.max(AbsUserCache.MIN_INTERVAL,Math.min(expireInterval,AbsUserCache.MAX_INTERVAL)); + count = 0; + renewPerm(); + } + + public void renewPerm() { + permExpires = System.currentTimeMillis()+interval; + } + + public long permExpires() { + return permExpires; + } + + public boolean permExpired() { + return System.currentTimeMillis() > permExpires; + } + + public boolean noPerms() { + return perms==null || perms==NULL_MAP || perms.values().size()==0; + } + + public synchronized void setNoPerms() { + perms=NULL_MAP; + renewPerm(); + } + + public boolean permsUnloaded() { + return perms==null || perms==NULL_MAP; + } + + public synchronized void incCount() { + ++count; + } + + public synchronized void resetCount() { + count=0; + } + + public Map<String,Permission> newMap() { + return new ConcurrentHashMap<String,Permission>(); + } + + public void add(LocalPermission permission) { + if(perms==NULL_MAP) { + perms=newMap(); + } + perms.put(permission.getKey(),permission); + } + + public void add(Map<String, Permission> newMap, PERM permission) { + newMap.put(permission.getKey(),permission); + } + + public synchronized void setMap(Map<String, Permission> newMap) { + perms = newMap; + renewPerm(); + } + + public boolean contains(Permission perm) { + for (Permission p : perms.values()) { + if (p.match(perm)) return true; + } + return false; + } + + public void copyPermsTo(List<Permission> sink) { + sink.addAll(perms.values()); + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(principal.getName()); + sb.append('|'); + boolean first = true; + synchronized(perms) { + for(Permission gp : perms.values()) { + if(first) { + first = false; + sb.append(':'); + } else { + sb.append(','); + } + sb.append(gp.getKey()); + } + } + return sb.toString(); + } + + public byte[] getCred() { + return cred; + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/UserChain.java b/cadi/core/src/main/java/org/onap/aaf/cadi/UserChain.java new file mode 100644 index 00000000..e423b8b1 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/UserChain.java @@ -0,0 +1,43 @@ +/** + * ============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.cadi; + +/** + * Interface to add a User Chain String to Principal + * + * + * + * Where + * APP is name suitable for Logging (i.e. official App Acronym) + * ID is official User or MechID, best if includes Identity Source (i.e. ab1234@csp.att.com) + * Protocol is the Security protocol, + * + * Format:<ID>:<APP>:<protocol>[:AS][,<ID>:<APP>:<protocol>]* + * + * + * @author Jonathan + * + */ +public interface UserChain { + public enum Protocol {BasicAuth,Cookie,Cert,OAuth}; + public String userChain(); +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/config/Config.java b/cadi/core/src/main/java/org/onap/aaf/cadi/config/Config.java new file mode 100644 index 00000000..dda4b6cd --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/config/Config.java @@ -0,0 +1,728 @@ +/** + * ============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.cadi.config; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.URI; +import java.net.UnknownHostException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.List; + +import org.onap.aaf.cadi.AbsUserCache; +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.CachingLur; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.Connector; +import org.onap.aaf.cadi.CredVal; +import org.onap.aaf.cadi.Locator; +import org.onap.aaf.cadi.Lur; +import org.onap.aaf.cadi.PropAccess; +import org.onap.aaf.cadi.Symm; +import org.onap.aaf.cadi.TrustChecker; +import org.onap.aaf.cadi.lur.EpiLur; +import org.onap.aaf.cadi.lur.LocalLur; +import org.onap.aaf.cadi.lur.NullLur; +import org.onap.aaf.cadi.taf.HttpEpiTaf; +import org.onap.aaf.cadi.taf.HttpTaf; +import org.onap.aaf.cadi.taf.basic.BasicHttpTaf; +import org.onap.aaf.cadi.taf.cert.X509Taf; +import org.onap.aaf.cadi.taf.dos.DenialOfServiceTaf; + +/** + * Create a Consistent Configuration mechanism, even when configuration styles are as vastly different as + * Properties vs JavaBeans vs FilterConfigs... + * + * @author Jonathan + * + */ +public class Config { + + public static final String UTF_8 = "UTF-8"; + + // Property Names associated with configurations. + // As of 1.0.2, these have had the dots removed so as to be compatible with JavaBean style + // configurations as well as property list style. + public static final String HOSTNAME = "hostname"; + public static final String CADI_PROP_FILES = "cadi_prop_files"; // Additional Properties files (separate with ;) + public static final String CADI_LOGLEVEL = "cadi_loglevel"; + public static final String CADI_LOGNAME = "cadi_logname"; + public static final String CADI_KEYFILE = "cadi_keyfile"; + public static final String CADI_KEYSTORE = "cadi_keystore"; + public static final String CADI_KEYSTORE_PASSWORD = "cadi_keystore_password"; + public static final String CADI_ALIAS = "cadi_alias"; + public static final String CADI_LOGINPAGE_URL = "cadi_loginpage_url"; + public static final String CADI_LATITUDE = "cadi_latitude"; + public static final String CADI_LONGITUDE = "cadi_longitude"; + + public static final String CADI_KEY_PASSWORD = "cadi_key_password"; + public static final String CADI_TRUSTSTORE = "cadi_truststore"; + public static final String CADI_TRUSTSTORE_PASSWORD = "cadi_truststore_password"; + public static final String CADI_X509_ISSUERS = "cadi_x509_issuers"; + public static final String CADI_TRUST_MASKS="cadi_trust_masks"; + public static final String CADI_TRUST_PERM="cadi_trust_perm"; // IDs with this perm can utilize the "AS " user concept + public static final String CADI_PROTOCOLS = "cadi_protocols"; + public static final String CADI_NOAUTHN = "cadi_noauthn"; + public static final String CADI_LOC_LIST = "cadi_loc_list"; + + public static final String CADI_USER_CHAIN_TAG = "cadi_user_chain"; + public static final String CADI_USER_CHAIN = "USER_CHAIN"; + + public static final String CADI_OAUTH2_URL="cadi_oauth2_url"; + public static final String CADI_TOKEN_DIR = "cadi_token_dir"; + + public static final String CSP_DOMAIN = "csp_domain"; + public static final String CSP_HOSTNAME = "csp_hostname"; + public static final String CSP_DEVL_LOCALHOST = "csp_devl_localhost"; + public static final String CSP_USER_HEADER = "CSP_USER"; + public static final String CSP_SYSTEMS_CONF = "CSPSystems.conf"; + public static final String CSP_SYSTEMS_CONF_FILE = "csp_systems_conf_file"; + + public static final String HTTPS_PROTOCOLS = "https.protocols"; + public static final String HTTPS_CIPHER_SUITES = "https.cipherSuites"; + public static final String HTTPS_CLIENT_PROTOCOLS="jdk.tls.client.protocols"; + public static final String HTTPS_CIPHER_SUITES_DEFAULT="TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA," + + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA," + + "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_DSS_WITH_AES_128_CBC_SHA," + + "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,TLS_ECDHE_RSA_WITH_RC4_128_SHA,TLS_ECDH_ECDSA_WITH_RC4_128_SHA," + + "TLS_ECDH_RSA_WITH_RC4_128_SHA,TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA," + + "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,TLS_EMPTY_RENEGOTIATION_INFO_SCSV"; + + + public static final String LOCALHOST_ALLOW = "localhost_allow"; + public static final String LOCALHOST_DENY = "localhost_deny"; + + public static final String BASIC_REALM = "basic_realm"; // what is sent to the client + public static final String BASIC_WARN = "basic_warn"; // Warning of insecure channel + public static final String USERS = "local_users"; + public static final String GROUPS = "local_groups"; + public static final String WRITE_TO = "local_writeto"; // dump RBAC to local file in Tomcat Style (some apps use) + + public static final String OAUTH_CLIENT_ID="client_id"; + public static final String OAUTH_CLIENT_SECRET="client_secret"; + + public static final String AAF_ENV = "aaf_env"; + public static final String AAF_URL = "aaf_url"; //URL for AAF... Use to trigger AAF configuration + public static final String AAF_ROOT_NS = "aaf_root_ns"; + public static final String AAF_ROOT_COMPANY = "aaf_root_company"; + public static final String AAF_LOCATE_URL = "aaf_locate_url"; //URL for AAF locator + private static final String AAF_LOCATE_URL_TAG = "AAF_LOCATE_URL"; // Name of Above for use in Config Variables. + public static final String AAF_APPID = "aaf_id"; + public static final String AAF_APPPASS = "aaf_password"; + public static final String AAF_LUR_CLASS = "aaf_lur_class"; + public static final String AAF_TAF_CLASS = "aaf_taf_class"; + public static final String AAF_TAF_CLASS_DEF = "org.osaaf.cadi.aaf.v2_0.AAFTaf"; + public static final String AAF_CONNECTOR_CLASS = "aaf_connector_class"; + public static final String AAF_LOCATOR_CLASS = "aaf_locator_class"; + public static final String AAF_CONN_TIMEOUT = "aaf_conn_timeout"; + public static final String AAF_CONN_TIMEOUT_DEF = "3000"; + public static final String AAF_CONN_IDLE_TIMEOUT = "aaf_conn_idle_timeout"; // only for Direct Jetty Access. + public static final String AAF_CONN_IDLE_TIMEOUT_DEF = "10000"; // only for Direct Jetty Access. + + public static final String AAF_CALL_TIMEOUT = "aaf_timeout"; + public static final String AAF_CALL_TIMEOUT_DEF = "5000"; + public static final String AAF_USER_EXPIRES = "aaf_user_expires"; + public static final String AAF_USER_EXPIRES_DEF = "600000"; // Default is 10 mins + public static final String AAF_CLEAN_INTERVAL = "aaf_clean_interval"; + public static final String AAF_CLEAN_INTERVAL_DEF = "30000"; // Default is 30 seconds + public static final String AAF_REFRESH_TRIGGER_COUNT = "aaf_refresh_trigger_count"; + public static final String AAF_REFRESH_TRIGGER_COUNT_DEF = "3"; // Default is 10 mins + + public static final String AAF_HIGH_COUNT = "aaf_high_count"; + public static final String AAF_HIGH_COUNT_DEF = "1000"; // Default is 1000 entries + public static final String AAF_PERM_MAP = "aaf_perm_map"; + public static final String AAF_COMPONENT = "aaf_component"; + public static final String AAF_CERT_IDS = "aaf_cert_ids"; + public static final String AAF_DEBUG_IDS = "aaf_debug_ids"; // comma delimited + public static final String AAF_DEFAULT_VERSION = "2.0"; + public static final String AAF_DATA_DIR = "aaf_data_dir"; // AAF processes and Components only. + + + + public static final String GW_URL = "gw_url"; + public static final String CM_URL = "cm_url"; + public static final String CM_TRUSTED_CAS = "cm_trusted_cas"; + + public static final String PATHFILTER_URLPATTERN = "pathfilter_urlpattern"; + public static final String PATHFILTER_STACK = "pathfilter_stack"; + public static final String PATHFILTER_NS = "pathfilter_ns"; + public static final String PATHFILTER_NOT_AUTHORIZED_MSG = "pathfilter_not_authorized_msg"; + + // This one should go unpublic + public static final String AAF_DEFAULT_REALM = "aaf_default_realm"; + private static String defaultRealm="none"; + + public static final String AAF_DOMAIN_SUPPORT = "aaf_domain_support"; + public static final String AAF_DOMAIN_SUPPORT_DEF = ".com"; + + // OAUTH2 + public static final String AAF_OAUTH2_TOKEN_URL = "aaf_oauth2_token_url"; + public static final String AAF_OAUTH2_INTROSPECT_URL = "aaf_oauth2_introspect_url"; + public static final String AAF_ALT_OAUTH2_TOKEN_URL = "aaf_alt_oauth2_token_url"; + public static final String AAF_ALT_OAUTH2_INTROSPECT_URL = "aaf_alt_oauth2_introspect_url"; + public static final String AAF_ALT_OAUTH2_DOMAIN = "aaf_alt_oauth2_domain"; + public static final String AAF_ALT_CLIENT_ID = "aaf_alt_oauth2_client_id"; + public static final String AAF_ALT_CLIENT_SECRET = "aaf_alt_oauth2_client_secret"; + public static final String AAF_OAUTH2_HELLO_URL = "aaf_oauth2_hello_url"; + + + + public static void setDefaultRealm(Access access) throws CadiException { + try { + boolean hasCSP; + try { + Class.forName("org.osaaf.cadi.taf.csp.CSPTaf"); + hasCSP=true; + } catch(ClassNotFoundException e) { + hasCSP = logProp(access,Config.CSP_DOMAIN, null)!=null; + } + defaultRealm = logProp(access,Config.AAF_DEFAULT_REALM, + hasCSP?"csp.att.com": + logProp(access,Config.BASIC_REALM, + logProp(access,HOSTNAME,InetAddress.getLocalHost().getHostName()) + ) + ); + } catch (UnknownHostException e) { + //defaultRealm="none"; + } + } + + public static HttpTaf configHttpTaf(Connector con, SecurityInfoC<HttpURLConnection> si, TrustChecker tc, CredVal up, Lur lur, Object ... additionalTafLurs) throws CadiException { + Access access = si.access; + ///////////////////////////////////////////////////// + // Setup AAFCon for any following + ///////////////////////////////////////////////////// + Class<?> aafConClass = loadClass(access,"org.onap.aaf.cadi.aaf.v2_0.AAFCon"); + Object aafcon = null; + if(con!=null && aafConClass!=null && aafConClass.isAssignableFrom(con.getClass())) { + aafcon = con; + } else if(lur != null) { + Field f = null; + try { + f = lur.getClass().getField("aaf"); + aafcon = f.get(lur); + } catch (Exception nsfe) { + } + } + + + boolean hasDirectAAF = hasDirect("DirectAAFLur",additionalTafLurs); + // IMPORTANT! Don't attempt to load AAF Connector if there is no AAF URL + String aafURL = access.getProperty(AAF_URL,null); + if(!hasDirectAAF && aafcon==null && aafURL!=null) { + aafcon = loadAAFConnector(si, aafURL); + } + + HttpTaf taf; + // Setup Host, in case Network reports an unusable Hostname (i.e. VTiers, VPNs, etc) + String hostname = logProp(access, HOSTNAME,null); + if(hostname==null) { + try { + hostname = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e1) { + throw new CadiException("Unable to determine Hostname",e1); + } + } + + access.log(Level.INIT, "Hostname set to",hostname); + // Get appropriate TAFs + ArrayList<HttpTaf> htlist = new ArrayList<HttpTaf>(); + + ///////////////////////////////////////////////////// + // Add a Denial of Service TAF + // Note: how IPs and IDs are added are up to service type. + // They call "DenialOfServiceTaf.denyIP(String) or denyID(String) + ///////////////////////////////////////////////////// + htlist.add(new DenialOfServiceTaf(access)); + + ///////////////////////////////////////////////////// + // Configure Client Cert TAF + ///////////////////////////////////////////////////// + + String truststore = logProp(access, CADI_TRUSTSTORE,null); + if(truststore!=null) { + String truststore_pwd = access.getProperty(CADI_TRUSTSTORE_PASSWORD,null); + if(truststore_pwd!=null) { + if(truststore_pwd.startsWith(Symm.ENC)) { + try { + truststore_pwd = access.decrypt(truststore_pwd,false); + } catch (IOException e) { + throw new CadiException(CADI_TRUSTSTORE_PASSWORD + " cannot be decrypted",e); + } + } + try { + htlist.add(new X509Taf(access,lur)); + access.log(Level.INIT,"Certificate Authorization enabled"); + } catch (SecurityException e) { + access.log(Level.INIT,"AAFListedCertIdentity cannot be instantiated. Certificate Authorization is now disabled",e); + } catch (IllegalArgumentException e) { + access.log(Level.INIT,"AAFListedCertIdentity cannot be instantiated. Certificate Authorization is now disabled",e); + } catch (CertificateException e) { + access.log(Level.INIT,"Certificate Authorization failed, it is disabled",e); + } catch (NoSuchAlgorithmException e) { + access.log(Level.INIT,"Certificate Authorization failed, wrong Security Algorithm",e); + } + } + } else { + access.log(Level.INIT,"Certificate Authorization not enabled"); + } + + ///////////////////////////////////////////////////// + // Configure Basic Auth (local content) + ///////////////////////////////////////////////////// + boolean hasOAuthDirectTAF = hasDirect("DirectOAuthTAF", additionalTafLurs); + String basic_realm = logProp(access, BASIC_REALM,null); + String aafCleanup = logProp(access, AAF_USER_EXPIRES,AAF_USER_EXPIRES_DEF); // Default is 10 mins + long userExp = Long.parseLong(aafCleanup); + boolean basic_warn = "TRUE".equals(access.getProperty(BASIC_WARN,"FALSE")); + + if(!hasDirectAAF) { + HttpTaf aaftaf=null; + if(!hasOAuthDirectTAF) { + if(basic_realm!=null) { + @SuppressWarnings("unchecked") + Class<HttpTaf> obasicCls = (Class<HttpTaf>)loadClass(access,"org.osaaf.cadi.obasic.OBasicHttpTaf"); + if(obasicCls!=null) { + try { + String tokenurl = logProp(access,Config.AAF_OAUTH2_TOKEN_URL, null); + String introspecturl = logProp(access,Config.AAF_OAUTH2_INTROSPECT_URL, null); + if(tokenurl==null || introspecturl==null) { + access.log(Level.INIT,"Both tokenurl and introspecturl are required. Oauth Authorization is disabled."); + } + Constructor<HttpTaf> obasicConst = obasicCls.getConstructor(PropAccess.class,String.class, String.class, String.class); + htlist.add(obasicConst.newInstance(access,basic_realm,tokenurl,introspecturl)); + access.log(Level.INIT,"Oauth supported Basic Authorization is enabled"); + } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + } + } else if(up!=null) { + access.log(Level.INIT,"Basic Authorization is enabled using realm",basic_realm); + // Allow warning about insecure channel to be turned off + if(!basic_warn)access.log(Level.INIT,"WARNING! The basic_warn property has been set to false.", + " There will be no additional warning if Basic Auth is used on an insecure channel" + ); + htlist.add(new BasicHttpTaf(access, up, basic_realm, userExp, basic_warn)); + access.log(Level.INIT,"Basic Authorization is enabled"); + } + } else { + access.log(Level.INIT,"Local Basic Authorization is disabled. Enable by setting basic_realm=<appropriate realm, i.e. my.att.com>"); + } + + ///////////////////////////////////////////////////// + // Configure AAF Driven Basic Auth + ///////////////////////////////////////////////////// + if(aafcon==null) { + access.log(Level.INIT,"AAF Connection (AAFcon) is null. Cannot create an AAF TAF"); + } else if(aafURL==null) { + access.log(Level.INIT,"No AAF URL in properties, Cannot create an AAF TAF"); + } else {// There's an AAF_URL... try to configure an AAF + String aafTafClassName = logProp(access, AAF_TAF_CLASS,AAF_TAF_CLASS_DEF); + // Only 2.0 available at this time + if(AAF_TAF_CLASS_DEF.equals(aafTafClassName)) { + try { + Class<?> aafTafClass = loadClass(access,aafTafClassName); + + Constructor<?> cstr = aafTafClass.getConstructor(Connector.class,boolean.class,AbsUserCache.class); + if(cstr!=null) { + if(lur instanceof AbsUserCache) { + aaftaf = (HttpTaf)cstr.newInstance(aafcon,basic_warn,lur); + } else { + cstr = aafTafClass.getConstructor(Connector.class,boolean.class); + if(cstr!=null) { + aaftaf = (HttpTaf)cstr.newInstance(aafcon,basic_warn); + } + } + if(aaftaf==null) { + access.log(Level.INIT,"ERROR! AAF TAF Failed construction. NOT Configured"); + } else { + access.log(Level.INIT,"AAF TAF Configured to ",aafURL); + // Note: will add later, after all others configured + } + } + } catch(Exception e) { + access.log(Level.INIT,"ERROR! AAF TAF Failed construction. NOT Configured",e); + } + } + } + } + + ///////////////////////////////////////////////////// + // Configure OAuth TAF + ///////////////////////////////////////////////////// + if(!hasOAuthDirectTAF) { + String oauth_token_url = logProp(access,Config.AAF_OAUTH2_TOKEN_URL,null); + if(additionalTafLurs!=null && additionalTafLurs.length>0 && additionalTafLurs[0].getClass().getName().equals("org.osaaf.authz.oauth.OAuthDirectTAF")) { + htlist.add((HttpTaf)additionalTafLurs[0]); + String array[] = new String[additionalTafLurs.length-1]; + if(array.length>0) { + System.arraycopy(htlist, 1, array, 0, array.length); + } + additionalTafLurs = array; + access.log(Level.INIT,"OAuth2 Direct is enabled"); + } else if(oauth_token_url!=null) { + String oauth_introspect_url = logProp(access,Config.AAF_OAUTH2_INTROSPECT_URL,null); + @SuppressWarnings("unchecked") + Class<HttpTaf> oaTCls = (Class<HttpTaf>)loadClass(access,"org.osaaf.cadi.oauth.OAuth2HttpTaf"); + if(oaTCls!=null) { + Class<?> oaTTmgrCls = loadClass(access, "org.osaaf.cadi.oauth.TokenMgr"); + if(oaTTmgrCls!=null) { + try { + Method oaTTmgrGI = oaTTmgrCls.getMethod("getInstance",PropAccess.class,String.class,String.class); + Object oaTTmgr = oaTTmgrGI.invoke(null /*this is static method*/,access,oauth_token_url,oauth_introspect_url); + Constructor<HttpTaf> oaTConst = oaTCls.getConstructor(Access.class,oaTTmgrCls); + htlist.add(oaTConst.newInstance(access,oaTTmgr)); + access.log(Level.INIT,"OAuth2 TAF is enabled"); + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | InstantiationException e) { + access.log(Level.INIT,"OAuth2HttpTaf cannot be instantiated. OAuth2 is disabled",e); + } + } + } + } else { + access.log(Level.INIT,"OAuth TAF is not configured"); + } + } + + ///////////////////////////////////////////////////// + // Adding BasicAuth (AAF) last, after other primary Cookie Based + // Needs to be before Cert... see below + ///////////////////////////////////////////////////// + if(aaftaf!=null) { + htlist.add(aaftaf); + } + } + + ///////////////////////////////////////////////////// + // Any Additional Lurs passed in Constructor + ///////////////////////////////////////////////////// + if(additionalTafLurs!=null) { + for(Object additional : additionalTafLurs) { + if(additional instanceof HttpTaf) { + htlist.add((HttpTaf)additional); + access.printf(Level.INIT,"%s Authentication is enabled",additional.getClass().getSimpleName()); + } else if(hasOAuthDirectTAF && additional.getClass().getSimpleName().equals("DirectAAFUserPass")) { + htlist.add(new BasicHttpTaf(access, (CredVal)additional , basic_realm, userExp, basic_warn)); + access.printf(Level.INIT,"Direct BasicAuth Authentication is enabled",additional.getClass().getSimpleName()); + } + } + } + + ///////////////////////////////////////////////////// + // Create EpiTaf from configured TAFs + ///////////////////////////////////////////////////// + if(htlist.size()==1) { + // just return the one + taf = htlist.get(0); + } else { + HttpTaf[] htarray = new HttpTaf[htlist.size()]; + htlist.toArray(htarray); + Locator<URI> locator = loadLocator(si, logProp(access, AAF_LOCATE_URL, null)); + + taf = new HttpEpiTaf(access,locator, tc, htarray); // ok to pass locator == null + String level = logProp(access, CADI_LOGLEVEL, null); + if(level!=null) { + access.setLogLevel(Level.valueOf(level)); + } + } + + return taf; + } + + public static String logProp(Access access,String tag, String def) { + String rv = access.getProperty(tag, def); + if(rv == null) { + access.log(Level.INIT,tag,"is not set"); + } else { + access.log(Level.INIT,tag,"is set to",rv); + } + return rv; + } + + public static Lur configLur(SecurityInfoC<HttpURLConnection> si, Connector con, Object ... additionalTafLurs) throws CadiException { + Access access = si.access; + List<Lur> lurs = new ArrayList<Lur>(); + + ///////////////////////////////////////////////////// + // Configure a Local Property Based RBAC/LUR + ///////////////////////////////////////////////////// + try { + String users = access.getProperty(USERS,null); + String groups = access.getProperty(GROUPS,null); + + if(groups!=null || users!=null) { + LocalLur ll; + lurs.add(ll = new LocalLur(access, users, groups)); // note b64==null is ok.. just means no encryption. + + String writeto = access.getProperty(WRITE_TO,null); + if(writeto!=null) { + String msg = UsersDump.updateUsers(writeto, ll); + if(msg!=null) access.log(Level.INIT,"ERROR! Error Updating ",writeto,"with roles and users:",msg); + } + } + } catch (IOException e) { + throw new CadiException(e); + } + + ///////////////////////////////////////////////////// + // Configure the OAuth Lur (if any) + ///////////////////////////////////////////////////// + String token_url = logProp(access,AAF_OAUTH2_TOKEN_URL, null); + String introspect_url = logProp(access,AAF_OAUTH2_INTROSPECT_URL, null); + if(token_url!=null && introspect_url !=null) { + try { + Class<?> olurCls = loadClass(access, "org.osaaf.cadi.olur.OLur"); + if(olurCls!=null) { + Constructor<?> olurCnst = olurCls.getConstructor(PropAccess.class,String.class,String.class); + Lur olur = (Lur)olurCnst.newInstance(access,token_url,introspect_url); + lurs.add(olur); + access.log(Level.INIT, "OAuth2 LUR enabled"); + } else { + access.log(Level.INIT,"AAF/OAuth LUR plugin is not available."); + } + } catch (NoSuchMethodException| SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + access.log(e,"AAF/OAuth LUR could not be constructed with given Constructors."); + } + } else { + access.log(Level.INIT, "OAuth2 Lur disabled"); + } + + if(con!=null) { // try to reutilize connector + lurs.add(con.newLur()); + } else { + ///////////////////////////////////////////////////// + // Configure the AAF Lur (if any) + ///////////////////////////////////////////////////// + String aafURL = logProp(access,AAF_URL,null); // Trigger Property + String aaf_env = access.getProperty(AAF_ENV,null); + if(aaf_env == null && aafURL!=null && access instanceof PropAccess) { // set AAF_ENV from AAF_URL + int ec = aafURL.indexOf("envContext="); + if(ec>0) { + ec += 11; // length of envContext= + int slash = aafURL.indexOf('/', ec); + if(slash>0) { + aaf_env = aafURL.substring(ec, slash); + ((PropAccess)access).setProperty(AAF_ENV, aaf_env); + access.printf(Level.INIT, "Setting aaf_env to %s from aaf_url value",aaf_env); + } + } + } + + // Don't configure AAF if it is using DirectAccess + if(!hasDirect("DirectAAFLur",additionalTafLurs)) { + if(aafURL==null) { + access.log(Level.INIT,"No AAF LUR properties, AAF will not be loaded"); + } else {// There's an AAF_URL... try to configure an AAF + String aafLurClassStr = logProp(access,AAF_LUR_CLASS,"org.osaaf.cadi.aaf.v2_0.AAFLurPerm"); + ////////////AAF Lur 2.0 ///////////// + if(aafLurClassStr.startsWith("org.osaaf.cadi.aaf.v2_0")) { + try { + Object aafcon = loadAAFConnector(si, aafURL); + if(aafcon==null) { + access.log(Level.INIT,"AAF LUR class,",aafLurClassStr,"cannot be constructed without valid AAFCon object."); + } else { + Class<?> aafAbsAAFCon = loadClass(access, "org.osaaf.cadi.aaf.v2_0.AAFCon"); + Method mNewLur = aafAbsAAFCon.getMethod("newLur"); + Object aaflur = mNewLur.invoke(aafcon); + + if(aaflur==null) { + access.log(Level.INIT,"ERROR! AAF LUR Failed construction. NOT Configured"); + } else { + access.log(Level.INIT,"AAF LUR Configured to ",aafURL); + lurs.add((Lur)aaflur); + String debugIDs = logProp(access,Config.AAF_DEBUG_IDS, null); + if(debugIDs !=null && aaflur instanceof CachingLur) { + ((CachingLur<?>)aaflur).setDebug(debugIDs); + } + } + } + } catch (Exception e) { + access.log(e,"AAF LUR class,",aafLurClassStr,"could not be constructed with given Constructors."); + } + } + } + } + } + + ///////////////////////////////////////////////////// + // Any Additional passed in Constructor + ///////////////////////////////////////////////////// + if(additionalTafLurs!=null) { + for(Object additional : additionalTafLurs) { + if(additional instanceof Lur) { + lurs.add((Lur)additional); + access.log(Level.INIT, additional); + } + } + } + + ///////////////////////////////////////////////////// + // Return a Lur based on how many there are... + ///////////////////////////////////////////////////// + switch(lurs.size()) { + case 0: + access.log(Level.INIT,"WARNING! No CADI LURs configured"); + // Return a NULL Lur that does nothing. + return new NullLur(); + case 1: + return lurs.get(0); // Only one, just return it, save processing + default: + // Multiple Lurs, use EpiLUR to handle + Lur[] la = new Lur[lurs.size()]; + lurs.toArray(la); + return new EpiLur(la); + } + } + + private static boolean hasDirect(String simpleClassName, Object[] additionalTafLurs) { + if(additionalTafLurs!=null) { + for(Object tf : additionalTafLurs) { + if(tf.getClass().getSimpleName().equals(simpleClassName)) { + return true; + } + } + } + return false; + } + + private static final String COM_ATT_CADI_AAF_V2_0_AAF_CON_HTTP = "org.osaaf.cadi.aaf.v2_0.AAFConHttp"; + public static Object loadAAFConnector(SecurityInfoC<HttpURLConnection> si, String aafURL) { + Access access = si.access; + Object aafcon = null; + Class<?> aafConClass = null; + + try { + if(aafURL!=null) { + String aafConnector = access.getProperty(AAF_CONNECTOR_CLASS, COM_ATT_CADI_AAF_V2_0_AAF_CON_HTTP); + if(COM_ATT_CADI_AAF_V2_0_AAF_CON_HTTP.equals(aafConnector)) { + aafConClass = loadClass(access, COM_ATT_CADI_AAF_V2_0_AAF_CON_HTTP); + for(Constructor<?> c : aafConClass.getConstructors()) { + List<Object> lo = new ArrayList<Object>(); + for(Class<?> pc : c.getParameterTypes()) { + if(pc.equals(PropAccess.class)) { + lo.add(access); + } else if(pc.equals(Locator.class)) { + lo.add(loadLocator(si, aafURL)); + } else { + continue; + } + } + if(c.getParameterTypes().length!=lo.size()) { + continue; // back to another Constructor + } else { + aafcon = c.newInstance(lo.toArray()); + } + break; + } + } + if(aafcon!=null) { + String mechid = logProp(access,Config.AAF_APPID, null); + String pass = access.getProperty(Config.AAF_APPPASS, null); + if(mechid!=null && pass!=null) { + try { + Method basicAuth = aafConClass.getMethod("basicAuth", String.class, String.class); + basicAuth.invoke(aafcon, mechid,pass); + } catch (NoSuchMethodException nsme) { + // it's ok, don't use + } + } + } + } + } catch (Exception e) { + access.log(e,"AAF Connector could not be constructed with given Constructors."); + } + + return aafcon; + } + + public static Class<?> loadClass(Access access, String className) { + Class<?> cls=null; + try { + cls = access.classLoader().loadClass(className); + } catch (ClassNotFoundException cnfe) { + try { + cls = access.getClass().getClassLoader().loadClass(className); + } catch (ClassNotFoundException cnfe2) { + // just return null + } + } + return cls; + } + + + @SuppressWarnings("unchecked") + public static Locator<URI> loadLocator(SecurityInfoC<HttpURLConnection> si, final String _url) { + Access access = si.access; + String url = _url, replacement; + int idxAAF_LOCATE_URL; + if((idxAAF_LOCATE_URL=_url.indexOf(AAF_LOCATE_URL_TAG))>0 && ((replacement=access.getProperty(AAF_LOCATE_URL, null))!=null)) { + url = replacement + "/locate" + _url.substring(idxAAF_LOCATE_URL+AAF_LOCATE_URL_TAG.length()); + } + + Locator<URI> locator = null; + if(url==null) { + access.log(Level.INIT,"No URL passed to 'loadLocator'. Disabled"); + } else { + try { + Class<?> lcls = loadClass(access,"org.onap.aaf.cadi.aaf.v2_0.AAFLocator"); + if(lcls==null) { + throw new CadiException("Need to include aaf-cadi-aaf jar for AAFLocator"); + } + // First check for preloaded + try { + Method meth = lcls.getMethod("create",String.class); + locator = (Locator<URI>)meth.invoke(null,url); + } catch (Exception e) { + locator = null; + } + if(locator==null) { + URI locatorURI = new URI(url); + Constructor<?> cnst = lcls.getConstructor(new Class[] {SecurityInfoC.class,URI.class}); + locator = (Locator<URI>)cnst.newInstance(new Object[] {si,locatorURI}); + int port = locatorURI.getPort(); + String portS = port<0?"":(":"+locatorURI.getPort()); + + access.log(Level.INFO, "AAFLocator enabled using " + locatorURI.getScheme() +"://"+locatorURI.getHost() + portS); + } else { + access.log(Level.INFO, "AAFLocator enabled using preloaded " + locator.getClass().getSimpleName()); + } + } catch (InvocationTargetException e) { + access.log(Level.INIT,e.getTargetException().getMessage(),"AAFLocator for",url,"could not be created.",e); + } catch (Exception e) { + access.log(Level.INIT,"AAFLocator for",url,"could not be created.",e); + } + } + return locator; + } + + // Set by CSP, or is hostname. + public static String getDefaultRealm() { + return defaultRealm; + } + +} + diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/config/Get.java b/cadi/core/src/main/java/org/onap/aaf/cadi/config/Get.java new file mode 100644 index 00000000..dfb7b4d3 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/config/Get.java @@ -0,0 +1,97 @@ +/** + * ============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.cadi.config; + +import java.lang.reflect.Method; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.Access.Level; + +public interface Get { + public String get(String name, String def, boolean print); + + + /** + * A class for Getting info out of "JavaBean" format + * @author Jonathan + * + */ + public static class Bean implements Get { + private Object bean; + private Class<?> bc; + private Class<?>[] params; + private Object[] args; + + public Bean(Object bean) { + this.bean = bean; + bc = bean.getClass(); + params = new Class<?>[0]; // note, this will allow to go out of scope after config + args = new Object[0]; + } + + public String get(String name, String def, boolean print) { + String str = null; + String gname = "get"+Character.toUpperCase(name.charAt(0))+name.substring(1); + try { + Method meth = bc.getMethod(gname, params); + Object obj = meth.invoke(bean, args); + str = obj==null?null:obj.toString(); // easy string convert... + } catch (Exception e) { + } + + // Take def if nothing else + if(str==null) { + str = def; + // don't log defaults + } else { + str = str.trim(); // this is vital in Property File based values, as spaces can hide easily + } + // Note: Can't log during configuration + return str; + } + } + + public static Get NULL = new Get() { + public String get(String name, String def, boolean print) { + return def; + } + }; + + public static class AccessGet implements Get { + private Access access; + public AccessGet(Access access) { + this.access = access; + } + public String get(String name, String def, boolean print) { + String gotten = access.getProperty(name, def); + if(print) { + if(gotten == null) { + access.log(Level.INIT,name, "is not set"); + } else { + access.log(Level.INIT,name, "is set to", gotten); + } + } + return gotten; + } + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/config/GetAccess.java b/cadi/core/src/main/java/org/onap/aaf/cadi/config/GetAccess.java new file mode 100644 index 00000000..05ee90cd --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/config/GetAccess.java @@ -0,0 +1,62 @@ +/** + * ============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.cadi.config; + +import org.onap.aaf.cadi.PropAccess; + +public class GetAccess extends PropAccess { + private final Get getter; + + public GetAccess(Get getter) { + super(new String[]{"cadi_prop_files="+getter.get("cadi_prop_files", null, true)}); + this.getter = getter; + } + + /* (non-Javadoc) + * @see org.onap.aaf.cadi.PropAccess#getProperty(java.lang.String, java.lang.String) + */ + @Override + public String getProperty(String tag, String def) { + String rv; + rv = super.getProperty(tag, null); + if(rv==null && getter!=null) { + rv = getter.get(tag, null, true); + } + return rv==null?def:rv; + } + /* (non-Javadoc) + * @see org.onap.aaf.cadi.PropAccess#getProperty(java.lang.String) + */ + @Override + public String getProperty(String tag) { + String rv; + rv = super.getProperty(tag, null); + if(rv==null && getter!=null) { + rv = getter.get(tag, null, true); + } + return rv; + } + + public Get get() { + return getter; + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/config/MultiGet.java b/cadi/core/src/main/java/org/onap/aaf/cadi/config/MultiGet.java new file mode 100644 index 00000000..a73df14e --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/config/MultiGet.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.cadi.config; + +public class MultiGet implements Get { + private Get[] getters; + + public MultiGet(Get ... getters) { + this.getters = getters; + } + + @Override + public String get(String name, String def, boolean print) { + String str; + for(Get getter : getters) { + str = getter.get(name, null, print); + if(str!=null) + return str; + } + return def; + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/config/SecurityInfo.java b/cadi/core/src/main/java/org/onap/aaf/cadi/config/SecurityInfo.java new file mode 100644 index 00000000..99f41362 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/config/SecurityInfo.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.cadi.config; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.rmi.AccessException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509KeyManager; +import javax.net.ssl.X509TrustManager; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.util.MaskFormatException; +import org.onap.aaf.cadi.util.NetMask; + +public class SecurityInfo { + private static final String SECURITY_ALGO = "RSA"; + private static final String HTTPS_PROTOCOLS = "https.protocols"; + private static final String JDK_TLS_CLIENT_PROTOCOLS = "jdk.tls.client.protocols"; + + public static final String HTTPS_PROTOCOLS_DEFAULT = "TLSv1.1,TLSv1.2"; + public static final String REGEX_COMMA = "\\s*,\\s*"; + public static final String SslKeyManagerFactoryAlgorithm; + + private SSLSocketFactory scf; + private X509KeyManager[] km; + private X509TrustManager[] tm; + public final String default_alias; + private NetMask[] trustMasks; + private SSLContext ctx; + private HostnameVerifier maskHV; + public final Access access; + + // Change Key Algorithms for IBM's VM. Could put in others, if needed. + static { + if(System.getProperty("java.vm.vendor").equalsIgnoreCase("IBM Corporation")) { + SslKeyManagerFactoryAlgorithm = "IbmX509"; + } else { + SslKeyManagerFactoryAlgorithm = "SunX509"; + } + } + + + public SecurityInfo(final Access access) throws CadiException { + try { + this.access = access; + // reuse DME2 Properties for convenience if specific Properties don't exist + String keyStore = access.getProperty(Config.CADI_KEYSTORE,null); + if(keyStore!=null && !new File(keyStore).exists()) { + throw new CadiException(keyStore + " does not exist"); + } + String keyStorePasswd = access.getProperty(Config.CADI_KEYSTORE_PASSWORD, null); + keyStorePasswd = keyStorePasswd==null?null:access.decrypt(keyStorePasswd,false); + String trustStore = access.getProperty(Config.CADI_TRUSTSTORE, null); + if(trustStore!=null && !new File(trustStore).exists()) { + throw new CadiException(trustStore + " does not exist"); + } + String trustStorePasswd = access.getProperty(Config.CADI_TRUSTSTORE_PASSWORD, null); + trustStorePasswd = trustStorePasswd==null?"changeit"/*defacto Java Trust Pass*/:access.decrypt(trustStorePasswd,false); + default_alias = access.getProperty(Config.CADI_ALIAS,null); + + String keyPasswd = access.getProperty(Config.CADI_KEY_PASSWORD,null); + keyPasswd = keyPasswd==null?keyStorePasswd:access.decrypt(keyPasswd,false); + String tips=access.getProperty(Config.CADI_TRUST_MASKS, null); + if(tips!=null) { + access.log(Level.INIT,"Explicitly accepting valid X509s from",tips); + String[] ipsplit = tips.split(REGEX_COMMA); + trustMasks = new NetMask[ipsplit.length]; + for(int i=0;i<ipsplit.length;++i) { + try { + trustMasks[i]=new NetMask(ipsplit[i]); + } catch (MaskFormatException e) { + throw new AccessException("Invalid IP Mask in " + Config.CADI_TRUST_MASKS,e); + } + } + } + String https_protocols = Config.logProp(access,Config.CADI_PROTOCOLS, + access.getProperty(HTTPS_PROTOCOLS,HTTPS_PROTOCOLS_DEFAULT) + ); + System.setProperty(HTTPS_PROTOCOLS,https_protocols); + System.setProperty(JDK_TLS_CLIENT_PROTOCOLS, https_protocols); + if("1.7".equals(System.getProperty("java.specification.version")) && https_protocols.contains("TLSv1.2")) { + System.setProperty(Config.HTTPS_CIPHER_SUITES, Config.HTTPS_CIPHER_SUITES_DEFAULT); + } + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(SslKeyManagerFactoryAlgorithm); + File file; + + + if(keyStore==null || keyStorePasswd == null) { + km = new X509KeyManager[0]; + } else { + ArrayList<X509KeyManager> kmal = new ArrayList<X509KeyManager>(); + for(String ksname : keyStore.split(REGEX_COMMA)) { + file = new File(ksname); + String keystoreFormat; + if(ksname.endsWith(".p12") || ksname.endsWith(".pkcs12")) { + keystoreFormat = "PKCS12"; + } else { + keystoreFormat = "JKS"; + } + if(file.exists()) { + FileInputStream fis = new FileInputStream(file); + try { + KeyStore ks = KeyStore.getInstance(keystoreFormat); + ks.load(fis, keyStorePasswd.toCharArray()); + kmf.init(ks, keyPasswd.toCharArray()); + } finally { + fis.close(); + } + } + } + for(KeyManager km : kmf.getKeyManagers()) { + if(km instanceof X509KeyManager) { + kmal.add((X509KeyManager)km); + } + } + km = new X509KeyManager[kmal.size()]; + kmal.toArray(km); + } + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(SslKeyManagerFactoryAlgorithm); + if(trustStore!=null) { + for(String tsname : trustStore.split(REGEX_COMMA)) { + file = new File(tsname); + if(file.exists()) { + FileInputStream fis = new FileInputStream(file); + try { + KeyStore ts = KeyStore.getInstance("JKS"); + ts.load(fis, trustStorePasswd.toCharArray()); + tmf.init(ts); + } finally { + fis.close(); + } + } + } + TrustManager tms[] = tmf.getTrustManagers(); + tm = new X509TrustManager[tms==null?0:tms.length]; + for(int i=0;i<tms.length;++i) { + try { + tm[i]=(X509TrustManager)tms[i]; + } catch (ClassCastException e) { + access.log(Level.WARN, "Non X509 TrustManager", tm[i].getClass().getName(),"skipped in SecurityInfo"); + } + } + } + + if(trustMasks!=null) { + final HostnameVerifier origHV = HttpsURLConnection.getDefaultHostnameVerifier(); + HttpsURLConnection.setDefaultHostnameVerifier(maskHV = new HostnameVerifier() { + @Override + public boolean verify(final String urlHostName, final SSLSession session) { + try { + // This will pick up /etc/host entries as well as DNS + InetAddress ia = InetAddress.getByName(session.getPeerHost()); + for(NetMask tmask : trustMasks) { + if(tmask.isInNet(ia.getHostAddress())) { + return true; + } + } + } catch (UnknownHostException e) { + // It's ok. do normal Verify + } + return origHV.verify(urlHostName,session); + }; + }); + } + ctx = SSLContext.getInstance("TLS"); + ctx.init(km, tm, null); + SSLContext.setDefault(ctx); + scf = ctx.getSocketFactory(); + } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | CertificateException | UnrecoverableKeyException | IOException e) { + throw new CadiException(e); + } + } + + /** + * @return the scf + */ + public SSLSocketFactory getSSLSocketFactory() { + return scf; + } + + public SSLContext getSSLContext() { + return ctx; + } + + /** + * @return the km + */ + public X509KeyManager[] getKeyManagers() { + return km; + } + + public void checkClientTrusted(X509Certificate[] certarr) throws CertificateException { + for(X509TrustManager xtm : tm) { + xtm.checkClientTrusted(certarr, SECURITY_ALGO); + } + } + + public void checkServerTrusted(X509Certificate[] certarr) throws CertificateException { + for(X509TrustManager xtm : tm) { + xtm.checkServerTrusted(certarr, SECURITY_ALGO); + } + } + + public void setSocketFactoryOn(HttpsURLConnection hsuc) { + hsuc.setSSLSocketFactory(scf); + if(maskHV!=null && !maskHV.equals(hsuc.getHostnameVerifier())) { + hsuc.setHostnameVerifier(maskHV); + } + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/config/SecurityInfoC.java b/cadi/core/src/main/java/org/onap/aaf/cadi/config/SecurityInfoC.java new file mode 100644 index 00000000..33aef6c9 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/config/SecurityInfoC.java @@ -0,0 +1,72 @@ +/** + * ============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.cadi.config; + +import java.util.HashMap; +import java.util.Map; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.SecuritySetter; + + +public class SecurityInfoC<CLIENT> extends SecurityInfo { + public static final String DEF_ID = "ID not Set"; + private static Map<Class<?>,SecurityInfoC<?>> sicMap = new HashMap<Class<?>,SecurityInfoC<?>>(); + public SecuritySetter<CLIENT> defSS; + + private SecurityInfoC(Access access) throws CadiException { + super(access); + defSS = new SecuritySetter<CLIENT>() { + @Override + public String getID() { + return DEF_ID; + } + + @Override + public void setSecurity(CLIENT client) throws CadiException { + throw new CadiException("No Client Credentials set."); + } + + @Override + public int setLastResponse(int respCode) { + return 0; + } + }; + } + + @SuppressWarnings("unchecked") + public static synchronized <CLIENT> SecurityInfoC<CLIENT> instance(Access access, Class<CLIENT> cls) throws CadiException { + SecurityInfoC<?> sic = sicMap.get(cls); + if(sic==null) { + sic = new SecurityInfoC<CLIENT>(access); + sicMap.put(cls, sic); + } + return (SecurityInfoC<CLIENT>)sic; + } + + public SecurityInfoC<CLIENT> set(SecuritySetter<CLIENT> defSS) { + this.defSS = defSS; + return this; + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/config/UsersDump.java b/cadi/core/src/main/java/org/onap/aaf/cadi/config/UsersDump.java new file mode 100644 index 00000000..c03be1fa --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/config/UsersDump.java @@ -0,0 +1,157 @@ +/** + * ============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.cadi.config; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Date; +import java.util.HashSet; + +import org.onap.aaf.cadi.AbsUserCache; +import org.onap.aaf.cadi.lur.LocalLur; + +public class UsersDump { + + /** + * @param args + */ + public static boolean write(OutputStream os, AbsUserCache<?> lur) { + PrintStream ps; + if(os instanceof PrintStream) { + ps = (PrintStream)os; + } else { + ps = new PrintStream(os); + } + try { + ps.println("<?xml version='1.0' encoding='utf-8'?>"); + ps.println("<!--"); + ps.print( " Code Generated Tomcat Users and Roles from AT&T LUR on "); + ps.println(new Date()); + ps.println( "-->"); + ps.println("<tomcat-users>"); + + // We loop through Users, but want to write Groups first... therefore, save off print + StringBuilder sb = new StringBuilder(); + + // Obtain all unique role names + HashSet<String> groups = new HashSet<String>(); + for(AbsUserCache<?>.DumpInfo di : lur.dumpInfo()) { + sb.append("\n <user username=\""); + sb.append(di.user); + sb.append("\" roles=\""); + boolean first = true; + for(String role : di.perms) { + groups.add(role); + if(first)first = false; + else sb.append(','); + sb.append(role); + } + sb.append("\"/>"); + + } + + // Print roles + for(String group : groups) { + ps.print(" <role rolename=\""); + ps.print(group); + ps.println("\"/>"); + } + + ps.println(sb); + + ps.println("</tomcat-users>"); + ps.flush(); + } catch (Throwable t) { + t.printStackTrace(ps); + return false; + } + return true; + } + + /** + * + * Note: This method returns a String if there's an error, or null if ok. + * This unusual style is necessitated by the fact that any Exceptions thrown are likely to + * be unlogged and hidden from view, making debugging almost impossible. + * + * @param writeto + * @param up + * @return + */ + public static String updateUsers(String writeto, LocalLur up) { + // Dump a Tomcat-user.xml lookalike (anywhere) + if(writeto!=null) { + // First read content + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + if(UsersDump.write(baos, up)) { + byte[] postulate = baos.toByteArray(); + // now get contents of file + File file = new File(writeto); + boolean writeIt; + if(file.exists()) { + try { + FileInputStream fis = new FileInputStream(file); + byte[] orig = new byte[(int)file.length()]; + try { + fis.read(orig); + } finally { + fis.close(); + } + // Starting at third "<" (<tomcat-users> line) + int startA=0, startB=0; + for(int i=0;startA<orig.length && i<3;++startA) if(orig[startA]=='<')++i; + for(int i=0;startB<orig.length && i<3;++startB) if(postulate[startB]=='<')++i; + + writeIt=orig.length-startA!=postulate.length-startB; // first, check if remaining length is the same + while(!writeIt && startA<orig.length && startB<postulate.length) { + if(orig[startA++]!=postulate[startB++])writeIt = true; + } + } catch (Exception e) { + writeIt = true; + } + } else { + writeIt = true; + } + + if(writeIt) { + try { + FileOutputStream fos = new FileOutputStream(file); + try { + fos.write(postulate); + } finally { + fos.close(); + } + } catch (IOException e) { + return e.getMessage(); + } + } + } + } + return null; // no message means ok. + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/filter/AUTHZ.java b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/AUTHZ.java new file mode 100644 index 00000000..7fd1e93c --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/AUTHZ.java @@ -0,0 +1,36 @@ +/** + * ============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.cadi.filter; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.servlet.Servlet; + +@Target({TYPE}) +@Retention(RUNTIME) +public @interface AUTHZ { + Class<? extends Servlet> value(); +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/filter/AUTHZServlet.java b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/AUTHZServlet.java new file mode 100644 index 00000000..f7c4b7f1 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/AUTHZServlet.java @@ -0,0 +1,100 @@ +/** + * ============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.cadi.filter; + +import java.io.IOException; + +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; + +/** + * + * @author Jonathan + * + */ +public class AUTHZServlet<S extends Servlet> implements Servlet { + private String[] roles; + private Servlet delegate; + + protected AUTHZServlet(Class<S> cls) { + try { + delegate = cls.newInstance(); + } catch (Exception e) { + delegate = null; + } + RolesAllowed rolesAllowed = cls.getAnnotation(RolesAllowed.class); + if(rolesAllowed == null) { + roles = null; + } else { + roles = rolesAllowed.value(); + } + } + + public void init(ServletConfig sc) throws ServletException { + if(delegate == null) throw new ServletException("Invalid Servlet Delegate"); + delegate.init(sc); + } + + public ServletConfig getServletConfig() { + return delegate.getServletConfig(); + } + + public String getServletInfo() { + return delegate.getServletInfo(); + } + + public void service(ServletRequest req, ServletResponse resp) throws ServletException, IOException { + if(roles==null) { + delegate.service(req,resp); + } else { // Validate + try { + HttpServletRequest hreq = (HttpServletRequest)req; + boolean proceed = false; + for(String role : roles) { + if(hreq.isUserInRole(role)) { + proceed = true; + break; + } + } + if(proceed) { + delegate.service(req,resp); + } else { + //baseRequest.getServletContext().log(hreq.getUserPrincipal().getName()+" Refused " + roles); + ((HttpServletResponse)resp).sendError(403); // forbidden + } + } catch(ClassCastException e) { + throw new ServletException("JASPIServlet only supports HTTPServletRequest/HttpServletResponse"); + } + } + } + + public void destroy() { + delegate.destroy(); + } + + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/filter/AccessGetter.java b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/AccessGetter.java new file mode 100644 index 00000000..ab34a0a4 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/AccessGetter.java @@ -0,0 +1,35 @@ +/** + * ============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.cadi.filter; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.config.Get; + +public class AccessGetter implements Get { + private final Access access; + public AccessGetter(Access access) { + this.access = access; + } + public String get(String name, String def, boolean print) { + return access.getProperty(name, def); + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/filter/CadiFilter.java b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/CadiFilter.java new file mode 100644 index 00000000..8577d55c --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/CadiFilter.java @@ -0,0 +1,332 @@ +/** + * ============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.cadi.filter; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.List; + +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.Lur; +import org.onap.aaf.cadi.PropAccess; +import org.onap.aaf.cadi.ServletContextAccess; +import org.onap.aaf.cadi.TrustChecker; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.config.Config; +import org.onap.aaf.cadi.config.Get; +import org.onap.aaf.cadi.taf.TafResp; +import org.onap.aaf.cadi.taf.TafResp.RESP; + +/** + * CadiFilter + * + * This class implements Servlet Filter, and ties together CADI implementations + * + * This class can be used in a standard J2EE Servlet manner. Optimal usage is for POJO operations, where + * one can enforce this Filter being first and primary. Depending on the Container, it + * may be more effective, in some cases, to utilize features that allow earlier determination of + * AUTHN (Authorization). An example would be "Tomcat Valve". These implementations, however, should + * be modeled after the "init" and "doFilter" functions, and be kept up to date as this class changes. + * + * + * @author Jonathan + * + */ +public class CadiFilter implements Filter { + private static CadiHTTPManip httpChecker; + private static String[] pathExceptions; + private static List<Pair> mapPairs; + private Access access; + private Object[] additionalTafLurs; + private Filter oauthFilter; + private static int count=0; + + public Lur getLur() { + return httpChecker.getLur(); + } + + /** + * Construct a viable Filter + * + * Due to the vagaries of many containers, there is a tendency to create Objects and call "Init" on + * them at a later time. Therefore, this object creates with an object that denies all access + * until appropriate Init happens, just in case the container lets something slip by in the meantime. + * + */ + public CadiFilter() { + additionalTafLurs = CadiHTTPManip.noAdditional; + } + + /** + * This constructor to be used when directly constructing and placing in HTTP Engine + * + * @param access + * @param moreTafLurs + * @throws ServletException + */ + public CadiFilter(Access access, Object ... moreTafLurs) throws ServletException { + additionalTafLurs = moreTafLurs; + init(new AccessGetter(this.access = access)); + } + + + /** + * Use this to pass in a PreContructed CADI Filter, but with initializing... let Servlet do it + * @param init + * @param access + * @param moreTafLurs + * @throws ServletException + */ + public CadiFilter(boolean init, PropAccess access, Object ... moreTafLurs) throws ServletException { + this.access = access; + additionalTafLurs = moreTafLurs; + if(init) { + init(new AccessGetter(access)); + } + } + + /** + * Init + * + * Standard Filter "init" call with FilterConfig to obtain properties. POJOs can construct a + * FilterConfig with the mechanism of their choice, and standard J2EE Servlet engines utilize this + * mechanism already. + */ + //TODO Always validate changes against Tomcat AbsCadiValve and Jaspi CadiSAM Init functions + public void init(FilterConfig filterConfig) throws ServletException { + // need the Context for Logging, instantiating ClassLoader, etc + ServletContextAccess sca=new ServletContextAccess(filterConfig); + if(access==null) { + access = sca; + } + + // Set Protected getter with base Access, for internal class instantiations + init(new FCGet(access, sca.context(), filterConfig)); + } + + + @SuppressWarnings("unchecked") + private void init(Get getter) throws ServletException { + // Start with the assumption of "Don't trust anyone". + TrustChecker tc = TrustChecker.NOTRUST; // default position + try { + Class<TrustChecker> ctc = (Class<TrustChecker>) Class.forName("org.onap.aaf.cadi.aaf.v2_0.AAFTrustChecker"); + if(ctc!=null) { + Constructor<TrustChecker> contc = ctc.getConstructor(Access.class); + if(contc!=null) { + tc = contc.newInstance(access); + } + } + } catch (Exception e) { + access.log(Level.INIT, "AAFTrustChecker cannot be loaded",e.getMessage()); + } + + try { + Class<Filter> cf=null; + try { + cf= (Class<Filter>) Class.forName("org.onap.aaf.cadi.oauth.OAuthFilter"); + oauthFilter = cf.newInstance(); + } catch (ClassNotFoundException e) { + oauthFilter = new Filter() { // Null Filter + @Override + public void destroy() { + } + + @Override + public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)throws IOException, ServletException { + chain.doFilter(req, resp); + } + + @Override + public void init(FilterConfig arg0) throws ServletException { + } + }; + } + } catch (Exception e) { + access.log(Level.INIT, "AAFTrustChecker cannot be loaded",e.getMessage()); + } + + + // Synchronize, because some instantiations call init several times on the same object + // In this case, the epiTaf will be changed to a non-NullTaf, and thus not instantiate twice. + synchronized(CadiHTTPManip.noAdditional /*will always remain same Object*/) { + ++count; + if(httpChecker == null) { + if(access==null) { + access = new PropAccess(); + } + try { + httpChecker = new CadiHTTPManip(access,null /*reuseable Con*/,tc, additionalTafLurs); + } catch (CadiException e1) { + throw new ServletException(e1); + } + } else if(access==null) { + access= httpChecker.getAccess(); + } + + /* + * Setup Authn Path Exceptions + */ + if(pathExceptions==null) { + String str = getter.get(Config.CADI_NOAUTHN, null, true); + if(str!=null) { + pathExceptions = str.split("\\s*:\\s*"); + } + } + + /* + * SETUP Permission Converters... those that can take Strings from a Vendor Product, and convert to appropriate AAF Permissions + */ + if(mapPairs==null) { + String str = getter.get(Config.AAF_PERM_MAP, null, true); + if(str!=null) { + String mstr = getter.get(Config.AAF_PERM_MAP, null, true); + if(mstr!=null) { + String map[] = mstr.split("\\s*:\\s*"); + if(map.length>0) { + MapPermConverter mpc=null; + int idx; + mapPairs = new ArrayList<Pair>(); + for(String entry : map) { + if((idx=entry.indexOf('='))<0) { // it's a Path, so create a new converter + access.log(Level.INIT,"Loading Perm Conversions for:",entry); + mapPairs.add(new Pair(entry,mpc=new MapPermConverter())); + } else { + if(mpc!=null) { + mpc.map().put(entry.substring(0,idx),entry.substring(idx+1)); + } else { + access.log(Level.ERROR,"cadi_perm_map is malformed; ",entry, "is skipped"); + } + } + } + } + } + } + } + } + + // Remove Getter + getter = Get.NULL; + } + + /** + * Containers call "destroy" when time to cleanup + */ + public void destroy() { + // Synchronize, in case multiCadiFilters are used. + synchronized(CadiHTTPManip.noAdditional) { + if(--count<=0 && httpChecker!=null) { + httpChecker.destroy(); + httpChecker=null; + access=null; + pathExceptions=null; + } + } + } + + /** + * doFilter + * + * This is the standard J2EE invocation. Analyze the request, modify response as necessary, and + * only call the next item in the filterChain if request is suitably Authenticated. + */ + //TODO Always validate changes against Tomcat AbsCadiValve and Jaspi CadiSAM functions + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + try { + HttpServletRequest hreq = (HttpServletRequest)request; + if(noAuthn(hreq)) { + chain.doFilter(request, response); + } else { + HttpServletResponse hresp = (HttpServletResponse)response; + TafResp tresp = httpChecker.validate(hreq, hresp, hreq); + if(tresp.isAuthenticated()==RESP.IS_AUTHENTICATED) { + CadiWrap cw = new CadiWrap(hreq, tresp, httpChecker.getLur(),getConverter(hreq)); + if(httpChecker.notCadi(cw, hresp)) { + oauthFilter.doFilter(cw,response,chain); + } + } + } + } catch (ClassCastException e) { + throw new ServletException("CadiFilter expects Servlet to be an HTTP Servlet",e); + } + } + + + /** + * If PathExceptions exist, report if these should not have Authn applied. + * @param hreq + * @return + */ + private boolean noAuthn(HttpServletRequest hreq) { + if(pathExceptions!=null) { + String pi = hreq.getPathInfo(); + if(pi==null) return false; // JBoss sometimes leaves null + for(String pe : pathExceptions) { + if(pi.startsWith(pe))return true; + } + } + return false; + } + + /** + * Get Converter by Path + */ + private PermConverter getConverter(HttpServletRequest hreq) { + if(mapPairs!=null) { + String pi = hreq.getPathInfo(); + if(pi !=null) { + for(Pair p: mapPairs) { + if(pi.startsWith(p.name))return p.pc; + } + } + } + return NullPermConverter.singleton(); + } + + /** + * store PermConverters by Path prefix + * @author Jonathan + * + */ + private class Pair { + public Pair(String key, PermConverter pc) { + name = key; + this.pc = pc; + } + public String name; + public PermConverter pc; + } + +} + diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/filter/CadiHTTPManip.java b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/CadiHTTPManip.java new file mode 100644 index 00000000..094c21b0 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/CadiHTTPManip.java @@ -0,0 +1,210 @@ +/** + * ============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.cadi.filter; + +import java.io.IOException; +import java.net.HttpURLConnection; + +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.CredVal; +import org.onap.aaf.cadi.Lur; +import org.onap.aaf.cadi.Taf; +import org.onap.aaf.cadi.TrustChecker; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.config.Config; +import org.onap.aaf.cadi.config.SecurityInfoC; +import org.onap.aaf.cadi.lur.EpiLur; +import org.onap.aaf.cadi.taf.HttpTaf; +import org.onap.aaf.cadi.taf.TafResp; +import org.onap.aaf.cadi.util.UserChainManip; + +/** + * Encapsulate common HTTP Manipulation Behavior. It will appropriately set + * HTTPServletResponse for Redirect or Forbidden, as needed. + * + * Further, this is useful, because it avoids multiple creates of Connections, where some Filters + * are created and destroyed regularly. + * + * @author Jonathan + * + */ +public class CadiHTTPManip { + private static final String ACCESS_CADI_CONTROL = ".access|cadi|control"; + private static final String METH = "OPTIONS"; + private static final String CADI = "/cadi/"; + private static final String CADI_CACHE_PRINT = "/cadi/cache/print"; + private static final String CADI_CACHE_CLEAR = "/cadi/cache/clear"; + private static final String CADI_LOG_SET = "/cadi/log/set/"; + private Access access; + private HttpTaf taf; + private CredVal up; + private Lur lur; + private String thisPerm,companyPerm,aaf_id; + + public static final Object[] noAdditional = new Object[0]; // CadiFilter can be created each call in some systems + + + public CadiHTTPManip(Access access, Connector con, TrustChecker tc, Object ... additionalTafLurs) throws CadiException { + synchronized(CADI) { + this.access = access; +// Get getter = new AccessGetter(access); + Config.setDefaultRealm(access); + + aaf_id = access.getProperty(Config.CADI_ALIAS,access.getProperty(Config.AAF_APPID, null)); + if(aaf_id==null) { + access.printf(Level.INIT, "%s is not set. %s can be used instead",Config.AAF_APPID,Config.CADI_ALIAS); + } else { + access.printf(Level.INIT, "%s is set to %s",Config.AAF_APPID,aaf_id); + } + String ns = aaf_id==null?null:UserChainManip.idToNS(aaf_id); + if(ns!=null) { + thisPerm = ns+ACCESS_CADI_CONTROL; + int dot = ns.indexOf('.'); + if(dot>=0) { + int dot2=ns.indexOf('.',dot+1); + if(dot2<0) { + dot2=dot; + } + companyPerm = ns.substring(0, dot2)+ACCESS_CADI_CONTROL; + } else { + companyPerm = "com"+ACCESS_CADI_CONTROL; + } + } else { + thisPerm = companyPerm = "com"+ACCESS_CADI_CONTROL; + } + SecurityInfoC<HttpURLConnection> si; + si = SecurityInfoC.instance(access, HttpURLConnection.class); + lur = Config.configLur(si, con, additionalTafLurs); + + tc.setLur(lur); + if(lur instanceof EpiLur) { + up = ((EpiLur)lur).getUserPassImpl(); + } else if(lur instanceof CredVal) { + up = (CredVal)lur; + } else { + up = null; + } + taf = Config.configHttpTaf(con,si, tc, up, lur, additionalTafLurs); + } + } + + public TafResp validate(HttpServletRequest hreq, HttpServletResponse hresp, Object state) throws IOException { + TafResp tresp = taf.validate(Taf.LifeForm.LFN, hreq, hresp); + switch(tresp.isAuthenticated()) { + case IS_AUTHENTICATED: + access.printf(Level.INFO,"Authenticated: %s from %s:%d", + tresp.desc(), hreq.getRemoteAddr(), hreq.getRemotePort()); + break; + case TRY_AUTHENTICATING: + switch (tresp.authenticate()) { + case IS_AUTHENTICATED: + access.printf(Level.INFO,"Authenticated: %s from %s:%d", + tresp.desc(), hreq.getRemoteAddr(), hreq.getRemotePort()); + break; + case HTTP_REDIRECT_INVOKED: + access.log(Level.INFO,"Authenticating via redirection: ", tresp.desc()); + break; + case NO_FURTHER_PROCESSING: + access.printf(Level.AUDIT,"Authentication Failure: %s from %s:%d" + , tresp.desc(), hreq.getRemoteAddr(), hreq.getRemotePort()); + hresp.sendError(403, tresp.desc()); // Forbidden + break; + + default: + access.printf(Level.AUDIT,"No TAF will authorize for request from %s:%d" + , hreq.getRemoteAddr(), hreq.getRemotePort()); + hresp.sendError(403, tresp.desc()); // Forbidden + } + break; + case NO_FURTHER_PROCESSING: + access.printf(Level.AUDIT,"Authentication Failure: %s from %s:%d", + tresp.desc(), hreq.getRemoteAddr(), hreq.getRemotePort()); + hresp.sendError(403, "Access Denied"); // FORBIDDEN + break; + default: + access.printf(Level.AUDIT,"No TAF will authorize for request from %s:%d" + , hreq.getRemoteAddr(), hreq.getRemotePort()); + hresp.sendError(403, "Access Denied"); // FORBIDDEN + } + return tresp; + } + + public boolean notCadi(CadiWrap req, HttpServletResponse resp) { + + String pathInfo = req.getPathInfo(); + if(METH.equalsIgnoreCase(req.getMethod()) && pathInfo!=null && pathInfo.contains(CADI)) { + if(req.getUser().equals(aaf_id) || req.isUserInRole(thisPerm) || req.isUserInRole(companyPerm)) { + try { + if(pathInfo.contains(CADI_CACHE_PRINT)) { + resp.getOutputStream().println(lur.toString()); + resp.setStatus(200); + return false; + } else if(pathInfo.contains(CADI_CACHE_CLEAR)) { + StringBuilder report = new StringBuilder(); + lur.clear(req.getUserPrincipal(), report); + resp.getOutputStream().println(report.toString()); + resp.setStatus(200); + return false; + } else if(pathInfo.contains(CADI_LOG_SET)) { + Level l; + int slash = pathInfo.lastIndexOf('/'); + String level = pathInfo.substring(slash+1); + try { + l = Level.valueOf(level); + access.printf(Level.AUDIT, "%s has set CADI Log Level to '%s'",req.getUser(),l.name()); + access.setLogLevel(l); + } catch (IllegalArgumentException e) { + access.printf(Level.AUDIT, "'%s' is not a valid CADI Log Level",level); + } + return false; + } + } catch (IOException e) { + access.log(e); + } + } + } + return true; + } + + public Lur getLur() { + return lur; + } + + public void destroy() { + access.log(Level.INFO,"CadiHttpChecker destroyed."); + if(lur!=null) { + lur.destroy(); + lur=null; + } + } + + public Access getAccess() { + return access; + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/filter/FCGet.java b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/FCGet.java new file mode 100644 index 00000000..9c4cca10 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/FCGet.java @@ -0,0 +1,76 @@ +/** + * ============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.cadi.filter; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.config.Get; + +/* + * A private method to query the Filter config and if not exists, return the default. This + * cleans up the initialization code. + */ +class FCGet implements Get { + /** + * + */ + private final Access access; + private FilterConfig filterConfig; + private ServletContext context; + + public FCGet(Access access, ServletContext context, FilterConfig filterConfig) { + this.access = access; + this.context = context; + this.filterConfig = filterConfig; + } + + public String get(String name, String def, boolean print) { + String str = null; + // Try Server Context First + if(context!=null) { + str = context.getInitParameter(name); + } + + // Try Filter Context next + if(str==null && filterConfig != null) { + str = filterConfig.getInitParameter(name); + } + + if(str==null) { + str = access.getProperty(name, def); + } + // Take def if nothing else + if(str==null) { + str = def; + // don't log defaults + } else { + str = str.trim(); // this is vital in Property File based values, as spaces can hide easily + if(print) { + access.log(Level.INFO,"Setting", name, "to", str); + } + } + return str; + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/filter/MapPermConverter.java b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/MapPermConverter.java new file mode 100644 index 00000000..052b9ff1 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/MapPermConverter.java @@ -0,0 +1,54 @@ +/** + * ============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.cadi.filter; + +import java.util.HashMap; +import java.util.Map; + +public class MapPermConverter implements PermConverter { + private HashMap<String,String> map; + + /** + * Create with colon separated name value pairs + * i.e. teAdmin=com.att.myNS.myPerm|*|*:teUser=... + * + * @param value + */ + public MapPermConverter() { + map = new HashMap<String,String>(); + } + + /** + * use to instantiate entries + * + * @return + */ + public Map<String,String> map() { + return map; + } + + public String convert(String minimal) { + String rv = map.get(minimal); + return rv==null?minimal:rv; + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/filter/NullPermConverter.java b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/NullPermConverter.java new file mode 100644 index 00000000..211a4bfe --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/NullPermConverter.java @@ -0,0 +1,43 @@ +/** + * ============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.cadi.filter; + + +/** + * A NullPermConverter + * + * Obey the PermConverter Interface, but passed in "minimal" String is not converted. + * + * @author Jonathan + * + */ +public class NullPermConverter implements PermConverter { + + private NullPermConverter() {} + private static final NullPermConverter singleton = new NullPermConverter(); + public static NullPermConverter singleton() {return singleton;} + + public String convert(String minimal) { + return minimal; + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/filter/PathFilter.java b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/PathFilter.java new file mode 100644 index 00000000..c508a5ce --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/PathFilter.java @@ -0,0 +1,183 @@ +/** + * ============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.cadi.filter; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +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.Access.Level; +import org.onap.aaf.cadi.config.Config; + +/** + * PathFilter + * + * This class implements Servlet Filter, and uses AAF to validate access to a Path. + * + * This class can be used in a standard J2EE Servlet manner. + * + * @author Jonathan, collaborating with Xue Gao + * + */ +public class PathFilter implements Filter { + private ServletContext context; + private String aaf_type; + private String not_authorized_msg; + private final Log log; + + /** + * Construct a viable Filter for installing in Container WEB.XML, etc. + * + */ + public PathFilter() { + log = new Log() { + public void info(String ... msg) { + context.log(build("INFO:",msg)); + } + public void audit(String ... msg) { + context.log(build("AUDIT:",msg)); + } + private String build(String type, String []msg) { + StringBuilder sb = new StringBuilder(type); + for(String s : msg) { + sb.append(' '); + sb.append(s); + } + return sb.toString(); + } + + }; + } + + /** + * Filter that can be constructed within Java + * @param access + */ + public PathFilter(final Access access) { + log = new Log() { + public void info(String ... msg) { + access.log(Level.INFO, (Object[])msg); + } + public void audit(String ... msg) { + access.log(Level.AUDIT, (Object[])msg); + } + }; + } + + /** + * Init + * + * Standard Filter "init" call with FilterConfig to obtain properties. POJOs can construct a + * FilterConfig with the mechanism of their choice, and standard J2EE Servlet engines utilize this + * mechanism already. + */ + public void init(FilterConfig filterConfig) throws ServletException { + // need the Context for Logging, instantiating ClassLoader, etc + context = filterConfig.getServletContext(); + StringBuilder sb = new StringBuilder(); + StringBuilder err = new StringBuilder(); + Object attr = context.getAttribute(Config.PATHFILTER_NS); + if(attr==null) { + err.append("PathFilter - pathfilter_ns is not set"); + } else { + sb.append(attr.toString()); + } + + attr = context.getAttribute(Config.PATHFILTER_STACK); + if(attr==null) { + log.info("PathFilter - No pathfilter_stack set, ignoring"); + } else { + sb.append('.'); + sb.append(attr.toString()); + } + + attr = context.getAttribute(Config.PATHFILTER_URLPATTERN); + if(attr==null) { + log.info("PathFilter - No pathfilter_urlpattern set, defaulting to 'urlpattern'"); + sb.append(".urlpattern"); + } else { + sb.append('.'); + sb.append(attr.toString()); + } + + log.info("PathFilter - AAF Permission Type is",sb.toString()); + + sb.append('|'); + + aaf_type = sb.toString(); + + attr = context.getAttribute(Config.PATHFILTER_NOT_AUTHORIZED_MSG); + if(attr==null) { + not_authorized_msg = "Forbidden - Not Authorized to access this Path"; + } else { + not_authorized_msg = attr.toString(); + } + + if(err.length()>0) { + throw new ServletException(err.toString()); + } + } + + private interface Log { + public void info(String ... msg); + public void audit(String ... msg); + } + + /** + * doFilter + * + * This is the standard J2EE invocation. Analyze the request, modify response as necessary, and + * only call the next item in the filterChain if request is suitably Authenticated. + */ + //TODO Always validate changes against Tomcat AbsCadiValve and Jaspi CadiSAM functions + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest hreq = (HttpServletRequest)request; + HttpServletResponse hresp = (HttpServletResponse)response; + String perm = aaf_type+hreq.getPathInfo()+'|'+hreq.getMethod(); + if(hreq.isUserInRole(perm)) { + chain.doFilter(request, response); + } else { + log.audit("PathFilter has denied",hreq.getUserPrincipal().getName(),"access to",perm); + hresp.sendError(403,not_authorized_msg); + } + } + + /** + * Containers call "destroy" when time to cleanup + */ + public void destroy() { + log.info("PathFilter destroyed."); + } + + + +} + diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/filter/PermConverter.java b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/PermConverter.java new file mode 100644 index 00000000..bb97894b --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/PermConverter.java @@ -0,0 +1,32 @@ +/** + * ============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.cadi.filter; + +/** + * Convert a simplistic, single string Permission into an Enterprise Scoped Perm + * + * @author Jonathan + * + */ +public interface PermConverter { + public String convert(String minimal); +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/filter/RolesAllowed.java b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/RolesAllowed.java new file mode 100644 index 00000000..5f709f12 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/RolesAllowed.java @@ -0,0 +1,56 @@ +/** + * ============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==================================================== + * + */ + +/** + * RolesAllowed + * + * @author Jonathan + * + * Similar to Java EE's Spec from Annotations 1.1, 2.8 + * + * That Spec, however, was geared towards being able to route calls to Methods on Objects, and thus needed a more refined + * sense of permissions hierarchy. The same mechanism, however, can easily be achieved on single Servlet/Handlers in + * POJOs like Jetty by simply adding the Roles Allowed in a similar Annotation + * + */ +package org.onap.aaf.cadi.filter; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * JASPI Style Annotation of RolesAllowed when the coding style is desired but actually including all + * JEE jars is not. If using actual JASPI, use official @interface classes, not this one... + * + * @author Jonathan + */ +@Target({TYPE}) +@Retention(RUNTIME) +public @interface RolesAllowed { + /** + * Security role of the implementation, which doesn't have to be an EJB or CORBA like object. Can be just a + * Handler + * @return + */ + String[] value(); +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/filter/ServletImpl.java b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/ServletImpl.java new file mode 100644 index 00000000..02c2600f --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/ServletImpl.java @@ -0,0 +1,56 @@ +/** + * ============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==================================================== + * + */ + +/** + * RolesAllowed + * + * @author Jonathan + * + * Similar to Java EE's Spec from Annotations 1.1, 2.8 + * + * That Spec, however, was geared towards being able to route calls to Methods on Objects, and thus needed a more refined + * sense of permissions hierarchy. The same mechanism, however, can easily be achieved on single Servlet/Handlers in + * POJOs like Jetty by simply adding the Roles Allowed in a similar Annotation + * + */ +package org.onap.aaf.cadi.filter; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.servlet.Servlet; + +/** + * + * @author Jonathan + */ +@Target({TYPE}) +@Retention(RUNTIME) +public @interface ServletImpl { + /** + * Security role of the implementation, which doesn't have to be an EJB or CORBA like object. Can be just a + * Handler + * @return + */ + Class<? extends Servlet> value(); +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/lur/ConfigPrincipal.java b/cadi/core/src/main/java/org/onap/aaf/cadi/lur/ConfigPrincipal.java new file mode 100644 index 00000000..43dd1018 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/lur/ConfigPrincipal.java @@ -0,0 +1,69 @@ +/** + * ============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.cadi.lur; + +import java.io.IOException; +import java.security.Principal; + +import org.onap.aaf.cadi.GetCred; +import org.onap.aaf.cadi.Symm; + +public class ConfigPrincipal implements Principal, GetCred { + private String name; + private byte[] cred; + private String content; + + public ConfigPrincipal(String name, String passwd) { + this.name = name; + this.cred = passwd.getBytes(); + content = null; + } + + public ConfigPrincipal(String name, byte[] cred) { + this.name = name; + this.cred = cred; + content = null; + } + + public String getName() { + return name; + } + + public byte[] getCred() { + return cred; + } + + public String toString() { + return name; + } + + public String getAsBasicAuthHeader() throws IOException { + if(content ==null) { + String s = name + ':' + new String(cred); + content = "Basic " + Symm.base64.encode(s); + } else if(!content.startsWith("Basic ")) { // content is the saved password from construction + String s = name + ':' + content; + content = "Basic " + Symm.base64.encode(s); + } + return content; + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/lur/EpiLur.java b/cadi/core/src/main/java/org/onap/aaf/cadi/lur/EpiLur.java new file mode 100644 index 00000000..2813dca8 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/lur/EpiLur.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.cadi.lur; + +import java.security.Principal; +import java.util.List; + +import org.onap.aaf.cadi.CachingLur; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.CredVal; +import org.onap.aaf.cadi.Lur; +import org.onap.aaf.cadi.Permission; + +/** + * EpiLUR + * + * Short for "Epic LUR". Be able to run through a series of LURs to obtain the validation needed. + * + * The pun is better for the other pattern... "TAF" (aka EpiTaf), but it's still the larger picture of + * LURs that will be accomplished. + * + * FYI, the reason we separate LURs, rather than combine, is that Various User Repository Resources have + * different Caching requirements. For instance, the Local User Repo (with stand alone names), never expire, but might be + * refreshed with a change in Configuration File, while the Remote Service based LURs will need to expire at prescribed intervals + * + * @author Jonathan + * + */ +public final class EpiLur implements Lur { + private final Lur[] lurs; + + /** + * EpiLur constructor + * + * Construct the EpiLur from variable TAF parameters + * @param lurs + * @throws CadiException + */ + public EpiLur(Lur ... lurs) throws CadiException{ + this.lurs = lurs; + if(lurs.length==0) throw new CadiException("Need at least one Lur implementation in constructor"); + } + + public boolean fish(Principal bait, Permission pond) { + if(pond==null) { + return false; + } + boolean rv = false; + Lur lur; + for(int i=0;!rv && i<lurs.length;++i) { + rv = (lur = lurs[i]).fish(bait, pond); + if(!rv && lur.handlesExclusively(pond)) break; + } + return rv; + } + + public void fishAll(Principal bait, List<Permission> permissions) { + for(Lur lur : lurs) { + lur.fishAll(bait, permissions); + } + } + + public void destroy() { + for(Lur lur : lurs) { + lur.destroy(); + } + } + + /** + * Return the first Lur (if any) which also implements UserPass + * @return + */ + public CredVal getUserPassImpl() { + for(Lur lur : lurs) { + if(lur instanceof CredVal) { + return (CredVal)lur; + } + } + return null; + } + + // Never needed... Only EpiLur uses... + public boolean handlesExclusively(Permission pond) { + return false; + } + + /** + * Get Lur for index. Returns null if out of range + * @param idx + * @return + */ + public Lur get(int idx) { + if(idx>=0 && idx<lurs.length) { + return lurs[idx]; + } + return null; + } + + public boolean handles(Principal p) { + for(Lur l : lurs) { + if(l.handles(p)) { + return true; + } + } + return false; + } + + public void remove(String id) { + for(Lur l : lurs) { + if(l instanceof CachingLur) { + ((CachingLur<?>)l).remove(id); + } + } + } + + public Lur subLur(Class<? extends Lur> cls ) { + for(Lur l : lurs) { + if(l.getClass().isAssignableFrom(cls)) { + return l; + } + } + return null; + } + + @Override + public Permission createPerm(String p) { + return new LocalPermission(p); + } + + /* (non-Javadoc) + * @see org.onap.aaf.cadi.Lur#clear(java.security.Principal, java.lang.StringBuilder) + */ + @Override + public void clear(Principal p, StringBuilder report) { + for(Lur lur : lurs) { + lur.clear(p, report); + } + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + for(Lur lur : lurs) { + sb.append(lur.getClass().getSimpleName()); + sb.append(": Report\n"); + sb.append(lur.toString()); + sb.append('\n'); + } + return sb.toString(); + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/lur/LocalLur.java b/cadi/core/src/main/java/org/onap/aaf/cadi/lur/LocalLur.java new file mode 100644 index 00000000..c1a27fa7 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/lur/LocalLur.java @@ -0,0 +1,196 @@ +/** + * ============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.cadi.lur; + +import java.io.IOException; +import java.security.Principal; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.onap.aaf.cadi.AbsUserCache; +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.CredVal; +import org.onap.aaf.cadi.Hash; +import org.onap.aaf.cadi.Lur; +import org.onap.aaf.cadi.Permission; +import org.onap.aaf.cadi.User; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.config.Config; + + +/** + * An in-memory Lur that can be configured locally with User info via properties, similar to Tomcat-users.xml mechanisms. + * + * @author Jonathan + * + */ +public final class LocalLur extends AbsUserCache<LocalPermission> implements Lur, CredVal { + public static final String SEMI = "\\s*;\\s*"; + public static final String COLON = "\\s*:\\s*"; + public static final String COMMA = "\\s*,\\s*"; + public static final String PERCENT = "\\s*%\\s*"; + + // Use to quickly determine whether any given group is supported by this LUR + private final Set<String> supportingGroups; + private String supportedRealm; + + /** + * Construct by building structure, see "build" + * + * Reconstruct with "build" + * + * @param userProperty + * @param groupProperty + * @param decryptor + * @throws IOException + */ + public LocalLur(Access access, String userProperty, String groupProperty) throws IOException { + super(access, 0, 0, Integer.MAX_VALUE); // data doesn't expire + supportedRealm = access.getProperty(Config.BASIC_REALM, "localized"); + supportingGroups = new TreeSet<String>(); + + if(userProperty!=null) { + // For each User name... + for(String user : userProperty.trim().split(SEMI)) { + String[] us = user.split(COLON,2); + String[] userpass = us[0].split(PERCENT,2); + String u; + User<LocalPermission> usr; + if(userpass.length>1) { + if(userpass.length>0 && userpass[0].indexOf('@')<0) { + userpass[0]=userpass[0] + '@' + access.getProperty(Config.AAF_DEFAULT_REALM,Config.getDefaultRealm()); + } + + u = userpass[0]; + byte[] pass = access.decrypt(userpass[1], true).getBytes(); + usr = new User<LocalPermission>(new ConfigPrincipal(u, pass)); + } else { + u = us[0]; + usr = new User<LocalPermission>(new ConfigPrincipal(u, (byte[])null)); + } + addUser(usr); + access.log(Level.INIT, "Local User:",usr.principal); + + if(us.length>1) { + Map<String, Permission> newMap = usr.newMap(); + for(String group : us[1].split(COMMA)) { + supportingGroups.add(group); + usr.add(newMap,new LocalPermission(group)); + } + usr.setMap(newMap); + } + } + } + if(groupProperty!=null) { + // For each Group name... + for(String group : groupProperty.trim().split(SEMI)) { + String[] gs = group.split(COLON,2); + if(gs.length>1) { + supportingGroups.add(gs[0]); + LocalPermission p = new LocalPermission(gs[0]); + // Add all users (known by comma separators) + + for(String grpMem : gs[1].split(COMMA)) { + // look for password, if so, put in passMap + String[] userpass = grpMem.split(PERCENT,2); + if(userpass.length>0 && userpass[0].indexOf('@')<0) { + userpass[0]=userpass[0] + '@' + access.getProperty(Config.AAF_DEFAULT_REALM,Config.getDefaultRealm()); + } + User<LocalPermission> usr = null; + if(userpass.length>1) { + byte[] pass = access.decrypt(userpass[1], true).getBytes(); + usr = getUser(userpass[0],pass); + if(usr==null)addUser(usr=new User<LocalPermission>(new ConfigPrincipal(userpass[0],pass))); + else usr.principal=new ConfigPrincipal(userpass[0],pass); + } else { + addUser(usr=new User<LocalPermission>(new ConfigPrincipal(userpass[0],(byte[])null))); + } + usr.add(p); + access.log(Level.INIT, "Local User:",usr.principal); + } + } + } + } + } + + public boolean validate(String user, CredVal.Type type, byte[] cred, Object state) { + User<LocalPermission> usr = getUser(user,cred); + switch(type) { + case PASSWORD: + // covers null as well as bad pass + if(usr!=null && cred!=null && usr.principal instanceof ConfigPrincipal) { + return Hash.isEqual(cred,((ConfigPrincipal)usr.principal).getCred()); + } + break; + } + return false; + } + + // @Override + public boolean fish(Principal bait, Permission pond) { + if(pond == null) { + return false; + } + if(handles(bait) && pond instanceof LocalPermission) { // local Users only have LocalPermissions + User<LocalPermission> user = getUser(bait); + return user==null?false:user.contains((LocalPermission)pond); + } + return false; + } + + // We do not want to expose the actual Group, so make a copy. + public void fishAll(Principal bait, List<Permission> perms) { + if(handles(bait)) { + User<LocalPermission> user = getUser(bait); + if(user!=null) { + user.copyPermsTo(perms); + } + } + } + + /* (non-Javadoc) + * @see org.onap.aaf.cadi.Lur#handles(java.security.Principal) + */ + @Override + public boolean handles(Principal principal) { + return principal!=null && principal.getName().endsWith(supportedRealm); + } + +// public boolean supports(String userName) { +// return userName!=null && userName.endsWith(supportedRealm); +// } +// + public boolean handlesExclusively(Permission pond) { + return supportingGroups.contains(pond.getKey()); + } + + /* (non-Javadoc) + * @see org.onap.aaf.cadi.Lur#createPerm(java.lang.String) + */ + @Override + public Permission createPerm(String p) { + return new LocalPermission(p); + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/lur/LocalPermission.java b/cadi/core/src/main/java/org/onap/aaf/cadi/lur/LocalPermission.java new file mode 100644 index 00000000..8d6f9698 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/lur/LocalPermission.java @@ -0,0 +1,50 @@ +/** + * ============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.cadi.lur; + +import org.onap.aaf.cadi.Permission; + +public class LocalPermission implements Permission { + private String key; + + public LocalPermission(String role) { + this.key = role; + } + + public String getKey() { + return key; + } + + public String toString() { + return key; + } + + public boolean match(Permission p) { + return key.equals(p.getKey()); + } + + public String permType() { + return "LOCAL"; + } + + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/lur/NullLur.java b/cadi/core/src/main/java/org/onap/aaf/cadi/lur/NullLur.java new file mode 100644 index 00000000..1e44726a --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/lur/NullLur.java @@ -0,0 +1,87 @@ +/** + * ============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.cadi.lur; + +import java.security.Principal; +import java.util.List; + +import org.onap.aaf.cadi.Lur; +import org.onap.aaf.cadi.Permission; + +public class NullLur implements Lur { + private static final Permission NULL = new Permission() { + @Override + public String permType() { + return ""; + } + + @Override + public String getKey() { + return ""; + } + + @Override + public boolean match(Permission p) { + return false; + }}; + + public boolean fish(Principal bait, Permission pond) { + // Well, for Jenkins, this is ok... It finds out it can't do J2EE Security, and then looks at it's own +// System.err.println("CADI's LUR has not been configured, but is still being called. Access is being denied"); + return false; + } + + public void fishAll(Principal bait, List<Permission> permissions) { + } + + public void destroy() { + } + + public boolean handlesExclusively(Permission pond) { + return false; + } + + public boolean handles(Principal p) { + return false; + } + + /* (non-Javadoc) + * @see org.onap.aaf.cadi.Lur#createPerm(java.lang.String) + */ + @Override + public Permission createPerm(String p) { + return NULL; + } + + /* (non-Javadoc) + * @see org.onap.aaf.cadi.Lur#clear(java.security.Principal, java.lang.StringBuilder) + */ + @Override + public void clear(Principal p, StringBuilder report) { + report.append(NullLur.class.getSimpleName()); + report.append('\n'); + } + + public String toString() { + return NullLur.class.getSimpleName() + '\n'; + } +}
\ No newline at end of file diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/principal/BasicPrincipal.java b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/BasicPrincipal.java new file mode 100644 index 00000000..6a49401c --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/BasicPrincipal.java @@ -0,0 +1,126 @@ +/** + * ============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.cadi.principal; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Date; + +import org.onap.aaf.cadi.BasicCred; +import org.onap.aaf.cadi.GetCred; +import org.onap.aaf.cadi.Symm; + +public class BasicPrincipal extends BearerPrincipal implements GetCred { + private static byte[] basic = "Basic ".getBytes(); + + private String name = null; + private String shortName = null; + private byte[] cred = null; + + private long created; + + public BasicPrincipal(String content,String domain) throws IOException { + created = System.currentTimeMillis(); + ByteArrayInputStream bis = new ByteArrayInputStream(content.getBytes()); + // Read past "Basic ", ensuring it starts with it. + for(int i=0;i<basic.length;++i) { + if(bis.read()!=basic[i]) { + name=content; + cred = null; + return; + } + } + BasicOS bos = new BasicOS(content.length()); + Symm.base64.decode(bis,bos); // note: writes directly to name until ':' + if(name==null) throw new IOException("Invalid Coding"); + else cred = bos.toCred(); + int at; + if((at=name.indexOf('@'))>0) { + domain=name.substring(at+1); + shortName=name.substring(0, at); + } else { + shortName = name; + name = name + '@' + domain; + } + } + + public BasicPrincipal(BasicCred bc, String domain) { + name = bc.getUser(); + cred = bc.getCred(); + } + + private class BasicOS extends OutputStream { + private boolean first = true; + private ByteArrayOutputStream baos; + + public BasicOS(int size) { + baos = new ByteArrayOutputStream(size); + } + + @Override + public void write(int b) throws IOException { + if(b==':' && first) { + first = false; + name = new String(baos.toByteArray()); + baos.reset(); // + } else { + baos.write(b); + } + } + + private byte[] toCred() { + return baos.toByteArray(); + } + } + + public String getName() { + return name; + } + + public String getShortName() { + return shortName; + } + + public byte[] getCred() { + return cred; + } + + public long created() { + return created; + } + + public String toString() { + return "Basic Authorization for " + name + " evaluated on " + new Date(created).toString(); + } + + @Override + public String tag() { + return "BAth"; + } + + @Override + public String personalName() { + return null; // personalName not available with Basic Auth + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/principal/BearerPrincipal.java b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/BearerPrincipal.java new file mode 100644 index 00000000..ea0ff2fe --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/BearerPrincipal.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.cadi.principal; + +public abstract class BearerPrincipal extends TaggedPrincipal { + private String bearer = null; + public BearerPrincipal setBearer(String bearer) { + this.bearer = bearer; + return this; + } + public String getBearer() { + return bearer; + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/principal/CachedBasicPrincipal.java b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/CachedBasicPrincipal.java new file mode 100644 index 00000000..68229d3d --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/CachedBasicPrincipal.java @@ -0,0 +1,65 @@ +/** + * ============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.cadi.principal; + +import java.io.IOException; + +import org.onap.aaf.cadi.BasicCred; +import org.onap.aaf.cadi.CachedPrincipal; +import org.onap.aaf.cadi.taf.HttpTaf; + +/** + * Cached Principals need to be able to revalidate in the Background + * + * @author Jonathan + * + */ +public class CachedBasicPrincipal extends BasicPrincipal implements CachedPrincipal { + private final HttpTaf creator; + private long timeToLive; + private long expires; + + public CachedBasicPrincipal(HttpTaf creator, BasicCred bc, String domain, long timeToLive) { + super(bc, domain); + this.creator = creator; + this.timeToLive = timeToLive; + expires = System.currentTimeMillis()+timeToLive; + } + + public CachedBasicPrincipal(HttpTaf creator, String content, String domain, long timeToLive) throws IOException { + super(content, domain); + this.creator = creator; + this.timeToLive = timeToLive; + expires = System.currentTimeMillis()+timeToLive; + } + + public CachedPrincipal.Resp revalidate(Object state) { + Resp resp = creator.revalidate(this, state); + if(resp.equals(Resp.REVALIDATED))expires = System.currentTimeMillis()+timeToLive; + return resp; + } + + public long expires() { + return expires; + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/principal/Kind.java b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/Kind.java new file mode 100644 index 00000000..bb6dc673 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/Kind.java @@ -0,0 +1,53 @@ +/** + * ============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.cadi.principal; + +import java.security.Principal; + +public class Kind { + public static final char X509 = 'X'; + public static final char OAUTH = 'O'; + public static final char AAF_OAUTH='A'; + public static final char BASIC_AUTH = 'B'; + public static final char UNKNOWN = 'U'; + + + public static char getKind(final Principal principal) { + Principal check; + if(principal instanceof TrustPrincipal) { + check = ((TrustPrincipal)principal).original(); + } else { + check = principal; + } + if(check instanceof X509Principal) { + return X509; + } + if(check instanceof OAuth2FormPrincipal) { + // Note: if AAF, will turn into 'A' + return OAUTH; + } + if(check instanceof BasicPrincipal) { + return BASIC_AUTH; + } + return UNKNOWN; + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/principal/OAuth2FormPrincipal.java b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/OAuth2FormPrincipal.java new file mode 100644 index 00000000..1df2bd3e --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/OAuth2FormPrincipal.java @@ -0,0 +1,61 @@ +/** + * ============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.cadi.principal; + +public class OAuth2FormPrincipal extends TaggedPrincipal { + private final String username; + private final String client_id; + + /* + * Note: client_id and username might be the same, if only authenticating the Client_ID + */ + public OAuth2FormPrincipal(final String client_id, final String username) { + this.username = username; + this.client_id = client_id; + } + + @Override + public String getName() { + return username; + } + + public String client_id() { + return client_id; + } + + @Override + public String tag() { + return "OAuth"; + } + + @Override + public String personalName() { + if(username!=null && username!=client_id) { + StringBuilder sb = new StringBuilder(); + sb.append(username); + sb.append('|'); + sb.append(client_id); + return sb.toString(); + } + return client_id; + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/principal/StringTagLookup.java b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/StringTagLookup.java new file mode 100644 index 00000000..a3927168 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/StringTagLookup.java @@ -0,0 +1,35 @@ +/** + * ============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.cadi.principal; + +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.principal.TaggedPrincipal.TagLookup; + +public class StringTagLookup implements TagLookup { + private String tag; + public StringTagLookup(final String tag) { + this.tag = tag; + } + @Override + public String lookup() throws CadiException { + return tag; + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/principal/TaggedPrincipal.java b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/TaggedPrincipal.java new file mode 100644 index 00000000..a3b07c6c --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/TaggedPrincipal.java @@ -0,0 +1,60 @@ +/** + * ============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.cadi.principal; + +import java.security.Principal; + +import org.onap.aaf.cadi.CadiException; + +public abstract class TaggedPrincipal implements Principal { + + public TaggedPrincipal() { + tagLookup = null; + } + + public TaggedPrincipal(final TagLookup tl) { + tagLookup = tl; + } + + public abstract String tag(); // String representing what kind of Authentication occurred. + + public interface TagLookup { + public String lookup() throws CadiException; + } + + private TagLookup tagLookup; + + public void setTagLookup(TagLookup tl) { + tagLookup = tl; + } + + public String personalName() { + if(tagLookup == null) { + return getName(); + } + try { + return tagLookup.lookup(); + } catch (CadiException e) { + return getName(); + } + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/principal/TrustPrincipal.java b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/TrustPrincipal.java new file mode 100644 index 00000000..09083316 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/TrustPrincipal.java @@ -0,0 +1,70 @@ +/** + * ============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.cadi.principal; + +import java.security.Principal; + +import org.onap.aaf.cadi.UserChain; + +public class TrustPrincipal extends BearerPrincipal implements UserChain { + private final String name; + private final Principal original; + private String userChain; + + public TrustPrincipal(final Principal actual, final String asName) { + this.original = actual; + name = asName.trim(); + if(actual instanceof UserChain) { + UserChain uc = (UserChain)actual; + userChain = uc.userChain(); + } else if(actual instanceof TaggedPrincipal) { + userChain=((TaggedPrincipal)actual).tag(); + } else { + userChain = actual.getClass().getSimpleName(); + } + } + + @Override + public String getName() { + return name; + } + + @Override + public String userChain() { + return userChain; + } + + public Principal original() { + return original; + } + + @Override + public String tag() { + return userChain; + } + + @Override + public String personalName() { + return original.getName() + '[' + userChain + ']'; + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/principal/UnAuthPrincipal.java b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/UnAuthPrincipal.java new file mode 100644 index 00000000..52f78e80 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/UnAuthPrincipal.java @@ -0,0 +1,37 @@ +/** + * ============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.cadi.principal; + +import java.security.Principal; + +public class UnAuthPrincipal implements Principal { + private String name; + + public UnAuthPrincipal(final String name) { + this.name = name; + } + @Override + public String getName() { + return name; + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/principal/X509Principal.java b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/X509Principal.java new file mode 100644 index 00000000..16f62171 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/principal/X509Principal.java @@ -0,0 +1,109 @@ +/** + * ============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.cadi.principal; + +import java.io.IOException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.regex.Pattern; + +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.GetCred; + +public class X509Principal extends BearerPrincipal implements GetCred { + private static final Pattern pattern = Pattern.compile("[a-zA-Z0-9]*\\@[a-zA-Z0-9.]*"); + private final X509Certificate cert; + private final String name; + private TagLookup tagLookup; + private byte[] content; + + public X509Principal(String identity, X509Certificate cert) { + name = identity; + content = null; + this.cert = cert; + tagLookup = null; + } + + public X509Principal(String identity, X509Certificate cert, byte[] content) { + name = identity; + this.content = content; + this.cert = cert; + tagLookup = null; + } + + public X509Principal(X509Certificate cert, byte[] content) throws IOException { + this.content=content; + this.cert = cert; + String _name = null; + String subj = cert.getSubjectDN().getName(); + int cn = subj.indexOf("OU="); + if(cn>=0) { + cn+=3; + int space = subj.indexOf(',',cn); + if(space>=0) { + String id = subj.substring(cn, space); + if(pattern.matcher(id).matches()) { + _name = id; + } + } + } + if(_name==null) { + throw new IOException("X509 does not have Identity as CN"); + } + name = _name; + tagLookup = null; + } + + public String getAsHeader() throws IOException { + try { + if(content==null) { + content=cert.getEncoded(); + } + } catch (CertificateEncodingException e) { + throw new IOException(e); + } + return "X509 " + content; + } + + public String toString() { + return "X509 Authentication for " + name; + } + + + public byte[] getCred() { + try { + return content==null?(content=cert.getEncoded()):content; + } catch (CertificateEncodingException e) { + return null; + } + } + + public String getName() { + return name; + } + + @Override + public String tag() { + return "x509"; + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/AbsTafResp.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/AbsTafResp.java new file mode 100644 index 00000000..a2fc730e --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/AbsTafResp.java @@ -0,0 +1,116 @@ +/** + * ============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.cadi.taf; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.principal.TaggedPrincipal; + +/** + * AbsTafResp + * + * Base class for TafResp (TAF Response Objects) + * + * @author Jonathan + * + */ +public abstract class AbsTafResp implements TafResp { + + protected final String desc; + protected final TaggedPrincipal principal; + protected final Access access; + + /** + * AbsTafResp + * + * Set and hold + * Description (for logging) + * Principal (as created by derived class) + * Access (for access to underlying container, i.e. for Logging, auditing, ClassLoaders, etc) + * + * @param access + * @param principal + * @param description + */ + public AbsTafResp(Access access, TaggedPrincipal principal, String description) { + this.access = access; + this.principal = principal; + this.desc = description; + } + + /** + * isValid() + * + * Respond in the affirmative if the TAF was able to Authenticate + */ + public boolean isValid() { + return principal!=null; + } + + /** + * desc() + * + * Respond with description of response as given by the TAF + */ + public String desc() { + return desc; + } + + /** + * isAuthenticated() + * + * Respond with the TAF's code of whether Authenticated, or suggested next steps + * default is either IS_AUTHENTICATED, or TRY_ANOTHER_TAF. The TAF can overload + * and suggest others, such as "NO_FURTHER_PROCESSING", if it can detect that this + * is some sort of security breach (i.e. Denial of Service) + */ + public RESP isAuthenticated() { + return principal==null?RESP.TRY_ANOTHER_TAF:RESP.IS_AUTHENTICATED; + } + + /** + * getPrincipal() + * + * Return the principal created by the TAF based on Authentication. + * + * Returns "null" if Authentication failed (no principal) + */ + public TaggedPrincipal getPrincipal() { + return principal; + } + + /** + * getAccess() + * + * Get the Access object from the TAF, so that appropriate Logging, etc can be coordinated. + */ + public Access getAccess() { + return access; + } + + /* (non-Javadoc) + * @see org.onap.aaf.cadi.taf.TafResp#isFailedAttempt() + */ + public boolean isFailedAttempt() { + return false; + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/EpiTaf.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/EpiTaf.java new file mode 100644 index 00000000..d772d493 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/EpiTaf.java @@ -0,0 +1,84 @@ +/** + * ============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.cadi.taf; + +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.Taf; + +/** + * EpiTAF + * + * Short for "Epic TAF". Be able to run through a series of TAFs to obtain the validation needed. + * + * OK, the name could probably be better as "Tafs", like it was originally, but the pun was too + * irresistible for this author to pass up. + * + * @author Jonathan + * + */ +public class EpiTaf implements Taf { + private Taf[] tafs; + + /** + * EpiTaf constructor + * + * Construct the EpiTaf from variable TAF parameters + * @param tafs + * @throws CadiException + */ + public EpiTaf(Taf ... tafs) throws CadiException{ + this.tafs = tafs; + if(tafs.length==0) throw new CadiException("Need at least one Taf implementation in constructor"); + } + + /** + * validate + * + * Respond with the first TAF to authenticate user based on variable info and "LifeForm" (is it + * a human behind an interface, or a server behind a protocol). + * + * If there is no TAF that can authenticate, respond with the first TAF that suggests it can + * establish an Authentication conversation (TRY_AUTHENTICATING). + * + * If no TAF declares either, respond with NullTafResp (which denies all questions) + */ + public TafResp validate(LifeForm reading, String... info) { + TafResp tresp,firstTryAuth=null; + for(Taf taf : tafs) { + tresp = taf.validate(reading, info); + switch(tresp.isAuthenticated()) { + case TRY_ANOTHER_TAF: + break; + case TRY_AUTHENTICATING: + if(firstTryAuth==null)firstTryAuth=tresp; + break; + default: + return tresp; + } + } + + // No TAFs configured, at this point. It is safer at this point to be "not validated", + // rather than "let it go" + return firstTryAuth == null?NullTafResp.singleton():firstTryAuth; + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/HttpEpiTaf.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/HttpEpiTaf.java new file mode 100644 index 00000000..e575be14 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/HttpEpiTaf.java @@ -0,0 +1,197 @@ +/** + * ============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.cadi.taf; + +import java.net.URI; +import java.security.Principal; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.CachedPrincipal; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.Locator; +import org.onap.aaf.cadi.TrustChecker; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.CachedPrincipal.Resp; +import org.onap.aaf.cadi.Taf.LifeForm; + +/** + * HttpEpiTaf + * + * An extension of the basic "EpiTAF" concept, check known HTTP Related TAFs for valid credentials + * + * @author Jonathan + * + */ +public class HttpEpiTaf implements HttpTaf { + private HttpTaf[] tafs; + private Access access; + private Locator<URI> locator; + private TrustChecker trustChecker; + + /** + * HttpEpiTaf constructor + * + * Construct the HttpEpiTaf from variable Http specific TAF parameters + + * @param tafs + * @throws CadiException + */ + public HttpEpiTaf(Access access, Locator<URI> locator, TrustChecker tc, HttpTaf ... tafs) throws CadiException{ + this.tafs = tafs; + this.access = access; + this.locator = locator; + this.trustChecker = tc; + // Establish what Header Property to look for UserChain/Trust Props +// trustChainProp = access.getProperty(Config.CADI_TRUST_PROP, Config.CADI_TRUST_PROP_DEFAULT); + + if(tafs.length==0) throw new CadiException("Need at least one HttpTaf implementation in constructor"); + } + + /** + * validate + * + * Respond with the first Http specific TAF to authenticate user based on variable info + * and "LifeForm" (is it a human behind a browser, or a server utilizing HTTP Protocol). + * + * If there is no HttpTAF that can authenticate, respond with the first TAF that suggests it can + * establish an Authentication conversation (TRY_AUTHENTICATING) (Examples include a redirect to CSP + * Servers for CSP Cookie, or BasicAuth 401 response, suggesting User/Password for given Realm + * submission + * + * If no TAF declares either, respond with NullTafResp (which denies all questions) + */ + public TafResp validate(LifeForm reading, HttpServletRequest req, HttpServletResponse resp) { + // Given a LifeForm Neutral, for HTTP, we need to discover true Life-Form Readings + if(reading==LifeForm.LFN) { + reading = tricorderScan(req); + } + TafResp tresp=null, firstTry = null; + List<Redirectable> redirectables = null; + List<TafResp> trlog = access.willLog(Level.DEBUG)?new ArrayList<TafResp>():null; + try { + for(HttpTaf taf : tafs) { + tresp = taf.validate(reading, req, resp); + if(trlog!=null) { + trlog.add(tresp); + } + switch(tresp.isAuthenticated()) { + case TRY_ANOTHER_TAF: + break; // and loop + case TRY_AUTHENTICATING: + if(tresp instanceof Redirectable) { + if(redirectables==null) { + redirectables = new ArrayList<Redirectable>(); + } + redirectables.add((Redirectable)tresp); + } else if(firstTry==null) { + firstTry = tresp; + } + break; + case IS_AUTHENTICATED: + tresp = trustChecker.mayTrust(tresp, req); + return tresp; + default: + return tresp; + } + } + } finally { + if(trlog!=null) { + for( TafResp tr : trlog) { + access.log(Level.DEBUG, tr.desc()); + } + } + } + + // If No TAFs configured, at this point. It is safer at this point to be "not validated", + // rather than "let it go" + // Note: if exists, there will always be more than 0 entries, according to above code + if(redirectables==null) { + return firstTry!=null?firstTry:NullTafResp.singleton(); + } + + // If there is one Tryable entry then return it + if(redirectables.size()>1) { + return LoginPageTafResp.create(access,locator,resp,redirectables); + } else { + return redirectables.get(0); + } + } + + public boolean revalidate(Principal prin) throws Exception { + return false; + } + + /* + * Since this is internal, we use a little Star Trek humor to indicate looking in the HTTP Request to see if we can determine what kind + * of "LifeForm" reading we can determine, i.e. is there a Human (CarbonBasedLifeForm) behind a browser, or is it mechanical + * id (SiliconBasedLifeForm)? This makes a difference in some Authentication, i.e CSP, which doesn't work well for SBLFs + */ + private LifeForm tricorderScan(HttpServletRequest req) { + // For simplicity's sake, we'll say Humans use FQDNs, not IPs. + + String auth = req.getParameter("Authentication"); + if(auth!=null) { + if("BasicAuth".equals(auth)) { + return LifeForm.SBLF; + } + } + // Current guess that only Browsers bother to set "Agent" codes that identify the kind of browser they are. + // If mechanical frameworks are found that populate this, then more advanced analysis may be required + // Jonathan 1/22/2013 + String agent = req.getHeader("User-Agent"); + if(agent!=null && agent.startsWith("Mozilla")) // covers I.E./Firefox/Safari/probably any other "advanced" Browser see http://en.wikipedia.org/wiki/User_agent + return LifeForm.CBLF; + return LifeForm.SBLF; // notably skips "curl","wget", (which is desired behavior. We don't want to try CSP, etc on these) + } + + public Resp revalidate(CachedPrincipal prin, Object state) { + Resp resp; + for(HttpTaf taf : tafs) { + resp = taf.revalidate(prin,state); + switch(resp) { + case NOT_MINE: + break; + default: + return resp; + } + } + return Resp.NOT_MINE; + } + + /** + * List HttpTafs with their "toString" representations... primarily useful for Debugging in an IDE + * like Eclipse. + */ + public String toString() { + StringBuilder sb = new StringBuilder(); + for(HttpTaf ht : tafs) { + sb.append(ht.toString()); + sb.append(". "); + } + return sb.toString(); + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/HttpTaf.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/HttpTaf.java new file mode 100644 index 00000000..9484458c --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/HttpTaf.java @@ -0,0 +1,60 @@ +/** + * ============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.cadi.taf; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.cadi.CachedPrincipal; +import org.onap.aaf.cadi.Taf.LifeForm; + +/** + * A TAF which is in a specific HTTP environment in which the engine implements + * javax Servlet. + * + * Using the Http Request and Response interfaces takes the effort out of implementing in almost any kind of + * HTTP Container or Engine. + * + * @author Jonathan + * + */ +public interface HttpTaf { + /** + * validate + * + * Validate the Request, and respond with created TafResp object. + * + * @param reading + * @param req + * @param resp + * @return + */ + public TafResp validate(LifeForm reading, HttpServletRequest req, HttpServletResponse resp); + + /** + * Re-Validate Credential + * + * @param prin + * @return + */ + public CachedPrincipal.Resp revalidate(CachedPrincipal prin,Object state); +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/LoginPageTafResp.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/LoginPageTafResp.java new file mode 100644 index 00000000..9c9cbc22 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/LoginPageTafResp.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.cadi.taf; + +import java.io.IOException; +import java.net.URI; +import java.util.List; + +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.Locator; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.Locator.Item; + +public class LoginPageTafResp extends AbsTafResp { + private final HttpServletResponse httpResp; + private final String loginPageURL; + + private LoginPageTafResp(Access access, final HttpServletResponse resp, String loginPageURL) { + super(access, null, "Multiple Possible HTTP Logins available. Redirecting to Login Choice Page"); + httpResp = resp; + this.loginPageURL = loginPageURL; + } + + @Override + public RESP authenticate() throws IOException { + httpResp.sendRedirect(loginPageURL); + return RESP.HTTP_REDIRECT_INVOKED; + } + + @Override + public RESP isAuthenticated() { + return RESP.TRY_AUTHENTICATING; + } + + public static TafResp create(Access access, Locator<URI> locator, final HttpServletResponse resp, List<Redirectable> redir) { + if(locator!=null) { + try { + Item item = locator.best(); + URI uri = locator.get(item); + if(uri!=null) { + StringBuilder sb = new StringBuilder(uri.toString()); + String query = uri.getQuery(); + boolean first = query==null || query.length()==0; + int count=0; + for(Redirectable t : redir) { + if(first) { + sb.append('?'); + first=false; + } + else sb.append('&'); + sb.append(t.get()); + ++count; + } + if(count>0)return new LoginPageTafResp(access, resp, sb.toString()); + } + } catch (Exception e) { + access.log(e, "Error deriving Login Page location"); + } + } else if(!redir.isEmpty()) { + access.log(Level.DEBUG,"LoginPage Locator is not configured. Taking first Redirectable Taf"); + return redir.get(0); + } + return NullTafResp.singleton(); + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/NullTaf.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/NullTaf.java new file mode 100644 index 00000000..e8293faa --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/NullTaf.java @@ -0,0 +1,64 @@ +/** + * ============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.cadi.taf; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.cadi.CachedPrincipal; +import org.onap.aaf.cadi.Taf; +import org.onap.aaf.cadi.CachedPrincipal.Resp; + + +/** + * This TAF is set at the very beginning of Filters and Valves so that if any configuration issues hit while + * starting, the default behavior is to shut down traffic rather than leaving an open hole + * + * @author Jonathan + * + */ +public class NullTaf implements Taf, HttpTaf { + // Singleton Pattern + public NullTaf() {} + + /** + * validate + * + * Always Respond with a NullTafResp, which declares it is unauthenticated, and unauthorized + */ + public TafResp validate(LifeForm reading, String... info) { + return NullTafResp.singleton(); + } + + /** + * validate + * + * Always Respond with a NullTafResp, which declares it is unauthenticated, and unauthorized + */ + public TafResp validate(LifeForm reading, HttpServletRequest req, HttpServletResponse resp) { + return NullTafResp.singleton(); + } + + public Resp revalidate(CachedPrincipal prin, Object state) { + return Resp.NOT_MINE; + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/NullTafResp.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/NullTafResp.java new file mode 100644 index 00000000..20fc944a --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/NullTafResp.java @@ -0,0 +1,73 @@ +/** + * ============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.cadi.taf; + +import java.io.IOException; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.principal.TaggedPrincipal; + +/** + * A Null Pattern for setting responses to "Deny" before configuration is setup. + * @author Jonathan + * + */ +class NullTafResp implements TafResp { + private NullTafResp(){} + + private static TafResp singleton = new NullTafResp(); + + public static TafResp singleton() { + return singleton; + } + + public boolean isValid() { + return false; + } + + public RESP isAuthenticated() { + return RESP.NO_FURTHER_PROCESSING; + } + + public String desc() { + return "All Authentication denied"; + } + + public RESP authenticate() throws IOException { + return RESP.NO_FURTHER_PROCESSING; + } + + public TaggedPrincipal getPrincipal() { + return null; + } + + public Access getAccess() { + return Access.NULL; + } + + /* (non-Javadoc) + * @see org.onap.aaf.cadi.taf.TafResp#isFailedAttempt() + */ + public boolean isFailedAttempt() { + return true; + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/PuntTafResp.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/PuntTafResp.java new file mode 100644 index 00000000..f496581b --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/PuntTafResp.java @@ -0,0 +1,69 @@ +/** + * ============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.cadi.taf; + +import java.io.IOException; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.principal.TaggedPrincipal; + +/** + * A Punt Resp to make it fast and easy for a Taf to respond that it cannot handle a particular kind of + * request. It is always the same object, so there is no cost for memory, etc. + * @author Jonathan + * + */ +public class PuntTafResp implements TafResp { + private final String desc; + + public PuntTafResp(String name, String explanation) { + desc = name + " is not processing this transaction: " + explanation; + } + + public boolean isValid() { + return false; + } + + public RESP isAuthenticated() { + return RESP.TRY_ANOTHER_TAF; + } + + public String desc() { + return desc; + } + + public RESP authenticate() throws IOException { + return RESP.TRY_ANOTHER_TAF; + } + + public TaggedPrincipal getPrincipal() { + return null; + } + + public Access getAccess() { + return NullTafResp.singleton().getAccess(); + } + + public boolean isFailedAttempt() { + return false; + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/Redirectable.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/Redirectable.java new file mode 100644 index 00000000..8dc5c118 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/Redirectable.java @@ -0,0 +1,31 @@ +/** + * ============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.cadi.taf; + +public interface Redirectable extends TafResp { + /** + * Create a Redirectable URL entry prefaced by a URLEncoder.String for a Menu + * example: + * "Global Login=https://xxxx....." + */ + public String get(); +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/TafResp.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/TafResp.java new file mode 100644 index 00000000..a679d994 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/TafResp.java @@ -0,0 +1,94 @@ +/** + * ============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.cadi.taf; + +import java.io.IOException; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.principal.TaggedPrincipal; + +/** + * Response from Taf objects, which inform users what has happened and/or what should be done + * + * @author Jonathan + * + */ +public interface TafResp { + public static enum RESP { + IS_AUTHENTICATED, + NO_FURTHER_PROCESSING, + TRY_AUTHENTICATING, + TRY_ANOTHER_TAF, + FAIL, + // A note was made to avoid the response REDIRECT. However, I have deemed that it is + // unavoidable when the underlying TAF did do a REDIRECT, because it requires a HTTP + // Service code to exit without modifying the Response any further. + // Therefore, I have changed this to indicate what HAS happened, with should accommodate + // both positions. Jonathan 10/18/2012 +// public static final int HTTP_REDIRECT_INVOKED = 11; + HTTP_REDIRECT_INVOKED, + HAS_PROCESSED}; + + /** + * Basic success check + * @return + */ + public boolean isValid(); + + /** + * String description of what has occurred (for logging/exceptions) + * @return + */ + public String desc(); + + /** + * Check Response + * @return + */ + public RESP isAuthenticated(); + + /** + * Authenticate, returning FAIL or Other Valid indication + * + * HTTP implementations should watch for "HTTP_REDIRECT_INVOKED", and end the HTTP call appropriately. + * @return + * @throws CadiException + */ + public RESP authenticate() throws IOException; + + /** + * Once authenticated, this object should hold a Principal created from the authorization + * @return + */ + public TaggedPrincipal getPrincipal(); + + /** + * get the Access object which created this object, allowing the responder to appropriate Log, etc + */ + public Access getAccess(); + + /** + * Be able to check if part of a Failed attempt + */ + public boolean isFailedAttempt(); +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/TrustNotTafResp.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/TrustNotTafResp.java new file mode 100644 index 00000000..24a79cf3 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/TrustNotTafResp.java @@ -0,0 +1,76 @@ +/** + * ============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.cadi.taf; + +import java.io.IOException; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.principal.TaggedPrincipal; + +public class TrustNotTafResp implements TafResp { + private final TafResp delegate; + private final String desc; + + public TrustNotTafResp(final TafResp delegate, final String desc) { + this.delegate = delegate; + this.desc = desc; + } + + @Override + public boolean isValid() { + return false; + } + + @Override + public String desc() { + return desc; + } + + @Override + public RESP isAuthenticated() { + return RESP.NO_FURTHER_PROCESSING; + } + + @Override + public RESP authenticate() throws IOException { + return RESP.NO_FURTHER_PROCESSING; + } + + @Override + public TaggedPrincipal getPrincipal() { + return delegate.getPrincipal(); + } + + @Override + public Access getAccess() { + return delegate.getAccess(); + } + + @Override + public boolean isFailedAttempt() { + return true; + } + + public String toString() { + return desc(); + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/TrustTafResp.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/TrustTafResp.java new file mode 100644 index 00000000..bc5e8db6 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/TrustTafResp.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.cadi.taf; + +import java.io.IOException; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.principal.TaggedPrincipal; + +public class TrustTafResp implements TafResp { + private final TafResp delegate; + private final TaggedPrincipal principal; + private final String desc; + + public TrustTafResp(final TafResp delegate, final TaggedPrincipal principal, final String desc) { + this.delegate = delegate; + this.principal = principal; + this.desc = desc + ' ' + delegate.desc(); + } + + @Override + public boolean isValid() { + return delegate.isValid(); + } + + @Override + public String desc() { + return desc; + } + + @Override + public RESP isAuthenticated() { + return delegate.isAuthenticated(); + } + + @Override + public RESP authenticate() throws IOException { + return delegate.authenticate(); + } + + @Override + public TaggedPrincipal getPrincipal() { + return principal; + } + + @Override + public Access getAccess() { + return delegate.getAccess(); + } + + @Override + public boolean isFailedAttempt() { + return delegate.isFailedAttempt(); + } + + public String toString() { + return principal.getName() + " by trust of " + desc(); + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/basic/BasicHttpTaf.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/basic/BasicHttpTaf.java new file mode 100644 index 00000000..6d516f00 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/basic/BasicHttpTaf.java @@ -0,0 +1,165 @@ +/** + * ============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.cadi.taf.basic; + +import java.io.IOException; +import java.security.Principal; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.BasicCred; +import org.onap.aaf.cadi.CachedPrincipal; +import org.onap.aaf.cadi.CredVal; +import org.onap.aaf.cadi.Taf; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.CachedPrincipal.Resp; +import org.onap.aaf.cadi.CredVal.Type; +import org.onap.aaf.cadi.principal.BasicPrincipal; +import org.onap.aaf.cadi.principal.CachedBasicPrincipal; +import org.onap.aaf.cadi.taf.HttpTaf; +import org.onap.aaf.cadi.taf.TafResp; +import org.onap.aaf.cadi.taf.TafResp.RESP; +import org.onap.aaf.cadi.taf.dos.DenialOfServiceTaf; + +/** + * BasicHttpTaf + * + * This TAF implements the "Basic Auth" protocol. + * + * WARNING! It is true for any implementation of "Basic Auth" that the password is passed unencrypted. + * This is because the expectation, when designed years ago, was that it would only be used in + * conjunction with SSL (https). It is common, however, for users to ignore this on the assumption that + * their internal network is secure, or just ignorance. Therefore, a WARNING will be printed + * when the HTTP Channel is not encrypted (unless explicitly turned off). + * + * @author Jonathan + * + */ +public class BasicHttpTaf implements HttpTaf { + private Access access; + private String realm; + private CredVal rbac; + private boolean warn; + private long timeToLive; + + public BasicHttpTaf(Access access, CredVal rbac, String realm, long timeToLive, boolean turnOnWarning) { + this.access = access; + this.realm = realm; + this.rbac = rbac; + this.warn = turnOnWarning; + this.timeToLive = timeToLive; + } + + /** + * Note: BasicHttp works for either Carbon Based (Humans) or Silicon Based (machine) Lifeforms. + * @see Taf + */ + public TafResp validate(Taf.LifeForm reading, HttpServletRequest req, HttpServletResponse resp) { + // See if Request implements BasicCred (aka CadiWrap or other), and if User/Pass has already been set separately + if(req instanceof BasicCred) { + BasicCred bc = (BasicCred)req; + if(bc.getUser()!=null) { // CadiWrap, if set, makes sure User & Password are both valid, or both null + if(DenialOfServiceTaf.isDeniedID(bc.getUser())!=null) { + return DenialOfServiceTaf.respDenyID(access,bc.getUser()); + } + CachedBasicPrincipal bp = new CachedBasicPrincipal(this,bc,realm,timeToLive); + // ONLY FOR Last Ditch DEBUGGING... + // access.log(Level.WARN,bp.getName() + ":" + new String(bp.getCred())); + + if(rbac.validate(bp.getName(),Type.PASSWORD,bp.getCred(),req)) { + return new BasicHttpTafResp(access,bp,bp.getName()+" authenticated by password",RESP.IS_AUTHENTICATED,resp,realm,false); + } else { + //TODO may need timed retries in a given time period + return new BasicHttpTafResp(access,null,buildMsg(bp,req,"user/pass combo invalid for ",bc.getUser(),"from",req.getRemoteAddr()), + RESP.TRY_AUTHENTICATING,resp,realm,true); + } + } + } + // Get User/Password from Authorization Header value + String authz = req.getHeader("Authorization"); + if(authz != null && authz.startsWith("Basic ")) { + if(warn&&!req.isSecure()) { + access.log(Level.WARN,"WARNING! BasicAuth has been used over an insecure channel"); + } + try { + CachedBasicPrincipal ba = new CachedBasicPrincipal(this,authz,realm,timeToLive); + if(DenialOfServiceTaf.isDeniedID(ba.getName())!=null) { + return DenialOfServiceTaf.respDenyID(access,ba.getName()); + } + + // ONLY FOR Last Ditch DEBUGGING... + // access.log(Level.WARN,ba.getName() + ":" + new String(ba.getCred())); + if(rbac.validate(ba.getName(), Type.PASSWORD, ba.getCred(), req)) { + return new BasicHttpTafResp(access,ba, ba.getName()+" authenticated by BasicAuth password",RESP.IS_AUTHENTICATED,resp,realm,false); + } else { + //TODO may need timed retries in a given time period + return new BasicHttpTafResp(access,null,buildMsg(ba,req,"user/pass combo invalid"), + RESP.TRY_AUTHENTICATING,resp,realm,true); + } + } catch (IOException e) { + String msg = buildMsg(null,req,"Failed HTTP Basic Authorization (", e.getMessage(), ')'); + access.log(Level.INFO,msg); + return new BasicHttpTafResp(access,null,msg, RESP.TRY_AUTHENTICATING, resp, realm,true); + } + } + return new BasicHttpTafResp(access,null,"Requesting HTTP Basic Authorization",RESP.TRY_AUTHENTICATING,resp,realm,false); + } + + protected String buildMsg(Principal pr, HttpServletRequest req, Object ... msg) { + StringBuilder sb = new StringBuilder(); + if(pr!=null) { + sb.append("user="); + sb.append(pr.getName()); + sb.append(','); + } + sb.append("ip="); + sb.append(req.getRemoteAddr()); + sb.append(",port="); + sb.append(req.getRemotePort()); + if(msg.length>0) { + sb.append(",msg=\""); + for(Object s : msg) { + sb.append(s.toString()); + } + sb.append('"'); + } + return sb.toString(); + } + + @Override + public Resp revalidate(CachedPrincipal prin, Object state) { + if(prin instanceof BasicPrincipal) { + BasicPrincipal ba = (BasicPrincipal)prin; + if(DenialOfServiceTaf.isDeniedID(ba.getName())!=null) { + return Resp.UNVALIDATED; + } + return rbac.validate(ba.getName(), Type.PASSWORD, ba.getCred(), state)?Resp.REVALIDATED:Resp.UNVALIDATED; + } + return Resp.NOT_MINE; + } + + public String toString() { + return "Basic Auth enabled on realm: " + realm; + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/basic/BasicHttpTafResp.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/basic/BasicHttpTafResp.java new file mode 100644 index 00000000..c17797b8 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/basic/BasicHttpTafResp.java @@ -0,0 +1,62 @@ +/** + * ============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.cadi.taf.basic; + +import java.io.IOException; + +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.principal.TaggedPrincipal; +import org.onap.aaf.cadi.taf.AbsTafResp; +import org.onap.aaf.cadi.taf.TafResp; + +public class BasicHttpTafResp extends AbsTafResp implements TafResp { + private HttpServletResponse httpResp; + private String realm; + private RESP status; + private final boolean wasFailed; + + public BasicHttpTafResp(Access access, TaggedPrincipal principal, String description, RESP status, HttpServletResponse resp, String realm, boolean wasFailed) { + super(access,principal, description); + httpResp = resp; + this.realm = realm; + this.status = status; + this.wasFailed = wasFailed; + } + + public RESP authenticate() throws IOException { + httpResp.setStatus(401); // Unauthorized + httpResp.setHeader("WWW-Authenticate", "Basic realm=\""+realm+'"'); + return RESP.HTTP_REDIRECT_INVOKED; + } + + public RESP isAuthenticated() { + return status; + } + + public boolean isFailedAttempt() { + return wasFailed; + } + + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/cert/CertIdentity.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/cert/CertIdentity.java new file mode 100644 index 00000000..0da41b81 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/cert/CertIdentity.java @@ -0,0 +1,46 @@ +/** + * ============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.cadi.taf.cert; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.servlet.http.HttpServletRequest; + +import org.onap.aaf.cadi.principal.TaggedPrincipal; + +public interface CertIdentity { + /** + * identity from X509Certificate Object and/or certBytes + * + * If you have both, include them. If you only have one, leave the other null, and it will be generated if needed + * + * The Request is there to obtain Header or Attribute info of ultimate user + * + * @param req + * @param cert + * @param certBytes + * @return + * @throws CertificateException + */ + public TaggedPrincipal identity(HttpServletRequest req, X509Certificate cert, byte[] certBytes) throws CertificateException; +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/cert/X509HttpTafResp.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/cert/X509HttpTafResp.java new file mode 100644 index 00000000..b7f63b8e --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/cert/X509HttpTafResp.java @@ -0,0 +1,51 @@ +/** + * ============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.cadi.taf.cert; + +import java.io.IOException; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.principal.TaggedPrincipal; +import org.onap.aaf.cadi.taf.AbsTafResp; +import org.onap.aaf.cadi.taf.TafResp; + +public class X509HttpTafResp extends AbsTafResp implements TafResp { + private RESP status; + + public X509HttpTafResp(Access access, TaggedPrincipal principal, String description, RESP status) { + super(access, principal, description); + this.status = status; + } + + public RESP authenticate() throws IOException { + return RESP.TRY_ANOTHER_TAF; + } + + public RESP isAuthenticated() { + return status; + } + + public String toString() { + return status.name(); + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/cert/X509Taf.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/cert/X509Taf.java new file mode 100644 index 00000000..b5ed7ad1 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/cert/X509Taf.java @@ -0,0 +1,261 @@ +/** + * ============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.cadi.taf.cert; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Signature; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; + +import javax.net.ssl.TrustManagerFactory; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.CachedPrincipal; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.Lur; +import org.onap.aaf.cadi.Symm; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.CachedPrincipal.Resp; +import org.onap.aaf.cadi.Taf.LifeForm; +import org.onap.aaf.cadi.config.Config; +import org.onap.aaf.cadi.config.SecurityInfo; +import org.onap.aaf.cadi.config.SecurityInfoC; +import org.onap.aaf.cadi.principal.TaggedPrincipal; +import org.onap.aaf.cadi.principal.X509Principal; +import org.onap.aaf.cadi.taf.HttpTaf; +import org.onap.aaf.cadi.taf.TafResp; +import org.onap.aaf.cadi.taf.TafResp.RESP; +import org.onap.aaf.cadi.util.Split; + +public class X509Taf implements HttpTaf { + + private static final String CERTIFICATE_NOT_VALID_FOR_AUTHENTICATION = "Certificate NOT valid for Authentication"; + public static final CertificateFactory certFactory; + public static final MessageDigest messageDigest; + public static final TrustManagerFactory tmf; + private Access access; + private CertIdentity[] certIdents; +// private Lur lur; + private ArrayList<String> cadiIssuers; + private String env; + private SecurityInfo si; + + static { + try { + certFactory = CertificateFactory.getInstance("X.509"); + messageDigest = MessageDigest.getInstance("SHA-256"); // use this to clone + tmf = TrustManagerFactory.getInstance(SecurityInfoC.SslKeyManagerFactoryAlgorithm); + } catch (Exception e) { + throw new RuntimeException("X.509 and SHA-256 are required for X509Taf",e); + } + } + + public X509Taf(Access access, Lur lur, CertIdentity ... cis) throws CertificateException, NoSuchAlgorithmException, CadiException { + this.access = access; + env = access.getProperty(Config.AAF_ENV,null); + if(env==null) { + throw new CadiException("X509Taf requires Environment ("+Config.AAF_ENV+") to be set."); + } +// this.lur = lur; + this.cadiIssuers = new ArrayList<String>(); + for(String ci : access.getProperty(Config.CADI_X509_ISSUERS, "").split(":")) { + access.printf(Level.INIT, "Trusting Identity for Certificates signed by \"%s\"",ci); + cadiIssuers.add(ci); + } + try { + Class<?> dci = access.classLoader().loadClass("org.onap.aaf.auth.direct.DirectCertIdentity"); + if(dci==null) { + certIdents = cis; + } else { + CertIdentity temp[] = new CertIdentity[cis.length+1]; + System.arraycopy(cis, 0, temp, 1, cis.length); + temp[0] = (CertIdentity) dci.newInstance(); + certIdents=temp; + } + } catch (Exception e) { + certIdents = cis; + } + + si = new SecurityInfo(access); + } + + public static final X509Certificate getCert(byte[] certBytes) throws CertificateException { + ByteArrayInputStream bais = new ByteArrayInputStream(certBytes); + return (X509Certificate)certFactory.generateCertificate(bais); + } + + public static final byte[] getFingerPrint(byte[] ba) { + MessageDigest md; + try { + md = (MessageDigest)messageDigest.clone(); + } catch (CloneNotSupportedException e) { + // should never get here + return new byte[0]; + } + md.update(ba); + return md.digest(); + } + + @Override + public TafResp validate(LifeForm reading, HttpServletRequest req, HttpServletResponse resp) { + // Check for Mutual SSL + try { + X509Certificate[] certarr = (X509Certificate[])req.getAttribute("javax.servlet.request.X509Certificate"); + if(certarr!=null && certarr.length>0) { + si.checkClientTrusted(certarr); + // Note: If the Issuer is not in the TrustStore, it's not added to the Cert list + if(cadiIssuers.contains(certarr[0].getIssuerDN().toString())) { + String subject = certarr[0].getSubjectDN().getName(); + // avoiding extra object creation, since this is validated EVERY transaction with a Cert + int at = subject.indexOf('@'); + if(at>=0) { + int start = subject.lastIndexOf(',', at); + if(start<0) { + start = 0; + } + int end = subject.indexOf(',', at); + if(end<0) { + end=subject.length(); + } + int temp; + if(((temp=subject.indexOf("OU=",start))>=0 && temp<end) || + ((temp=subject.indexOf("CN=",start))>=0 && temp<end)) { + String[] sa = Split.splitTrim(':', subject, temp+3,end); + if(sa.length==1 || (sa.length>1 && env!=null && env.equals(sa[1]))) { // Check Environment + return new X509HttpTafResp(access, + new X509Principal(sa[0], certarr[0],(byte[])null), + "X509Taf validated " + sa[0] + (sa.length<2?"":" for aaf_env " + env ), RESP.IS_AUTHENTICATED); + } + } + + } + } + } + + + byte[] array = null; + byte[] certBytes = null; + X509Certificate cert=null; + String responseText=null; + String authHeader = req.getHeader("Authorization"); + + if(certarr!=null) { // If cert !=null, Cert is Tested by Mutual Protocol. + if(authHeader!=null) { // This is only intended to be a Secure Connection, not an Identity + for(String auth : Split.split(',',authHeader)) { + if(auth.startsWith("Bearer ")) { // Bearer = OAuth... Don't use as Authenication + return new X509HttpTafResp(access, null, "Certificate verified, but Bearer Token is presented", RESP.TRY_ANOTHER_TAF); + } + } + } + cert = certarr[0]; + responseText = ", validated by Mutual SSL Protocol"; + } else { // If cert == null, Get Declared Cert (in header), but validate by having them sign something + if(authHeader != null) { + for(String auth : Split.splitTrim(',',authHeader)) { + if(auth.startsWith("x509 ")) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(auth.length()); + try { + array = auth.getBytes(); + ByteArrayInputStream bais = new ByteArrayInputStream(array); + Symm.base64noSplit.decode(bais, baos, 5); + certBytes = baos.toByteArray(); + cert = getCert(certBytes); + + /** + * Identity from CERT if well know CA and specific encoded information + */ + // If found Identity doesn't work, try SignedStuff Protocol + // cert.checkValidity(); + // cert.--- GET FINGERPRINT? + String stuff = req.getHeader("Signature"); + if(stuff==null) + return new X509HttpTafResp(access, null, "Header entry 'Signature' required to validate One way X509 Certificate", RESP.TRY_ANOTHER_TAF); + String data = req.getHeader("Data"); + // if(data==null) + // return new X509HttpTafResp(access, null, "No signed Data to validate with X509 Certificate", RESP.TRY_ANOTHER_TAF); + + // Note: Data Pos shows is "<signatureType> <data>" + // int dataPos = (stuff.indexOf(' ')); // determine what is Algorithm + // Get Signature + bais = new ByteArrayInputStream(stuff.getBytes()); + baos = new ByteArrayOutputStream(stuff.length()); + Symm.base64noSplit.decode(bais, baos); + array = baos.toByteArray(); + // Signature sig = Signature.getInstance(stuff.substring(0, dataPos)); // get Algorithm from first part of Signature + + Signature sig = Signature.getInstance(cert.getSigAlgName()); + sig.initVerify(cert.getPublicKey()); + sig.update(data.getBytes()); + if(!sig.verify(array)) { + access.log(Level.ERROR, "Signature doesn't Match"); + return new X509HttpTafResp(access, null, CERTIFICATE_NOT_VALID_FOR_AUTHENTICATION, RESP.TRY_ANOTHER_TAF); + } + responseText = ", validated by Signed Data"; + } catch (Exception e) { + access.log(e, "Exception while validating Cert"); + return new X509HttpTafResp(access, null, CERTIFICATE_NOT_VALID_FOR_AUTHENTICATION, RESP.TRY_ANOTHER_TAF); + } + } + } + } + if(cert==null) { + return new X509HttpTafResp(access, null, "No Certificate Info on Transaction", RESP.TRY_ANOTHER_TAF); + } + + // A cert has been found, match Identify + TaggedPrincipal prin=null; + + for(int i=0;prin==null && i<certIdents.length;++i) { + if((prin=certIdents[i].identity(req, cert, certBytes))!=null) { + responseText = prin.getName() + " matches Certificate " + cert.getSubjectX500Principal().getName() + responseText; + } + } + + // if Principal is found, check for "AS_USER" and whether this entity is trusted to declare + if(prin!=null) { + return new X509HttpTafResp( + access, + prin, + responseText, + RESP.IS_AUTHENTICATED); + } + } + } catch(Exception e) { + return new X509HttpTafResp(access, null, e.getMessage(), RESP.TRY_ANOTHER_TAF); + } + + return new X509HttpTafResp(access, null, "Certificate cannot be used for authentication", RESP.TRY_ANOTHER_TAF); + } + + @Override + public Resp revalidate(CachedPrincipal prin, Object state) { + return null; + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/dos/DenialOfServiceTaf.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/dos/DenialOfServiceTaf.java new file mode 100644 index 00000000..5eca0f2d --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/dos/DenialOfServiceTaf.java @@ -0,0 +1,374 @@ +/** + * ============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.cadi.taf.dos; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.CachedPrincipal; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.CachedPrincipal.Resp; +import org.onap.aaf.cadi.Taf.LifeForm; +import org.onap.aaf.cadi.config.Config; +import org.onap.aaf.cadi.taf.HttpTaf; +import org.onap.aaf.cadi.taf.PuntTafResp; +import org.onap.aaf.cadi.taf.TafResp; +import org.onap.aaf.cadi.taf.TafResp.RESP; + +public class DenialOfServiceTaf implements HttpTaf { + private static Map<String, Counter> deniedIP=null, deniedID=null; + private Access access; + private final TafResp puntNotDenied; + private static File dosIP, dosID; + + /** + * + * @param hostname + * @param prod + * @throws CadiException + */ + public DenialOfServiceTaf(Access access) throws CadiException { + puntNotDenied = new PuntTafResp("DenialOfServiceTaf", "This Transaction is not denied"); + this.access = access; + if(dosIP==null || dosID == null) { + String dirStr; + if((dirStr = access.getProperty(Config.AAF_DATA_DIR, null))!=null) { + dosIP = new File(dirStr+"/dosIP"); + readIP(); + dosID = new File(dirStr+"/dosID"); + readID(); + } + } + } + + @Override + public TafResp validate(LifeForm reading, HttpServletRequest req, final HttpServletResponse resp) { + // Performance, when not needed + if(deniedIP != null) { + String ip; + Counter c = deniedIP.get(ip=req.getRemoteAddr()); + if(c!=null) { + c.inc(); + return respDenyIP(access,ip); + } + } + + // Note: Can't process Principal, because this is the first TAF, and no Principal is created. + // Other TAFs use "isDenied()" on this Object to validate. + return puntNotDenied; + } + + @Override + public Resp revalidate(CachedPrincipal prin, Object state) { + // We always return NOT MINE, because DOS Taf does not ever validate + return Resp.NOT_MINE; + } + + /* + * for use in Other TAFs, before they attempt backend validation of + */ + public static Counter isDeniedID(String identity) { + if(deniedID!=null) { + return deniedID.get(identity); + } + return null; + } + + /** + * + */ + public static Counter isDeniedIP(String ipvX) { + if(deniedID!=null) { + return deniedID.get(ipvX); + } + return null; + } + + /** + * Return of "True" means IP has been added. + * Return of "False" means IP already added. + * + * @param ip + * @return + */ + public static synchronized boolean denyIP(String ip) { + boolean rv = false; + if(deniedIP==null) { + deniedIP = new HashMap<String,Counter>(); + deniedIP.put(ip, new Counter(ip)); // Noted duplicated for minimum time spent + rv= true; + } else if(deniedIP.get(ip)==null) { + deniedIP.put(ip, new Counter(ip)); + rv = true; + } + if(rv) { + writeIP(); + } + return rv; + } + + private static void writeIP() { + if(dosIP!=null && deniedIP!=null) { + if(deniedIP.isEmpty()) { + if(dosIP.exists()) { + dosIP.delete(); + } + } else { + PrintStream fos; + try { + fos = new PrintStream(new FileOutputStream(dosIP,false)); + try { + for(String ip: deniedIP.keySet()) { + fos.println(ip); + } + } finally { + fos.close(); + } + } catch (IOException e) { + e.printStackTrace(System.err); + } + } + } + } + + private static void readIP() { + if(dosIP!=null && dosIP.exists()) { + BufferedReader br; + try { + br = new BufferedReader(new FileReader(dosIP)); + if(deniedIP==null) { + deniedIP=new HashMap<String,Counter>(); + } + + try { + String line; + while((line=br.readLine())!=null) { + deniedIP.put(line, new Counter(line)); + } + } finally { + br.close(); + } + } catch (IOException e) { + e.printStackTrace(System.err); + } + } + } + + + /** + * Return of "True" means IP has was removed. + * Return of "False" means IP wasn't being denied. + * + * @param ip + * @return + */ + public static synchronized boolean removeDenyIP(String ip) { + if(deniedIP!=null && deniedIP.remove(ip)!=null) { + writeIP(); + if(deniedIP.isEmpty()) { + deniedIP=null; + } + return true; + } + return false; + } + + /** + * Return of "True" means ID has been added. + * Return of "False" means ID already added. + * + * @param ip + * @return + */ + public static synchronized boolean denyID(String id) { + boolean rv = false; + if(deniedID==null) { + deniedID = new HashMap<String,Counter>(); + deniedID.put(id, new Counter(id)); // Noted duplicated for minimum time spent + rv = true; + } else if(deniedID.get(id)==null) { + deniedID.put(id, new Counter(id)); + rv = true; + } + if(rv) { + writeID(); + } + return rv; + + } + + private static void writeID() { + if(dosID!=null && deniedID!=null) { + if(deniedID.isEmpty()) { + if(dosID.exists()) { + dosID.delete(); + } + } else { + PrintStream fos; + try { + fos = new PrintStream(new FileOutputStream(dosID,false)); + try { + for(String ip: deniedID.keySet()) { + fos.println(ip); + } + } finally { + fos.close(); + } + } catch (IOException e) { + e.printStackTrace(System.err); + } + } + } + } + + private static void readID() { + if(dosID!=null && dosID.exists()) { + BufferedReader br; + try { + br = new BufferedReader(new FileReader(dosID)); + if(deniedID==null) { + deniedID=new HashMap<String,Counter>(); + } + try { + String line; + while((line=br.readLine())!=null) { + deniedID.put(line, new Counter(line)); + } + } finally { + br.close(); + } + } catch (IOException e) { + e.printStackTrace(System.err); + } + } + } + + /** + * Return of "True" means ID has was removed. + * Return of "False" means ID wasn't being denied. + * + * @param ip + * @return + */ + public static synchronized boolean removeDenyID(String id) { + if(deniedID!=null && deniedID.remove(id)!=null) { + writeID(); + if(deniedID.isEmpty()) { + deniedID=null; + } + + return true; + } + return false; + } + + public List<String> report() { + int initSize = 0; + if(deniedIP!=null)initSize+=deniedIP.size(); + if(deniedID!=null)initSize+=deniedID.size(); + ArrayList<String> al = new ArrayList<String>(initSize); + if(deniedID!=null) { + for(Counter c : deniedID.values()) { + al.add(c.toString()); + } + } + if(deniedIP!=null) { + for(Counter c : deniedIP.values()) { + al.add(c.toString()); + } + } + return al; + } + + public static class Counter { + private final String name; + private int count = 0; + private Date first; + private long last; // note, we use "last" as long, to avoid popping useless dates on Heap. + + public Counter(String name) { + this.name = name; + first = null; + last = 0L; + count = 0; + } + + public String getName() { + return name; + } + + public int getCount() { + return count; + } + + public long getLast() { + return last; + } + + /* + * Only allow Denial of ServiceTaf to increment + */ + private synchronized void inc() { + ++count; + last = System.currentTimeMillis(); + if(first==null) { + first = new Date(last); + } + } + + public String toString() { + if(count==0) + return name + " is on the denied list, but has not attempted Access"; + else + return + name + + " has been denied " + + count + + " times since " + + first + + ". Last denial was " + + new Date(last); + } + } + + public static TafResp respDenyID(Access access, String identity) { + return new DenialOfServiceTafResp(access, RESP.NO_FURTHER_PROCESSING, identity + " is on the Identity Denial list"); + } + + public static TafResp respDenyIP(Access access, String ip) { + return new DenialOfServiceTafResp(access, RESP.NO_FURTHER_PROCESSING, ip + " is on the IP Denial list"); + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/taf/dos/DenialOfServiceTafResp.java b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/dos/DenialOfServiceTafResp.java new file mode 100644 index 00000000..b156392d --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/taf/dos/DenialOfServiceTafResp.java @@ -0,0 +1,47 @@ +/** + * ============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.cadi.taf.dos; + +import java.io.IOException; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.taf.AbsTafResp; + +public class DenialOfServiceTafResp extends AbsTafResp { + private RESP ect; // Homage to Arethra Franklin + + public DenialOfServiceTafResp(Access access, RESP resp, String description ) { + super(access, null, description); + ect = resp; + } + + // Override base behavior of checking Principal and trying another TAF + @Override + public RESP isAuthenticated() { + return ect; + } + + + public RESP authenticate() throws IOException { + return ect; + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/util/Chmod.java b/cadi/core/src/main/java/org/onap/aaf/cadi/util/Chmod.java new file mode 100644 index 00000000..74bf805d --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/util/Chmod.java @@ -0,0 +1,62 @@ +/** + * ============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.cadi.util; + +import java.io.File; +import java.io.IOException; + +public interface Chmod { + public void chmod(File f) throws IOException; + + public static final Chmod to755 = new Chmod() { + public void chmod(File f) throws IOException { + f.setExecutable(true, false); + f.setExecutable(true, true); + f.setReadable(true, false); + f.setReadable(true, true); + f.setWritable(false, false); + f.setWritable(true, true); + } + }; + + public static final Chmod to644 = new Chmod() { + public void chmod(File f) throws IOException { + f.setExecutable(false, false); + f.setExecutable(false, true); + f.setReadable(true, false); + f.setReadable(true, true); + f.setWritable(false, false); + f.setWritable(true, true); + } + }; + + public static final Chmod to400 = new Chmod() { + public void chmod(File f) throws IOException { + f.setExecutable(false, false); + f.setExecutable(false, true); + f.setReadable(false, false); + f.setReadable(true, true); + f.setWritable(false, false); + f.setWritable(false, true); + } + }; +}
\ No newline at end of file diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/util/FQI.java b/cadi/core/src/main/java/org/onap/aaf/cadi/util/FQI.java new file mode 100644 index 00000000..b953c871 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/util/FQI.java @@ -0,0 +1,51 @@ +/** + * ============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.cadi.util; + +public class FQI { + /** + * Take a Fully Qualified User, and get a Namespace from it. + * @param fqi + * @return + */ + public final static String reverseDomain(final String fqi) { + StringBuilder sb = null; + String[] split = Split.split('.',fqi); + int at; + for(int i=split.length-1;i>=0;--i) { + if(sb == null) { + sb = new StringBuilder(); + } else { + sb.append('.'); + } + + if((at = split[i].indexOf('@'))>0) { + sb.append(split[i].subSequence(at+1, split[i].length())); + } else { + sb.append(split[i]); + } + } + + return sb.toString(); + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/util/JsonOutputStream.java b/cadi/core/src/main/java/org/onap/aaf/cadi/util/JsonOutputStream.java new file mode 100644 index 00000000..7b04942f --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/util/JsonOutputStream.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.cadi.util; + +import java.io.IOException; +import java.io.OutputStream; + +public class JsonOutputStream extends OutputStream { + private static final byte[] TWO_SPACE = " ".getBytes(); + private OutputStream os; + private boolean closeable; + private int indent = 0; + private int prev,ret=0; + + public JsonOutputStream(OutputStream os) { + // Don't close these, or dire consequences. + closeable = !os.equals(System.out) && !os.equals(System.err); + this.os = os; + } + + @Override + public void write(int b) throws IOException { + if(ret=='\n') { + ret = 0; + if(prev!=',' || (b!='{' && b!='[')) { + os.write('\n'); + for(int i=0;i<indent;++i) { + os.write(TWO_SPACE); + } + } + } + switch(b) { + case '{': + case '[': + ret = '\n'; + ++indent; + break; + case '}': + case ']': + --indent; + os.write('\n'); + for(int i=0;i<indent;++i) { + os.write(TWO_SPACE); + } + break; + case ',': + ret = '\n'; + break; + + } + os.write(b); + prev = b; + } + public void resetIndent() { + indent = 1; + } + + @Override + public void flush() throws IOException { + os.flush(); + } + + @Override + public void close() throws IOException { + if(closeable) { + os.close(); + } + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/util/MaskFormatException.java b/cadi/core/src/main/java/org/onap/aaf/cadi/util/MaskFormatException.java new file mode 100644 index 00000000..7dd51c0a --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/util/MaskFormatException.java @@ -0,0 +1,31 @@ +/** + * ============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.cadi.util; + +@SuppressWarnings("serial") +public class MaskFormatException extends Exception { + + public MaskFormatException(String string) { + super(string); + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/util/MyConsole.java b/cadi/core/src/main/java/org/onap/aaf/cadi/util/MyConsole.java new file mode 100644 index 00000000..2312d00b --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/util/MyConsole.java @@ -0,0 +1,28 @@ +/** + * ============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.cadi.util; + +public interface MyConsole { + public String readLine(String fmt, Object ... args); + public char[] readPassword(String fmt, Object ... args); + public void printf(String fmt, Object ...args); +}
\ No newline at end of file diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/util/NetMask.java b/cadi/core/src/main/java/org/onap/aaf/cadi/util/NetMask.java new file mode 100644 index 00000000..fccb04fc --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/util/NetMask.java @@ -0,0 +1,99 @@ +/** + * ============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.cadi.util; + +/* + * NetMask - a class to quickly validate whether a given IP is part of a mask, as defined by bytes or standard String format. + * + * Needs the IPV6 Mask Builder. + */ +public class NetMask { + private long mask; + + public NetMask(byte[] inBytes) { + mask = derive(inBytes); + } + + public NetMask(String string) throws MaskFormatException { + mask = derive(string,true); + } + + public boolean isInNet(byte[] inBytes) { + long addr = derive(inBytes); + return (mask & addr) == addr; + } + + public boolean isInNet(String str) { + long addr; + try { + addr = derive(str,false); + return (mask & addr) == addr; + } catch (MaskFormatException e) { + // will not hit this code; + return false; + } + } + + public static long derive(byte[] inBytes) { + long addr = 0L; + int offset = inBytes.length*8; + for(int i=0;i<inBytes.length;++i) { + addr&=(inBytes[i]<<offset); + offset-=8; + } + return addr; + } + + public static long derive(String str, boolean check) throws MaskFormatException { + long rv=0L; + int idx=str.indexOf(':'); + int slash = str.indexOf('/'); + + if(idx<0) { // Not IPV6, so it's IPV4... Is there a mask of 123/254? + idx=str.indexOf('.'); + int offset = 24; + int end = slash>=0?slash:str.length(); + int bits = slash>=0?Integer.parseInt(str.substring(slash+1)):32; + if(check && bits>32) { + throw new MaskFormatException("Invalid Mask Offset in IPV4 Address"); + } + int prev = 0; + long lbyte; + while(prev<end) { + if(idx<0) { + idx = end; + } + lbyte = Long.parseLong(str.substring(prev, idx)); + if(check && (lbyte>255 || lbyte<0)) { + throw new MaskFormatException("Invalid Byte in IPV4 Address"); + } + rv|=lbyte<<offset; + prev = ++idx; + idx=str.indexOf('.',prev); + offset-=8; + } + rv|=0x00000000FFFFFFFFL>>bits; + } + return rv; + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/util/Pool.java b/cadi/core/src/main/java/org/onap/aaf/cadi/util/Pool.java new file mode 100644 index 00000000..d61aee21 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/util/Pool.java @@ -0,0 +1,380 @@ +/** + * ============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==================================================== + * + */ + +/* + * Pool + * + * Author: Jonathan + * 5/27/2011 + */ +package org.onap.aaf.cadi.util; + +import java.util.LinkedList; + +import org.onap.aaf.cadi.CadiException; + +/** + * This Class pools on an As-Needed-Basis any particular kind of class, which is + * quite suitable for expensive operations. + * + * The user calls "get" on a Pool, and if a waiting resource (T) is available, + * it will be returned. Otherwise, one will be created with the "Creator" class + * (must be defined for (T)). + * + * You can Prime the instances to avoid huge startup costs + * + * The returned "Pooled" object simply has to call "done()" and the object is + * returned to the pool. If the developer does not return the object, a memory + * leak does not occur. There are no references to the object once "get" is + * called. However, the developer who does not return the object when done + * obviates the point of the pool, as new Objects are created in place of the + * Object not returned when another call to "get" is made. + * + * There is a cushion of extra objects, currently defaulted to MAX_RANGE. If the + * items returned become higher than the MAX_RANGE, the object is allowed to go + * out of scope, and be cleaned up. the default can be changed on a per-pool + * basis. + * + * Class revamped for CadiExceptions and Access logging 10/4/2017 + * + * @author Jonathan + * + * @param <T> + */ +public class Pool<T> { + /** + * This is a constant which specified the default maximum number of unused + * objects to be held at any given time. + */ + private static final int MAX_RANGE = 6; // safety + + /** + * only Simple List needed. + * + * NOTE TO MAINTAINERS: THIS OBJECT DOES IT'S OWN SYNCHRONIZATION. All + * changes that touch list must account for correctly synchronizing list. + */ + private LinkedList<Pooled<T>> list; + + /** + * keep track of how many elements exist, to avoid asking list. + */ + private int count; + + /** + * Spares are those Object that are primed and ready to go. + */ + private int spares; + + /** + * Actual MAX number of spares allowed to hang around. Can be set to + * something besides the default MAX_RANGE. + */ + private int max_range = MAX_RANGE; + + /** + * The Creator for this particular pool. It must work for type T. + */ + private Creator<T> creator; + + private Log logger; + + /** + * Create a new Pool, given the implementation of Creator<T>, which must be + * able to create/destroy T objects at will. + * + * @param creator + */ + public Pool(Creator<T> creator) { + count = spares = 0; + this.creator = creator; + list = new LinkedList<Pooled<T>>(); + logger = Log.NULL; + } + + /** + * Attach Pool Logging activities to any other Logging Mechanism. + * @param logger + */ + public void setLogger(Log logger) { + this.logger = logger; + } + + public void log(Object ...objects) { + logger.log(objects); + } + + /** + * Preallocate a certain number of T Objects. Useful for services so that + * the first transactions don't get hit with all the Object creation costs + * + * @param lt + * @param prime + * @throws CadiException + */ + public void prime(int prime) throws CadiException { + for (int i = 0; i < prime; ++i) { + Pooled<T> pt = new Pooled<T>(creator.create(), this); + synchronized (list) { + list.addFirst(pt); + ++count; + } + } + + } + + /** + * Destroy and remove all remaining objects. This is valuable for closing + * down all Allocated objects cleanly for exiting. It is also a good method + * for removing objects when, for instance, all Objects are invalid because + * of broken connections, etc. + */ + public void drain() { + synchronized (list) { + for (int i = 0; i < list.size(); ++i) { + Pooled<T> pt = list.remove(); + creator.destroy(pt.content); + logger.log("Pool drained ", creator.toString()); + } + count = spares = 0; + } + + } + + /** + * This is the essential function for Pool. Get an Object "T" inside a + * "Pooled<T>" object. If there is a spare Object, then use it. If not, then + * create and pass back. + * + * This one uses a Null LogTarget + * + * IMPORTANT: When the use of this object is done (and the object is still + * in a valid state), then "done()" should be called immediately to allow + * the object to be reused. That is the point of the Pool... + * + * If the Object is in an invalid state, then "toss()" should be used so the + * Pool doesn't pass on invalid objects to others. + * + * @param lt + * @return + * @throws CadiException + */ + public Pooled<T> get() throws CadiException { + Pooled<T> pt; + synchronized (list) { + if (list.isEmpty()) { + pt = null; + } else { + pt = list.removeLast(); + --count; + creator.reuse(pt.content); + } + } + if (pt == null) { + if (spares < max_range) + ++spares; + pt = new Pooled<T>(creator.create(), this); + } else { + if (spares > 1) + --spares; + } + return pt; + } + + /** + * This function will validate whether the Objects are still in a usable + * state. If not, they are tossed from the Pool. This is valuable to have + * when Remote Connections go down, and there is a question on whether the + * Pooled Objects are still functional. + * + * @return + */ + public boolean validate() { + boolean rv = true; + synchronized (list) { + for (Pooled<T> t : list) { + if (!creator.isValid(t.content)) { + rv = false; + t.toss(); + list.remove(t); + } + } + } + return rv; + } + + /** + * This is an internal method, used only by the Internal Pooled<T> class. + * + * The Pooled<T> class "offers" it's Object back after use. It is an + * "offer", because Pool will simply destroy and remove the object if it has + * more than enough spares. + * + * @param lt + * @param used + * @return + */ + // Used only by Pooled<T> + private boolean offer(Pooled<T> used) { + if (count < spares) { + synchronized (list) { + list.addFirst(used); + ++count; + } + logger.log("Pool recovered ", creator); + } else { + logger.log("Pool destroyed ", creator); + creator.destroy(used.content); + } + return false; + } + + /** + * The Creator Interface give the Pool the ability to Create, Destroy and + * Validate the Objects it is maintaining. Thus, it is a specially written + * Implementation for each type. + * + * @author Jonathan + * + * @param <T> + */ + public interface Creator<T> { + public T create() throws CadiException; + + public void destroy(T t); + + public boolean isValid(T t); + + public void reuse(T t); + } + + public interface Log { + public void log(Object ... o); + + public final static Log NULL = new Log() { + @Override + public void log(Object ... o) { + } + }; + } + /** + * The "Pooled<T>" class is the transient class that wraps the actual Object + * T for API use/ It gives the ability to return ("done()", or "toss()") the + * Object to the Pool when processing is finished. + * + * For Safety, i.e. to avoid memory leaks and invalid Object States, there + * is a "finalize" method. It is strictly for when coder forgets to return + * the object, or perhaps hasn't covered the case during Exceptions or + * Runtime Exceptions with finally (preferred). This should not be + * considered normal procedure, as finalize() is called at an undetermined + * time during garbage collection, and is thus rather useless for a Pool. + * However, we don't want Coding Mistakes to put the whole program in an + * invalid state, so if something happened such that "done()" or "toss()" + * were not called, the resource is still cleaned up as well as possible. + * + * @author Jonathan + * + * @param <T> + */ + public static class Pooled<T> { + public final T content; + private Pool<T> pool; + + /** + * Create the Wrapping Object Pooled<T>. + * + * @param t + * @param pool + * @param logTarget + */ + public Pooled(T t, Pool<T> pool) { + content = t; + this.pool = pool; + + } + + /** + * This is the key API for the Pool, as calling "done()" offers this + * object back to the Pool for reuse. + * + * Do not use the Pooled<T> object again after calling "done()". + */ + public void done() { + if (pool != null) { + pool.offer(this); + } + } + + /** + * The user of the Object may discover that the Object t is no longer in + * a valid state. Don't put Garbage back in the Refrigerator... Toss it, + * if it's no longer valid. + * + * toss() is also used for draining the Pool, etc. + * + * toss() will attempt to destroy the Object by using the Creator + * Interface. + * + */ + public void toss() { + if (pool != null) { + pool.creator.destroy(content); + } + // Don't allow finalize to put it back in. + pool = null; + } + + /** + * Just in case someone neglected to offer back object... Do not rely on + * this, as there is no specific time when finalize is called, which + * rather defeats the purpose of a Pool. + */ + @Override + protected void finalize() throws Throwable { + if (pool != null) { + done(); + pool = null; + } + } + } + + /** + * Get the maximum number of spare objects allowed at any moment + * + * @return + */ + public int getMaxRange() { + return max_range; + } + + /** + * Set a Max Range for numbers of spare objects waiting to be used. + * + * No negative numbers are allowed + * + * @return + */ + public void setMaxRange(int max_range) { + // Do not allow negative numbers + this.max_range = Math.max(0, max_range); + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/util/Split.java b/cadi/core/src/main/java/org/onap/aaf/cadi/util/Split.java new file mode 100644 index 00000000..3fa9a3f1 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/util/Split.java @@ -0,0 +1,114 @@ +/** + * ============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.cadi.util; + +/** + * Split by Char, optional Trim + * + * Note: Copied from Inno to avoid linking issues. + * Note: I read the String split and Pattern split code, and we can do this more efficiently for a single Character + * + * 8/20/2015 + */ + +public class Split { + public static String[] split(char c, String value) { + return split(c,value,0,value.length()); + } + + public static String[] split(char c, String value, int start, int end) { + if(value==null) { + return new String[0]; + } + + // Count items to preallocate Array (memory alloc is more expensive than counting twice) + int count,idx; + for(count=1,idx=value.indexOf(c,start);idx>=0 && idx<end;idx=value.indexOf(c,++idx),++count); + String[] rv = new String[count]; + if(count==1) { + rv[0]=value.substring(start,end); + } else { + int last=0; + count=-1; + for(idx=value.indexOf(c,start);idx>=0 && idx<end;idx=value.indexOf(c,idx)) { + rv[++count]=value.substring(last,idx); + last = ++idx; + } + rv[++count]=value.substring(last,end); + } + return rv; + } + + public static String[] splitTrim(char c, String value, int start, int end) { + if(value==null) { + return new String[0]; + } + + // Count items to preallocate Array (memory alloc is more expensive than counting twice) + int count,idx; + for(count=1,idx=value.indexOf(c,start);idx>=0 && idx<end;idx=value.indexOf(c,++idx),++count); + String[] rv = new String[count]; + if(count==1) { + rv[0]=value.substring(start,end).trim(); + } else { + int last=0; + count=-1; + for(idx=value.indexOf(c,start);idx>=0 && idx<end;idx=value.indexOf(c,idx)) { + rv[++count]=value.substring(last,idx).trim(); + last = ++idx; + } + rv[++count]=value.substring(last,end).trim(); + } + return rv; + } + + public static String[] splitTrim(char c, String value) { + return splitTrim(c,value,0,value.length()); + } + + public static String[] splitTrim(char c, String value, int size) { + if(value==null) { + return new String[0]; + } + + int idx; + String[] rv = new String[size]; + if(size==1) { + rv[0]=value.trim(); + } else { + int last=0; + int count=-1; + size-=2; + for(idx=value.indexOf(c);idx>=0 && count<size;idx=value.indexOf(c,idx)) { + rv[++count]=value.substring(last,idx).trim(); + last = ++idx; + } + if(idx>0) { + rv[++count]=value.substring(last,idx).trim(); + } else { + rv[++count]=value.substring(last).trim(); + } + } + return rv; + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/util/SubStandardConsole.java b/cadi/core/src/main/java/org/onap/aaf/cadi/util/SubStandardConsole.java new file mode 100644 index 00000000..8d528119 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/util/SubStandardConsole.java @@ -0,0 +1,62 @@ +/** + * ============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.cadi.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +// Substandard, because System.in doesn't do Passwords.. +public class SubStandardConsole implements MyConsole { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + @Override + public String readLine(String fmt, Object... args) { + String rv; + try { + System.out.printf(fmt,args); + rv = br.readLine(); + if(args.length==1 && rv.length()==0) { + rv = args[0].toString(); + } + } catch (IOException e) { + System.err.println("uh oh..."); + rv = ""; + } + return rv; + } + + @Override + public char[] readPassword(String fmt, Object... args) { + try { + System.out.printf(fmt,args); + return br.readLine().toCharArray(); + } catch (IOException e) { + System.err.println("uh oh..."); + return new char[0]; + } + } + + @Override + public void printf(String fmt, Object... args) { + System.out.printf(fmt, args); + } +}
\ No newline at end of file diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/util/TheConsole.java b/cadi/core/src/main/java/org/onap/aaf/cadi/util/TheConsole.java new file mode 100644 index 00000000..4c5d35b7 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/util/TheConsole.java @@ -0,0 +1,47 @@ +/** + * ============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.cadi.util; + +public class TheConsole implements MyConsole { + @Override + public String readLine(String fmt, Object... args) { + String rv = System.console().readLine(fmt, args); + if(args.length>0 && args[0]!=null && rv.length()==0) { + rv = args[0].toString(); + } + return rv; + } + + @Override + public char[] readPassword(String fmt, Object... args) { + return System.console().readPassword(fmt, args); + } + + public static boolean implemented() { + return System.console()!=null; + } + + @Override + public void printf(String fmt, Object... args) { + System.console().printf(fmt, args); + } +}
\ No newline at end of file diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/util/UserChainManip.java b/cadi/core/src/main/java/org/onap/aaf/cadi/util/UserChainManip.java new file mode 100644 index 00000000..a8c0690f --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/util/UserChainManip.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.cadi.util; + +import org.onap.aaf.cadi.UserChain; + +public class UserChainManip { + /** + Build an element in the correct format for UserChain. + Format:<APP>:<ID>:<protocol>[:AS][,<APP>:<ID>:<protocol>]* + @see UserChain + */ + public static StringBuilder build(StringBuilder sb, String app, String id, UserChain.Protocol proto, boolean as) { + boolean mayAs; + if(!(mayAs=sb.length()==0)) { + sb.append(','); + } + sb.append(app); + sb.append(':'); + sb.append(id); + sb.append(':'); + sb.append(proto.name()); + if(as && mayAs) { + sb.append(":AS"); + } + return sb; + } + + public static String idToNS(String id) { + if(id==null) { + return ""; + } else { + StringBuilder sb = new StringBuilder(); + char c; + int end; + boolean first = true; + for(int idx = end = id.length()-1;idx>=0;--idx) { + if((c = id.charAt(idx))=='@' || c=='.') { + if(idx<end) { + if(first) { + first = false; + } else { + sb.append('.'); + } + for(int i=idx+1;i<=end;++i) { + sb.append(id.charAt(i)); + } + } + end=idx-1; + if(c=='@') { + break; + } + } + } + return sb.toString(); + } + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/util/Vars.java b/cadi/core/src/main/java/org/onap/aaf/cadi/util/Vars.java new file mode 100644 index 00000000..55470f99 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/util/Vars.java @@ -0,0 +1,120 @@ +/** + * ============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.cadi.util; + +import java.util.List; + +public class Vars { + /** + * Simplified Conversion based on typical use of getting AT&T style RESTful Error Messages + * @param text + * @param vars + * @return + */ + public static String convert(final String text, final List<String> vars) { + String[] array = new String[vars.size()]; + StringBuilder sb = new StringBuilder(); + convert(sb,text,vars.toArray(array)); + return sb.toString(); + } + /** + * Convert a format string with "%s" into AT&T RESTful Error %1 %2 (number) format + * If "holder" is passed in, it is built with full Message extracted (typically for Logging) + * @param holder + * @param text + * @param vars + * @return + */ + public static String convert(final StringBuilder holder, final String text, final String ... vars) { + StringBuilder sb = null; + int idx,index=0,prev = 0; + + if(text.contains("%s")) { + sb = new StringBuilder(); + } + + StringBuilder[] sbs = new StringBuilder[] {sb,holder}; + boolean replace, clearIndex = false; + int c; + while((idx=text.indexOf('%',prev))>=0) { + replace = false; + if(clearIndex) { + index=0; + } + if(sb!=null) { + sb.append(text,prev,idx); + } + if(holder!=null) { + holder.append(text,prev,idx); + } + + boolean go = true; + while(go) { + if(text.length()>++idx) { + switch(c=text.charAt(idx)) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + index *=10; + index +=(c-'0'); + clearIndex=replace=true; + continue; + case 's': + ++index; + replace = true; + continue; + default: + break; + } + } + prev = idx; + go=false; + if(replace) { + if(sb!=null) { + sb.append('%'); + sb.append(index); + } + if(index<=vars.length) { + if(holder!=null) { + holder.append(vars[index-1]); + } + } + } else { + for(StringBuilder s : sbs) { + if(s!=null) { + s.append("%"); + } + } + } + } + } + + if(sb!=null) { + sb.append(text,prev,text.length()); + } + if(holder!=null) { + holder.append(text,prev,text.length()); + } + + return sb==null?text:sb.toString(); + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/wsse/Action.java b/cadi/core/src/main/java/org/onap/aaf/cadi/wsse/Action.java new file mode 100644 index 00000000..dff18acd --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/wsse/Action.java @@ -0,0 +1,37 @@ +/** + * ============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.cadi.wsse; + +/** + * Interface to specify an action deep within a parsing tree on a local object + * + * We use a Generic so as to be flexible on create what that object actually is. This is passed in at the + * root "parse" call of Match. Similar to a "Visitor" Pattern, this object is passed upon reaching the right + * point in a parse tree. + * + * @author Jonathan + * + * @param <OUTPUT> + */ +interface Action<OUTPUT> { + public boolean content(OUTPUT output, String text); +}
\ No newline at end of file diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/wsse/Match.java b/cadi/core/src/main/java/org/onap/aaf/cadi/wsse/Match.java new file mode 100644 index 00000000..2582bc17 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/wsse/Match.java @@ -0,0 +1,130 @@ +/** + * ============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.cadi.wsse; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.XMLEvent; + +/** + * Match Class allows you to build an automatic Tree of StAX (or StAX like) + * Objects for frequent use. + * + * OBJECT is a type which you which to do some end Actions on, similar to a Visitor pattern, see Action + * + * Note: We have implemented with XReader and XEvent, rather than StAX for performance reasons. + * + * @see Action + * @see Match + * @see XEvent + * @see XReader + * + * @author Jonathan + * + * @param <OUTPUT> + */ +//@SuppressWarnings("restriction") +public class Match<OUTPUT> { + private QName qname; + private Match<OUTPUT>[] next; + private Match<OUTPUT> prev; + private Action<OUTPUT> action = null; + private boolean stopAfter; + private boolean exclusive; + + + @SafeVarargs + public Match(String ns, String name, Match<OUTPUT> ... next) { + this.qname = new QName(ns,name); + this.next = next; + stopAfter = exclusive = false; + for(Match<OUTPUT> m : next) { // add the possible tags to look for + if(!m.stopAfter)m.prev = this; + } + } + + public Match<OUTPUT> onMatch(OUTPUT output, XReader reader) throws XMLStreamException { + while(reader.hasNext()) { + XEvent event = reader.nextEvent(); + switch(event.getEventType()) { + case XMLEvent.START_ELEMENT: + QName e_qname = event.asStartElement().getName(); + //System.out.println("Start - " + e_qname); + boolean match = false; + for(Match<OUTPUT> m : next) { + if(e_qname.equals(m.qname)) { + match=true; + if(m.onMatch(output, reader)==null) { + return null; // short circuit Parsing + } + break; + } + } + if(exclusive && !match) // When Tag MUST be present, i.e. the Root Tag, versus info we're not interested in + return null; + break; + case XMLEvent.CHARACTERS: + //System.out.println("Data - " +event.asCharacters().getData()); + if(action!=null) { + if(!action.content(output,event.asCharacters().getData())) { + return null; + } + } + break; + case XMLEvent.END_ELEMENT: + //System.out.println("End - " + event.asEndElement().getName()); + if(event.asEndElement().getName().equals(qname)) { + return prev; + } + break; + case XMLEvent.END_DOCUMENT: + return null; // Exit Chain + } + } + return this; + } + + /** + * When this Matched Tag has completed, Stop parsing and end + * @return + */ + public Match<OUTPUT> stopAfter() { + stopAfter = true; + return this; + } + + /** + * Mark that this Object MUST be matched at this level or stop parsing and end + * + * @param action + * @return + */ + public Match<OUTPUT> exclusive() { + exclusive = true; + return this; + } + + public Match<OUTPUT> set(Action<OUTPUT> action) { + this.action = action; + return this; + } +}
\ No newline at end of file diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/wsse/WSSEParser.java b/cadi/core/src/main/java/org/onap/aaf/cadi/wsse/WSSEParser.java new file mode 100644 index 00000000..9e36c11f --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/wsse/WSSEParser.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.cadi.wsse; + +import java.io.IOException; +import java.io.InputStream; + +import javax.xml.stream.XMLStreamException; + +import org.onap.aaf.cadi.BasicCred; + + +/** + * WSSE Parser + * + * Read the User and Password from WSSE Formatted SOAP Messages + * + * This class uses StAX so that processing is stopped as soon as the Security User/Password are read into BasicCred, or the Header Ends + * + * This class is intended to be created once (or very few times) and reused as much as possible. + * + * It is as thread safe as StAX parsing is. + * + * @author Jonathan + */ +public class WSSEParser { + private static final String SOAP_NS = "http://schemas.xmlsoap.org/soap/envelope/"; + private static final String WSSE_NS = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; + private Match<BasicCred> parseTree; + //private XMLInputFactory inputFactory; + + public WSSEParser() { + // soap:Envelope/soap:Header/wsse:Security/wsse:UsernameToken/[wsse:Password&wsse:Username] + parseTree = new Match<BasicCred>(SOAP_NS,"root", // need a root level to start from... Doesn't matter what the tag is + new Match<BasicCred>(SOAP_NS,"Envelope", + new Match<BasicCred>(SOAP_NS,"Header", + new Match<BasicCred>(WSSE_NS,"Security", + new Match<BasicCred>(WSSE_NS,"UsernameToken", + new Match<BasicCred>(WSSE_NS,"Password").set(new Action<BasicCred>() { + public boolean content(BasicCred bc,String text) { + bc.setCred(text.getBytes()); + return true; + } + }), + new Match<BasicCred>(WSSE_NS,"Username").set(new Action<BasicCred>() { + public boolean content(BasicCred bc,String text) { + bc.setUser(text); + return true; + } + }) + ).stopAfter() // if found, end when UsernameToken ends (no further processing needed) + ) + ).stopAfter() // Stop Processing when Header Ends + ).exclusive()// Envelope must match Header, and no other. FYI, Body comes after Header short circuits (see above), so it's ok + ).exclusive(); // root must be Envelope + //inputFactory = XMLInputFactory.newInstance(); + } + + public XMLStreamException parse(BasicCred bc, InputStream is) throws IOException { + try { + parseTree.onMatch(bc, new XReader(is)); + return null; + } catch (XMLStreamException e) { + return e; + } + } +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/wsse/XEvent.java b/cadi/core/src/main/java/org/onap/aaf/cadi/wsse/XEvent.java new file mode 100644 index 00000000..12de366e --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/wsse/XEvent.java @@ -0,0 +1,135 @@ +/** + * ============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.cadi.wsse; + +import javax.xml.namespace.QName; +import javax.xml.stream.events.XMLEvent; + +/** + * XEvent + * + * This mechanism mimics a minimal portion of StAX "XMLEvent", enough to work with minimal XReader. + * + * We implement the same interface, as much as minimally necessary, as XMLEvent for these small usages so as to + * be interchangeable in the future, if so desired + * + * @author Jonathan + * + */ +// @SuppressWarnings("restriction") +public abstract class XEvent { + + public abstract int getEventType(); + + public StartElement asStartElement() { + return (StartElement)this; + } + + public Characters asCharacters() { + return (Characters)this; + } + + public EndElement asEndElement() { + return (EndElement)this; + } + + public static abstract class NamedXEvent extends XEvent { + private QName qname; + + public NamedXEvent(QName qname) { + this.qname = qname; + } + + public QName getName() { + return qname; + } + } + public static class StartElement extends NamedXEvent { + + public StartElement(String ns, String tag) { + super(new QName(ns,tag)); + } + + @Override + public int getEventType() { + return XMLEvent.START_ELEMENT; + } + } + + public static class EndElement extends NamedXEvent { + public EndElement(String ns, String tag) { + super(new QName(ns,tag)); + } + + @Override + public int getEventType() { + return XMLEvent.END_ELEMENT; + } + } + + public static class Characters extends XEvent { + private String data; + + public Characters(String data) { + this.data = data; + } + @Override + public int getEventType() { + return XMLEvent.CHARACTERS; + } + + public String getData() { + return data; + } + } + + public static class StartDocument extends XEvent { + + @Override + public int getEventType() { + return XMLEvent.START_DOCUMENT; + } + + } + + public static class EndDocument extends XEvent { + + @Override + public int getEventType() { + return XMLEvent.END_DOCUMENT; + } + + } + public static class Comment extends XEvent { + public final String value; + public Comment(String value) { + this.value = value; + } + + @Override + public int getEventType() { + return XMLEvent.COMMENT; + } + + } + +} diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/wsse/XReader.java b/cadi/core/src/main/java/org/onap/aaf/cadi/wsse/XReader.java new file mode 100644 index 00000000..4e5048b8 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/wsse/XReader.java @@ -0,0 +1,417 @@ +/** + * ============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.cadi.wsse; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; + +import javax.xml.stream.XMLStreamException; + +/** + * XReader + * This class works similarly as StAX, except StAX has more behavior than is needed. That would be ok, but + * StAX also was Buffering in their code in such as way as to read most if not all the incoming stream into memory, + * defeating the purpose of pre-reading only the Header + * + * This Reader does no back-tracking, but is able to create events based on syntax and given state only, leaving the + * Read-ahead mode of the InputStream up to the other classes. + * + * At this time, we only implement the important events, though if this is good enough, it could be expanded, perhaps to + * replace the original XMLReader from StAX. + * + * @author Jonathan + * + */ +// @SuppressWarnings("restriction") +public class XReader { + private XEvent curr,another; + private InputStream is; + private ByteArrayOutputStream baos; + private int state, count, last; + + private Stack<Map<String,String>> nsses; + + public XReader(InputStream is) { + this.is = is; + curr = another = null; + baos = new ByteArrayOutputStream(); + state = BEGIN_DOC; + count = 0; + nsses = new Stack<Map<String,String>>(); + } + + public boolean hasNext() throws XMLStreamException { + if(curr==null) { + curr = parse(); + } + return curr!=null; + } + + public XEvent nextEvent() { + XEvent xe = curr; + curr = null; + return xe; + } + + // + // State Flags + // + // Note: The State of parsing XML can be complicated. There are too many to cleanly keep in "booleans". Additionally, + // there are certain checks that can be better made with Bitwise operations within switches + // Keeping track of state this way also helps us to accomplish logic without storing any back characters except one + private final static int BEGIN_DOC= 0x000001; + private final static int DOC_TYPE= 0x000002; + private final static int QUESTION_F= 0x000004; + private final static int QUESTION = 0x000008; + private final static int START_TAG = 0x000010; + private final static int END_TAG = 0x000020; + private final static int VALUE= 0x000040; + private final static int COMMENT = 0x001000; + private final static int COMMENT_E = 0x002000; + private final static int COMMENT_D1 =0x010000; + private final static int COMMENT_D2 =0x020000; + private final static int COMMENT_D3 =0x040000; + private final static int COMMENT_D4 =0x080000; + // useful combined Comment states + private final static int IN_COMMENT=COMMENT|COMMENT_E|COMMENT_D1|COMMENT_D2; + private final static int COMPLETE_COMMENT = COMMENT|COMMENT_E|COMMENT_D1|COMMENT_D2|COMMENT_D3|COMMENT_D4; + + + private XEvent parse() throws XMLStreamException { + Map<String,String> nss = nsses.isEmpty()?null:nsses.peek(); + + XEvent rv; + if((rv=another)!=null) { // "another" is a tag that may have needed to be created, but not + // immediately returned. Save for next parse. If necessary, this could be turned into + // a FIFO storage, but a single reference is enough for now. + another = null; // "rv" is now set for the Event, and will be returned. Set to Null. + } else { + boolean go = true; + int c=0; + + try { + while(go && (c=is.read())>=0) { + ++count; + switch(c) { + case '<': // Tag is opening + state|=~BEGIN_DOC; // remove BEGIN_DOC flag, this is possibly an XML Doc + XEvent cxe = null; + if(baos.size()>0) { // If there are any characters between tags, we send as Character Event + String chars = baos.toString().trim(); // Trim out WhiteSpace before and after + if(chars.length()>0) { // don't send if Characters were only whitespace + cxe = new XEvent.Characters(chars); + baos.reset(); + go = false; + } + } + last = c; // make sure "last" character is set for use in "ParseTag" + Tag t = parseTag(); // call subroutine to process the tag as a unit + String ns; + switch(t.state&(START_TAG|END_TAG)) { + case START_TAG: + nss = getNss(nss,t); // Only Start Tags might have NS Attributes + // Get any NameSpace elements from tag. If there are, nss will become + // a new Map with all the previous NSs plus the new. This provides + // scoping behavior when used with the Stack + // drop through on purpose + case END_TAG: + ns = t.prefix==null?"":nss.get(t.prefix); // Get the namespace from prefix (if exists) + break; + default: + ns = ""; + } + if(ns==null) + throw new XMLStreamException("Invalid Namespace Prefix at " + count); + go = false; + switch(t.state) { // based on + case DOC_TYPE: + rv = new XEvent.StartDocument(); + break; + case COMMENT: + rv = new XEvent.Comment(t.value); + break; + case START_TAG: + rv = new XEvent.StartElement(ns,t.name); + nsses.push(nss); // Change potential scope for Namespace + break; + case END_TAG: + rv = new XEvent.EndElement(ns,t.name); + nss = nsses.pop(); // End potential scope for Namespace + break; + case START_TAG|END_TAG: // This tag is both start/end aka <myTag/> + rv = new XEvent.StartElement(ns,t.name); + if(last=='/')another = new XEvent.EndElement(ns,t.name); + } + if(cxe!=null) { // if there is a Character Event, it actually should go first. ow. + another = rv; // Make current Event the "another" or next event, and + rv = cxe; // send Character Event now + } + break; + case ' ': + case '\t': + case '\n': + if((state&BEGIN_DOC)==BEGIN_DOC) { // if Whitespace before doc, just ignore + break; + } + // fallthrough on purpose + default: + if((state&BEGIN_DOC)==BEGIN_DOC) { // if there is any data at the start other than XML Tag, it's not XML + throw new XMLStreamException("Parse Error: This is not an XML Doc"); + } + baos.write(c); // save off Characters + } + last = c; // Some processing needs to know what the last character was, aka Escaped characters... ex \" + } + } catch (IOException e) { + throw new XMLStreamException(e); // all errors parsing will be treated as XMLStreamErrors (like StAX) + } + if(c==-1 && (state&BEGIN_DOC)==BEGIN_DOC) { // Normally, end of stream is ok, however, we need to know if the + throw new XMLStreamException("Premature End of File"); // document isn't an XML document, so we throw exception if it + } // hasn't yet been determined to be an XML Doc + } + return rv; + } + + /** + * parseTag + * + * Parsing a Tag is somewhat complicated, so it's helpful to separate this process from the + * higher level Parsing effort + * @return + * @throws IOException + * @throws XMLStreamException + */ + private Tag parseTag() throws IOException, XMLStreamException { + Tag tag = null; + boolean go = true; + state = 0; + int c, quote=0; // If "quote" is 0, then we're not in a quote. We set ' (in pretag) or " in attribs accordingly to denote quoted + String prefix=null,name=null,value=null; + baos.reset(); + + while(go && (c=is.read())>=0) { + ++count; + if(quote!=0) { // If we're in a quote, we only end if we hit another quote of the same time, not preceded by \ + if(c==quote && last!='\\') { + quote=0; + } else { + baos.write(c); + } + } else if((state&COMMENT)==COMMENT) { // similar to Quote is being in a comment + switch(c) { + case '-': + switch(state) { // XML has a complicated Quote set... <!-- --> ... we keep track if each has been met with flags. + case COMMENT|COMMENT_E: + state|=COMMENT_D1; + break; + case COMMENT|COMMENT_E|COMMENT_D1: + state|=COMMENT_D2; + baos.reset(); // clear out "!--", it's a Comment + break; + case COMMENT|COMMENT_E|COMMENT_D1|COMMENT_D2: + state|=COMMENT_D3; + baos.write(c); + break; + case COMMENT|COMMENT_E|COMMENT_D1|COMMENT_D2|COMMENT_D3: + state|=COMMENT_D4; + baos.write(c); + break; + } + break; + case '>': // Tag indicator has been found, do we have all the comment characters in line? + if((state&COMPLETE_COMMENT)==COMPLETE_COMMENT) { + byte ba[] = baos.toByteArray(); + tag = new Tag(null,null, new String(ba,0,ba.length-2)); + baos.reset(); + go = false; + break; + } + // fall through on purpose + default: + state&=~(COMMENT_D3|COMMENT_D4); + if((state&IN_COMMENT)!=IN_COMMENT) state&=~IN_COMMENT; // false alarm, it's not actually a comment + baos.write(c); + } + } else { // Normal Tag Processing loop + switch(c) { + case '?': + switch(state & (QUESTION_F|QUESTION)) { // Validate the state of Doc tag... <?xml ... ?> + case QUESTION_F: + state |= DOC_TYPE; + state &= ~QUESTION_F; + break; + case 0: + state |=QUESTION_F; + break; + default: + throw new IOException("Bad character [?] at " + count); + } + break; + case '!': + if(last=='<') { + state|=COMMENT|COMMENT_E; // likely a comment, continue processing in Comment Loop + } + baos.write(c); + break; + case '/': + state|=(last=='<'?END_TAG:(END_TAG|START_TAG)); // end tag indicator </xxx>, ,or both <xxx/> + break; + case ':': + prefix=baos.toString(); // prefix indicator + baos.reset(); + break; + case '=': // used in Attributes + name=baos.toString(); + baos.reset(); + state|=VALUE; + break; + case '>': // end the tag, which causes end of this subprocess as well as formulation of the found data + go = false; + // passthrough on purpose + case ' ': + case '\t': + case '\n': // white space indicates change in internal tag state, ex between name and between attributes + if((state&VALUE)==VALUE) { + value = baos.toString(); // we're in VALUE state, add characters to Value + } else if(name==null) { + name = baos.toString(); // we're in Name state (default) add characters to Name + } + baos.reset(); // we've assigned chars, reset buffer + if(name!=null) { // Name is not null, there's a tag in the offing here... + Tag t = new Tag(prefix,name,value); + if(tag==null) { // Set as the tag to return, if not exists + tag = t; + } else { // if we already have a Tag, then we'll treat this one as an attribute + tag.add(t); + } + } + prefix=name=value=null; // reset these values in case we loop for attributes. + break; + case '\'': // is the character one of two kinds of quote? + case '"': + if(last!='\\') { + quote=c; + break; + } + // Fallthrough ok + default: + baos.write(c); // write any unprocessed bytes into buffer + + } + } + last = c; + } + int type = state&(DOC_TYPE|COMMENT|END_TAG|START_TAG); // get just the Tag states and turn into Type for Tag + if(type==0) { + type=START_TAG; + } + tag.state|=type; // add the appropriate Tag States + return tag; + } + + /** + * getNSS + * + * If the tag contains some Namespace attributes, create a new nss from the passed in one, copy all into it, then add + * This provides Scoping behavior + * + * if Nss is null in the first place, create an new nss, so we don't have to deal with null Maps. + * + * @param nss + * @param t + * @return + */ + private Map<String, String> getNss(Map<String, String> nss, Tag t) { + Map<String,String> newnss = null; + if(t.attribs!=null) { + for(Tag tag : t.attribs) { + if("xmlns".equals(tag.prefix)) { + if(newnss==null) { + newnss = new HashMap<String,String>(); + if(nss!=null)newnss.putAll(nss); + } + newnss.put(tag.name, tag.value); + } + } + } + return newnss==null?(nss==null?new HashMap<String,String>():nss):newnss; + } + + /** + * The result of the parseTag method + * + * Data is split up into prefix, name and value portions. "Tags" with Values that are inside a Tag are known in XLM + * as Attributes. + * + * @author Jonathan + * + */ + public class Tag { + public int state; + public String prefix,name,value; + public List<Tag> attribs; + + public Tag(String prefix, String name, String value) { + this.prefix = prefix; + this.name = name; + this.value = value; + attribs = null; + } + + /** + * add an attribute + * Not all tags need attributes... lazy instantiate to save time and memory + * @param tag + */ + public void add(Tag attrib) { + if(attribs == null) { + attribs = new ArrayList<Tag>(); + } + attribs.add(attrib); + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + if(prefix!=null) { + sb.append(prefix); + sb.append(':'); + } + sb.append(name==null?"!!ERROR!!":name); + + char quote = ((state&DOC_TYPE)==DOC_TYPE)?'\'':'"'; + if(value!=null) { + sb.append('='); + sb.append(quote); + sb.append(value); + sb.append(quote); + } + return sb.toString(); + } + } + +} diff --git a/cadi/core/src/test/java/.gitignore b/cadi/core/src/test/java/.gitignore new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/cadi/core/src/test/java/.gitignore diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_AES.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_AES.java new file mode 100644 index 00000000..4fb92429 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_AES.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * ============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.cadi.test; + +import static org.junit.Assert.*; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Paths; + +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.SecretKey; + +import org.junit.Test; +import org.onap.aaf.cadi.AES; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.Symm; +import org.junit.Before; + +public class JU_AES { + private AES aes; + private ByteArrayInputStream baisEncrypt; + private ByteArrayInputStream baisDecrypt; + private ByteArrayOutputStream baosEncrypt; + private ByteArrayOutputStream baosDecrypt; + + @Before + public void setup() throws Exception { + byte[] keyBytes = new byte[AES.AES_KEY_SIZE/8]; + char[] codeset = Symm.base64.codeset; + int offset = (Math.abs(codeset[0])+47)%(codeset.length-keyBytes.length); + for(int i=0;i<keyBytes.length;++i) { + keyBytes[i] = (byte)codeset[i+offset]; + } + aes = new AES(keyBytes,0,keyBytes.length); + } + + @Test + public void newKeyTest() throws Exception { + SecretKey secretKey = AES.newKey(); + assertEquals(secretKey.getAlgorithm(), AES.class.getSimpleName()); + } + + @Test + public void encryptDecrpytFromBytes() throws Exception { + String orig = "I'm a password, really"; + byte[] encrypted = aes.encrypt(orig.getBytes()); + byte[] decrypted = aes.decrypt(encrypted); + assertEquals(orig, new String(decrypted)); + + Field aeskeySpec_field = AES.class.getDeclaredField("aeskeySpec"); + aeskeySpec_field.setAccessible(true); + aeskeySpec_field.set(aes, null); + + try { + aes.encrypt(orig.getBytes()); + fail("Should have thrown an exception"); + } catch (CadiException e) { + } + try { + aes.decrypt(encrypted); + fail("Should have thrown an exception"); + } catch (CadiException e) { + } + } + + @Test + public void saveToFileTest() throws Exception { + String filePath = "test/output_key"; + File keyfile = new File(filePath); + aes.save(keyfile); + assertTrue(Files.isReadable(Paths.get(filePath))); + assertFalse(Files.isWritable(Paths.get(filePath))); + assertFalse(Files.isExecutable(Paths.get(filePath))); + keyfile.delete(); + } + + @Test + public void encryptDecryptFromInputStream() throws Exception { + String orig = "I'm a password, really"; + byte[] b64encrypted; + String output; + + CipherInputStream cisEncrypt; + CipherInputStream cisDecrypt; + + // Test CipherInputStream + baisEncrypt = new ByteArrayInputStream(orig.getBytes()); + cisEncrypt = aes.inputStream(baisEncrypt, true); + baosEncrypt = new ByteArrayOutputStream(); + transferFromInputStreamToOutputStream(cisEncrypt, baosEncrypt); + cisEncrypt.close(); + + b64encrypted = baosEncrypt.toByteArray(); + + baisDecrypt = new ByteArrayInputStream(b64encrypted); + cisDecrypt = aes.inputStream(baisDecrypt, false); + baosDecrypt = new ByteArrayOutputStream(); + transferFromInputStreamToOutputStream(cisDecrypt, baosDecrypt); + cisDecrypt.close(); + + output = new String(baosDecrypt.toByteArray()); + assertEquals(orig, output); + + Field aeskeySpec_field = AES.class.getDeclaredField("aeskeySpec"); + aeskeySpec_field.setAccessible(true); + aeskeySpec_field.set(aes, null); + + assertNull(aes.inputStream(baisEncrypt, true)); + } + + @Test + public void encryptDecryptFromOutputStream() throws Exception { + String orig = "I'm a password, really"; + byte[] b64encrypted; + String output; + + CipherOutputStream cosEncrypt; + CipherOutputStream cosDecrypt; + + // Test CipherOutputStream + baisEncrypt = new ByteArrayInputStream(orig.getBytes()); + baosEncrypt = new ByteArrayOutputStream(); + cosEncrypt = aes.outputStream(baosEncrypt, true); + transferFromInputStreamToOutputStream(baisEncrypt, cosEncrypt); + cosEncrypt.close(); + + b64encrypted = baosEncrypt.toByteArray(); + + baosDecrypt = new ByteArrayOutputStream(); + cosDecrypt = aes.outputStream(baosDecrypt, false); + baisDecrypt = new ByteArrayInputStream(b64encrypted); + transferFromInputStreamToOutputStream(baisDecrypt, cosDecrypt); + cosDecrypt.close(); + + output = new String(baosDecrypt.toByteArray()); + assertEquals(orig, output); + + Field aeskeySpec_field = AES.class.getDeclaredField("aeskeySpec"); + aeskeySpec_field.setAccessible(true); + aeskeySpec_field.set(aes, null); + + assertNull(aes.outputStream(baosEncrypt, true)); + } + + public void transferFromInputStreamToOutputStream(InputStream is, OutputStream os) throws IOException { + byte[] buffer = new byte[200]; + int len; + while ((len = is.read(buffer)) != -1) { + os.write(buffer, 0, len); + } + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_Access.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_Access.java new file mode 100644 index 00000000..d62144ce --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_Access.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * ============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.cadi.test; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import org.junit.*; + +import java.io.IOException; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.Access.Level; + +public class JU_Access { + + @Test + public void levelTests() { + assertTrue(Level.DEBUG.inMask(0x1)); + for (int i = 2; i > 0; i <<= 1) { + assertFalse(Level.DEBUG.inMask(i)); + } + assertFalse(Level.DEBUG.inMask(0x80000000)); + + assertThat(Level.DEBUG.addToMask(0x2), is(0x3)); + assertThat(Level.DEBUG.delFromMask(0x1), is(0x0)); + assertThat(Level.DEBUG.toggle(0x2), is(0x3)); + assertThat(Level.DEBUG.toggle(0x1), is(0x0)); + assertThat(Level.DEBUG.maskOf(), is(57617)); + assertThat(Level.NONE.maskOf(), is(0)); + } + + @Test + public void nullTests() throws IOException { + // These are entirely for coverage + Access.NULL.log(Level.DEBUG); + Access.NULL.printf(Level.DEBUG, ""); + Access.NULL.log(new Exception()); + Access.NULL.classLoader(); + assertThat(Access.NULL.getProperty("", ""), is(nullValue())); + Access.NULL.load(System.in); + Access.NULL.setLogLevel(Level.DEBUG); + assertThat(Access.NULL.decrypt("test", true), is("test")); + assertFalse(Access.NULL.willLog(Level.DEBUG)); + assertThat(Access.NULL.getProperties(), is(not(nullValue()))); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_Base64.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_Base64.java new file mode 100644 index 00000000..801259d4 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_Base64.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * ============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.cadi.test; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.SecureRandom; + +import org.junit.Test; +import org.onap.aaf.cadi.Symm; +import org.onap.aaf.cadi.config.Config; + +public class JU_Base64 { + private static final String encoding = "Man is distinguished, not only by his reason, but by this singular " + + "passion from other animals, which is a lust of the mind, that by a " + + "perseverance of delight in the continued and indefatigable generation of " + + "knowledge, exceeds the short vehemence of any carnal pleasure."; + + private static final String expected = + "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz\n" + + "IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg\n" + + "dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu\n" + + "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo\n" + + "ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="; + + @Test + public void test() throws Exception { + // Test with different Padding + assertEncoded("leas", "bGVhcw=="); + assertEncoded("leasu", "bGVhc3U="); + assertEncoded("leasur", "bGVhc3Vy"); + assertEncoded("leasure", "bGVhc3VyZQ=="); + assertEncoded("leasure.", "bGVhc3VyZS4="); + + // Test with line ends + assertEncoded(encoding, expected); + } + + @Test + public void symmetric() throws IOException { + String symmetric = new String(Symm.keygen()); + Symm bsym = Symm.obtain(symmetric); + String result = bsym.encode(encoding); + assertThat(bsym.decode(result), is(encoding)); + + char[] manipulate = symmetric.toCharArray(); + int spot = new SecureRandom().nextInt(manipulate.length); + manipulate[spot]|=0xFF; + String newsymmetric = new String(manipulate); + assertThat(symmetric, is(not(newsymmetric))); + try { + bsym = Symm.obtain(newsymmetric); + result = bsym.decode(result); + assertThat(result, is(encoding)); + } catch (IOException e) { + // this is what we want to see if key wrong + } + } + + private void assertEncoded(String toEncode, String expected) throws IOException { + String result = Symm.base64.encode(toEncode); + assertThat(result, is(expected)); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Symm.base64.decode(new ByteArrayInputStream(result.getBytes()), baos); + result = baos.toString(Config.UTF_8); + assertThat(result, is(toEncode)); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_BufferedCadiWrap.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_BufferedCadiWrap.java new file mode 100644 index 00000000..172270da --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_BufferedCadiWrap.java @@ -0,0 +1,45 @@ +/** + * ============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.cadi.test; + +import javax.servlet.http.HttpServletRequest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class JU_BufferedCadiWrap { + @Mock + private HttpServletRequest request; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void constructorTest() { + // TODO: Ian - This will always fail beacuse the constructor is invalid + // BufferedCadiWrap bcw = new BufferedCadiWrap(request); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_BufferedServletInputStream.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_BufferedServletInputStream.java new file mode 100644 index 00000000..66ac3610 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_BufferedServletInputStream.java @@ -0,0 +1,320 @@ +/******************************************************************************* + * ============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.cadi.test; + +import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.reflect.*; +import junit.framework.Assert; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onap.aaf.cadi.BufferedServletInputStream; + +import static junit.framework.Assert.assertEquals; + +public class JU_BufferedServletInputStream { + private BufferedServletInputStream bsis; + private String expected; + + @Before + public void setup() throws FileNotFoundException { + expected = new String("This is the expected output"); + bsis = new BufferedServletInputStream(new ByteArrayInputStream(expected.getBytes())); + } + + @After + public void tearDown() throws IOException { + bsis.close(); + } + + @Test + public void ByteReadNoMarkTest() throws Exception { + int c; + int i = 0; + byte output[] = new byte[100]; + while ((c = bsis.read()) != -1) { + output[i++] = (byte)c; + } + Assert.assertEquals(new String(output, 0, i), expected); + } + + @Test + public void ByteReadMarkTest() throws Exception { + bsis.mark(0); + int c; + int i = 0; + byte output[] = new byte[100]; + while ((c = bsis.read()) != -1) { + output[i++] = (byte)c; + } + Assert.assertEquals(new String(output, 0, i), expected); + } + + @Test + public void ByteReadStateIsStoreTest() throws Exception { + Field state_field = BufferedServletInputStream.class.getDeclaredField("state"); + state_field.setAccessible(true); + bsis.mark(0); + int c; + int i = 0; + byte output[] = new byte[100]; + while ((c = bsis.read()) != -1) { + output[i++] = (byte)c; + } + bsis.reset(); + Assert.assertEquals(state_field.get(bsis), 2); // state == READ + } + + @Test + public void ByteReadStateIsReadTest() throws Exception { + bsis.mark(0); // Initialize the capacitor + boolean isReset = false; + int c; + int i = 0; + byte output[] = new byte[100]; + while ((c = bsis.read()) != -1) { + output[i++] = (byte)c; + if ((i > 5) && !isReset) { + // Close the capacitor and start over. This is done for coverage purposes + i = 0; + isReset = true; + bsis.reset(); // Sets state to READ + } + } + Assert.assertEquals(new String(output, 0, i), expected); + } + + @Test + public void ByteReadStateIsNoneTest() throws Exception { + Field state_field = BufferedServletInputStream.class.getDeclaredField("state"); + state_field.setAccessible(true); + bsis.mark(0); // Initialize the capacitor + int c; + c = bsis.read(); + // Close the capacitor. This is done for coverage purposes + bsis.reset(); // Sets state to READ + state_field.setInt(bsis, 0); // state == NONE + c = bsis.read(); + Assert.assertEquals(c, -1); + } + + @Test + public void ByteArrayReadNoMarkTest() throws Exception { + byte output[] = new byte[100]; + int count = bsis.read(output, 0, expected.length()); + Assert.assertEquals(new String(output, 0, count), expected); + Assert.assertEquals(count, expected.length()); + } + + @Test + public void ByteArrayReadTest() throws Exception { + byte[] output = new byte[100]; + bsis.mark(0); + bsis.read(output); + Assert.assertEquals(new String(output, 0, expected.length()), expected); + } + + @Test + public void ByteArrayReadStateIsStoreTest() throws Exception { + byte output[] = new byte[100]; + bsis.mark(0); + int count = bsis.read(output, 0, expected.length()); + Assert.assertEquals(new String(output, 0, count), expected); + Assert.assertEquals(count, expected.length()); + + count = bsis.read(output, 0, 0); + Assert.assertEquals(count, -1); + } + + @Test + public void ByteArrayReadStateIsReadTest() throws Exception { + byte output[] = new byte[200]; + for(int i = 0; i < 2; ++i) { + bsis.mark(0); + bsis.read(output, 0, 100); + Assert.assertEquals(new String(output, 0, expected.length()), expected); + + bsis.reset(); + bsis.read(output, 0, output.length); + Assert.assertEquals(new String(output, 0, expected.length()), expected); + bsis = new BufferedServletInputStream(new ByteArrayInputStream(output)); + if(i == 0) { + output = new byte[200]; + } + } + + Assert.assertEquals(new String(output, 0, expected.length()), expected); + } + + @Test + public void ByteArrayReadStateIsNoneTest() throws Exception { + byte output[] = new byte[100]; + bsis.mark(0); + + Field state_field = BufferedServletInputStream.class.getDeclaredField("state"); + state_field.setAccessible(true); + state_field.setInt(bsis, 0); // state == NONE + + int count = bsis.read(output, 0, 100); + Assert.assertEquals(count, -1); + } + + @Test + public void skipTest() throws Exception { + byte output[] = new byte[100]; + bsis.mark(0); + bsis.read(output, 0, 10); + long count = bsis.skip(200); + // skip returns the number left _before_ skipping. that number starts at 256 + Assert.assertEquals(count, 246); + + count = bsis.skip(200); + Assert.assertEquals(count, 17); + } + + @Test + public void availableTest() throws Exception { + int count = bsis.available(); + Assert.assertEquals(count, 27); + bsis.mark(0); + count = bsis.available(); + Assert.assertEquals(count, 27); + } + + @Test + public void bufferedTest() throws Exception { + bsis.mark(0); + Assert.assertEquals(bsis.buffered(), 0); + } + + @Test + public void closeTest() throws Exception { + Field capacitor_field = BufferedServletInputStream.class.getDeclaredField("capacitor"); + capacitor_field.setAccessible(true); + bsis.mark(0); + Assert.assertNotNull(capacitor_field.get(bsis)); + bsis.close(); + Assert.assertNull(capacitor_field.get(bsis)); + } + + @Test + public void markTest() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Field state_field = BufferedServletInputStream.class.getDeclaredField("state"); + Field capacitor_field = BufferedServletInputStream.class.getDeclaredField("capacitor"); + capacitor_field.setAccessible(true); + state_field.setAccessible(true); + + // capacitor is null initially + Assert.assertNull(capacitor_field.get(bsis)); + + state_field.setInt(bsis, 0); // state == NONE + bsis.mark(0); // the value passed into mark is ignored + Assert.assertNotNull(capacitor_field.get(bsis)); + Assert.assertEquals(state_field.get(bsis), 1); // state == STORE + + state_field.setInt(bsis, 1); // state == STORE + bsis.mark(0); // the value passed into mark is ignored + Assert.assertEquals(state_field.get(bsis), 1); // state == STORE + + state_field.setInt(bsis, 2); // state == READ + bsis.mark(0); // the value passed into mark is ignored + Assert.assertEquals(state_field.get(bsis), 1); // state == STORE + } + + @Test + public void resetTest() throws Exception { + Field state_field = BufferedServletInputStream.class.getDeclaredField("state"); + state_field.setAccessible(true); + + bsis.mark(0); + Assert.assertEquals(state_field.get(bsis), 1); // state == STORE + bsis.reset(); + Assert.assertEquals(state_field.get(bsis), 2); // state == READ + bsis.reset(); + Assert.assertEquals(state_field.get(bsis), 2); // state == READ + + state_field.setInt(bsis, -1); // state is invalid + bsis.reset(); // This call does nothing. It is for coverage alone + Assert.assertEquals(state_field.get(bsis), -1); // state doesn't change + + try { + state_field.setInt(bsis, 0); // state == NONE + bsis.reset(); + } catch (IOException e) { + Assert.assertEquals(e.getMessage(), "InputStream has not been marked"); + } + } + + @Test + public void markSupportedTest() { + Assert.assertTrue(bsis.markSupported()); + } + + // "Bug" 4/22/2013 + // Some XML code expects Buffered InputStream can never return 0... This isn't actually true, but we'll accommodate as far + // as we can. + // Here, we make sure we set and read the Buffered data, making sure the buffer is empty on the last test... + @Test + public void issue04_22_2013() throws IOException { + String testString = "We want to read in and get out with a Buffered Stream seamlessly."; + ByteArrayInputStream bais = new ByteArrayInputStream(testString.getBytes()); + BufferedServletInputStream bsis = new BufferedServletInputStream(bais); + try { + bsis.mark(0); + byte aa[] = new byte[testString.length()]; // 65 count... important for our test (divisible by 5); + + int read; + for(int i=0;i<aa.length;i+=5) { + read = bsis.read(aa, i, 5); + assertEquals(5,read); + } + // System.out.println(new String(aa)); + + bsis.reset(); + + byte bb[] = new byte[aa.length]; + read = 0; + for(int i=0;read>=0;i+=read) { + read = bsis.read(bb,i,5); + switch(i) { + case 65: + assertEquals(read,-1); + break; + default: + assertEquals(read,5); + } + } + // System.out.println(new String(bb)); + assertEquals(testString,new String(aa)); + assertEquals(testString,new String(bb)); + + } finally { + bsis.close(); + bais.close(); + } + + } + + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_CadiExceptionTest.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_CadiExceptionTest.java new file mode 100644 index 00000000..03b20375 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_CadiExceptionTest.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * ============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.cadi.test; + +import static org.junit.Assert.*; + +import org.junit.Test; +import org.onap.aaf.cadi.CadiException; + +import static org.hamcrest.CoreMatchers.is; + +public class JU_CadiExceptionTest { + @Test + public void testCadiException() { + CadiException exception = new CadiException(); + + assertNotNull(exception); + } + + @Test + public void testCadiExceptionString() { + CadiException exception = new CadiException("New Exception"); + assertNotNull(exception); + assertThat(exception.getMessage(), is("New Exception")); + } + + @Test + public void testCadiExceptionThrowable() { + CadiException exception = new CadiException(new Throwable("New Exception")); + assertNotNull(exception); + assertThat(exception.getMessage(), is("java.lang.Throwable: New Exception")); + } + + @Test + public void testCadiExceptionStringThrowable() { + CadiException exception = new CadiException("New Exception",new Throwable("New Exception")); + assertNotNull(exception); + assertThat(exception.getMessage(), is("New Exception")); + + } + + @Test + public void testCadiException1() { + CadiException exception = new CadiException(); + + assertNotNull(exception); + } + + @Test + public void testCadiExceptionString1() { + CadiException exception = new CadiException("New Exception"); + assertNotNull(exception); + assertThat(exception.getMessage(), is("New Exception")); + } + + @Test + public void testCadiExceptionThrowable1() { + CadiException exception = new CadiException(new Throwable("New Exception")); + assertNotNull(exception); + assertThat(exception.getMessage(), is("java.lang.Throwable: New Exception")); + } + + @Test + public void testCadiExceptionStringThrowable1() { + CadiException exception = new CadiException("New Exception",new Throwable("New Exception")); + assertNotNull(exception); + assertThat(exception.getMessage(), is("New Exception")); + + } + + @Test + public void testCadiException2() { + CadiException exception = new CadiException(); + + assertNotNull(exception); + } + + @Test + public void testCadiExceptionString2() { + CadiException exception = new CadiException("New Exception"); + assertNotNull(exception); + assertThat(exception.getMessage(), is("New Exception")); + } + + @Test + public void testCadiExceptionThrowable2() { + CadiException exception = new CadiException(new Throwable("New Exception")); + assertNotNull(exception); + assertThat(exception.getMessage(), is("java.lang.Throwable: New Exception")); + } + + @Test + public void testCadiExceptionStringThrowable2() { + CadiException exception = new CadiException("New Exception",new Throwable("New Exception")); + assertNotNull(exception); + assertThat(exception.getMessage(), is("New Exception")); + + } + + + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_CadiWrapTest.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_CadiWrapTest.java new file mode 100644 index 00000000..14b2999a --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_CadiWrapTest.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * ============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.cadi.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.isA; +import static org.mockito.Mockito.when; + +import java.security.Principal; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.CachingLur; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.CadiWrap; +import org.onap.aaf.cadi.Lur; +import org.onap.aaf.cadi.Permission; +import org.onap.aaf.cadi.PropAccess; +import org.onap.aaf.cadi.User; +import org.onap.aaf.cadi.CachedPrincipal.Resp; +import org.onap.aaf.cadi.filter.MapPermConverter; +import org.onap.aaf.cadi.lur.EpiLur; +import org.onap.aaf.cadi.principal.TaggedPrincipal; +import org.onap.aaf.cadi.taf.TafResp; + +public class JU_CadiWrapTest { + + @Mock + private HttpServletRequest request; + + @Mock + private TafResp tafResp; + + @Mock + private TaggedPrincipal principle; + + @Mock + private Lur lur; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testInstantiate() throws CadiException { + Access a = new PropAccess(); + when(tafResp.getAccess()).thenReturn(a); + + lur.fishAll(isA(Principal.class), isA(List.class)); + + EpiLur lur1 = new EpiLur(lur); + + CadiWrap wrap = new CadiWrap(request, tafResp, lur1); + + assertNull(wrap.getUserPrincipal()); + assertNull(wrap.getRemoteUser()); + assertNull(wrap.getUser()); + assertEquals(wrap.getPermissions(principle).size(), 0); + assertTrue(wrap.access() instanceof PropAccess); + + byte[] arr = {'1','2'}; + wrap.setCred(arr); + + assertEquals(arr, wrap.getCred()); + + wrap.setUser("User1"); + assertEquals("User1", wrap.getUser()); + + wrap.invalidate("1"); + + assertFalse(wrap.isUserInRole(null)); + + wrap.set(tafResp, lur); + + wrap.invalidate("2"); + + wrap.isUserInRole("User1"); + } + + @Test + public void testInstantiateWithPermConverter() throws CadiException { + Access a = new PropAccess(); + when(tafResp.getAccess()).thenReturn(a); + when(tafResp.getPrincipal()).thenReturn(principle); + + // Anonymous object for testing purposes + CachingLur<Permission> lur1 = new CachingLur<Permission>() { + @Override public Permission createPerm(String p) { return null; } + @Override public boolean fish(Principal bait, Permission pond) { return true; } + @Override public void fishAll(Principal bait, List<Permission> permissions) { } + @Override public void destroy() { } + @Override public boolean handlesExclusively(Permission pond) { return false; } + @Override public boolean handles(Principal principal) { return false; } + @Override public void remove(String user) { } + @Override public Resp reload(User<Permission> user) { return null; } + @Override public void setDebug(String commaDelimIDsOrNull) { } + @Override public void clear(Principal p, StringBuilder sb) { } + }; + + MapPermConverter pc = new MapPermConverter(); + + CadiWrap wrap = new CadiWrap(request, tafResp, lur1, pc); + + assertNotNull(wrap.getUserPrincipal()); + assertNull(wrap.getRemoteUser()); + assertNull(wrap.getUser()); + + byte[] arr = {'1','2'}; + wrap.setCred(arr); + + assertEquals(arr, wrap.getCred()); + + wrap.setUser("User1"); + assertEquals("User1", wrap.getUser()); + + wrap.invalidate("1"); + wrap.setPermConverter(new MapPermConverter()); + + assertTrue(wrap.getLur() instanceof CachingLur); + assertTrue(wrap.isUserInRole("User1")); + + wrap.set(tafResp, lur); + assertFalse(wrap.isUserInRole("Perm1")); + } +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_Capacitor.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_Capacitor.java new file mode 100644 index 00000000..e9bceccd --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_Capacitor.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * ============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.cadi.test; + +import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import org.junit.Before; +import org.junit.Test; +import org.onap.aaf.cadi.Capacitor; + +import java.lang.reflect.*; + +public class JU_Capacitor { + private Capacitor cap; + public final static String TEST_DATA = + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + + "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + + "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" + + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + + @Before + public void setup() { + cap = new Capacitor(); + } + + @Test + public void singleByteTest() throws Exception { + assertEquals(cap.read(), -1); + cap.setForRead(); + Field curr_field = Capacitor.class.getDeclaredField("curr"); + curr_field.setAccessible(true); + Field idx_field = Capacitor.class.getDeclaredField("idx"); + idx_field.setAccessible(true); + assertNull(curr_field.get(cap)); + assertEquals(idx_field.get(cap), 0); + + for(int iter = 0; iter < 20; ++iter) { + for(int i = 0; i < 20; ++i) { + cap.put((byte)('a' + i)); + } + cap.setForRead(); + byte[] array = new byte[20]; + for(int i = 0; i < 20; ++i) { + array[i]=(byte)cap.read(); + } + assertEquals("abcdefghijklmnopqrst", new String(array)); + assertEquals(-1, cap.read()); + + cap.done(); + } + + for(int i = 0; i < 500; i++) { + cap.put((byte)'a'); + } + cap.setForRead(); + byte[] array = new byte[500]; + for(int i = 0; i < 500; ++i) { + array[i]=(byte)cap.read(); + } + assertEquals((new String(array)).length(), 500); + assertEquals(-1, cap.read()); + } + + @Test + public void availableTest() { + assertEquals(cap.available(), 0); + for(int i = 0; i < 100; ++i) { + cap.put((byte)'a'); + } + // The Capacitor can hold 256 bytes. After reading 100 bytes, + // it should have 156 available + assertEquals(cap.available(), 156); + } + + @Test + public void byteArrayTest() { + byte[] arrayA = TEST_DATA.getBytes(); + assertEquals(cap.read(arrayA, 0, arrayA.length), -1); + + cap.put(arrayA, 0, arrayA.length); + + byte[] arrayB = new byte[arrayA.length]; + cap.setForRead(); + assertEquals(arrayA.length, cap.read(arrayB, 0, arrayB.length)); + assertEquals(TEST_DATA, new String(arrayB)); + assertEquals(-1, cap.read()); + cap.done(); + + String b = "This is some content that we want to read"; + byte[] a = b.getBytes(); + byte[] c = new byte[b.length()]; // we want to use this to test reading offsets, etc + + for(int i = 0; i < a.length; i += 11) { + cap.put(a, i, Math.min(11, a.length-i)); + } + cap.reset(); + int read; + for(int i = 0; i < c.length; i += read) { + read = cap.read(c, i, Math.min(3, c.length-i)); + } + assertEquals(b, new String(c)); + } + + @Test + public void resetTest() throws Exception { + cap.reset(); + Field curr_field = Capacitor.class.getDeclaredField("curr"); + curr_field.setAccessible(true); + Field idx_field = Capacitor.class.getDeclaredField("idx"); + idx_field.setAccessible(true); + assertNull(curr_field.get(cap)); + assertEquals(idx_field.get(cap), 0); + + cap.put((byte)'a'); + cap.reset(); + assertNotNull(curr_field.get(cap)); + assertEquals(idx_field.get(cap), 1); + } + + @Test + public void skipTest() throws Exception { + // capacitor can't skip if nothing has been put into it + assertEquals(cap.skip(10), 0); + cap.put((byte)'a'); + // The Capacitor can hold 256 bytes. If we try to skip 100 bytes, + // it should only skip 1 byte, leaving 255 remaining + assertEquals(cap.skip(100), 255); + + // Skipping 200 bytes leaves 0 remaining + assertEquals(cap.skip(200), 0); + } +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_CmdLine.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_CmdLine.java new file mode 100644 index 00000000..5da67ce0 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_CmdLine.java @@ -0,0 +1,273 @@ +/******************************************************************************* + * ============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.cadi.test; + +import static org.mockito.Matchers.*; +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import org.junit.*; +import org.mockito.*; +import static org.mockito.Mockito.*; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Properties; + +import org.onap.aaf.cadi.CmdLine; +import org.onap.aaf.cadi.PropAccess; +import org.onap.aaf.cadi.Symm; + +public class JU_CmdLine { + + @Mock + private OutputStream thrower; + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + + private String password; + private String keyfile; + private String quickBrownFoxPlain = "The quick brown fox jumps over the lazy dog"; + private String quickBrownFoxMD5 = "0x9e107d9d372bb6826bd81d3542a419d6"; + private String quickBrownFoxSHA256 = "0xd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592"; + private Symm symm; + + @Before + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + + System.setOut(new PrintStream(outContent)); + + Properties p = new Properties(); + p.setProperty("force_exit", "false"); + + CmdLine.access = new PropAccess(p); + + password = "password"; + keyfile = "test/keyfile"; + + FileInputStream fis = new FileInputStream(keyfile); + try { + symm = Symm.obtain(fis); + } finally { + fis.close(); + } + } + + @After + public void restoreStreams() throws IOException { + System.setOut(System.out); + System.setIn(System.in); + } + + @Test + public void digestTest() throws Exception { + CmdLine.main(new String[]{"digest", password, keyfile}); + String decrypted = symm.depass(outContent.toString()); + assertThat(decrypted, is(password)); + + System.setIn(new ByteArrayInputStream(password.getBytes())); + CmdLine.main(new String[]{"digest", "-i", keyfile}); + decrypted = symm.depass(outContent.toString()); + assertThat(decrypted, is(password)); + } + + @Test + public void regurgitateTest() { + // TODO: We may still want to remove the regurgitate functionality + // from the CmdLine - Ian + fail("Tests not yet implemented"); + } + + @Test + public void encode64Test() throws Exception { + CmdLine.main(new String[]{"encode64", password}); + String decrypted = Symm.base64.decode(outContent.toString()); + assertThat(decrypted, is(password)); + } + + @Test + public void decode64Test() throws Exception { + String encrypted = Symm.base64.encode(password); + CmdLine.main(new String[]{"decode64", encrypted}); + assertThat(outContent.toString(), is(password + "\n")); + } + + @Test + public void encode64urlTest() throws Exception { + CmdLine.main(new String[]{"encode64url", password}); + String decrypted = Symm.base64url.decode(outContent.toString()); + assertThat(decrypted, is(password)); + } + + @Test + public void decode64urlTest() throws Exception { + String encrypted = Symm.base64url.encode(password); + CmdLine.main(new String[]{"decode64url", encrypted}); + assertThat(outContent.toString(), is(password + "\n")); + } + + @Test + public void md5Test() throws Exception { + CmdLine.main(new String[]{"md5", quickBrownFoxPlain}); + assertThat(outContent.toString(), is(quickBrownFoxMD5 + "\n")); + } + + @Test + public void sha256Test() throws Exception { + CmdLine.main(new String[]{"sha256", quickBrownFoxPlain}); + assertThat(outContent.toString(), is(quickBrownFoxSHA256 + "\n")); + + outContent.reset(); + CmdLine.main(new String[]{"sha256", quickBrownFoxPlain, "10"}); + String hash1 = outContent.toString(); + + outContent.reset(); + CmdLine.main(new String[]{"sha256", quickBrownFoxPlain, "10"}); + String hash2 = outContent.toString(); + + outContent.reset(); + CmdLine.main(new String[]{"sha256", quickBrownFoxPlain, "11"}); + String hash3 = outContent.toString(); + + assertThat(hash1, is(hash2)); + assertThat(hash1, is(not(hash3))); + } + + @Test + public void keygenTest() throws Exception { + CmdLine.main(new String[]{"keygen"}); + assertThat(outContent.toString().length(), is(2074)); + + String filePath = "test/output_key"; + CmdLine.main(new String[]{"keygen", filePath}); + File keyfile = new File(filePath); + assertTrue(Files.isReadable(Paths.get(filePath))); + assertFalse(Files.isWritable(Paths.get(filePath))); + assertFalse(Files.isExecutable(Paths.get(filePath))); + keyfile.delete(); + } + + @Test + public void passgenTest() throws Exception { + CmdLine.main(new String[]{"passgen"}); + String output = outContent.toString().trim(); + assertThat(output.length(), is(24)); + assertTrue(containsAny(output, "+!@#$%^&*(){}[]?:;,.")); + assertTrue(containsAny(output, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")); + assertTrue(containsAny(output, "abcdefghijklmnopqrstuvwxyz")); + assertTrue(containsAny(output, "0123456789")); + + int length = 10; + outContent.reset(); + CmdLine.main(new String[]{"passgen", String.valueOf(length)}); + output = outContent.toString().trim(); + assertThat(output.length(), is(length)); + + length = 5; + outContent.reset(); + CmdLine.main(new String[]{"passgen", String.valueOf(length)}); + output = outContent.toString().trim(); + assertThat(output.length(), is(8)); + + // Check that the custom hasRepeats method works + assertTrue(hasRepeats("aa")); + assertTrue(hasRepeats("baa")); + assertTrue(hasRepeats("aab")); + assertTrue(hasRepeats("baab")); + assertFalse(hasRepeats("abc")); + assertFalse(hasRepeats("aba")); + + // Run this a bunch of times for coverage + for (int i = 0; i < 1000; i++) { + outContent.reset(); + CmdLine.main(new String[]{"passgen"}); + output = outContent.toString().trim(); + assertFalse(hasRepeats(output)); + } + } + + @Test + public void urlgenTest() throws Exception { + CmdLine.main(new String[]{"urlgen"}); + String output = outContent.toString().trim(); + assertThat(output.length(), is(24)); + + int length = 5; + outContent.reset(); + CmdLine.main(new String[]{"urlgen", String.valueOf(length)}); + output = outContent.toString().trim(); + assertThat(output.length(), is(5)); + } + + @Test + public void showHelpTest() { + String expected = + "Usage: java -jar <this jar> ...\n" + + " keygen [<keyfile>] (Generates Key on file, or Std Out)\n" + + " digest [<passwd>|-i|] <keyfile> (Encrypts Password with \"keyfile\"\n" + + " if passwd = -i, will read StdIin\n" + + " if passwd is blank, will ask securely)\n" + + " passgen <digits> (Generate Password of given size)\n" + + " urlgen <digits> (Generate URL field of given size)\n" + + " csptest (Tests for CSP compatibility)\n" + + " encode64 <your text> (Encodes to Base64)\n" + + " decode64 <base64 encoded text> (Decodes from Base64)\n" + + " encode64url <your text> (Encodes to Base64 URL charset)\n" + + " decode64url <base64url encoded text> (Decodes from Base64 URL charset)\n" + + " sha256 <text> <salts(s)> (Digest String into SHA256 Hash)\n" + + " md5 <text> (Digest String into MD5 Hash)\n"; + + CmdLine.main(new String[]{}); + + assertThat(outContent.toString(), is(expected)); + } + + private boolean containsAny(String str, String searchChars) { + for (char c : searchChars.toCharArray()) { + if (str.indexOf(c) >= 0) { + return true; + } + } + return false; + } + + private boolean hasRepeats(String str) { + int c = -1; + int last; + for (int i = 0; i < str.length(); i++) { + last = c; + c = str.charAt(i); + if (c == last) { + return true; + } + } + return false; + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_Hash.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_Hash.java new file mode 100644 index 00000000..f5c4d872 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_Hash.java @@ -0,0 +1,210 @@ +/******************************************************************************* + * ============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.cadi.test; + +import org.junit.Test; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.Hash; + +import static org.junit.Assert.*; + +import org.junit.BeforeClass; + +import static org.hamcrest.CoreMatchers.*; + +public class JU_Hash { + // Some common test vectors + private String quickBrownFoxVector = "The quick brown fox jumps over the lazy dog"; + private String quickBrownFoxMD5 = "0x9e107d9d372bb6826bd81d3542a419d6"; + private String quickBrownFoxSHA256 = "0xd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592"; + + private String emptyVector = ""; + private String emptyMD5 = "0xd41d8cd98f00b204e9800998ecf8427e"; + private String emptySHA256 = "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + + + private byte[] same1 = "this is a twin".getBytes(); + private byte[] same2 = "this is a twin".getBytes(); + private byte[] different1 = "guvf vf n gjva".getBytes(); + private byte[] different2 = "this is an only child".getBytes(); + + + private String uppersDec = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private String uppersHex1 = "0x4142434445464748494A4B4C4D4E4F505152535455565758595A"; + private String uppersHex2 = "0x4142434445464748494a4b4c4d4e4f505152535455565758595a"; + private String uppersHexNo0x1 = "4142434445464748494a4b4c4d4e4f505152535455565758595a"; + private String uppersHexNo0x2 = "4142434445464748494A4B4C4D4E4F505152535455565758595A"; + + private String lowersDec = "abcdefghijklmnopqrstuvwxyz"; + private String lowersHex = "0x6162636465666768696a6b6c6d6e6f707172737475767778797a"; + private String lowersHexNo0x1 = "6162636465666768696a6b6c6d6e6f707172737475767778797a"; + private String lowersHexNo0x2 = "6162636465666768696A6B6C6D6E6F707172737475767778797A"; + + private String numbersDec = "1234567890"; + private String numbersHex = "0x31323334353637383930"; + private String numbersHexNo0x = "31323334353637383930"; + + @SuppressWarnings("unused") + @BeforeClass + public static void getCoverage() { + // All of this class's methods are static, so we never need to instantiate an object. + // That said, we can't get 100% coverage unless we instantiate one + Hash hash = new Hash(); + } + + @Test + public void hashMD5Test() throws Exception { + byte[] output = Hash.hashMD5(quickBrownFoxVector.getBytes()); + assertEquals(quickBrownFoxMD5, new String(Hash.toHex(output))); + + output = Hash.hashMD5(emptyVector.getBytes()); + assertEquals(emptyMD5, new String(Hash.toHex(output))); + } + + @Test + public void hashMD5WithOffsetTest() throws Exception { + byte[] output = Hash.hashMD5(quickBrownFoxVector.getBytes(), 0, quickBrownFoxVector.length()); + assertEquals(quickBrownFoxMD5, new String(Hash.toHex(output))); + + output = Hash.hashMD5(emptyVector.getBytes(), 0, emptyVector.length()); + assertEquals(emptyMD5, new String(Hash.toHex(output))); + } + + @Test + public void hashMD5AsStringHexTest() throws Exception { + String output = Hash.hashMD5asStringHex(quickBrownFoxVector); + assertEquals(quickBrownFoxMD5, output); + + output = Hash.hashMD5asStringHex(emptyVector); + assertEquals(emptyMD5, output); + } + + @Test + public void hashSHA256Test() throws Exception { + byte[] output = Hash.hashSHA256(quickBrownFoxVector.getBytes()); + assertEquals(quickBrownFoxSHA256, new String(Hash.toHex(output))); + + output = Hash.hashSHA256(emptyVector.getBytes()); + assertEquals(emptySHA256, new String(Hash.toHex(output))); + } + + @Test + public void hashSHA256WithOffsetTest() throws Exception { + byte[] output = Hash.hashSHA256(quickBrownFoxVector.getBytes(), 0, quickBrownFoxVector.length()); + assertEquals(quickBrownFoxSHA256, new String(Hash.toHex(output))); + + output = Hash.hashSHA256(emptyVector.getBytes(), 0, emptyVector.length()); + assertEquals(emptySHA256, new String(Hash.toHex(output))); + } + + @Test + public void hashSHA256AsStringHexTest() throws Exception { + String output = Hash.hashSHA256asStringHex(quickBrownFoxVector); + assertEquals(quickBrownFoxSHA256, output); + + output = Hash.hashSHA256asStringHex(emptyVector); + assertEquals(emptySHA256, output); + } + + @Test + public void hashSaltySHA256AsStringHexTest() throws Exception { + String input = "password"; + String hash1 = Hash.hashSHA256asStringHex(input, 10); + String hash2 = Hash.hashSHA256asStringHex(input, 10); + String hash3 = Hash.hashSHA256asStringHex(input, 11); + + assertEquals(hash1, hash2); + assertThat(hash1, not(equalTo(hash3))); + } + + @Test + public void isEqualTest() throws Exception { + assertTrue(Hash.isEqual(same1, same2)); + assertFalse(Hash.isEqual(same1, different1)); + assertFalse(Hash.isEqual(same1, different2)); + } + + @Test + public void compareToTest() throws Exception { + assertEquals(0, Hash.compareTo(same1, same2)); + // different1 is rot13(same1), so the difference should be 13 + assertEquals(13, Hash.compareTo(same1, different1)); + assertEquals(-78, Hash.compareTo(same1, different2)); + } + + @Test + public void toHexNo0xTest() throws Exception { + assertEquals(uppersHexNo0x1, Hash.toHexNo0x(uppersDec.getBytes())); + assertEquals(lowersHexNo0x1, Hash.toHexNo0x(lowersDec.getBytes())); + assertEquals(numbersHexNo0x, Hash.toHexNo0x(numbersDec.getBytes())); + } + + @Test + public void toHexTest() throws Exception { + assertEquals(uppersHex2, Hash.toHex(uppersDec.getBytes())); + assertEquals(lowersHex, Hash.toHex(lowersDec.getBytes())); + assertEquals(numbersHex, Hash.toHex(numbersDec.getBytes())); + } + + @Test + public void toHexWithOffset() throws Exception { + assertEquals(uppersHex2, Hash.toHex(uppersDec.getBytes(), 0, uppersDec.length())); + assertEquals(lowersHex, Hash.toHex(lowersDec.getBytes(), 0, lowersDec.length())); + assertEquals(numbersHex, Hash.toHex(numbersDec.getBytes(), 0, numbersDec.length())); + } + + @Test + public void fromHexTest() throws Exception { + assertEquals(uppersDec, new String(Hash.fromHex(uppersHex1))); + assertEquals(lowersDec, new String(Hash.fromHex(lowersHex))); + assertEquals(numbersDec, new String(Hash.fromHex(numbersHex))); + + try { + // This string doesn't begin with "0x" + Hash.fromHex("0X65"); + fail("Should have thrown CadiException"); + } catch (CadiException e) { + assertEquals("HexString must start with \"0x\"", e.getMessage()); + } + + try { + // This string has invalid hex characters + Hash.fromHex("0xQ"); + fail("Should have thrown CadiException"); + } catch (CadiException e) { + // 81 is dec(Q) + assertEquals("Invalid char '81' in HexString", e.getMessage()); + } + } + + @Test + public void fromHexNo0xTest() throws Exception { + assertEquals(uppersDec, new String(Hash.fromHexNo0x(uppersHexNo0x1))); + assertEquals(lowersDec, new String(Hash.fromHexNo0x(lowersHexNo0x1))); + assertEquals(uppersDec, new String(Hash.fromHexNo0x(uppersHexNo0x2))); + assertEquals(lowersDec, new String(Hash.fromHexNo0x(lowersHexNo0x2))); + assertEquals(numbersDec, new String(Hash.fromHexNo0x(numbersHexNo0x))); + byte[] output = Hash.fromHexNo0x("ABC"); + assertEquals(new String(new byte[] {(byte)0x0A, (byte)0xB0}), new String(output)); + assertNull(Hash.fromHexNo0x("~~")); + } +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_LocatorException.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_LocatorException.java new file mode 100644 index 00000000..96cf8e51 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_LocatorException.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * ============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.cadi.test; + +import static org.junit.Assert.*; + +import org.junit.Test; +import org.onap.aaf.cadi.LocatorException; + +import static org.hamcrest.CoreMatchers.is; + +public class JU_LocatorException { + @Test + public void stringTest() { + LocatorException exception = new LocatorException("New Exception"); + assertNotNull(exception); + assertThat(exception.getMessage(), is("New Exception")); + } + + @Test + public void throwableTest() { + LocatorException exception = new LocatorException(new Throwable("New Exception")); + assertNotNull(exception); + assertThat(exception.getMessage(), is("java.lang.Throwable: New Exception")); + } + + @Test + public void stringThrowableTest() { + LocatorException exception = new LocatorException("New Exception",new Throwable("New Exception")); + assertNotNull(exception); + assertThat(exception.getMessage(), is("New Exception")); + } + + @Test + public void characterSequenceTest() { + CharSequence testCS = new String("New Exception"); + LocatorException exception = new LocatorException(testCS); + assertNotNull(exception); + assertThat(exception.getMessage(), is("New Exception")); + } +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_PropAccess.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_PropAccess.java new file mode 100644 index 00000000..d74648a4 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_PropAccess.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * ============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.cadi.test; + +import static org.junit.Assert.*; +import org.junit.Test; +import org.onap.aaf.cadi.PropAccess; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.PropAccess.LogIt; + +import static org.mockito.Mockito.*; +import static org.hamcrest.CoreMatchers.*; + +import java.lang.reflect.Field; + +import java.io.ByteArrayInputStream; +import java.io.PrintStream; +import java.util.Properties; + +@SuppressWarnings("unused") +public class JU_PropAccess { + // Note: We can't actually get coverage of the protected constructor - + // that will be done later, when testing the child class "ServletContextAccess" + + + @Test + public void ConstructorTest() throws Exception { + PropAccess prop = new PropAccess(); + assertThat(prop.getProperties(), is(not(nullValue()))); + } + + @Test + public void noPrintStreamConstructionTest() throws Exception { + // Test for coverage + PropAccess prop = new PropAccess((PrintStream)null, new String[]{"Invalid argument"}); + } + + @Test + public void noLogItConstructionTest() throws Exception { + // Test for coverage + PropAccess prop = new PropAccess((LogIt)null, new String[]{"Invalid argument"}); + } + + @Test + public void propertiesConstructionTest() throws Exception { + // Coverage tests + PropAccess prop = new PropAccess(System.getProperties()); + prop = new PropAccess((PrintStream)null, System.getProperties()); + } + + @Test + public void stringConstructionTest() throws Exception { + Properties testSystemProps = new Properties(System.getProperties()); + testSystemProps.setProperty("cadi_name", "user"); + System.setProperties(testSystemProps); + PropAccess prop = new PropAccess("cadi_keyfile=test/keyfile", "cadi_loglevel=DEBUG", "cadi_prop_files=test/cadi.properties:not_a_file"); + } + + @Test + public void loadTest() throws Exception { + // Coverage tests + Properties props = mock(Properties.class); + when(props.getProperty("cadi_prop_files")).thenReturn("test/cadi.properties").thenReturn(null); + PropAccess pa = new PropAccess(); + Field props_field = PropAccess.class.getDeclaredField("props"); + props_field.setAccessible(true); + props_field.set(pa, props); + ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); + pa.load(bais); + } + + @Test + public void specialConversionsTest() throws Exception { + // Coverage tests + Properties testSystemProps = new Properties(System.getProperties()); + testSystemProps.setProperty("java.specification.version", "1.7"); + System.setProperties(testSystemProps); + PropAccess pa = new PropAccess("AFT_LATITUDE=1", "AFT_LONGITUDE=1", "cadi_protocols=TLSv1.2"); + } + + @Test + public void logTest() throws Exception { + // Coverage tests + PropAccess pa = new PropAccess(); + + pa.log(Level.DEBUG); + pa.printf(Level.DEBUG, "not a real format string"); + + pa.setLogLevel(Level.DEBUG); + pa.log(Level.DEBUG); + pa.log(Level.DEBUG, 1, " ", null, ""); + pa.log(Level.DEBUG, "This is a string", "This is another"); + pa.set(new LogIt() { + @Override public void push(Level level, Object ... elements) {} + }); + try { + pa.log(new Exception("This exception was thrown intentionally, please ignore it")); + } catch(Exception e) { + fail("Should have thrown an exception"); + } + } + + @Test + public void classLoaderTest() { + PropAccess pa = new PropAccess(); + assertThat(pa.classLoader(), instanceOf(ClassLoader.class)); + } + + @Test + public void encryptionTest() throws Exception { + PropAccess pa = new PropAccess(); + String plainText = "This is a secret message"; + String secret_message = pa.encrypt(plainText); + String modified = secret_message.substring(4); + // Plenty of assertions to hit all branches + assertThat(pa.decrypt(secret_message, false), is(plainText)); + assertThat(pa.decrypt(null, false), is(nullValue())); + assertThat(pa.decrypt(modified, true), is(plainText)); + assertThat(pa.decrypt(modified, false), is(modified)); + } + + @Test + public void setPropertyTest() { + PropAccess pa = new PropAccess(); + pa.setProperty("test", null); + String prop = "New Property"; + String val ="And it's faithful value"; + pa.setProperty(prop, val); + + assertThat(pa.getProperty(prop), is(val)); + } +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_ServletContextAccess.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_ServletContextAccess.java new file mode 100644 index 00000000..8531e1d1 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_ServletContextAccess.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * ============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.cadi.test; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; +import org.onap.aaf.cadi.PropAccess; +import org.onap.aaf.cadi.ServletContextAccess; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.PropAccess.LogIt; + +import static org.mockito.Mockito.*; +import static org.hamcrest.CoreMatchers.*; + +import java.lang.reflect.Field; + +import java.io.ByteArrayInputStream; +import java.io.PrintStream; +import java.util.Enumeration; +import java.util.Properties; +import java.util.StringTokenizer; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; + +@SuppressWarnings("unused") +public class JU_ServletContextAccess { + + private FilterConfig filter_mock; + Enumeration<String> enumeration; + + private class CustomEnumeration implements Enumeration<String> { + private int idx = 0; + private final String[] elements = {"This", "is", "a", "test"}; + @Override + public String nextElement() { + return idx >= elements.length ? null : elements[idx++]; + } + @Override + public boolean hasMoreElements() { + return idx < elements.length; + } + } + + @Before + public void setup() { + enumeration = new CustomEnumeration(); + filter_mock = mock(FilterConfig.class); + when(filter_mock.getInitParameterNames()).thenReturn(enumeration); + } + + @Test + public void ConstructorTest() throws Exception { + ServletContextAccess sca = new ServletContextAccess(filter_mock); + } + + @Test + public void logTest() throws Exception { + ServletContext sc_mock = mock(ServletContext.class); + when(filter_mock.getServletContext()).thenReturn(sc_mock); + ServletContextAccess sca = new ServletContextAccess(filter_mock); + + sca.log(Level.DEBUG); + + sca.setLogLevel(Level.DEBUG); + sca.log(Level.DEBUG); + + try { + sca.log(new Exception("This exception was thrown intentionally, please ignore it")); + } catch(Exception e) { + fail("Should have thrown an exception"); + } + } + + @Test + public void contextTest() { + ServletContext sc_mock = mock(ServletContext.class); + when(filter_mock.getServletContext()).thenReturn(sc_mock); + ServletContextAccess sca = new ServletContextAccess(filter_mock); + assertThat(sca.context(), instanceOf(ServletContext.class)); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_Symm.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_Symm.java new file mode 100644 index 00000000..34f528f4 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_Symm.java @@ -0,0 +1,203 @@ +/******************************************************************************* + * ============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.cadi.test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.Arrays; + +import java.lang.reflect.*; +import org.junit.Before; +import org.junit.Test; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.PropAccess; +import org.onap.aaf.cadi.Symm; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +public class JU_Symm { + private Symm defaultSymm; + + @Before + public void setup() throws Exception { + defaultSymm = new Symm( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray() + ,76, "Use default!" ,true); + } + + @Test + public void constructorTest() throws Exception { + Symm myCustomSymm = new Symm( + "ACEGIKMOQSUWYacegikmoqsuwy02468+/".toCharArray(), 76, "Default", true); + Field convert_field = Symm.class.getDeclaredField("convert"); + convert_field.setAccessible(true); + + Class<?> Unordered_class = Class.forName("org.onap.aaf.cadi.Symm$Unordered"); + assertThat(convert_field.get(myCustomSymm), instanceOf(Unordered_class)); + } + + @SuppressWarnings("unused") + @Test + public void copyTest() throws Exception { + Symm copy = Symm.base64.copy(76); + } + + @SuppressWarnings("deprecation") + @Test + public void deprecatedTest() { + assertEquals(Symm.base64(), Symm.base64); + assertEquals(Symm.base64noSplit(), Symm.base64noSplit); + assertEquals(Symm.base64url(), Symm.base64url); + assertEquals(Symm.baseCrypt(), Symm.encrypt); + } + + @Test + public void encodeDecodeStringTest() throws Exception { + String orig = "hello"; + String b64encrypted = Symm.base64.encode(orig); + assertEquals(Symm.base64.decode(b64encrypted), orig); + + String defaultEnrypted = defaultSymm.encode(orig); + assertEquals(defaultSymm.decode(defaultEnrypted), orig); + } + + @Test + public void encodeDecodeByteArrayTest() throws Exception { + String orig = "hello"; + byte[] b64encrypted = Symm.base64.encode(orig.getBytes()); + assertEquals(new String(Symm.base64.decode(b64encrypted)), orig); + + byte[] empty = null; + assertTrue(Arrays.equals(Symm.base64.encode(empty), new byte[0])); + } + + @Test + public void encodeDecodeStringToStreamTest() throws Exception { + String orig = "I'm a password, really"; + String b64encrypted; + String output; + + ByteArrayOutputStream baosEncrypt = new ByteArrayOutputStream(); + Symm.base64.encode(orig, baosEncrypt); + b64encrypted = new String(baosEncrypt.toByteArray()); + + ByteArrayOutputStream baosDecrypt = new ByteArrayOutputStream(); + Symm.base64.decode(b64encrypted, baosDecrypt); + output = new String(baosDecrypt.toByteArray()); + + assertEquals(orig, output); + } + + @Test + public void encryptDecryptStreamWithPrefixTest() throws Exception { + String orig = "I'm a password, really"; + byte[] b64encrypted; + String output; + + byte[] prefix = "enc:".getBytes(); + + ByteArrayInputStream baisEncrypt = new ByteArrayInputStream(orig.getBytes()); + ByteArrayOutputStream baosEncrypt = new ByteArrayOutputStream(); + Symm.base64.encode(baisEncrypt, baosEncrypt, prefix); + + b64encrypted = baosEncrypt.toByteArray(); + + ByteArrayInputStream baisDecrypt = new ByteArrayInputStream(b64encrypted); + ByteArrayOutputStream baosDecrypt = new ByteArrayOutputStream(); + Symm.base64.decode(baisDecrypt, baosDecrypt, prefix.length); + + output = new String(baosDecrypt.toByteArray()); + assertEquals(orig, output); + } + + @Test + public void randomGenTest() { + // Ian - There really isn't a great way to test for randomness... + String prev = null; + for (int i = 0; i < 10; i++) { + String current = Symm.randomGen(100); + if (current.equals(prev)) { + fail("I don't know how, but you generated the exact same random string twice in a row"); + } + prev = current; + } + assertTrue(true); + } + + @Test + public void obtainTest() throws Exception { + Symm symm = Symm.base64.obtain(); + + String orig ="Another Password, please"; + String encrypted = symm.enpass(orig); + String decrypted = symm.depass(encrypted); + assertEquals(orig, decrypted); + } + + @Test + public void InputStreamObtainTest() throws Exception { + byte[] keygen = Symm.keygen(); + + Symm symm = Symm.obtain(new ByteArrayInputStream(keygen)); + + String orig ="Another Password, please"; + String encrypted = symm.enpass(orig); + String decrypted = symm.depass(encrypted); + assertEquals(orig, decrypted); + } + + @Test + public void StringObtainTest() throws Exception { + byte[] keygen = Symm.keygen(); + + Symm symm = Symm.obtain(new String(keygen)); + + String orig ="Another Password, please"; + String encrypted = symm.enpass(orig); + String decrypted = symm.depass(encrypted); + assertEquals(orig, decrypted); + } + + @Test + public void AccessObtainTest() throws Exception { + PropAccess pa = new PropAccess("cadi_keyfile=test/keyfile"); + Symm symm = Symm.obtain(pa); + String orig ="Another Password, please"; + String encrypted = symm.enpass(orig); + String decrypted = symm.depass(encrypted); + assertEquals(orig, decrypted); + + try { + PropAccess badPa = mock(PropAccess.class); + when(badPa.getProperty("cadi_keyfile", null)).thenReturn("not_a_real_file.txt"); + symm = Symm.obtain(badPa); + fail("Should have thrown an exception"); + } catch (CadiException e) { + assertTrue(e.getMessage().contains("ERROR: ")); + assertTrue(e.getMessage().contains("not_a_real_file.txt")); + assertTrue(e.getMessage().contains(" does not exist!")); + } + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_TrustChecker.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_TrustChecker.java new file mode 100644 index 00000000..511c6ee3 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_TrustChecker.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * ============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.cadi.test; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import org.junit.*; + +import org.onap.aaf.cadi.TrustChecker; + +public class JU_TrustChecker { + + @Test + public void noTrustTests() { + assertThat(TrustChecker.NOTRUST.mayTrust(null, null), is(nullValue())); + TrustChecker.NOTRUST.setLur(null); + } +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_User.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_User.java new file mode 100644 index 00000000..25683249 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/JU_User.java @@ -0,0 +1,185 @@ +/******************************************************************************* + * ============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.cadi.test; + +import static org.junit.Assert.*; + +import org.junit.Test; + + +import static org.hamcrest.CoreMatchers.*; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Field; +import java.security.Principal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Before; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.aaf.cadi.Permission; +import org.onap.aaf.cadi.User; +import org.onap.aaf.cadi.lur.LocalPermission; + +public class JU_User { + + private final Long SECOND = 1000L; + private final String name = "Fakey McFake"; + private final String cred = "Fake credentials"; + + private Field perms_field; + private Field count_field; + + @Mock + private Principal principal; + + @Mock + private LocalPermission permission; + @Mock + private LocalPermission permission2; + + @Before + public void setup() throws NoSuchFieldException, SecurityException { + MockitoAnnotations.initMocks(this); + + when(principal.getName()).thenReturn("Principal"); + + when(permission.getKey()).thenReturn("NewKey"); + when(permission.match(permission)).thenReturn(true); + + when(permission2.getKey()).thenReturn("NewKey2"); + when(permission2.match(permission)).thenReturn(false); + + perms_field = User.class.getDeclaredField("perms"); + perms_field.setAccessible(true); + + count_field = User.class.getDeclaredField("count"); + count_field.setAccessible(true); + } + + @Test + public void constructorPrincipalTest() throws IllegalArgumentException, IllegalAccessException { + User<Permission> user = new User<Permission>(principal); + assertThat(user.name, is(principal.getName())); + assertThat(user.principal, is(principal)); + assertThat(user.permExpires(), is(Long.MAX_VALUE)); + assertThat((int)count_field.get(user), is(0)); + } + + @Test + public void constructorNameCredTest() throws IllegalArgumentException, IllegalAccessException { + User<Permission> user = new User<Permission>(name, cred.getBytes()); + assertThat(user.name, is(name)); + assertThat(user.principal, is(nullValue())); + assertThat(user.permExpires(), is(Long.MAX_VALUE)); + assertThat((int)count_field.get(user), is(0)); + assertThat(user.getCred(), is(cred.getBytes())); + } + + @Test + public void constructorPrincipalIntervalTest() throws IllegalArgumentException, IllegalAccessException { + User<Permission> user = new User<Permission>(principal, 61 * SECOND); + Long approxExpiration = System.currentTimeMillis() + 61 * SECOND; + assertThat(user.name, is(principal.getName())); + assertThat(user.principal, is(principal)); + assertTrue(Math.abs(user.permExpires() - approxExpiration) < 10L); + assertThat((int)count_field.get(user), is(0)); + } + + @Test + public void constructorNameCredIntervalTest() throws IllegalArgumentException, IllegalAccessException { + String name = "Fakey McFake"; + User<Permission> user = new User<Permission>(name, cred.getBytes(), 61 * SECOND); + Long approxExpiration = System.currentTimeMillis() + 61 * SECOND; + assertThat(user.name, is(name)); + assertThat(user.principal, is(nullValue())); + assertTrue(Math.abs(user.permExpires() - approxExpiration) < 10L); + assertThat((int)count_field.get(user), is(0)); + assertThat(user.getCred(), is(cred.getBytes())); + } + + @Test + public void countCheckTest() throws IllegalArgumentException, IllegalAccessException { + User<Permission> user = new User<Permission>(principal); + user.resetCount(); + assertThat((int)count_field.get(user), is(0)); + user.incCount(); + assertThat((int)count_field.get(user), is(1)); + user.incCount(); + assertThat((int)count_field.get(user), is(2)); + user.resetCount(); + assertThat((int)count_field.get(user), is(0)); + } + + @Test + public void permTest() throws InterruptedException, IllegalArgumentException, IllegalAccessException { + User<Permission> user = new User<Permission>(principal); + assertThat(user.permExpires(), is(Long.MAX_VALUE)); + user.renewPerm(); + Thread.sleep(1); // Let it expire + assertThat(user.permExpired(), is(true)); + + user = new User<Permission>(principal,100); + assertTrue(user.noPerms()); + user.add(permission); + assertFalse(user.permsUnloaded()); + assertFalse(user.noPerms()); + user.setNoPerms(); + assertThat(user.permExpired(), is(false)); + assertTrue(user.permsUnloaded()); + assertTrue(user.noPerms()); + perms_field.set(user, null); + assertTrue(user.permsUnloaded()); + assertTrue(user.noPerms()); + } + + @Test + public void addValuesToNewMapTest() { + User<Permission> user = new User<Permission>(principal); + Map<String, Permission> newMap = new HashMap<String,Permission>(); + + assertFalse(user.contains(permission)); + + user.add(newMap, permission); + user.setMap(newMap); + + assertTrue(user.contains(permission)); + + List<Permission> sink = new ArrayList<Permission>(); + user.copyPermsTo(sink); + + assertThat(sink.size(), is(1)); + assertTrue(sink.contains(permission)); + + assertThat(user.toString(), is("Principal|:NewKey")); + + user.add(newMap, permission2); + user.setMap(newMap); + assertFalse(user.contains(permission2)); + + assertThat(user.toString(), is("Principal|:NewKey2,NewKey")); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/filter/JU_FCGetTest.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/filter/JU_FCGetTest.java new file mode 100644 index 00000000..5aa9d79c --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/filter/JU_FCGetTest.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * ============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.cadi.test.filter; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.when; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.aaf.cadi.PropAccess; + +public class JU_FCGetTest { + + @Test + public void netYetTested() { + fail("Tests not yet implemented"); + } + +// @Mock +// private ServletContext context; + +// @Mock +// private FilterConfig config; + +// @Mock +// private PropAccess access = new PropAccess(); + +// @Before +// public void setUp() { +// MockitoAnnotations.initMocks(this); +// } + +// @Test +// public void testGetStringFromDef() { +// PropAccess access = new PropAccess(); + +// FCGet fcGet = new FCGet(access, context, config); + +// String user = fcGet.get("user", "DefaultUser", true); + +// assertEquals(user, "DefaultUser"); +// } + +// @Test +// public void testGetStringFromContext() { +// PropAccess access = new PropAccess(); +// when(context.getInitParameter("user")).thenReturn("ContextUser"); + +// FCGet fcGet = new FCGet(access, context, null); + +// String user = fcGet.get("user", "DefaultUser", true); + +// assertEquals(user,"ContextUser"); +// } + +// @Test +// public void testGetStringFromFilter() { +// PropAccess access = new PropAccess(); +// when(config.getInitParameter("user")).thenReturn("FilterUser"); + +// FCGet fcGet = new FCGet(access, null, config); + +// String user = fcGet.get("user", "DefaultUser", true); + +// assertEquals(user,"FilterUser"); +// } + +// @Test +// public void testGetStringWithNullContextFilter() { + +// when(access.getProperty("user", "DefaultUser")).thenReturn(null); + +// FCGet fcGet = new FCGet(access, null, null); + +// String user = fcGet.get("user", "DefaultUser", true); + +// assertEquals(user,"DefaultUser"); +// } +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/lur/JU_ConfigPrincipal.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/lur/JU_ConfigPrincipal.java new file mode 100644 index 00000000..897744ec --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/lur/JU_ConfigPrincipal.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * ============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.cadi.test.lur; + +import org.junit.*; +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import java.lang.reflect.Field; + +import java.io.IOException; + +import org.onap.aaf.cadi.lur.ConfigPrincipal; + +public class JU_ConfigPrincipal { + + private final String name = "User"; + private final String pass = "pass"; + + // Expected output of base64("User:pass") + private final String b64encoded = "VXNlcjpwYXNz"; + + private Field content_field; + + @Before + public void setup() throws NoSuchFieldException { + content_field = ConfigPrincipal.class.getDeclaredField("content"); + content_field.setAccessible(true); + } + + @Test + public void testConfigPrincipalStringString() throws IOException, IllegalArgumentException, IllegalAccessException { + ConfigPrincipal p = new ConfigPrincipal(name, pass); + + assertThat(p.getName(), is(name)); + assertThat(p.toString(), is(name)); + assertThat(p.getCred(), is(pass.getBytes())); + assertThat(p.getAsBasicAuthHeader(), is("Basic " + b64encoded)); + content_field.set(p, "pass"); + assertThat(p.getAsBasicAuthHeader(), is("Basic " + b64encoded)); + + // One more time for coverage purposes + assertThat(p.getAsBasicAuthHeader(), is("Basic " + b64encoded)); + } + + @Test + public void testConfigPrincipalStringByteArray() throws IOException, IllegalArgumentException, IllegalAccessException { + ConfigPrincipal p = new ConfigPrincipal(name, pass.getBytes()); + + assertThat(p.getName(), is(name)); + assertThat(p.toString(), is(name)); + assertThat(p.getCred(), is(pass.getBytes())); + assertThat(p.getAsBasicAuthHeader(), is("Basic " + b64encoded)); + content_field.set(p, "pass"); + assertThat(p.getAsBasicAuthHeader(), is("Basic " + b64encoded)); + + // One more time for coverage purposes + assertThat(p.getAsBasicAuthHeader(), is("Basic " + b64encoded)); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/lur/JU_LocalLur.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/lur/JU_LocalLur.java new file mode 100644 index 00000000..8388fa5f --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/lur/JU_LocalLur.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * ============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.cadi.test.lur; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.junit.Test; +import org.onap.aaf.cadi.Lur; +import org.onap.aaf.cadi.Permission; +import org.onap.aaf.cadi.PropAccess; +import org.onap.aaf.cadi.Symm; +import org.onap.aaf.cadi.CredVal.Type; +import org.onap.aaf.cadi.config.UsersDump; +import org.onap.aaf.cadi.lur.LocalLur; +import org.onap.aaf.cadi.lur.LocalPermission; + +public class JU_LocalLur { + + @Test + public void test() throws IOException { + Symm symmetric = Symm.baseCrypt().obtain(); + LocalLur up; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(Symm.ENC.getBytes()); + symmetric.enpass("<pass>", baos); + PropAccess ta = new PropAccess(); + Lur ml = up = new LocalLur(ta,"myname:groupA,groupB","admin:myname,yourname;suser:hisname,hername,m1234%"+baos.toString()); + + +// Permission admin = new LocalPermission("admin"); +// Permission suser = new LocalPermission("suser"); +// +// // Check User fish +// assertTrue(ml.fish(new JUPrincipal("myname"),admin)); +// assertTrue(ml.fish(new JUPrincipal("hisname"),admin)); +// assertFalse(ml.fish(new JUPrincipal("noname"),admin)); +// assertTrue(ml.fish(new JUPrincipal("itsname"),suser)); +// assertTrue(ml.fish(new JUPrincipal("hername"),suser)); +// assertFalse(ml.fish(new JUPrincipal("myname"),suser)); +// +// // Check validate password +// assertTrue(up.validate("m1234",Type.PASSWORD, "<pass>".getBytes())); +// assertFalse(up.validate("m1234",Type.PASSWORD, "badPass".getBytes())); +// + // Check fishAll + Set<String> set = new TreeSet<String>(); + List<Permission> perms = new ArrayList<Permission>(); + ml.fishAll(new JUPrincipal("myname"), perms); + for(Permission p : perms) { + set.add(p.getKey()); + } +// assertEquals("[admin, groupA, groupB]",set.toString()); + UsersDump.write(System.out, up); + System.out.flush(); + + } + + // Simplistic Principal for testing purposes + private static class JUPrincipal implements Principal { + private String name; + public JUPrincipal(String name) { + this.name = name; + } +// @Override + public String getName() { + return name; + } + } + + + + + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/lur/JU_LocalPermission.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/lur/JU_LocalPermission.java new file mode 100644 index 00000000..abc178a8 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/lur/JU_LocalPermission.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * ============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,Z + * * 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.cadi.test.lur; + +import static org.junit.Assert.*; + +import static org.hamcrest.CoreMatchers.*; +import org.junit.*; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import static org.mockito.Mockito.*; + +import org.onap.aaf.cadi.lur.LocalPermission; +import org.onap.aaf.cadi.Permission; + +public class JU_LocalPermission { + + @Mock + Permission perm; + + private LocalPermission localPerm; + private String role = "Fake Role"; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(perm.getKey()).thenReturn(role); + + localPerm = new LocalPermission(role); + } + + @Test + public void getKeyTest() { + assertThat(localPerm.getKey(), is(role)); + } + + @Test + public void toStringTest() { + assertThat(localPerm.toString(), is(role)); + } + + @Test + public void matchTest() { + assertTrue(localPerm.match(perm)); + } + + @Test + public void permTypeTest() { + assertThat(localPerm.permType(), is("LOCAL")); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/lur/JU_NullLur.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/lur/JU_NullLur.java new file mode 100644 index 00000000..d5740b3a --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/lur/JU_NullLur.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * ============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,Z + * * 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.cadi.test.lur; + +import java.security.Principal; +import java.util.List; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import org.junit.*; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import java.lang.reflect.*; + +import org.onap.aaf.cadi.Permission; +import org.onap.aaf.cadi.lur.NullLur; + +public class JU_NullLur { + + @Mock + Principal p; + + @Mock + Permission perm; + + @Mock + List<Permission> perms; + + private NullLur nullLur; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + nullLur = new NullLur(); + } + + @Test + public void coverageTests() throws Exception { + + Field nullClass = NullLur.class.getDeclaredField("NULL"); + nullClass.setAccessible(true); + assertThat(((Permission) nullClass.get(NullLur.class)).permType(), is("")); + assertThat(((Permission) nullClass.get(NullLur.class)).getKey(), is("")); + assertFalse(((Permission) nullClass.get(NullLur.class)).match(perm)); + + nullLur.fishAll(p, perms); + nullLur.destroy(); + + assertFalse(nullLur.fish(p, perm)); + assertFalse(nullLur.handlesExclusively(perm)); + assertFalse(nullLur.handles(p)); + assertThat(nullLur.createPerm(""), is(nullClass.get(NullLur.class))); + + StringBuilder sb = new StringBuilder(); + nullLur.clear(p, sb); + assertThat(sb.toString(), is("NullLur\n")); + assertThat(nullLur.toString(), is("NullLur\n")); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_BasicPrincipal.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_BasicPrincipal.java new file mode 100644 index 00000000..0faaca1a --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_BasicPrincipal.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * ============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.cadi.test.principal; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import static org.mockito.Mockito.*; +import org.junit.*; + +import java.io.IOException; +import java.util.Date; + +import org.onap.aaf.cadi.BasicCred; +import org.onap.aaf.cadi.Symm; +import org.onap.aaf.cadi.principal.BasicPrincipal; +import org.onap.aaf.cadi.principal.StringTagLookup; +import org.onap.aaf.cadi.principal.TaggedPrincipal; +import org.onap.aaf.cadi.principal.TaggedPrincipal.TagLookup; + +public class JU_BasicPrincipal { + + @Test + public void Constructor1Test() throws Exception { + // Test that everything works when the content doesn't contain "Basic" + BasicPrincipal bp = new BasicPrincipal("content", "domain"); + assertThat(bp.getName(), is("content")); + assertThat(bp.getCred(), is(nullValue())); + + // Test sending a user without an implicit domain + String name = "User"; + String password = "password"; + String content = name + ":" + password; + String domain = "exampledomain.com"; + String encrypted = new String(Symm.base64.encode(content.getBytes())); + bp = new BasicPrincipal("Basic " + encrypted, domain); + assertThat(bp.getShortName(), is(name)); + assertThat(bp.getName(), is(name + "@" + domain)); + assertThat(bp.getCred(), is(password.getBytes())); + + // Test sending a user with an implicit domain + String longName = name + "@" + domain + ":" + password; + encrypted = new String(Symm.base64.encode(longName.getBytes())); + bp = new BasicPrincipal("Basic " + encrypted, domain); + assertThat(bp.getShortName(), is(name)); + assertThat(bp.getName(), is(name + "@" + domain)); + assertThat(bp.getCred(), is(password.getBytes())); + + // Check that an exception is throw if no name is given in the content + try { + bp = new BasicPrincipal("Basic " + new String(Symm.base64.encode("no name".getBytes())), ""); + fail("Should have thrown an exception"); + } catch (IOException e) { + assertThat(e.getMessage(), is("Invalid Coding")); + } + } + + @Test + public void Constructor2Test() { + String name = "User"; + String password = "password"; + BasicCred bc = mock(BasicCred.class); + when(bc.getUser()).thenReturn(name); + when(bc.getCred()).thenReturn(password.getBytes()); + + BasicPrincipal bp = new BasicPrincipal(bc, "domain"); + assertThat(bp.getName(), is(name)); + assertThat(bp.getCred(), is(password.getBytes())); + } + + @Test + public void accessorsTest() throws IOException { + String name = "User"; + String password = "password"; + String content = name + ":" + password; + String domain = "exampledomain.com"; + String encrypted = new String(Symm.base64.encode(content.getBytes())); + String bearer = "bearer"; + long created = System.currentTimeMillis(); + BasicPrincipal bp = new BasicPrincipal("Basic " + encrypted, domain); + bp.setBearer(bearer); + + String expected = "Basic Authorization for " + name + "@" + domain + " evaluated on " + new Date(bp.created()).toString(); + assertTrue(Math.abs(bp.created() - created) < 10); + assertThat(bp.toString(), is(expected)); + assertThat(bp.tag(), is("BAth")); + assertThat(bp.personalName(), is(nullValue())); + + // This test hits the abstract class BearerPrincipal + assertThat(bp.getBearer(), is(bearer)); + } + + + @Test + public void coverageTest() throws IOException { + String name = "User"; + String password = "password:with:colons"; + String content = name + ":" + password; + String encrypted = new String(Symm.base64.encode(content.getBytes())); + @SuppressWarnings("unused") + BasicPrincipal bp = new BasicPrincipal("Basic " + encrypted, "domain"); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_CachedBasicPrincipal.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_CachedBasicPrincipal.java new file mode 100644 index 00000000..39da6c09 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_CachedBasicPrincipal.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * ============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.cadi.test.principal; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import static org.mockito.Mockito.*; +import org.junit.*; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Date; + +import org.onap.aaf.cadi.BasicCred; +import org.onap.aaf.cadi.CachedPrincipal; +import org.onap.aaf.cadi.CachedPrincipal.Resp; +import org.onap.aaf.cadi.Symm; +import org.onap.aaf.cadi.principal.CachedBasicPrincipal; +import org.onap.aaf.cadi.principal.StringTagLookup; +import org.onap.aaf.cadi.principal.TaggedPrincipal; +import org.onap.aaf.cadi.principal.TaggedPrincipal.TagLookup; +import org.onap.aaf.cadi.taf.HttpTaf; + +public class JU_CachedBasicPrincipal { + private Field creatorField; + private Field timeToLiveField; + + @Mock + private HttpTaf creator; + + private CachedPrincipal.Resp resp; + + @Before + public void setup() throws NoSuchFieldException, SecurityException { + MockitoAnnotations.initMocks(this); + + creatorField = CachedBasicPrincipal.class.getDeclaredField("creator"); + timeToLiveField = CachedBasicPrincipal.class.getDeclaredField("timeToLive"); + + creatorField.setAccessible(true); + timeToLiveField.setAccessible(true); + } + + @Test + public void Constructor1Test() throws IllegalArgumentException, IllegalAccessException { + String name = "User"; + String password = "password"; + BasicCred bc = mock(BasicCred.class); + when(bc.getUser()).thenReturn(name); + when(bc.getCred()).thenReturn(password.getBytes()); + + long timeToLive = 10000L; + long expires = System.currentTimeMillis() + timeToLive; + CachedBasicPrincipal cbp = new CachedBasicPrincipal(creator, bc, "domain", timeToLive); + + assertThat((HttpTaf)creatorField.get(cbp), is(creator)); + assertThat((Long)timeToLiveField.get(cbp), is(timeToLive)); + assertTrue(Math.abs(cbp.expires() - expires) < 10); + } + + @Test + public void Constructor2Test() throws Exception { + String name = "User"; + String password = "password"; + String content = name + ":" + password; + long timeToLive = 10000L; + long expires = System.currentTimeMillis() + timeToLive; + CachedBasicPrincipal cbp = new CachedBasicPrincipal(creator, content, "domain", timeToLive); + + assertThat((HttpTaf)creatorField.get(cbp), is(creator)); + assertThat((Long)timeToLiveField.get(cbp), is(timeToLive)); + assertTrue(Math.abs(cbp.expires() - expires) < 10); + } + + @Test + public void revalidateTest() throws IOException, IllegalArgumentException, IllegalAccessException, InterruptedException { + resp = CachedPrincipal.Resp.REVALIDATED; + when(creator.revalidate((CachedPrincipal)any(), any())).thenReturn(resp); + + String name = "User"; + String password = "password"; + String content = name + ":" + password; + long timeToLive = 10000L; + long expires = System.currentTimeMillis() + timeToLive; + CachedBasicPrincipal cbp = new CachedBasicPrincipal(creator, content, "domain", timeToLive); + + assertTrue(Math.abs(cbp.expires() - expires) < 10); + + Thread.sleep(1); + expires = System.currentTimeMillis() + timeToLive; + assertThat(cbp.revalidate(new Object()), is(resp)); + assertTrue(Math.abs(cbp.expires() - expires) < 10); + + resp = CachedPrincipal.Resp.UNVALIDATED; + when(creator.revalidate((CachedPrincipal)any(), any())).thenReturn(resp); + expires = System.currentTimeMillis() + timeToLive; + cbp = new CachedBasicPrincipal(creator, content, "domain", timeToLive); + + assertThat(cbp.revalidate(new Object()), is(resp)); + assertTrue(Math.abs(cbp.expires() - expires) < 10); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_Kind.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_Kind.java new file mode 100644 index 00000000..c66539c4 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_Kind.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * ============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.cadi.test.principal; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import org.junit.*; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.onap.aaf.cadi.principal.BasicPrincipal; +import org.onap.aaf.cadi.principal.Kind; +import org.onap.aaf.cadi.principal.OAuth2FormPrincipal; +import org.onap.aaf.cadi.principal.TrustPrincipal; +import org.onap.aaf.cadi.principal.X509Principal; + +public class JU_Kind { + + @Mock + private TrustPrincipal trust; + + @Mock + private X509Principal x509; + + @Mock + private OAuth2FormPrincipal oauth; + + @Mock + private BasicPrincipal basic; + + @Before + public void setup() throws SecurityException { + MockitoAnnotations.initMocks(this); + } + + @Test + public void getKind() { + assertThat(Kind.getKind(trust), is('U')); + assertThat(Kind.getKind(x509), is('X')); + assertThat(Kind.getKind(oauth), is('O')); + assertThat(Kind.getKind(basic), is('B')); + } + + @Test + public void coverageTest() { + @SuppressWarnings("unused") + Kind kind = new Kind(); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_OAuth2FormPrincipal.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_OAuth2FormPrincipal.java new file mode 100644 index 00000000..a7c14aa8 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_OAuth2FormPrincipal.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * ============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.cadi.test.principal; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import org.junit.*; + +import org.onap.aaf.cadi.principal.OAuth2FormPrincipal; + +public class JU_OAuth2FormPrincipal { + + private String username = "user"; + private String id = "id"; + + @Test + public void accessorsTest() { + OAuth2FormPrincipal oauth = new OAuth2FormPrincipal(id, username); + assertThat(oauth.getName(), is(username)); + assertThat(oauth.client_id(), is(id)); + assertThat(oauth.tag(), is("OAuth")); + } + + @Test + public void personalNameTest() { + OAuth2FormPrincipal oauth = new OAuth2FormPrincipal(id, username); + assertThat(oauth.personalName(), is(username + "|" + id)); + + oauth = new OAuth2FormPrincipal(id, null); + assertThat(oauth.personalName(), is(id)); + + oauth = new OAuth2FormPrincipal(id, id); + assertThat(oauth.personalName(), is(id)); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_StringTagLookup.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_StringTagLookup.java new file mode 100644 index 00000000..ff1f3f8a --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_StringTagLookup.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * ============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.cadi.test.principal; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import org.junit.*; + +import org.onap.aaf.cadi.principal.StringTagLookup; + +public class JU_StringTagLookup { + + @Test + public void accessorsTest() throws Exception { + String tag = "tag"; + StringTagLookup stl = new StringTagLookup(tag); + assertThat(stl.lookup(), is(tag)); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_TaggedPrincipal.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_TaggedPrincipal.java new file mode 100644 index 00000000..4674db70 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_TaggedPrincipal.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * ============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.cadi.test.principal; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import org.junit.*; + +import org.onap.aaf.cadi.principal.TaggedPrincipal; +import org.onap.aaf.cadi.principal.TaggedPrincipal.TagLookup; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.principal.StringTagLookup; + +public class JU_TaggedPrincipal { + + private final String name = "stubbedName"; + private final String tag = "tag"; + + private class TaggedPrincipalStub extends TaggedPrincipal { + public TaggedPrincipalStub() { super(); } + public TaggedPrincipalStub(final TagLookup tl) { super(tl); } + @Override public String getName() { return name; } + @Override public String tag() { return null; } + } + + private class WhinyTagLookup implements TagLookup { + public WhinyTagLookup(final String tag) { } + @Override + public String lookup() throws CadiException { + throw new CadiException(); + } + } + + @Test + public void personalNameTest() { + TaggedPrincipal tp = new TaggedPrincipalStub(); + assertThat(tp.personalName(), is(name)); + + StringTagLookup stl = new StringTagLookup(tag); + tp = new TaggedPrincipalStub(stl); + assertThat(tp.personalName(), is(tag)); + + WhinyTagLookup wtl = new WhinyTagLookup(tag); + tp.setTagLookup(wtl); + assertThat(tp.personalName(), is(name)); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_TrustPrincipal.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_TrustPrincipal.java new file mode 100644 index 00000000..4a7333e2 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_TrustPrincipal.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * ============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.cadi.test.principal; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import org.junit.*; + +import java.security.Principal; + +import org.onap.aaf.cadi.UserChain; +import org.onap.aaf.cadi.principal.TaggedPrincipal; +import org.onap.aaf.cadi.principal.TrustPrincipal; + +public class JU_TrustPrincipal { + + private final String ucName = "UserChain"; + private final String uc = "This is a UserChain"; + private final String taggedName = "TaggedPrincipal"; + private final String tag = "tag"; + private final String pName = "Principal"; + + private class UserChainPrincipalStub implements Principal, UserChain { + @Override public String userChain() { return uc; } + @Override public String getName() { return ucName; } + } + + private class TaggedPrincipalStub extends TaggedPrincipal { + public TaggedPrincipalStub() { super(); } + @Override public String getName() { return taggedName; } + @Override public String tag() { return tag; } + } + + private class PrincipalStub implements Principal { + @Override public String getName() { return pName; } + } + + @Test + public void userChainConstructorTest() { + UserChainPrincipalStub ucps = new UserChainPrincipalStub(); + TrustPrincipal tp = new TrustPrincipal(ucps, taggedName); + assertThat(tp.getName(), is(taggedName)); + assertThat(tp.userChain(), is(uc)); + assertSame(tp.original(), ucps); + assertThat(tp.tag(), is(uc)); + assertThat(tp.personalName(), is(ucName + '[' + uc + ']')); + } + + @Test + public void taggedPrincipalConstructorTest() { + TaggedPrincipal tagged = new TaggedPrincipalStub(); + TrustPrincipal tp = new TrustPrincipal(tagged, taggedName); + assertThat(tp.getName(), is(taggedName)); + assertThat(tp.userChain(), is(tag)); + assertSame(tp.original(), tagged); + assertThat(tp.tag(), is(tag)); + assertThat(tp.personalName(), is(taggedName + '[' + tag + ']')); + } + + @Test + public void principalConstructorTest() { + Principal principal = new PrincipalStub(); + TrustPrincipal tp = new TrustPrincipal(principal, pName); + assertThat(tp.getName(), is(pName)); + assertThat(tp.userChain(), is(principal.getClass().getSimpleName())); + assertSame(tp.original(), principal); + assertThat(tp.tag(), is(principal.getClass().getSimpleName())); + assertThat(tp.personalName(), is(pName + '[' + principal.getClass().getSimpleName() + ']')); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_UnAuthPrincipal.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_UnAuthPrincipal.java new file mode 100644 index 00000000..9d694a53 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_UnAuthPrincipal.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * ============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.cadi.test.principal; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import org.junit.*; + +import org.onap.aaf.cadi.principal.UnAuthPrincipal; + +public class JU_UnAuthPrincipal { + + private final String name = "name"; + + @Test + public void accessorsTest() { + UnAuthPrincipal up = new UnAuthPrincipal(name); + assertThat(up.getName(), is(name)); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_X509Principal.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_X509Principal.java new file mode 100644 index 00000000..a45ce24e --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/principal/JU_X509Principal.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * ============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.cadi.test.principal; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import static org.mockito.Mockito.*; +import org.junit.*; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.security.Principal; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; + +import org.onap.aaf.cadi.principal.X509Principal; + +public class JU_X509Principal { + + private final String name = "x509 name"; + private final byte[] cred = "super duper secret password".getBytes(); + + @Mock + X509Certificate cert; + + @Mock + Principal subject; + + @Before + public void setup() throws CertificateEncodingException { + MockitoAnnotations.initMocks(this); + when(cert.getEncoded()).thenReturn(cred); + } + + @Test + public void constructor1Test() throws IOException { + X509Principal x509 = new X509Principal(name, cert); + // Call twice to hit both branches + assertThat(x509.getAsHeader(), is("X509 " + cred)); + assertThat(x509.getAsHeader(), is("X509 " + cred)); + assertThat(x509.toString(), is("X509 Authentication for " + name)); + assertTrue(x509.getCred().equals(cred)); + assertThat(x509.getName(), is(name)); + assertThat(x509.tag(), is("x509")); + } + + @Test + public void constructor2Test() throws IOException { + X509Principal x509 = new X509Principal(name, cert, cred); + // Call twice to hit both branches + assertThat(x509.getAsHeader(), is("X509 " + cred)); + assertThat(x509.toString(), is("X509 Authentication for " + name)); + assertTrue(x509.getCred().equals(cred)); + assertThat(x509.getName(), is(name)); + assertThat(x509.tag(), is("x509")); + } + + @Test + public void constructor3Test() throws IOException { + final String longName = "name@domain"; + when(subject.getName()).thenReturn("OU=" + longName + ",extra"); + when(cert.getSubjectDN()).thenReturn(subject); + X509Principal x509 = new X509Principal(cert, cred); + // Call twice to hit both branches + assertThat(x509.getAsHeader(), is("X509 " + cred)); + assertThat(x509.toString(), is("X509 Authentication for " + longName)); + assertTrue(x509.getCred().equals(cred)); + assertThat(x509.getName(), is(longName)); + + when(subject.getName()).thenReturn(longName + ",extra"); + when(cert.getSubjectDN()).thenReturn(subject); + try { + x509 = new X509Principal(cert, cred); + fail("Should have thrown an Exception"); + } catch(IOException e) { + assertThat(e.getMessage(), is("X509 does not have Identity as CN")); + } + + when(subject.getName()).thenReturn("OU=" + longName); + when(cert.getSubjectDN()).thenReturn(subject); + try { + x509 = new X509Principal(cert, cred); + fail("Should have thrown an Exception"); + } catch(IOException e) { + assertThat(e.getMessage(), is("X509 does not have Identity as CN")); + } + + when(subject.getName()).thenReturn("OU=" + name + ",exta"); + when(cert.getSubjectDN()).thenReturn(subject); + try { + x509 = new X509Principal(cert, cred); + fail("Should have thrown an Exception"); + } catch(IOException e) { + assertThat(e.getMessage(), is("X509 does not have Identity as CN")); + } + + } + + @Test + public void throwsTest() throws CertificateEncodingException { + when(cert.getEncoded()).thenThrow(new CertificateEncodingException()); + X509Principal x509 = new X509Principal(name, cert); + assertThat(x509.getCred(), is(nullValue())); + try { + x509.getAsHeader(); + fail("Should have thrown an Exception"); + } catch (IOException e) { + } + } + + @Test + public void getCredTest() { + X509Principal x509 = new X509Principal(name, cert); + // Call twice to hit both branches + assertTrue(x509.getCred().equals(cred)); + assertTrue(x509.getCred().equals(cred)); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/taf/JU_NullTafRespTest.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/taf/JU_NullTafRespTest.java new file mode 100644 index 00000000..aabed1ed --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/taf/JU_NullTafRespTest.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * ============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.cadi.test.taf; + +import static org.junit.Assert.*; + +import java.io.IOException; + +import org.junit.Before; +import org.junit.Test; +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.taf.TafResp; +import org.onap.aaf.cadi.taf.TafResp.RESP; + +public class JU_NullTafRespTest { + +// @Before +// public void setUp() throws Exception { +// } + +// @Test +// public void test() throws IOException { +// TafResp singleton = NullTafResp.singleton(); +// TafResp singleton1 = NullTafResp.singleton(); + +// assertEquals(singleton, singleton1); + +// assertFalse(singleton.isValid()); + +// assertEquals(singleton.isAuthenticated(), RESP.NO_FURTHER_PROCESSING); + +// assertEquals(singleton.desc(), "All Authentication denied"); + +// assertEquals(singleton.authenticate(), RESP.NO_FURTHER_PROCESSING); + +// assertNull(singleton.getPrincipal()); + +// assertEquals(singleton.getAccess(), Access.NULL); + +// assertEquals(singleton.isFailedAttempt(), true); +// } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_FQI.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_FQI.java new file mode 100644 index 00000000..57177839 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_FQI.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * ============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.cadi.test.util; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import org.junit.*; + +import org.onap.aaf.cadi.util.FQI; + +public class JU_FQI { + + @Test + public void reverseDomainTest() { + assertThat(FQI.reverseDomain("user@att.com"), is("com.att")); + } + + @Test + public void coverageTest() { + @SuppressWarnings("unused") + FQI fqi = new FQI(); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_JsonOutputStream.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_JsonOutputStream.java new file mode 100644 index 00000000..d07652a1 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_JsonOutputStream.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * ============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.cadi.test.util; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; + +import java.io.ByteArrayOutputStream; + +import org.junit.*; + +import java.io.IOException; +import java.lang.reflect.Field; + +import org.onap.aaf.cadi.util.JsonOutputStream; + +public class JU_JsonOutputStream { + + private JsonOutputStream jos; + + @Before + public void setup() { + jos = new JsonOutputStream(new ByteArrayOutputStream()); + } + + @Test + public void constructorTest() { + jos = new JsonOutputStream(System.out); + jos = new JsonOutputStream(System.err); + } + + @Test + public void writeTest() throws IOException { + byte[] json = ("{" + + "name: user," + + "password: pass," + + "contact: {" + + "email: user@att.com," + + "phone: 555-5555" + + "}," + + "list: [" + + "item1," + + "item2" + + "],[],{}," + + "list:" + + "[" + + "item1," + + "item2" + + "]" + + "}").getBytes(); + jos.write(json); + } + + @Test + public void resetIndentTest() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { + Field indentField = JsonOutputStream.class.getDeclaredField("indent"); + indentField.setAccessible(true); + + assertThat((int)indentField.get(jos), is(0)); + jos.resetIndent(); + assertThat((int)indentField.get(jos), is(1)); + } + + @Test + public void coverageTest() throws IOException { + jos.flush(); + jos.close(); + + jos = new JsonOutputStream(System.out); + jos.close(); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_MaskFormatException.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_MaskFormatException.java new file mode 100644 index 00000000..6a60df68 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_MaskFormatException.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * ============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.cadi.test.util; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import org.junit.*; + +import org.onap.aaf.cadi.util.MaskFormatException; + +public class JU_MaskFormatException { + + @Test + public void throwsTest() { + String errorMessage = "This is a MaskFormatException"; + try { + throw new MaskFormatException(errorMessage); + } catch (Exception e) { + assertThat(e.getMessage(), is(errorMessage)); + assertTrue(e instanceof MaskFormatException); + } + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_NetMask.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_NetMask.java new file mode 100644 index 00000000..be3b1b32 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_NetMask.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * * 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.cadi.test.util; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.onap.aaf.cadi.util.NetMask; + +public class JU_NetMask { + + @Test + public void deriveTest() { + String test = "test"; + assertEquals(NetMask.derive(test.getBytes()), 0); + } + + @Test + public void deriveTest2() { + String test = "1.2.3.4"; + assertEquals(NetMask.derive(test.getBytes()), 0); + } + + @Test + public void deriveTest3() { + String test = "1.2.4"; + assertEquals(NetMask.derive(test.getBytes()), 0); + } + + @Test + public void deriveTest4() { + String test = "1.3.4"; + assertEquals(NetMask.derive(test.getBytes()), 0); + } + + @Test + public void deriveTest5() { + String test = "2.3.4"; + assertEquals(NetMask.derive(test.getBytes()), 0); + } + + @Test + public void deriveTest6() { + String test = "3.4"; + assertEquals(NetMask.derive(test.getBytes()), 0); + } +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_Split.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_Split.java new file mode 100644 index 00000000..e375f6c0 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_Split.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * * 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.cadi.test.util; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import org.junit.*; + +import org.onap.aaf.cadi.util.Split; + +public class JU_Split { + + @Test + public void splitTest() { + String[] output = Split.split('c', "ctestctc", 0, "ctestctc".length()); + assertThat(output.length, is(4)); + assertThat(output[0], is("")); + assertThat(output[1], is("test")); + assertThat(output[2], is("t")); + assertThat(output[3], is("")); + + output = Split.split('c', "ctestctc", 0, 4); + assertThat(output.length, is(2)); + assertThat(output[0], is("")); + assertThat(output[1], is("tes")); + + output = Split.split('c', "test", 0, "test".length()); + assertThat(output.length, is(1)); + assertThat(output[0], is("test")); + + assertThat(Split.split('c', null, 0, 0).length, is(0)); + + // Test with fewer arguments + output = Split.split('c', "ctestctc"); + assertThat(output.length, is(4)); + assertThat(output[0], is("")); + assertThat(output[1], is("test")); + assertThat(output[2], is("t")); + assertThat(output[3], is("")); + } + + @Test + public void splitTrimTest() { + String[] output = Split.splitTrim('c', " cte stc ctc ", 0, " cte stc ctc ".length()); + assertThat(output.length, is(5)); + assertThat(output[0], is("")); + assertThat(output[1], is("te st")); + assertThat(output[2], is("")); + assertThat(output[3], is("t")); + assertThat(output[4], is("")); + + output = Split.splitTrim('c', " cte stc ctc ", 0, 5); + assertThat(output.length, is(2)); + assertThat(output[0], is("")); + assertThat(output[1], is("te")); + + assertThat(Split.splitTrim('c', " te st ", 0, " te st ".length())[0], is("te st")); + + assertThat(Split.splitTrim('c', null, 0, 0).length, is(0)); + + // Test with 2 arguments + output = Split.splitTrim('c', " cte stc ctc "); + assertThat(output.length, is(5)); + assertThat(output[0], is("")); + assertThat(output[1], is("te st")); + assertThat(output[2], is("")); + assertThat(output[3], is("t")); + assertThat(output[4], is("")); + + // Tests with 1 argument + output = Split.splitTrim('c', " cte stc ctc ", 1); + assertThat(output.length, is(1)); + assertThat(output[0], is("cte stc ctc")); + + output = Split.splitTrim('c', "testctest2", 2); + assertThat(output.length, is(2)); + assertThat(output[0], is("test")); + assertThat(output[1], is("test2")); + + output = Split.splitTrim('c', " cte stc ctc ", 4); + assertThat(output.length, is(4)); + assertThat(output[0], is("")); + assertThat(output[1], is("te st")); + assertThat(output[2], is("")); + + assertThat(Split.splitTrim('c', null, 0).length, is(0)); + } + + @Test + public void coverageTest() { + @SuppressWarnings("unused") + Split split = new Split(); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_TheConsole.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_TheConsole.java new file mode 100644 index 00000000..f168c7f4 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_TheConsole.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * * 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.cadi.test.util; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.onap.aaf.cadi.util.TheConsole; + +public class JU_TheConsole { + + @Test + public void implemented(){ + assertEquals(TheConsole.implemented(),false); + } +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_UserChainManip.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_UserChainManip.java new file mode 100644 index 00000000..d335edae --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_UserChainManip.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * * 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.cadi.test.util; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; +import org.onap.aaf.cadi.UserChain; +import org.onap.aaf.cadi.util.UserChainManip; + +public class JU_UserChainManip { + + @Test + public void build(){ + UserChain.Protocol baseAuth=UserChain.Protocol.BasicAuth; + assertEquals(UserChainManip.build(new StringBuilder(""), "app", "id", baseAuth, true).toString(), "app:id:BasicAuth:AS"); + } + + + @Test + public void idToNS(){ + assertEquals(UserChainManip.idToNS(null), ""); + } + + @Test + public void idToNS1(){ + assertEquals(UserChainManip.idToNS("t@st"), "st"); + } + + @Test + public void test() { + assertEquals("",UserChainManip.idToNS(null)); + assertEquals("",UserChainManip.idToNS("")); + assertEquals("",UserChainManip.idToNS("something")); + assertEquals("",UserChainManip.idToNS("something@@")); + assertEquals("",UserChainManip.idToNS("something@@.")); + assertEquals("com",UserChainManip.idToNS("something@com")); + assertEquals("com.random",UserChainManip.idToNS("something@random.com")); + assertEquals("com.random",UserChainManip.idToNS("@random.com")); + assertEquals("com.random",UserChainManip.idToNS("something@random.com.")); + assertEquals("com.random",UserChainManip.idToNS("something@..random...com...")); + assertEquals("com.random.this",UserChainManip.idToNS("something@this.random.com")); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_Vars.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_Vars.java new file mode 100644 index 00000000..345c8ca2 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/util/JU_Vars.java @@ -0,0 +1,150 @@ +/******************************************************************************* + * * 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.cadi.test.util; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; +import org.onap.aaf.cadi.util.Vars; + +public class JU_Vars { + + @Test + public void coverage() { + @SuppressWarnings("unused") + Vars my_nonstatic_object_for_coverage = new Vars(); + } + + @Test + public void convert() { + String test = "test"; + List<String> list = new ArrayList<String>(); + list.add("method"); + assertEquals(Vars.convert(test, list), test); + } + + @Test + public void convertTest1() { + String test = "te%t"; + List<String> list = new ArrayList<String>(); + list.add("method"); + assertEquals(Vars.convert("test", list), "test"); + } + + @Test + public void convertTest2() { + String test = "te%s%t"; + List<String> list = new ArrayList<String>(); + list.add("method"); + assertEquals(Vars.convert("test", list), "test"); + } + + @Test + public void test() { + StringBuilder holder = new StringBuilder(); + String str,bstr; + assertEquals(str = "set %1 to %2",Vars.convert(holder,str, "a","b")); + assertEquals("set a to b",holder.toString()); + assertEquals(str,Vars.convert(null,str, "a","b")); + holder.setLength(0); + assertEquals(str,Vars.convert(holder,bstr="set %s to %s", "a","b")); + assertEquals("set a to b",holder.toString()); + assertEquals(str,Vars.convert(null,bstr, "a","b")); + + holder.setLength(0); + assertEquals(str = "%1=%2",Vars.convert(holder,str, "a","b")); + assertEquals("a=b",holder.toString()); + assertEquals(str,Vars.convert(null,str, "a","b")); + holder.setLength(0); + assertEquals(str,Vars.convert(holder,bstr="%s=%s", "a","b")); + assertEquals("a=b",holder.toString()); + assertEquals(str,Vars.convert(null,bstr, "a","b")); + + holder.setLength(0); + assertEquals(str = "%1%2",Vars.convert(holder,str, "a","b")); + assertEquals("ab",holder.toString()); + assertEquals(str ,Vars.convert(null,str, "a","b")); + holder.setLength(0); + assertEquals(str,Vars.convert(holder,bstr="%s%s", "a","b")); + assertEquals("ab",holder.toString()); + assertEquals(str ,Vars.convert(null,bstr, "a","b")); + + + holder.setLength(0); + assertEquals(str = " %1=%2 ",Vars.convert(holder,str, "a","b")); + assertEquals(" a=b ",holder.toString()); + assertEquals(str ,Vars.convert(null,str, "a","b")); + holder.setLength(0); + assertEquals(str,Vars.convert(holder,bstr = " %s=%s ", "a","b")); + assertEquals(" a=b ",holder.toString()); + assertEquals(str ,Vars.convert(null,bstr, "a","b")); + + holder.setLength(0); + assertEquals(str = " %1%2%10 ",Vars.convert(holder,str, "a","b","c","d","e","f","g","h","i","j")); + assertEquals(" abj ",holder.toString()); + assertEquals(str,Vars.convert(null,str, "a","b","c","d","e","f","g","h","i","j")); + holder.setLength(0); + assertEquals(str=" %1%2%3 ",Vars.convert(holder,bstr = " %s%s%s ", "a","b","c","d","e","f","g","h","i","j")); + assertEquals(" abc ",holder.toString()); + assertEquals(str,Vars.convert(null,bstr, "a","b","c","d","e","f","g","h","i","j")); + + + holder.setLength(0); + assertEquals(str = "set %1 to %2",Vars.convert(holder,str, "Something much","larger")); + assertEquals("set Something much to larger",holder.toString()); + assertEquals(str,Vars.convert(null,str,"Something much","larger")); + holder.setLength(0); + assertEquals(str,Vars.convert(holder,bstr="set %s to %s", "Something much","larger")); + assertEquals("set Something much to larger",holder.toString()); + assertEquals(str,Vars.convert(null,bstr, "Something much","larger")); + + holder.setLength(0); + assertEquals(str = "Text without Vars",Vars.convert(holder,str)); + assertEquals(str,holder.toString()); + assertEquals(str = "Text without Vars",Vars.convert(null,str)); + + + holder.setLength(0); + assertEquals(str = "Not %1 Enough %2 Vars %3",Vars.convert(holder,str, "a","b")); + assertEquals("Not a Enough b Vars ",holder.toString()); + assertEquals(str ,Vars.convert(null,str, "a","b")); + holder.setLength(0); + assertEquals(str,Vars.convert(holder,bstr="Not %s Enough %s Vars %s", "a","b")); + assertEquals("Not a Enough b Vars ",holder.toString()); + assertEquals(str ,Vars.convert(null,bstr, "a","b")); + + holder.setLength(0); + assertEquals(str = "!@#$%^*()-+?/,:;.",Vars.convert(holder,str, "a","b")); + assertEquals(str,holder.toString()); + assertEquals(str ,Vars.convert(null,str, "a","b")); + + holder.setLength(0); + bstr = "%s !@#$%^*()-+?/,:;."; + str = "%1 !@#$%^*()-+?/,:;."; + assertEquals(str,Vars.convert(holder,bstr, "Not Acceptable")); + assertEquals("Not Acceptable !@#$%^*()-+?/,:;.",holder.toString()); + assertEquals(str ,Vars.convert(null,bstr, "Not Acceptable")); + } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/wsse/JU_WSSE_Read.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/wsse/JU_WSSE_Read.java new file mode 100644 index 00000000..26dd43b1 --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/wsse/JU_WSSE_Read.java @@ -0,0 +1,189 @@ +/******************************************************************************* + * ============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.cadi.test.wsse; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; + +import javax.xml.stream.XMLStreamException; + +import org.junit.Test; +import org.onap.aaf.cadi.BasicCred; +import org.onap.aaf.cadi.BufferedServletInputStream; +import org.onap.aaf.cadi.wsse.WSSEParser; + +public class JU_WSSE_Read { + +// @Test +// public void test() { +// try { +// final BasicCred bc = new BasicCred() { + +// private String user; +// private byte[] password; + +// public void setUser(String user) { +// this.user = user; +// } + +// public void setCred(byte[] passwd) { +// this.password = passwd; +// } + +// public String getUser() { +// return user; +// } + +// public byte[] getCred() { +// return password; +// } +// }; + +// WSSEParser wp = new WSSEParser(); + +// FileInputStream fis; +// fis = new FileInputStream("test/example.xml"); +// BufferedServletInputStream is = new BufferedServletInputStream(fis); +// try { +// is.mark(1536); +// try { +// assertNull(wp.parse(bc, is)); +// } finally { +// is.reset(); +// assertEquals(814,is.buffered()); +// } +// String password = new String(bc.getCred()); +// System.out.println("CadiWrap credentials are: " + bc.getUser() + ", " + password); +// assertEquals("some_user", bc.getUser()); +// assertEquals("some_password", password); + +// } finally { +// fis.close(); +// } + +// // CBUS (larger) +// fis = new FileInputStream("test/CBUSevent.xml"); +// is = new BufferedServletInputStream(fis); +// try { +// is.mark(1536); +// try { +// assertNull(wp.parse(bc, is)); +// } finally { +// is.reset(); +// assertEquals(667,is.buffered()); +// } +// String password = new String(bc.getCred()); +// System.out.println("CadiWrap credentials are: " + bc.getUser() + ", " + password); +// assertEquals("none", bc.getUser()); +// assertEquals("none", password); + +// } finally { +// fis.close(); +// } + +// // Closed Stream +// fis = new FileInputStream("test/example.xml"); +// fis.close(); +// bc.setCred(null); +// bc.setUser(null); +// XMLStreamException ex = wp.parse(bc, fis); +// assertNotNull(ex); +// assertNull(bc.getUser()); +// assertNull(bc.getCred()); + + +// fis = new FileInputStream("test/exampleNoSecurity.xml"); +// try { +// bc.setCred(null); +// bc.setUser(null); +// assertNull(wp.parse(bc, fis)); +// assertNull(bc.getUser()); +// assertNull(bc.getCred()); +// } finally { +// fis.close(); +// } + +// fis = new FileInputStream("test/exampleBad1.xml"); +// try { +// bc.setCred(null); +// bc.setUser(null); +// assertNull(wp.parse(bc, fis)); +// assertNull(bc.getUser()); +// assertNull(bc.getCred()); +// } finally { +// fis.close(); +// } + +// XMLStreamException e = wp.parse(bc, new ByteArrayInputStream("Not XML".getBytes())); // empty +// assertNotNull(e); + +// e = wp.parse(bc, new ByteArrayInputStream("".getBytes())); // empty +// assertNotNull(e); + + +// long start, count = 0L; +// int iter = 30000; +// File f = new File("test/CBUSevent.xml"); +// fis = new FileInputStream(f); +// is = new BufferedServletInputStream(fis); +// is.mark(0); +// try { +// while(is.read()>=0); +// } finally { +// fis.close(); +// } + +// for(int i=0;i<iter;++i) { +// start = System.nanoTime(); +// is.reset(); +// try { +// assertNull(wp.parse(bc, is)); +// } finally { +// count += System.nanoTime()-start; +// } +// } +// float ms = count/1000000f; +// System.out.println("Executed " + iter + " WSSE reads from Memory Stream in " + ms + "ms. " + ms/iter + "ms per trans"); + +// // SPECIFIC ISSUES + +// fis = new FileInputStream("test/error2013_04_23.xml"); +// try { +// bc.setCred(null); +// bc.setUser(null); +// assertNull(wp.parse(bc, fis)); +// assertNull(bc.getUser()); +// assertNull(bc.getCred()); +// } finally { +// fis.close(); +// } +// } catch(Exception e) { +// e.printStackTrace(System.err); +// } +// } + +} diff --git a/cadi/core/src/test/java/org/onap/aaf/cadi/test/wsse/JU_XReader.java b/cadi/core/src/test/java/org/onap/aaf/cadi/test/wsse/JU_XReader.java new file mode 100644 index 00000000..5d765c5a --- /dev/null +++ b/cadi/core/src/test/java/org/onap/aaf/cadi/test/wsse/JU_XReader.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * ============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.cadi.test.wsse; + +import java.io.FileInputStream; + +import javax.xml.stream.events.XMLEvent; + +import org.junit.Test; +import org.onap.aaf.cadi.wsse.XEvent; +import org.onap.aaf.cadi.wsse.XReader; + +public class JU_XReader { + //TODO: Gabe [JUnit] Class not found error + @Test + public void test() throws Exception { + FileInputStream fis = new FileInputStream("test/CBUSevent.xml"); + try { + XReader xr = new XReader(fis); + while(xr.hasNext()) { + XEvent xe = xr.nextEvent(); + switch(xe.getEventType()) { + case XMLEvent.START_DOCUMENT: + System.out.println("Start Document"); + break; + case XMLEvent.START_ELEMENT: + System.out.println("Start Event: " + xe.asStartElement().getName()); + break; + case XMLEvent.END_ELEMENT: + System.out.println("End Event: " + xe.asEndElement().getName()); + break; + case XMLEvent.CHARACTERS: + System.out.println("Characters: " + xe.asCharacters().getData()); + break; + case XMLEvent.COMMENT: + System.out.println("Comment: " + ((XEvent.Comment)xe).value); + break; + } + } + } finally { + fis.close(); + } + + } + +} |