29234ed7a6d8fd409e1001aab7f3294bdd872724
[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 Filter oauthFilter;
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         private void init(Get getter) throws ServletException {
143         // Start with the assumption of "Don't trust anyone".
144            TrustChecker tc = TrustChecker.NOTRUST; // default position
145            try {
146                    Class<TrustChecker> ctc = (Class<TrustChecker>) Class.forName("org.onap.aaf.cadi.aaf.v2_0.AAFTrustChecker");
147                    if(ctc!=null) {
148                            Constructor<TrustChecker> contc = ctc.getConstructor(Access.class);
149                            if(contc!=null) {
150                                    tc = contc.newInstance(access);
151                            }
152                    }
153            } catch (Exception e) {
154                    access.log(Level.INIT, "AAFTrustChecker cannot be loaded",e.getMessage());
155            }
156            
157            try {
158                    Class<Filter> cf=null;
159                    try {
160                            cf= (Class<Filter>) Class.forName("org.onap.aaf.cadi.oauth.OAuthFilter");
161                            oauthFilter = cf.newInstance();
162                    } catch (ClassNotFoundException e) {
163                            oauthFilter = new Filter() { // Null Filter
164                                         @Override
165                                         public void destroy() {
166                                         }
167         
168                                         @Override
169                                         public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)throws IOException, ServletException {
170                                                 chain.doFilter(req, resp);
171                                         }
172         
173                                         @Override
174                                         public void init(FilterConfig arg0) throws ServletException {
175                                         }
176                            };
177                    }
178            } catch (Exception e) {
179                    access.log(Level.INIT, "AAFTrustChecker cannot be loaded",e.getMessage());
180            }
181
182         
183         // Synchronize, because some instantiations call init several times on the same object
184         // In this case, the epiTaf will be changed to a non-NullTaf, and thus not instantiate twice.
185                 synchronized(CadiHTTPManip.noAdditional /*will always remain same Object*/) {
186                         ++count;
187                         if(httpChecker == null) {
188                                 if(access==null) {
189                                         access = new PropAccess();
190                                 }
191                                 try {
192                                         httpChecker = new CadiHTTPManip(access,null /*reuseable Con*/,tc, additionalTafLurs);
193                                 } catch (CadiException | LocatorException e1) {
194                                         throw new ServletException(e1);
195                                 }
196                         } else if(access==null) {
197                                 access= httpChecker.getAccess();
198                         }
199
200                         /*
201                          * Setup Authn Path Exceptions
202                          */
203                         if(pathExceptions==null) {
204                                 String str = getter.get(Config.CADI_NOAUTHN, null, true);
205                                 if(str!=null) {
206                                         pathExceptions = str.split("\\s*:\\s*");
207                                 }
208                         }
209         
210                         /* 
211                          * SETUP Permission Converters... those that can take Strings from a Vendor Product, and convert to appropriate AAF Permissions
212                          */
213                         if(mapPairs==null) {
214                                 String str = getter.get(Config.AAF_PERM_MAP, null, true);
215                                 if(str!=null) {
216                                         String mstr = getter.get(Config.AAF_PERM_MAP, null, true);
217                                         if(mstr!=null) {
218                                                 String map[] = mstr.split("\\s*:\\s*");
219                                                 if(map.length>0) {
220                                                         MapPermConverter mpc=null;
221                                                         int idx;
222                                                         mapPairs = new ArrayList<>();
223                                                         for(String entry : map) {
224                                                                 if((idx=entry.indexOf('='))<0) { // it's a Path, so create a new converter
225                                                                         access.log(Level.INIT,"Loading Perm Conversions for:",entry);
226                                                                         mapPairs.add(new Pair(entry,mpc=new MapPermConverter()));
227                                                                 } else {
228                                                                         if(mpc!=null) {
229                                                                                 mpc.map().put(entry.substring(0,idx),entry.substring(idx+1));
230                                                                         } else {
231                                                                                 access.log(Level.ERROR,"cadi_perm_map is malformed; ",entry, "is skipped");
232                                                                         }
233                                                                 }
234                                                         }
235                                                 }
236                                         }
237                                 }
238                         }
239                 }
240
241                 // Remove Getter
242         getter = Get.NULL;
243         }
244
245         /**
246          * Containers call "destroy" when time to cleanup 
247          */
248         public void destroy() {
249                 // Synchronize, in case multiCadiFilters are used.
250                 synchronized(CadiHTTPManip.noAdditional) {
251                         if(--count<=0 && httpChecker!=null) {
252                                 httpChecker.destroy();
253                                 httpChecker=null;
254                                 access=null;
255                                 pathExceptions=null;
256                         }
257                 }
258         }
259
260         /**
261          * doFilter
262          * 
263          * This is the standard J2EE invocation.  Analyze the request, modify response as necessary, and
264          * only call the next item in the filterChain if request is suitably Authenticated.
265          */
266         //TODO Always validate changes against Tomcat AbsCadiValve and Jaspi CadiSAM functions
267         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
268                 final long startAll = System.nanoTime();
269                 long startCode, startValidate;
270                 float code=0f, validate=0f;
271                 String user = "n/a";
272                 String tag = "";
273                 try {
274                         HttpServletRequest hreq = (HttpServletRequest)request;
275                         if(noAuthn(hreq)) {
276                                 startCode=System.nanoTime();
277                                 chain.doFilter(request, response);
278                                 code = Timing.millis(startCode);
279                         } else {
280                                 HttpServletResponse hresp = (HttpServletResponse)response;
281                                 startValidate=System.nanoTime();
282                                 TafResp tresp = httpChecker.validate(hreq, hresp, hreq);
283                                 validate = Timing.millis(startValidate);
284                                 if(tresp.isAuthenticated()==RESP.IS_AUTHENTICATED) {
285                                         user = tresp.getPrincipal().personalName();
286                                         tag = tresp.getPrincipal().tag();
287                                         CadiWrap cw = new CadiWrap(hreq, tresp, httpChecker.getLur(),getConverter(hreq));
288                                         if(httpChecker.notCadi(cw, hresp)) {
289                                                 startCode=System.nanoTime();
290                                                 oauthFilter.doFilter(cw,response,chain);
291                                                 code = Timing.millis(startCode);
292                                         }
293                                 }
294                         }
295                 } catch (ClassCastException e) {
296                         throw new ServletException("CadiFilter expects Servlet to be an HTTP Servlet",e);
297                 } finally {
298                         access.printf(Level.WARN, "Trans: user=%s[%s],ip=%s,ms=%f,validate=%f,code=%f",
299                                 user,tag,request.getRemoteAddr(),
300                                 Timing.millis(startAll),validate,code);
301                 }
302         }
303
304
305         /** 
306          * If PathExceptions exist, report if these should not have Authn applied.
307          * @param hreq
308          * @return
309          */
310         private boolean noAuthn(HttpServletRequest hreq) {
311                 if(pathExceptions!=null) {
312                         String pi = hreq.getPathInfo();
313                         if(pi==null) return false; // JBoss sometimes leaves null
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