summaryrefslogtreecommitdiffstats
path: root/cadi/core/src/main/java
diff options
context:
space:
mode:
authorInstrumental <jcgmisc@stl.gathman.org>2018-03-26 13:49:56 -0700
committerInstrumental <jcgmisc@stl.gathman.org>2018-03-26 13:50:07 -0700
commita20accc73189d8e5454cd26049c0e6fae75da16f (patch)
tree3526110a1f65591354eff6400383512df4828903 /cadi/core/src/main/java
parentac1e1ec76e9125206be91a2f32c7104c9392dc9a (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/main/java')
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/AES.java131
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/AbsUserCache.java463
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/Access.java180
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/BasicCred.java36
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/BufferedServletInputStream.java200
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/CachedPrincipal.java47
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/CachingLur.java34
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/CadiException.java50
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/CadiWrap.java198
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/Capacitor.java241
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/CmdLine.java361
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/Connector.java26
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/CredVal.java42
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/GetCred.java26
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/Hash.java258
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/Locator.java36
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/LocatorException.java46
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/Lur.java92
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/Permission.java28
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/PropAccess.java387
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/Revalidator.java34
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/SecuritySetter.java44
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/ServletContextAccess.java67
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/Symm.java854
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/Taf.java57
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/Transmutate.java45
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/TrustChecker.java52
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/User.java177
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/UserChain.java43
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/config/Config.java728
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/config/Get.java97
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/config/GetAccess.java62
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/config/MultiGet.java42
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/config/SecurityInfo.java255
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/config/SecurityInfoC.java72
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/config/UsersDump.java157
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/filter/AUTHZ.java36
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/filter/AUTHZServlet.java100
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/filter/AccessGetter.java35
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/filter/CadiFilter.java332
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/filter/CadiHTTPManip.java210
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/filter/FCGet.java76
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/filter/MapPermConverter.java54
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/filter/NullPermConverter.java43
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/filter/PathFilter.java183
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/filter/PermConverter.java32
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/filter/RolesAllowed.java56
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/filter/ServletImpl.java56
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/lur/ConfigPrincipal.java69
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/lur/EpiLur.java169
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/lur/LocalLur.java196
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/lur/LocalPermission.java50
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/lur/NullLur.java87
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/principal/BasicPrincipal.java126
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/principal/BearerPrincipal.java33
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/principal/CachedBasicPrincipal.java65
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/principal/Kind.java53
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/principal/OAuth2FormPrincipal.java61
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/principal/StringTagLookup.java35
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/principal/TaggedPrincipal.java60
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/principal/TrustPrincipal.java70
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/principal/UnAuthPrincipal.java37
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/principal/X509Principal.java109
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/AbsTafResp.java116
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/EpiTaf.java84
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/HttpEpiTaf.java197
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/HttpTaf.java60
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/LoginPageTafResp.java86
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/NullTaf.java64
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/NullTafResp.java73
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/PuntTafResp.java69
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/Redirectable.java31
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/TafResp.java94
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/TrustNotTafResp.java76
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/TrustTafResp.java78
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/basic/BasicHttpTaf.java165
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/basic/BasicHttpTafResp.java62
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/cert/CertIdentity.java46
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/cert/X509HttpTafResp.java51
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/cert/X509Taf.java261
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/dos/DenialOfServiceTaf.java374
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/taf/dos/DenialOfServiceTafResp.java47
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/util/Chmod.java62
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/util/FQI.java51
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/util/JsonOutputStream.java89
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/util/MaskFormatException.java31
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/util/MyConsole.java28
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/util/NetMask.java99
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/util/Pool.java380
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/util/Split.java114
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/util/SubStandardConsole.java62
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/util/TheConsole.java47
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/util/UserChainManip.java77
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/util/Vars.java120
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/wsse/Action.java37
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/wsse/Match.java130
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/wsse/WSSEParser.java86
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/wsse/XEvent.java135
-rw-r--r--cadi/core/src/main/java/org/onap/aaf/cadi/wsse/XReader.java417
99 files changed, 12100 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();
+ }
+ }
+
+}