052b292e5a32da8bfa1137195b3e5fc4086a72e1
[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 }