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