1e4b6cbb0d0ea09f8b7b36e99c2e8a93a3252827
[aaf/authz.git] / auth / auth-oauth / src / main / java / org / onap / aaf / auth / oauth / service / OAuthService.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.service;
23
24 import java.io.IOException;
25 import java.security.GeneralSecurityException;
26 import java.util.Date;
27 import java.util.List;
28 import java.util.Set;
29 import java.util.UUID;
30
31 import javax.servlet.http.HttpServletRequest;
32
33 import org.onap.aaf.auth.dao.DAO;
34 import org.onap.aaf.auth.dao.cass.OAuthTokenDAO;
35 import org.onap.aaf.auth.dao.cass.Status;
36 import org.onap.aaf.auth.dao.cass.OAuthTokenDAO.Data;
37 import org.onap.aaf.auth.dao.hl.Question;
38 import org.onap.aaf.auth.direct.DirectAAFUserPass;
39 import org.onap.aaf.auth.env.AuthzTrans;
40 import org.onap.aaf.auth.env.NullTrans;
41 import org.onap.aaf.auth.layer.Result;
42 import org.onap.aaf.cadi.Access;
43 import org.onap.aaf.cadi.CadiException;
44 import org.onap.aaf.cadi.LocatorException;
45 import org.onap.aaf.cadi.CredVal.Type;
46 import org.onap.aaf.cadi.client.Holder;
47 import org.onap.aaf.cadi.config.Config;
48 import org.onap.aaf.cadi.oauth.AAFToken;
49 import org.onap.aaf.cadi.oauth.TokenClient;
50 import org.onap.aaf.cadi.oauth.TokenClientFactory;
51 import org.onap.aaf.cadi.util.Split;
52 import org.onap.aaf.misc.env.APIException;
53
54 import aafoauth.v2_0.Introspect;
55
56 public class OAuthService {
57     
58     private static final int TOK_EXP = 60*60*1000; // 1 hour, millis.
59
60     public enum TOKEN_TYPE {unknown,bearer,refresh}
61     public enum GRANT_TYPE {unknown,password,client_credentials,refresh_token};
62     public enum CLIENT_TYPE {unknown,confidential};
63     
64     // Additional Expires
65     private final DAO<AuthzTrans, ?>[] daos;
66     public final OAuthTokenDAO tokenDAO;
67     private final DirectAAFUserPass directUserPass;
68     private final TokenClientFactory tcf;
69     private TokenClient altIntrospectClient;
70     private String altDomain;
71     private final JSONPermLoader permLoader;
72
73
74     // If we add more CAs, may want to parameterize
75
76     @SuppressWarnings("unchecked")
77     public OAuthService(final Access access, final AuthzTrans trans, final Question q) throws APIException, IOException {
78         permLoader = JSONPermLoaderFactory.direct(q);
79         tokenDAO = new OAuthTokenDAO(trans, q.historyDAO());
80         daos =(DAO<AuthzTrans, ?>[]) new DAO<?,?>[] {
81             tokenDAO
82         };
83         try {
84             String alt_url = access.getProperty(Config.AAF_ALT_OAUTH2_INTROSPECT_URL,null);
85             if (alt_url!=null) {
86                 tcf = TokenClientFactory.instance(access);
87                 String[] split = Split.split(',', alt_url);
88                 int timeout = split.length>1?Integer.parseInt(split[1]):3000;
89                 altIntrospectClient = tcf.newClient(split[0], timeout);
90                 altIntrospectClient.client_creds(access.getProperty(Config.AAF_ALT_CLIENT_ID,null), 
91                                            access.getProperty(Config.AAF_ALT_CLIENT_SECRET,null));
92                 altDomain = '@'+access.getProperty(Config.AAF_ALT_OAUTH2_DOMAIN,null);
93             } else {
94                 tcf = null;
95             }
96             directUserPass = new DirectAAFUserPass(trans.env(), q);
97         } catch (GeneralSecurityException | CadiException | LocatorException e) {
98             throw new APIException("Could not construct TokenClientFactory",e);
99         }
100     
101     }
102
103     public Result<Void> validate(AuthzTrans trans, OCreds creds) {
104         if (directUserPass.validate(creds.username, Type.PASSWORD, creds.password, trans)) {
105             return Result.ok();
106         } else {
107             return Result.err(Result.ERR_Security, "Invalid Credential for ",creds.username);
108         }
109     }
110
111     public Result<Data> createToken(AuthzTrans trans, HttpServletRequest req, OAuthTokenDAO.Data odd, Holder<GRANT_TYPE> hgt) {
112         switch(hgt.get()) {
113             case client_credentials:
114             case password:
115                 return createBearerToken(trans, odd);
116             case refresh_token:
117                 return refreshBearerToken(trans, odd);
118             default:
119                 return Result.err(Result.ERR_BadData, "Unknown Grant Type");
120         }
121     }
122     
123     private Result<Data> createBearerToken(AuthzTrans trans, OAuthTokenDAO.Data odd) {
124         if (odd.user==null) {
125             odd.user = trans.user();
126         }
127         odd.id = AAFToken.toToken(UUID.randomUUID());
128         odd.refresh = AAFToken.toToken(UUID.randomUUID());
129         odd.active = true;
130         long exp;
131         odd.expires = new Date(exp=(System.currentTimeMillis()+TOK_EXP));
132         odd.exp_sec = exp/1000;
133         odd.req_ip = trans.ip();
134     
135         try {
136             Result<Data> rd = loadToken(trans, odd);
137             if (rd.notOK()) {
138                 return rd;
139             }
140         } catch (APIException | CadiException e) {
141             return Result.err(e);
142         }
143         return tokenDAO.create(trans, odd);
144     }
145     
146     private Result<Data> loadToken(AuthzTrans trans, Data odd) throws APIException, CadiException {
147         Result<String> rs = permLoader.loadJSONPerms(trans,odd.user,odd.scopes(false));
148         if (rs.isOK()) {
149             odd.content = rs.value;
150             odd.type = TOKEN_TYPE.bearer.ordinal();
151             return Result.ok(odd);
152         } else if (rs.status == Result.ERR_NotFound || rs.status==Status.ERR_UserRoleNotFound) {
153             odd.type = TOKEN_TYPE.bearer.ordinal();
154             return Result.ok(odd);
155         } else {
156             return Result.err(Result.ERR_Backend,"Error accessing AAF Info: %s",rs.errorString());
157         }
158     }
159     
160     
161
162     private Result<Data> refreshBearerToken(AuthzTrans trans, Data odd) {
163         Result<List<Data>> rld = tokenDAO.readByUser(trans, trans.user());
164         if (rld.notOK()) {
165             return Result.err(rld);
166         }
167         if (rld.isEmpty()) {
168             return Result.err(Result.ERR_NotFound,"Data not Found for %1 %2",trans.user(),odd.refresh==null?"":odd.refresh.toString());
169         }
170         Data token = null;
171         for (Data d : rld.value) {
172             if (d.refresh.equals(odd.refresh)) {
173                 token = d;
174                 boolean scopesNE = false;
175                 Set<String> scopes = odd.scopes(false);
176                 if (scopes.size()>0) { // only check if Scopes listed, RFC 6749, Section 6
177                     if (scopesNE=!(scopes.size() == d.scopes(false).size())) {
178                         for (String s : odd.scopes(false)) {
179                             if (!d.scopes(false).contains(s)) {
180                                 scopesNE=true;
181                                 break;
182                             }
183                         }
184                     }
185                     if (scopesNE) {
186                         return Result.err(Result.ERR_BadData,"Requested Scopes do not match existing Token");
187                     }
188                 }
189                 break;
190             }
191         }
192         
193         if (token==null) {
194             trans.audit().printf("Duplicate Refresh Token (%s) attempted for %s. Possible Replay Attack",odd.refresh.toString(),trans.user());
195             return Result.err(Result.ERR_Security,"Invalid Refresh Token");
196         } else {
197             // Got the Result
198             Data deleteMe = new Data();
199             deleteMe.id = token.id;
200             token.id = AAFToken.toToken(UUID.randomUUID());
201             token.client_id = trans.user();
202             token.refresh = AAFToken.toToken(UUID.randomUUID());
203             long exp;
204             token.expires = new Date(exp=(System.currentTimeMillis()+TOK_EXP));
205             token.exp_sec = exp/1000;
206             token.req_ip = trans.ip();
207             Result<Data> rd = tokenDAO.create(trans, token);
208             if (rd.notOK()) {
209                 return Result.err(rd);
210             }
211             Result<Void> rv = tokenDAO.delete(trans, deleteMe,false);
212             if (rv.notOK()) {
213                 trans.error().log("Unable to delete token", token);
214             }
215         }
216         return Result.ok(token);
217     }
218
219     public Result<OAuthTokenDAO.Data> introspect(AuthzTrans trans, String token) {
220         Result<List<Data>> rld;
221         try {
222             UUID uuid = AAFToken.fromToken(token);
223             if (uuid==null) { // not an AAF Token
224                 // Attempt to get Alternative Token
225                 if (altIntrospectClient!=null) {
226                      org.onap.aaf.cadi.client.Result<Introspect> rai = altIntrospectClient.introspect(token);
227                      if (rai.isOK()) {
228                          Introspect in = rai.value;
229                          if (in.getExp()==null) {
230                             trans.audit().printf("Alt OAuth sent back inactive, empty token: requesting_id,%s,access_token=%s,ip=%s\n",trans.user(),token,trans.ip());
231                          }
232                          long expires = in.getExp()*1000;
233                          if (in.isActive() && expires>System.currentTimeMillis()) {
234                             // We have a good Token, modify to be Fully Qualified
235                             String fqid = in.getUsername()+altDomain;
236                             // read contents
237                             rld = tokenDAO.read(trans, token);
238                             if (rld.isOKhasData()) {
239                                 Data td = rld.value.get(0);
240                                 in.setContent(td.content);
241                             } else {
242                                 Data td = new Data();
243                                 td.id = token;
244                                 td.client_id = in.getClientId();
245                                 td.user = fqid;
246                                 td.active=true;
247                                 td.type = TOKEN_TYPE.bearer.ordinal();
248                                 td.expires = new Date(expires);
249                                 td.exp_sec = in.getExp();
250                                 Set<String> scopes = td.scopes(true);
251                                 if (in.getScope()!=null) {
252                                     for (String s : Split.split(' ', in.getScope())) {
253                                         scopes.add(s);
254                                     }
255                                 }
256                                 // td.state = nothing to add at this point
257                                 td.req_ip = trans.ip();
258                                 trans.checkpoint(td.user + ':' + td.client_id + ", " + td.id);
259                                 return loadToken(trans, td);
260                             }
261                          }
262 //                         System.out.println(rai.value.getClientId());
263                      } else {
264                         trans.audit().printf("Alt OAuth rejects: requesting_id,%s,access_token=%s,ip=%s,code=%d,error=%s\n",trans.user(),token,trans.ip(),rai.code,rai.error);
265                      }
266                 } else {
267                     trans.audit().printf("Bad Token: requesting_id,%s,access_token=%s,ip=%s\n",trans.user(),token,trans.ip());
268                 }
269                 return Result.err(Result.ERR_Denied,"Bad Token");
270             } else {
271                 return dbIntrospect(trans,token);
272             }
273         } catch (CadiException | APIException | LocatorException e) {
274             return Result.err(e);
275         }
276     }
277
278     public Result<Data> dbIntrospect(final AuthzTrans trans, final String token) {
279         Result<List<Data>> rld = tokenDAO.read(trans, token);
280         if (rld.notOKorIsEmpty()) {
281             return Result.err(rld);
282         }
283         OAuthTokenDAO.Data odd = rld.value.get(0);
284         trans.checkpoint(odd.user + ':' + odd.client_id + ", " + odd.id);
285         if (odd.active) {
286             if (odd.expires.before(trans.now())) {
287                 return Result.err(Result.ERR_Policy,"Token %1 has expired",token);
288             }
289             return Result.ok(rld.value.get(0)); // ok keyed on id/token.
290         } else {
291             return Result.err(Result.ERR_Denied,"Token %1 is inactive",token);
292         }
293     }
294
295     public void close() {
296         for (DAO<AuthzTrans,?> dao : daos) {
297             dao.close(NullTrans.singleton());
298         }
299     }
300
301 }