Add a MassMail Batch Program
[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.util.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         exp=(System.currentTimeMillis()+TOK_EXP);
132         odd.expires = new Date(exp);
133         odd.exp_sec = exp/1000;
134         odd.req_ip = trans.ip();
135
136         try {
137             Result<Data> rd = loadToken(trans, odd);
138             if (rd.notOK()) {
139                 return rd;
140             }
141         } catch (APIException | CadiException e) {
142             return Result.err(e);
143         }
144         return tokenDAO.create(trans, odd);
145     }
146
147     private Result<Data> loadToken(AuthzTrans trans, Data odd) throws APIException, CadiException {
148         Result<String> rs = permLoader.loadJSONPerms(trans,odd.user,odd.scopes(false));
149         if (rs.isOK()) {
150             odd.content = rs.value;
151             odd.type = TOKEN_TYPE.bearer.ordinal();
152             return Result.ok(odd);
153         } else if (rs.status == Result.ERR_NotFound || rs.status==Status.ERR_UserRoleNotFound) {
154             odd.type = TOKEN_TYPE.bearer.ordinal();
155             return Result.ok(odd);
156         } else {
157             return Result.err(Result.ERR_Backend,"Error accessing AAF Info: %s",rs.errorString());
158         }
159     }
160
161
162
163     private Result<Data> refreshBearerToken(AuthzTrans trans, Data odd) {
164         Result<List<Data>> rld = tokenDAO.readByUser(trans, trans.user());
165         if (rld.notOK()) {
166             return Result.err(rld);
167         }
168         if (rld.isEmpty()) {
169             return Result.err(Result.ERR_NotFound,"Data not Found for %1 %2",trans.user(),odd.refresh==null?"":odd.refresh.toString());
170         }
171         Data token = null;
172         for (Data d : rld.value) {
173             if (d.refresh.equals(odd.refresh)) {
174                 token = d;
175                 boolean scopesNE = false;
176                 Set<String> scopes = odd.scopes(false);
177                 if (scopes.size()>0) { // only check if Scopes listed, RFC 6749, Section 6
178                     if (scopesNE=!(scopes.size() == d.scopes(false).size())) {
179                         for (String s : odd.scopes(false)) {
180                             if (!d.scopes(false).contains(s)) {
181                                 scopesNE=true;
182                                 break;
183                             }
184                         }
185                     }
186                     if (scopesNE) {
187                         return Result.err(Result.ERR_BadData,"Requested Scopes do not match existing Token");
188                     }
189                 }
190                 break;
191             }
192         }
193
194         if (token==null) {
195             trans.audit().printf("Duplicate Refresh Token (%s) attempted for %s. Possible Replay Attack",odd.refresh.toString(),trans.user());
196             return Result.err(Result.ERR_Security,"Invalid Refresh Token");
197         } else {
198             // Got the Result
199             Data deleteMe = new Data();
200             deleteMe.id = token.id;
201             token.id = AAFToken.toToken(UUID.randomUUID());
202             token.client_id = trans.user();
203             token.refresh = AAFToken.toToken(UUID.randomUUID());
204             long exp;
205             token.expires = new Date(exp=(System.currentTimeMillis()+TOK_EXP));
206             token.exp_sec = exp/1000;
207             token.req_ip = trans.ip();
208             Result<Data> rd = tokenDAO.create(trans, token);
209             if (rd.notOK()) {
210                 return Result.err(rd);
211             }
212             Result<Void> rv = tokenDAO.delete(trans, deleteMe,false);
213             if (rv.notOK()) {
214                 trans.error().log("Unable to delete token", token);
215             }
216         }
217         return Result.ok(token);
218     }
219
220     public Result<OAuthTokenDAO.Data> introspect(AuthzTrans trans, String token) {
221         Result<List<Data>> rld;
222         try {
223             UUID uuid = AAFToken.fromToken(token);
224             if (uuid==null) { // not an AAF Token
225                 // Attempt to get Alternative Token
226                 if (altIntrospectClient!=null) {
227                      org.onap.aaf.cadi.client.Result<Introspect> rai = altIntrospectClient.introspect(token);
228                      if (rai.isOK()) {
229                          Introspect in = rai.value;
230                          if (in.getExp()==null) {
231                             trans.audit().printf("Alt OAuth sent back inactive, empty token: requesting_id,%s,access_token=%s,ip=%s\n",trans.user(),token,trans.ip());
232                          }
233                          long expires = in.getExp()*1000;
234                          if (in.isActive() && expires>System.currentTimeMillis()) {
235                             // We have a good Token, modify to be Fully Qualified
236                             String fqid = in.getUsername()+altDomain;
237                             // read contents
238                             rld = tokenDAO.read(trans, token);
239                             if (rld.isOKhasData()) {
240                                 Data td = rld.value.get(0);
241                                 in.setContent(td.content);
242                             } else {
243                                 Data td = new Data();
244                                 td.id = token;
245                                 td.client_id = in.getClientId();
246                                 td.user = fqid;
247                                 td.active=true;
248                                 td.type = TOKEN_TYPE.bearer.ordinal();
249                                 td.expires = new Date(expires);
250                                 td.exp_sec = in.getExp();
251                                 Set<String> scopes = td.scopes(true);
252                                 if (in.getScope()!=null) {
253                                     for (String s : Split.split(' ', in.getScope())) {
254                                         scopes.add(s);
255                                     }
256                                 }
257                                 // td.state = nothing to add at this point
258                                 td.req_ip = trans.ip();
259                                 trans.checkpoint(td.user + ':' + td.client_id + ", " + td.id);
260                                 return loadToken(trans, td);
261                             }
262                          }
263 //                         System.out.println(rai.value.getClientId());
264                      } else {
265                         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);
266                      }
267                 } else {
268                     trans.audit().printf("Bad Token: requesting_id,%s,access_token=%s,ip=%s\n",trans.user(),token,trans.ip());
269                 }
270                 return Result.err(Result.ERR_Denied,"Bad Token");
271             } else {
272                 return dbIntrospect(trans,token);
273             }
274         } catch (CadiException | APIException | LocatorException e) {
275             return Result.err(e);
276         }
277     }
278
279     public Result<Data> dbIntrospect(final AuthzTrans trans, final String token) {
280         Result<List<Data>> rld = tokenDAO.read(trans, token);
281         if (rld.notOKorIsEmpty()) {
282             return Result.err(rld);
283         }
284         OAuthTokenDAO.Data odd = rld.value.get(0);
285         trans.checkpoint(odd.user + ':' + odd.client_id + ", " + odd.id);
286         if (odd.active) {
287             if (odd.expires.before(trans.now())) {
288                 return Result.err(Result.ERR_Policy,"Token %1 has expired",token);
289             }
290             return Result.ok(rld.value.get(0)); // ok keyed on id/token.
291         } else {
292             return Result.err(Result.ERR_Denied,"Token %1 is inactive",token);
293         }
294     }
295
296     public void close() {
297         for (DAO<AuthzTrans,?> dao : daos) {
298             dao.close(NullTrans.singleton());
299         }
300     }
301
302 }