AT&T 2.0.19 Code drop, stage 3
[aaf/authz.git] / auth / auth-oauth / src / main / java / org / onap / aaf / auth / oauth / facade / OAFacadeImpl.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.auth.oauth.facade;
23
24 import static org.onap.aaf.auth.layer.Result.ERR_ActionNotCompleted;
25 import static org.onap.aaf.auth.layer.Result.ERR_BadData;
26 import static org.onap.aaf.auth.layer.Result.ERR_ConflictAlreadyExists;
27 import static org.onap.aaf.auth.layer.Result.ERR_Denied;
28 import static org.onap.aaf.auth.layer.Result.ERR_NotFound;
29 import static org.onap.aaf.auth.layer.Result.ERR_NotImplemented;
30 import static org.onap.aaf.auth.layer.Result.ERR_Policy;
31 import static org.onap.aaf.auth.layer.Result.ERR_Security;
32 import static org.onap.aaf.auth.layer.Result.OK;
33
34 import java.security.Principal;
35
36 import javax.servlet.http.HttpServletRequest;
37 import javax.servlet.http.HttpServletResponse;
38
39 import org.onap.aaf.auth.dao.cass.OAuthTokenDAO;
40 import org.onap.aaf.auth.dao.cass.Status;
41 import org.onap.aaf.auth.dao.hl.Question;
42 import org.onap.aaf.auth.env.AuthzEnv;
43 import org.onap.aaf.auth.env.AuthzTrans;
44 import org.onap.aaf.auth.layer.Result;
45 import org.onap.aaf.auth.oauth.AAF_OAuth;
46 import org.onap.aaf.auth.oauth.mapper.Mapper;
47 import org.onap.aaf.auth.oauth.mapper.Mapper.API;
48 import org.onap.aaf.auth.oauth.service.OAuthService;
49 import org.onap.aaf.auth.oauth.service.OAuthService.GRANT_TYPE;
50 import org.onap.aaf.cadi.client.Holder;
51 import org.onap.aaf.cadi.oauth.OAuth2Principal;
52 import org.onap.aaf.cadi.principal.OAuth2FormPrincipal;
53 import org.onap.aaf.misc.env.APIException;
54 import org.onap.aaf.misc.env.Data;
55 import org.onap.aaf.misc.env.Env;
56 import org.onap.aaf.misc.env.TimeTaken;
57 import org.onap.aaf.misc.rosetta.env.RosettaDF;
58 import org.onap.aaf.misc.rosetta.env.RosettaData;
59
60 import aaf.v2_0.Perms;
61
62 /**
63  * AuthzFacade
64  * 
65  * This Service Facade encapsulates the essence of the API Service can do, and provides
66  * a single created object for elements such as RosettaDF.
67  *
68  * The Responsibilities of this class are to:
69  * 1) Interact with the Service Implementation (which might be supported by various kinds of Backend Storage)
70  * 2) Validate incoming data (if applicable)
71  * 3) Convert the Service response into the right Format, and mark the Content Type
72  *              a) In the future, we may support multiple Response Formats, aka JSON or XML, based on User Request.
73  * 4) Log Service info, warnings and exceptions as necessary
74  * 5) When asked by the API layer, this will create and write Error content to the OutputStream
75  * 
76  * Note: This Class does NOT set the HTTP Status Code.  That is up to the API layer, so that it can be 
77  * clearly coordinated with the API Documentation
78  * 
79  * @author Jonathan
80  *
81  */
82 public abstract class OAFacadeImpl<TOKEN_REQ,TOKEN,INTROSPECT,ERROR> 
83                 extends DirectIntrospectImpl<INTROSPECT> implements OAFacade<INTROSPECT> {
84         private static final String INVALID_INPUT = "Invalid Input";
85         private final RosettaDF<TOKEN> tokenDF;
86         private final RosettaDF<TOKEN_REQ> tokenReqDF;
87         private final RosettaDF<INTROSPECT> introspectDF;
88         private final RosettaDF<ERROR> errDF;
89         public final RosettaDF<Perms> permsDF;
90         private final Mapper<TOKEN_REQ, TOKEN, INTROSPECT, ERROR> mapper;
91         
92         public OAFacadeImpl(AAF_OAuth api,
93                                           OAuthService service, 
94                                           Mapper<TOKEN_REQ,TOKEN,INTROSPECT,ERROR> mapper,
95                                           Data.TYPE dataType) throws APIException {
96                 super(service, mapper);
97                 this.mapper = mapper;
98                 AuthzEnv env = api.env;
99                 (tokenReqDF             = env.newDataFactory(mapper.getClass(API.TOKEN_REQ))).in(dataType).out(dataType);
100                 (tokenDF                = env.newDataFactory(mapper.getClass(API.TOKEN))).in(dataType).out(dataType);
101                 (introspectDF   = env.newDataFactory(mapper.getClass(API.INTROSPECT))).in(dataType).out(dataType);
102                 (permsDF                = env.newDataFactory(Perms.class)).in(dataType).out(dataType);
103                 (errDF                  = env.newDataFactory(mapper.getClass(API.ERROR))).in(dataType).out(dataType);
104         }
105         
106         ///////////////////////////
107         // Tokens
108         ///////////////////////////
109         public static final String CREATE_TOKEN = "createToken";
110         public static final String INTROSPECT = "introspect";
111         
112         /* (non-Javadoc)
113          * @see org.onap.aaf.auth.oauth.facade.OAFacade#getToken(org.onap.aaf.auth.env.test.AuthzTrans, javax.servlet.http.HttpServletResponse, org.onap.aaf.auth.oauth.service.OAuthAPI)
114          */
115         @Override
116         public Result<Void> createBearerToken(AuthzTrans trans, HttpServletRequest req, HttpServletResponse resp) {
117                 TimeTaken tt = trans.start(CREATE_TOKEN, Env.SUB|Env.ALWAYS);
118                 try {
119                         TOKEN_REQ request;
120                         try {
121                                 request = mapper.tokenReqFromParams(req);
122                                 if(request==null) {
123                                         Data<TOKEN_REQ> rd = tokenReqDF.newData().load(req.getInputStream());
124                                         if(Question.willSpecialLog(trans, trans.user())) {
125                                                 Question.logEncryptTrace(trans,rd.asString());
126                                         }
127                                         request = rd.asObject();
128                                 }
129                         } catch(APIException e) {
130                                 trans.error().log(INVALID_INPUT,IN,CREATE_TOKEN);
131                                 return Result.err(Status.ERR_BadData,INVALID_INPUT);
132                         }
133
134                         // Already validated for Oauth2FormPrincipal
135 //                      Result<Void> rv = service.validate(trans,mapper.credsFromReq(request));
136 //                      if(rv.notOK()) {
137 //                              return rv;
138 //                      }
139                         Holder<GRANT_TYPE> hgt = new Holder<GRANT_TYPE>(GRANT_TYPE.unknown);
140                         Result<OAuthTokenDAO.Data> rs = service.createToken(trans,req,mapper.clientTokenReq(request,hgt),hgt);
141                         Result<TOKEN> rp;
142                         if(rs.isOKhasData()) {
143                                 rp = mapper.tokenFromData(rs);
144                         } else {
145                                 rp = Result.err(rs);
146                         }
147                         switch(rp.status) {
148                                 case OK: 
149                                         RosettaData<TOKEN> data = tokenDF.newData(trans).load(rp.value);
150                                         if(Question.willSpecialLog(trans, trans.user())) {
151                                                 Question.logEncryptTrace(trans,data.asString());
152                                         }
153                                         data.to(resp.getOutputStream());
154                                         resp.getOutputStream().print('\n');
155                                         setContentType(resp,tokenDF.getOutType());
156                                         return Result.ok();
157                                 default:
158                                         return Result.err(rp);
159                         }
160                 } catch (Exception e) {
161                         trans.error().log(e,IN,CREATE_TOKEN);
162                         return Result.err(e);
163                 } finally {
164                         tt.done();
165                 }
166
167         }
168
169 /* (non-Javadoc)
170          * @see org.onap.aaf.auth.oauth.facade.OAFacade#Introspect(org.onap.aaf.auth.env.test.AuthzTrans, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
171          */
172         @Override
173         public Result<Void> introspect(AuthzTrans trans, HttpServletRequest req, HttpServletResponse resp) {
174                 TimeTaken tt = trans.start(INTROSPECT, Env.SUB|Env.ALWAYS);
175                 try {
176                         Principal p = req.getUserPrincipal();
177                         String token=null;
178                         if(p != null) {
179                                 if(p instanceof OAuth2Principal) {
180                                         RosettaData<INTROSPECT> data = introspectDF.newData(trans).load(mapper.fromPrincipal((OAuth2Principal)p));
181                                         if(Question.willSpecialLog(trans, trans.user())) {
182                                                 Question.logEncryptTrace(trans,data.asString());
183                                         }
184                                         data.to(resp.getOutputStream());
185                                         resp.getOutputStream().print('\n');
186                                         setContentType(resp,tokenDF.getOutType());
187                                         return Result.ok();
188                                 } else if(p instanceof OAuth2FormPrincipal) {
189                                         token = req.getParameter("token"); 
190                                 }
191                         }
192                         
193                         if(token==null) {
194                                 token = req.getParameter("access_token");
195                                 if(token==null || token.isEmpty()) {
196                                         token = req.getHeader("Authorization");
197                                         if(token != null && token.startsWith("Bearer ")) {
198                                                 token = token.substring(7);
199                                         } else {
200                                                 token = req.getParameter("token");
201                                                 if(token==null) {
202                                                         return Result.err(Result.ERR_Security,"token is required");
203                                                 }
204                                         }
205                                 }
206                         }
207
208                         Result<INTROSPECT> rti = mappedIntrospect(trans,token);
209                         switch(rti.status) {
210                                 case OK: 
211                                         RosettaData<INTROSPECT> data = introspectDF.newData(trans).load(rti.value);
212                                         if(Question.willSpecialLog(trans, trans.user())) {
213                                                 Question.logEncryptTrace(trans,data.asString());
214                                         }
215                                         data.to(resp.getOutputStream());
216                                         resp.getOutputStream().print('\n');
217                                         setContentType(resp,tokenDF.getOutType());
218                                         return Result.ok();
219                                 default:
220                                         return Result.err(rti);
221                         }
222                 } catch (Exception e) {
223                         trans.error().log(e,IN,INTROSPECT);
224                         return Result.err(e);
225                 } finally {
226                         tt.done();
227                 }
228         }
229         
230
231         /* (non-Javadoc)
232          * @see com.att.authz.facade.AuthzFacade#error(org.onap.aaf.auth.env.test.AuthzTrans, javax.servlet.http.HttpServletResponse, int)
233          * 
234          * Note: Conforms to AT&T TSS RESTful Error Structure
235          */
236         @Override
237         public void error(AuthzTrans trans, HttpServletResponse response, Result<?> result) {
238                 error(trans, response, result.status,
239                                 result.details==null?"":result.details.trim(),
240                                 result.variables==null?new String[0]:result.variables);
241         }
242                 
243         @Override
244         public void error(AuthzTrans trans, HttpServletResponse response, int status, final String _msg, final String ... _detail) {
245                 String msgId;
246                 String prefix;
247                 boolean hidemsg=false;
248                 switch(status) {
249                         case 202:
250                         case ERR_ActionNotCompleted:
251                                 msgId = "SVC1202";
252                                 prefix = "Accepted, Action not complete";
253                                 response.setStatus(/*httpstatus=*/202);
254                                 break;
255
256                         case 403:
257                         case ERR_Policy:
258                         case ERR_Security:
259                         case ERR_Denied:
260                                 msgId = "SVC1403";
261                                 prefix = "Forbidden";
262                                 response.setStatus(/*httpstatus=*/403);
263                                 break;
264                                 
265                         case 404:
266                         case ERR_NotFound:
267                                 msgId = "SVC1404";
268                                 prefix = "Not Found";
269                                 response.setStatus(/*httpstatus=*/404);
270                                 break;
271
272                         case 406:
273                         case ERR_BadData:
274                                 msgId="SVC1406";
275                                 prefix = "Not Acceptable";
276                                 response.setStatus(/*httpstatus=*/406);
277                                 break;
278                                 
279                         case 409:
280                         case ERR_ConflictAlreadyExists:
281                                 msgId = "SVC1409";
282                                 prefix = "Conflict Already Exists";
283                                 response.setStatus(/*httpstatus=*/409);
284                                 break;
285                         
286                         case 501:
287                         case ERR_NotImplemented:
288                                 msgId = "SVC1501";
289                                 prefix = "Not Implemented"; 
290                                 response.setStatus(/*httpstatus=*/501);
291                                 break;
292                                 
293
294                         default:
295                                 msgId = "SVC1500";
296                                 prefix = "General Service Error";
297                                 response.setStatus(/*httpstatus=*/500);
298                                 hidemsg=true;
299                                 break;
300                 }
301
302                 try {
303                         StringBuilder holder = new StringBuilder();
304                         ERROR em = mapper.errorFromMessage(holder, msgId,prefix + ": " + _msg,_detail);
305                         trans.checkpoint(
306                                         "ErrResp [" + 
307                                         msgId +
308                                         "] " +
309                                         holder.toString(),
310                                         Env.ALWAYS);
311                         if(hidemsg) {
312                                 holder.setLength(0);
313                                 em = mapper.errorFromMessage(holder, msgId, "Server had an issue processing this request");
314                         }
315                         errDF.newData(trans).load(em).to(response.getOutputStream());
316                         
317                 } catch (Exception e) {
318                         trans.error().log(e,"unable to send response for",_msg);
319                 }
320         }
321         
322         public Mapper<TOKEN_REQ,TOKEN,INTROSPECT,ERROR> mapper() {
323                 return mapper;
324         }
325         
326         /* (non-Javadoc)
327          * @see org.onap.aaf.auth.oauth.facade.OAFacade#service()
328          */
329         @Override
330         public OAuthService service() {
331                 return service;
332         }
333 }