diff options
author | Instrumental <jonathan.gathman@att.com> | 2018-10-12 12:55:37 -0500 |
---|---|---|
committer | Instrumental <jonathan.gathman@att.com> | 2018-10-12 12:55:40 -0500 |
commit | 7e5ccdd25e377cfa2dd5850ac3c2c1428c40b078 (patch) | |
tree | 9e7a737c934e39a8809339d2efedca1dd27ce3b4 /cadi/core/src/main | |
parent | bdaf445e08978aadeca4232030fdbf6d39797dc8 (diff) |
CADI ID Translate
Issue-ID: AAF-556
Change-Id: Ifd6c42012a90b369b41ad5ae8e724fb5950df958
Signed-off-by: Instrumental <jonathan.gathman@att.com>
Diffstat (limited to 'cadi/core/src/main')
4 files changed, 379 insertions, 0 deletions
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 index 088227ed..2479a058 100644 --- 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 @@ -103,6 +103,7 @@ public class Config { 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_BATH_CONVERT = "cadi_bath_convert"; public static final String CADI_USER_CHAIN_TAG = "cadi_user_chain"; public static final String CADI_USER_CHAIN = "USER_CHAIN"; diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/filter/MapBathConverter.java b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/MapBathConverter.java new file mode 100644 index 00000000..7a138e97 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/filter/MapBathConverter.java @@ -0,0 +1,170 @@ +/** + * ============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.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import javax.xml.ws.Holder; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.Symm; +import org.onap.aaf.cadi.util.CSV; +import org.onap.aaf.cadi.util.CSV.Visitor; + +/** + * This Filter is designed to help MIGRATE users from systems that don't match the FQI style. + * + * Style 1, where just the ID is translated, i.e. OLD => new@something.onap.org, that is acceptable + * longer term, because it does not store Creds locally. The passwords are in appropriate systems, but + * it's still painful operationally, though it does ease migration. + * + * Style 3, however, which is Direct match of Authorization Header to replacement, is only there + * because some passwords are simply not acceptable for AAF, (too easy, for instance), and it is + * not feasible to break Organization Password rules for a Migration. Therefore, this method + * should not considered something that is in any way a permanent + * + + * + * It goes without saying that any file with the password conversion should be protected by "400", etc. + * + * @author Instrumental (Jonathan) + * + */ +public class MapBathConverter { + private static final String BASIC = "Basic "; + private final Map<String,String> map; + + /** + * Create with colon separated name value pairs + * Enter the entire "Basic dXNlcjpwYXNz" "Authorization" header, where "dXNlcjpwYXNz" is + * base64 encoded, which can be created with "cadi" tool (in jar) + * + * The replacement should also be an exact replacement of what you want. Recognize that + * this should be TEMPORARY as you are storing credentials outside the users control. + * + * @param value + * @throws IOException + * @throws CadiException + */ + public MapBathConverter(final Access access, final CSV csv) throws IOException, CadiException { + map = new TreeMap<>(); + final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + final Date now = new Date(); + csv.visit(new Visitor() { + @Override + public void visit(List<String> row) throws CadiException { + if(row.size()<3) { + throw new CadiException("CSV file " + csv + " must have at least 2 Basic Auth columns and an Expiration Date(YYYYMMDD) in each row"); + } + try { + Date date = sdf.parse(row.get(2)); + String oldID = row.get(0); + String newID = row.get(1); + if(date.after(now)) { + if(!oldID.startsWith(BASIC) && newID.startsWith(BASIC)) { + throw new CadiException("CSV file " + csv + ": Uncredentialed ID " + idFromBasic(oldID,null) + + " may not transfer to credentialed ID " + idFromBasic(newID,null)); + } else { + map.put(oldID,newID); + access.printf(Level.INIT, "ID Conversion from %s to %s enabled", + idFromBasic(oldID,null), + idFromBasic(newID,null)); + } + } else { + access.printf(Level.INIT, "ID Conversion from %s to %s has expired.", + idFromBasic(oldID,null), + idFromBasic(newID,null)); + } + } catch (ParseException e) { + throw new CadiException("Cannot Parse Date: " + row.get(2)); + } catch (IOException e) { + throw new CadiException(e); + } + } + }); + } + + private static String idFromBasic(String bath, Holder<String> hpass) throws IOException, CadiException { + if(bath.startsWith(BASIC)) { + String cred = Symm.base64noSplit.decode(bath.substring(6)); + int colon = cred.indexOf(':'); + if(colon<0) { + throw new CadiException("Invalid Authentication Credential for " + cred); + } + if(hpass!=null) { + hpass.value = cred.substring(colon+1); + } + return cred.substring(0, colon); + } else { + return bath; + } + } + + /** + * use to instantiate entries + * + * @return + */ + public Map<String,String> map() { + return map; + } + + public String convert(Access access, final String bath) { + String rv = map.get(bath); + String cred=null; + Holder<String> hpass=null; + try { + if(rv==null || !rv.startsWith(BASIC)) { + if(bath.startsWith(BASIC)) { + cred = idFromBasic(bath,(hpass=new Holder<String>())); + } + } + + if(cred!=null) { + if(rv==null) { + rv = map.get(cred); + } + // for SAFETY REASONS, we WILL NOT allow a non validated cred to + // pass a password from file. Should be caught from Instation, but... + if(rv!=null) { + if(rv.startsWith(BASIC)) { + return bath; + } else { + rv = BASIC + Symm.base64noSplit.encode(rv+':'+hpass.value); + } + } + } + } catch (IOException | CadiException e) { + access.log(e,"Invalid Authorization"); + } + + return rv; + } +} 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 index d5f6b032..3466a8d8 100644 --- 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 @@ -34,16 +34,20 @@ import org.onap.aaf.cadi.Access.Level; import org.onap.aaf.cadi.BasicCred; import org.onap.aaf.cadi.CachedPrincipal; import org.onap.aaf.cadi.CachedPrincipal.Resp; +import org.onap.aaf.cadi.CadiException; import org.onap.aaf.cadi.CredVal; import org.onap.aaf.cadi.CredVal.Type; import org.onap.aaf.cadi.CredValDomain; import org.onap.aaf.cadi.Taf; +import org.onap.aaf.cadi.config.Config; +import org.onap.aaf.cadi.filter.MapBathConverter; 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; +import org.onap.aaf.cadi.util.CSV; /** * BasicHttpTaf @@ -66,6 +70,7 @@ public class BasicHttpTaf implements HttpTaf { private Map<String,CredVal> rbacs = new TreeMap<>(); private boolean warn; private long timeToLive; + private MapBathConverter mapIds; public BasicHttpTaf(Access access, CredVal rbac, String realm, long timeToLive, boolean turnOnWarning) { this.access = access; @@ -73,6 +78,16 @@ public class BasicHttpTaf implements HttpTaf { this.rbac = rbac; this.warn = turnOnWarning; this.timeToLive = timeToLive; + String csvFile = access.getProperty(Config.CADI_BATH_CONVERT, null); + if(csvFile==null) { + mapIds=null; + } else { + try { + mapIds = new MapBathConverter(access, new CSV(csvFile)); + } catch (IOException | CadiException e) { + access.log(e,"Bath Map Conversion is not initialzed (non fatal)"); + } + } } public void add(final CredValDomain cvd) { @@ -116,6 +131,9 @@ public class BasicHttpTaf implements HttpTaf { if (warn&&!req.isSecure()) { access.log(Level.WARN,"WARNING! BasicAuth has been used over an insecure channel"); } + if(mapIds != null) { + authz = mapIds.convert(access, authz); + } try { CachedBasicPrincipal ba = new CachedBasicPrincipal(this,authz,realm,timeToLive); if (DenialOfServiceTaf.isDeniedID(ba.getName())!=null) { diff --git a/cadi/core/src/main/java/org/onap/aaf/cadi/util/CSV.java b/cadi/core/src/main/java/org/onap/aaf/cadi/util/CSV.java new file mode 100644 index 00000000..4ae68310 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/aaf/cadi/util/CSV.java @@ -0,0 +1,190 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + */ + +package org.onap.aaf.cadi.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +import org.onap.aaf.cadi.CadiException; + +/** + * Read CSV file for various purposes + * + * @author Instrumental(Jonathan) + * + */ +public class CSV { + private File csv; + + public CSV(File file) { + csv = file; + } + + public CSV(String csvOfProperties) { + csv = new File(csvOfProperties); + } + + + /** + * Create your code to accept the List<String> row. + * + * Your code may keep the List... CSV does not hold onto it. + * + * @author Instrumental(Jonathan) + * + */ + public interface Visitor { + void visit(List<String> row) throws IOException, CadiException; + } + + public void visit(Visitor visitor) throws IOException, CadiException { + BufferedReader br = new BufferedReader(new FileReader(csv)); + try { + String line; + StringBuilder sb = new StringBuilder(); + while((line = br.readLine())!=null) { + line=line.trim(); + if(!line.startsWith("#") && line.length()>0) { +// System.out.println(line); uncomment to debug + List<String> row = new ArrayList<>(); + boolean quotes=false; + boolean escape=false; + char c; + for(int i=0;i<line.length();++i) { + switch(c=line.charAt(i)) { + case '"': + if(quotes) { + if(i<line.length()-1) { // may look ahead + if('"' == line.charAt(i+1)) { + sb.append(c); + ++i; + } else { + quotes = false; + } + } else { + quotes=false; + } + } else { + quotes=true; + } + break; + case '\\': + if(escape) { + sb.append(c); + escape = false; + } else { + escape = true; + } + break; + case ',': + if(quotes) { + sb.append(c); + } else { + row.add(sb.toString()); + sb.setLength(0); + } + break; + default: + sb.append(c); + } + } + if(sb.length()>0) { + row.add(sb.toString()); + sb.setLength(0); + } + visitor.visit(row); + } + } + } finally { + br.close(); + } + } + + public Writer writer() throws FileNotFoundException { + return new Writer(); + } + + public class Writer { + private PrintStream ps; + private Writer() throws FileNotFoundException { + ps = new PrintStream(new FileOutputStream(csv)); + } + public void row(Object ... strings) { + if(strings.length>0) { + boolean first = true; + boolean quote; + String s; + for(Object o : strings) { + if(first) { + first = false; + } else { + ps.append(','); + } + s = o.toString(); + quote = s.matches(".*[,|\"].*"); + if(quote) { + ps.append('"'); + ps.print(s.replace("\"", "\"\"") + .replace("'", "''") + .replace("\\", "\\\\")); + ps.append('"'); + } else { + ps.append(s); + } + } + ps.println(); + } + } + + /** + * Note: CSV files do not actually support Comments as a standard, but it is useful + * @param comment + */ + public void comment(String comment) { + ps.print("# "); + ps.println(comment); + } + + public void flush() { + ps.flush(); + } + + public void close() { + ps.close(); + } + } + + public void delete() { + csv.delete(); + } + + public String toString() { + return csv.getAbsolutePath(); + } + +} |