AT&T 2.0.19 Code drop, stage 2
[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.CadiException;
40 import org.onap.aaf.cadi.CadiWrap;
41 import org.onap.aaf.cadi.Lur;
42 import org.onap.aaf.cadi.PropAccess;
43 import org.onap.aaf.cadi.ServletContextAccess;
44 import org.onap.aaf.cadi.TrustChecker;
45 import org.onap.aaf.cadi.Access.Level;
46 import org.onap.aaf.cadi.config.Config;
47 import org.onap.aaf.cadi.config.Get;
48 import org.onap.aaf.cadi.taf.TafResp;
49 import org.onap.aaf.cadi.taf.TafResp.RESP;
50
51 /**
52  * CadiFilter
53  * 
54  * This class implements Servlet Filter, and ties together CADI implementations
55  * 
56  * This class can be used in a standard J2EE Servlet manner.  Optimal usage is for POJO operations, where
57  * one can enforce this Filter being first and primary.  Depending on the Container, it 
58  * may be more effective, in some cases, to utilize features that allow earlier determination of 
59  * AUTHN (Authorization).  An example would be "Tomcat Valve".  These implementations, however, should
60  * be modeled after the "init" and "doFilter" functions, and be kept up to date as this class changes.
61  * 
62  * 
63  * @author Jonathan
64  *
65  */
66 public class CadiFilter implements Filter {
67         private static CadiHTTPManip httpChecker;
68         private static String[] pathExceptions;
69         private static List<Pair> mapPairs;
70         private Access access;
71         private Object[] additionalTafLurs;
72         private Filter oauthFilter;
73         private static int count=0;
74         
75         public Lur getLur() {
76                 return httpChecker.getLur();
77         }
78         
79         /**
80          * Construct a viable Filter
81          * 
82          * Due to the vagaries of many containers, there is a tendency to create Objects and call "Init" on 
83          * them at a later time.  Therefore, this object creates with an object that denies all access
84          * until appropriate Init happens, just in case the container lets something slip by in the meantime.
85          * 
86          */
87         public CadiFilter() {
88                 additionalTafLurs = CadiHTTPManip.noAdditional;
89         }
90
91         /**
92          * This constructor to be used when directly constructing and placing in HTTP Engine
93          * 
94          * @param access
95          * @param moreTafLurs
96          * @throws ServletException 
97          */
98         public CadiFilter(Access access, Object ... moreTafLurs) throws ServletException {
99                 additionalTafLurs = moreTafLurs;
100                 init(new AccessGetter(this.access = access));
101         }
102
103
104         /**
105          * Use this to pass in a PreContructed CADI Filter, but with initializing... let Servlet do it
106          * @param init
107          * @param access
108          * @param moreTafLurs
109          * @throws ServletException
110          */
111         public CadiFilter(boolean init, PropAccess access, Object ... moreTafLurs) throws ServletException {
112                 this.access = access;
113                 additionalTafLurs = moreTafLurs;
114                 if(init) {
115                         init(new AccessGetter(access));
116                 }
117         }
118
119         /**
120          * Init
121          * 
122          * Standard Filter "init" call with FilterConfig to obtain properties.  POJOs can construct a
123          * FilterConfig with the mechanism of their choice, and standard J2EE Servlet engines utilize this
124          * mechanism already.
125          */
126         //TODO Always validate changes against Tomcat AbsCadiValve and Jaspi CadiSAM Init functions
127         public void init(FilterConfig filterConfig) throws ServletException {
128                 // need the Context for Logging, instantiating ClassLoader, etc
129                 ServletContextAccess sca=new ServletContextAccess(filterConfig); 
130                 if(access==null) {
131                         access = sca;
132                 }
133                 
134                 // Set Protected getter with base Access, for internal class instantiations
135                 init(new FCGet(access, sca.context(), filterConfig));
136         }
137         
138
139         @SuppressWarnings("unchecked")
140         private void init(Get getter) throws ServletException {
141         // Start with the assumption of "Don't trust anyone".
142            TrustChecker tc = TrustChecker.NOTRUST; // default position
143            try {
144                    Class<TrustChecker> ctc = (Class<TrustChecker>) Class.forName("org.onap.aaf.cadi.aaf.v2_0.AAFTrustChecker");
145                    if(ctc!=null) {
146                            Constructor<TrustChecker> contc = ctc.getConstructor(Access.class);
147                            if(contc!=null) {
148                                    tc = contc.newInstance(access);
149                            }
150                    }
151            } catch (Exception e) {
152                    access.log(Level.INIT, "AAFTrustChecker cannot be loaded",e.getMessage());
153            }
154            
155            try {
156                    Class<Filter> cf=null;
157                    try {
158                            cf= (Class<Filter>) Class.forName("org.onap.aaf.cadi.oauth.OAuthFilter");
159                            oauthFilter = cf.newInstance();
160                    } catch (ClassNotFoundException e) {
161                            oauthFilter = new Filter() { // Null Filter
162                                         @Override
163                                         public void destroy() {
164                                         }
165         
166                                         @Override
167                                         public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)throws IOException, ServletException {
168                                                 chain.doFilter(req, resp);
169                                         }
170         
171                                         @Override
172                                         public void init(FilterConfig arg0) throws ServletException {
173                                         }
174                            };
175                    }
176            } catch (Exception e) {
177                    access.log(Level.INIT, "AAFTrustChecker cannot be loaded",e.getMessage());
178            }
179
180         
181         // Synchronize, because some instantiations call init several times on the same object
182         // In this case, the epiTaf will be changed to a non-NullTaf, and thus not instantiate twice.
183                 synchronized(CadiHTTPManip.noAdditional /*will always remain same Object*/) {
184                         ++count;
185                         if(httpChecker == null) {
186                                 if(access==null) {
187                                         access = new PropAccess();
188                                 }
189                                 try {
190                                         httpChecker = new CadiHTTPManip(access,null /*reuseable Con*/,tc, additionalTafLurs);
191                                 } catch (CadiException e1) {
192                                         throw new ServletException(e1);
193                                 }
194                         } else if(access==null) {
195                                 access= httpChecker.getAccess();
196                         }
197
198                         /*
199                          * Setup Authn Path Exceptions
200                          */
201                         if(pathExceptions==null) {
202                                 String str = getter.get(Config.CADI_NOAUTHN, null, true);
203                                 if(str!=null) {
204                                         pathExceptions = str.split("\\s*:\\s*");
205                                 }
206                         }
207         
208                         /* 
209                          * SETUP Permission Converters... those that can take Strings from a Vendor Product, and convert to appropriate AAF Permissions
210                          */
211                         if(mapPairs==null) {
212                                 String str = getter.get(Config.AAF_PERM_MAP, null, true);
213                                 if(str!=null) {
214                                         String mstr = getter.get(Config.AAF_PERM_MAP, null, true);
215                                         if(mstr!=null) {
216                                                 String map[] = mstr.split("\\s*:\\s*");
217                                                 if(map.length>0) {
218                                                         MapPermConverter mpc=null;
219                                                         int idx;
220                                                         mapPairs = new ArrayList<Pair>();
221                                                         for(String entry : map) {
222                                                                 if((idx=entry.indexOf('='))<0) { // it's a Path, so create a new converter
223                                                                         access.log(Level.INIT,"Loading Perm Conversions for:",entry);
224                                                                         mapPairs.add(new Pair(entry,mpc=new MapPermConverter()));
225                                                                 } else {
226                                                                         if(mpc!=null) {
227                                                                                 mpc.map().put(entry.substring(0,idx),entry.substring(idx+1));
228                                                                         } else {
229                                                                                 access.log(Level.ERROR,"cadi_perm_map is malformed; ",entry, "is skipped");
230                                                                         }
231                                                                 }
232                                                         }
233                                                 }
234                                         }
235                                 }
236                         }
237                 }
238
239                 // Remove Getter
240         getter = Get.NULL;
241         }
242
243         /**
244          * Containers call "destroy" when time to cleanup 
245          */
246         public void destroy() {
247                 // Synchronize, in case multiCadiFilters are used.
248                 synchronized(CadiHTTPManip.noAdditional) {
249                         if(--count<=0 && httpChecker!=null) {
250                                 httpChecker.destroy();
251                                 httpChecker=null;
252                                 access=null;
253                                 pathExceptions=null;
254                         }
255                 }
256         }
257
258         /**
259          * doFilter
260          * 
261          * This is the standard J2EE invocation.  Analyze the request, modify response as necessary, and
262          * only call the next item in the filterChain if request is suitably Authenticated.
263          */
264         //TODO Always validate changes against Tomcat AbsCadiValve and Jaspi CadiSAM functions
265         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
266                 try {
267                         HttpServletRequest hreq = (HttpServletRequest)request;
268                         if(noAuthn(hreq)) {
269                                 chain.doFilter(request, response);
270                         } else {
271                                 HttpServletResponse hresp = (HttpServletResponse)response;
272                                 TafResp tresp = httpChecker.validate(hreq, hresp, hreq);
273                                 if(tresp.isAuthenticated()==RESP.IS_AUTHENTICATED) {
274                                         CadiWrap cw = new CadiWrap(hreq, tresp, httpChecker.getLur(),getConverter(hreq));
275                                         if(httpChecker.notCadi(cw, hresp)) {
276                                                 oauthFilter.doFilter(cw,response,chain);
277                                         }
278                                 }                                               
279                         }
280                 } catch (ClassCastException e) {
281                         throw new ServletException("CadiFilter expects Servlet to be an HTTP Servlet",e);
282                 }
283         }
284
285
286         /** 
287          * If PathExceptions exist, report if these should not have Authn applied.
288          * @param hreq
289          * @return
290          */
291         private boolean noAuthn(HttpServletRequest hreq) {
292                 if(pathExceptions!=null) {
293                         String pi = hreq.getPathInfo();
294                         if(pi==null) return false; // JBoss sometimes leaves null
295                         for(String pe : pathExceptions) {
296                                 if(pi.startsWith(pe))return true;
297                         }
298                 }
299                 return false;
300         }
301         
302         /**
303          * Get Converter by Path
304          */
305         private PermConverter getConverter(HttpServletRequest hreq) {
306                 if(mapPairs!=null) {
307                         String pi = hreq.getPathInfo();
308                         if(pi !=null) {
309                                 for(Pair p: mapPairs) {
310                                         if(pi.startsWith(p.name))return p.pc;
311                                 }
312                         }
313                 }
314                 return NullPermConverter.singleton();
315         }
316         
317         /**
318          * store PermConverters by Path prefix
319          * @author Jonathan
320          *
321          */
322         private class Pair {
323                 public Pair(String key, PermConverter pc) {
324                         name = key;
325                         this.pc = pc;
326                 }
327                 public String name;
328                 public PermConverter pc;
329         }
330
331 }
332