Update DCAE Startup Info
[aaf/authz.git] / cadi / core / src / main / java / org / onap / aaf / cadi / filter / CadiFilter.java
1 /**
2  * ============LICENSE_START====================================================
3  * org.onap.aaf
4  * ===========================================================================
5  * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
6  * ===========================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END====================================================
19  *
20  */
21
22 package org.onap.aaf.cadi.filter;
23
24 import java.io.IOException;
25 import java.lang.reflect.Constructor;
26 import java.util.ArrayList;
27 import java.util.List;
28
29 import javax.servlet.Filter;
30 import javax.servlet.FilterChain;
31 import javax.servlet.FilterConfig;
32 import javax.servlet.ServletException;
33 import javax.servlet.ServletRequest;
34 import javax.servlet.ServletResponse;
35 import javax.servlet.http.HttpServletRequest;
36 import javax.servlet.http.HttpServletResponse;
37
38 import org.onap.aaf.cadi.Access;
39 import org.onap.aaf.cadi.Access.Level;
40 import org.onap.aaf.cadi.CadiException;
41 import org.onap.aaf.cadi.CadiWrap;
42 import org.onap.aaf.cadi.LocatorException;
43 import org.onap.aaf.cadi.Lur;
44 import org.onap.aaf.cadi.PropAccess;
45 import org.onap.aaf.cadi.ServletContextAccess;
46 import org.onap.aaf.cadi.TrustChecker;
47 import org.onap.aaf.cadi.config.Config;
48 import org.onap.aaf.cadi.config.Get;
49 import org.onap.aaf.cadi.taf.TafResp;
50 import org.onap.aaf.cadi.taf.TafResp.RESP;
51 import org.onap.aaf.cadi.util.Timing;
52
53 /**
54  * CadiFilter
55  *
56  * This class implements Servlet Filter, and ties together CADI implementations
57  *
58  * This class can be used in a standard J2EE Servlet manner.  Optimal usage is for POJO operations, where
59  * one can enforce this Filter being first and primary.  Depending on the Container, it
60  * may be more effective, in some cases, to utilize features that allow earlier determination of
61  * AUTHN (Authorization).  An example would be "Tomcat Valve".  These implementations, however, should
62  * be modeled after the "init" and "doFilter" functions, and be kept up to date as this class changes.
63  *
64  *
65  * @author Jonathan
66  *
67  */
68 public class CadiFilter implements Filter {
69     private static CadiHTTPManip httpChecker;
70     private static String[] pathExceptions;
71     private static List<Pair> mapPairs;
72     private Access access;
73     private Object[] additionalTafLurs;
74     private SideChain sideChain;
75     private static int count=0;
76
77     public Lur getLur() {
78         return httpChecker.getLur();
79     }
80
81     /**
82      * Construct a viable Filter
83      *
84      * Due to the vagaries of many containers, there is a tendency to create Objects and call "Init" on
85      * them at a later time.  Therefore, this object creates with an object that denies all access
86      * until appropriate Init happens, just in case the container lets something slip by in the meantime.
87      *
88      */
89     public CadiFilter() {
90         additionalTafLurs = CadiHTTPManip.noAdditional;
91     }
92
93     /**
94      * This constructor to be used when directly constructing and placing in HTTP Engine
95      *
96      * @param access
97      * @param moreTafLurs
98      * @throws ServletException
99      */
100     public CadiFilter(Access access, Object ... moreTafLurs) throws ServletException {
101         additionalTafLurs = moreTafLurs;
102         init(new AccessGetter(this.access = access));
103     }
104
105
106     /**
107      * Use this to pass in a PreContructed CADI Filter, but with initializing... let Servlet do it
108      * @param init
109      * @param access
110      * @param moreTafLurs
111      * @throws ServletException
112      */
113     public CadiFilter(boolean init, PropAccess access, Object ... moreTafLurs) throws ServletException {
114         this.access = access;
115         additionalTafLurs = moreTafLurs;
116         if (init) {
117             init(new AccessGetter(access));
118         }
119     }
120
121     /**
122      * Init
123      *
124      * Standard Filter "init" call with FilterConfig to obtain properties.  POJOs can construct a
125      * FilterConfig with the mechanism of their choice, and standard J2EE Servlet engines utilize this
126      * mechanism already.
127      */
128     //TODO Always validate changes against Tomcat AbsCadiValve and Jaspi CadiSAM Init functions
129     public void init(FilterConfig filterConfig) throws ServletException {
130         // need the Context for Logging, instantiating ClassLoader, etc
131         ServletContextAccess sca=new ServletContextAccess(filterConfig);
132         if (access==null) {
133             access = sca;
134         }
135
136         // Set Protected getter with base Access, for internal class instantiations
137         init(new FCGet(access, sca.context(), filterConfig));
138     }
139
140
141     @SuppressWarnings("unchecked")
142     protected void init(Get getter) throws ServletException {
143        sideChain = new SideChain();
144         // Start with the assumption of "Don't trust anyone".
145        TrustChecker tc = TrustChecker.NOTRUST; // default position
146        try {
147            Class<TrustChecker> ctc = (Class<TrustChecker>) Class.forName("org.onap.aaf.cadi.aaf.v2_0.AAFTrustChecker");
148            if (ctc!=null) {
149                Constructor<TrustChecker> contc = ctc.getConstructor(Access.class);
150                if (contc!=null) {
151                    tc = contc.newInstance(access);
152                }
153            }
154        } catch (Exception e) {
155            access.log(Level.INIT, "AAFTrustChecker cannot be loaded",e.getMessage());
156        }
157
158        try {
159            Class<Filter> cf=null;
160            try {
161                cf= (Class<Filter>) Class.forName("org.onap.aaf.cadi.oauth.OAuthFilter");
162                sideChain.add(cf.newInstance());
163            } catch (ClassNotFoundException e) {
164                access.log(Level.DEBUG, "OAuthFilter not enabled");
165            }
166        } catch (Exception e) {
167            access.log(Level.INIT, "AAFTrustChecker cannot be loaded",e.getMessage());
168        }
169
170
171         // Synchronize, because some instantiations call init several times on the same object
172         // In this case, the epiTaf will be changed to a non-NullTaf, and thus not instantiate twice.
173         synchronized(CadiHTTPManip.noAdditional /*will always remain same Object*/) {
174             ++count;
175             if (httpChecker == null) {
176                 if (access==null) {
177                     access = new PropAccess();
178                 }
179                 try {
180                     httpChecker = new CadiHTTPManip(access,null /*reuseable Con*/,tc, additionalTafLurs);
181                 } catch (CadiException | LocatorException e1) {
182                     throw new ServletException(e1);
183                 }
184             } else if (access==null) {
185                 access= httpChecker.getAccess();
186             }
187
188             /*
189              * Setup Authn Path Exceptions
190              */
191             if (pathExceptions==null) {
192                 String str = getter.get(Config.CADI_NOAUTHN, null, true);
193                 if (str!=null) {
194                     pathExceptions = str.split("\\s*:\\s*");
195                 }
196             }
197
198             /*
199              * SETUP Permission Converters... those that can take Strings from a Vendor Product, and convert to appropriate AAF Permissions
200              */
201             if (mapPairs==null) {
202                 String str = getter.get(Config.AAF_PERM_MAP, null, true);
203                 if (str!=null) {
204                     String mstr = getter.get(Config.AAF_PERM_MAP, null, true);
205                     if (mstr!=null) {
206                         String map[] = mstr.split("\\s*:\\s*");
207                         if (map.length>0) {
208                             MapPermConverter mpc=null;
209                             int idx;
210                             mapPairs = new ArrayList<>();
211                             for (String entry : map) {
212                                 if ((idx=entry.indexOf('='))<0) { // it's a Path, so create a new converter
213                                     access.log(Level.INIT,"Loading Perm Conversions for:",entry);
214                                     mapPairs.add(new Pair(entry,mpc=new MapPermConverter()));
215                                 } else {
216                                     if (mpc!=null) {
217                                         mpc.map().put(entry.substring(0,idx),entry.substring(idx+1));
218                                     } else {
219                                         access.log(Level.ERROR,"cadi_perm_map is malformed; ",entry, "is skipped");
220                                     }
221                                 }
222                             }
223                         }
224                     }
225                 }
226             }
227         }
228
229         // Add API Enforcement Point
230         String enforce = getter.get(Config.CADI_API_ENFORCEMENT, null, true);
231         if(enforce!=null && enforce.length()>0) {
232             sideChain.add(new CadiApiEnforcementFilter(access,enforce));
233         }
234         // Remove Getter
235         getter = Get.NULL;
236     }
237
238     /**
239      * Containers call "destroy" when time to cleanup
240      */
241     public void destroy() {
242         // Synchronize, in case multiCadiFilters are used.
243         synchronized(CadiHTTPManip.noAdditional) {
244             if (--count<=0 && httpChecker!=null) {
245                 httpChecker.destroy();
246                 httpChecker=null;
247                 access=null;
248                 pathExceptions=null;
249             }
250         }
251     }
252
253     /**
254      * doFilter
255      *
256      * This is the standard J2EE invocation.  Analyze the request, modify response as necessary, and
257      * only call the next item in the filterChain if request is suitably Authenticated.
258      */
259     //TODO Always validate changes against Tomcat AbsCadiValve and Jaspi CadiSAM functions
260     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
261         final long startAll = System.nanoTime();
262         long startCode, startValidate;
263         float code=0f, validate=0f;
264         String user = "n/a";
265         String tag = "";
266         try {
267             HttpServletRequest hreq = (HttpServletRequest)request;
268             if (noAuthn(hreq)) {
269                 startCode=System.nanoTime();
270                 chain.doFilter(request, response);
271                 code = Timing.millis(startCode);
272             } else {
273                 HttpServletResponse hresp = (HttpServletResponse)response;
274                 startValidate=System.nanoTime();
275                 TafResp tresp = httpChecker.validate(hreq, hresp, hreq);
276                 validate = Timing.millis(startValidate);
277                 if (tresp.isAuthenticated()==RESP.IS_AUTHENTICATED) {
278                     user = tresp.getPrincipal().personalName();
279                     tag = tresp.getPrincipal().tag();
280                     CadiWrap cw = new CadiWrap(hreq, tresp, httpChecker.getLur(),getConverter(hreq));
281                     if (httpChecker.notCadi(cw, hresp)) {
282                         startCode=System.nanoTime();
283                         sideChain.doFilter(cw,response,chain);
284                         code = Timing.millis(startCode);
285                     }
286                 }
287             }
288         } catch (ClassCastException e) {
289             throw new ServletException("CadiFilter expects Servlet to be an HTTP Servlet",e);
290         } finally {
291             access.printf(Level.WARN, "Trans: user=%s[%s],ip=%s,ms=%f,validate=%f,code=%f",
292                 user,tag,request.getRemoteAddr(),
293                 Timing.millis(startAll),validate,code);
294         }
295     }
296
297
298     /**
299      * If PathExceptions exist, report if these should not have Authn applied.
300      * @param hreq
301      * @return
302      */
303     private boolean noAuthn(HttpServletRequest hreq) {
304         if (pathExceptions!=null) {
305             String pi = hreq.getPathInfo();
306             if (pi==null) {
307                 // Attempt to get from URI only  (Daniel Rose)
308                 pi = hreq.getRequestURI().substring(hreq.getContextPath().length());
309                 if(pi==null) {
310                     // Nothing works.
311                     return false; // JBoss sometimes leaves null
312                 }
313             }
314             for (String pe : pathExceptions) {
315                 if (pi.startsWith(pe))return true;
316             }
317         }
318         return false;
319     }
320
321     /**
322      * Get Converter by Path
323      */
324     private PermConverter getConverter(HttpServletRequest hreq) {
325         if (mapPairs!=null) {
326             String pi = hreq.getPathInfo();
327             if (pi !=null) {
328                 for (Pair p: mapPairs) {
329                     if (pi.startsWith(p.name))return p.pc;
330                 }
331             }
332         }
333         return NullPermConverter.singleton();
334     }
335
336     /**
337      * store PermConverters by Path prefix
338      * @author Jonathan
339      *
340      */
341     private class Pair {
342         public Pair(String key, PermConverter pc) {
343             name = key;
344             this.pc = pc;
345         }
346         public String name;
347         public PermConverter pc;
348     }
349
350 }
351