diff options
Diffstat (limited to 'cadi/core/src/main/java/org/onap/ccsdk/apps/cadi/filter/CadiFilter.java')
-rw-r--r-- | cadi/core/src/main/java/org/onap/ccsdk/apps/cadi/filter/CadiFilter.java | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/cadi/core/src/main/java/org/onap/ccsdk/apps/cadi/filter/CadiFilter.java b/cadi/core/src/main/java/org/onap/ccsdk/apps/cadi/filter/CadiFilter.java new file mode 100644 index 00000000..35d59d52 --- /dev/null +++ b/cadi/core/src/main/java/org/onap/ccsdk/apps/cadi/filter/CadiFilter.java @@ -0,0 +1,358 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.ccsdk + * =========================================================================== + * Copyright (c) 2023 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.ccsdk.apps.cadi.filter; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.List; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.onap.ccsdk.apps.cadi.Access; +import org.onap.ccsdk.apps.cadi.Access.Level; +import org.onap.ccsdk.apps.cadi.CadiException; +import org.onap.ccsdk.apps.cadi.CadiWrap; +import org.onap.ccsdk.apps.cadi.LocatorException; +import org.onap.ccsdk.apps.cadi.Lur; +import org.onap.ccsdk.apps.cadi.PropAccess; +import org.onap.ccsdk.apps.cadi.ServletContextAccess; +import org.onap.ccsdk.apps.cadi.TrustChecker; +import org.onap.ccsdk.apps.cadi.config.Config; +import org.onap.ccsdk.apps.cadi.config.Get; +import org.onap.ccsdk.apps.cadi.taf.TafResp; +import org.onap.ccsdk.apps.cadi.taf.TafResp.RESP; +import org.onap.ccsdk.apps.cadi.util.Timing; + +/** + * 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 SideChain sideChain; + 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") + protected void init(Get getter) throws ServletException { + sideChain = new SideChain(); + // 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.ccsdk.apps.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.ccsdk.apps.cadi.oauth.OAuthFilter"); + sideChain.add(cf.newInstance()); + } catch (ClassNotFoundException e) { + access.log(Level.DEBUG, "OAuthFilter not enabled"); + } + } 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 | LocatorException 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<>(); + 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"); + } + } + } + } + } + } + } + } + + // Add API Enforcement Point + String enforce = getter.get(Config.CADI_API_ENFORCEMENT, null, true); + if(enforce!=null && enforce.length()>0) { + sideChain.add(new CadiApiEnforcementFilter(access,enforce)); + } + // 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 { + final long startAll = System.nanoTime(); + long startCode, startValidate; + float code=0f, validate=0f; + String user = "n/a"; + String tag = ""; + TafResp tresp = null; + try { + HttpServletRequest hreq = (HttpServletRequest)request; + if (noAuthn(hreq)) { + startCode=System.nanoTime(); + chain.doFilter(request, response); + code = Timing.millis(startCode); + } else { + HttpServletResponse hresp = (HttpServletResponse)response; + startValidate=System.nanoTime(); + tresp = httpChecker.validate(hreq, hresp, hreq); + validate = Timing.millis(startValidate); + if (tresp.isAuthenticated()==RESP.IS_AUTHENTICATED) { + user = tresp.getPrincipal().personalName(); + tag = tresp.getPrincipal().tag(); + CadiWrap cw = new CadiWrap(hreq, tresp, httpChecker.getLur(),getConverter(hreq)); + if (httpChecker.notCadi(cw, hresp)) { + startCode=System.nanoTime(); + sideChain.doFilter(cw,response,chain); + code = Timing.millis(startCode); + } + } + } + } catch (ClassCastException e) { + throw new ServletException("CadiFilter expects Servlet to be an HTTP Servlet",e); + } finally { + if (tresp != null) { + access.printf(Level.INFO, "Trans: user=%s[%s],ip=%s,ms=%f,validate=%f,code=%f,result=%s", + user,tag,request.getRemoteAddr(), + Timing.millis(startAll),validate,code,tresp.isAuthenticated().toString()); + } else { + access.printf(Level.INFO, "Trans: user=%s[%s],ip=%s,ms=%f,validate=%f,code=%f,result=FAIL", + user,tag,request.getRemoteAddr(), + Timing.millis(startAll),validate,code); + } + } + } + + + /** + * 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) { + // Attempt to get from URI only (Daniel Rose) + pi = hreq.getRequestURI().substring(hreq.getContextPath().length()); + if(pi==null) { + // Nothing works. + 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; + } + +} + |