Collection syntax change because of Sonar
[aaf/authz.git] / cadi / aaf / src / main / java / org / onap / aaf / cadi / oauth / TokenClient.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.cadi.oauth;
23
24 import java.io.IOException;
25 import java.net.ConnectException;
26 import java.net.URLEncoder;
27 import java.security.NoSuchAlgorithmException;
28 import java.util.ArrayList;
29 import java.util.List;
30
31 import org.onap.aaf.cadi.Access;
32 import org.onap.aaf.cadi.CadiException;
33 import org.onap.aaf.cadi.Hash;
34 import org.onap.aaf.cadi.LocatorException;
35 import org.onap.aaf.cadi.SecuritySetter;
36 import org.onap.aaf.cadi.Access.Level;
37 import org.onap.aaf.cadi.aaf.v2_0.AAFCon;
38 import org.onap.aaf.cadi.aaf.v2_0.AAFCon.GetSetter;
39 import org.onap.aaf.cadi.client.Future;
40 import org.onap.aaf.cadi.client.Rcli;
41 import org.onap.aaf.cadi.client.Result;
42 import org.onap.aaf.cadi.client.Retryable;
43 import org.onap.aaf.cadi.config.Config;
44 import org.onap.aaf.cadi.persist.Persist.Loader;
45 import org.onap.aaf.cadi.principal.Kind;
46 import org.onap.aaf.cadi.util.FQI;
47 import org.onap.aaf.misc.env.APIException;
48 import org.onap.aaf.misc.rosetta.env.RosettaDF;
49
50 import aafoauth.v2_0.Introspect;
51 import aafoauth.v2_0.Token;
52
53 public class TokenClient {
54         private static final String UTF_8 = "UTF-8";
55
56         public enum AUTHN_METHOD {client_credentials,password,payload,basic_auth,certificate,refresh_token, none}
57
58         private final TokenClientFactory factory;
59         private final AAFCon<?> tkCon;
60         private static RosettaDF<Token> tokenDF;
61         protected static RosettaDF<Introspect> introspectDF;
62
63
64         private int timeout;
65         private String client_id, username;
66         private byte[] enc_client_secret, enc_password;
67
68         private GetSetter ss;
69         private AUTHN_METHOD authn_method;
70         private byte[] hash;
71         private final char okind;
72         private String default_scope;
73
74         // Package on Purpose
75         TokenClient(char okind, final TokenClientFactory tcf, final AAFCon<?> tkCon, final int timeout, AUTHN_METHOD am) throws CadiException, APIException {
76                 this.okind = okind;
77                 factory = tcf;
78                 this.tkCon = tkCon;
79                 this.timeout = timeout;
80                 ss = null;
81                 authn_method = am;
82                 synchronized(tcf) {
83                         if(introspectDF==null) {
84                                 tokenDF = tkCon.env().newDataFactory(Token.class);
85                                 introspectDF = tkCon.env().newDataFactory(Introspect.class);
86                         }
87                 }
88  
89         }
90
91         public void client_id(String client_id) {
92                 this.client_id = client_id;
93                 default_scope = FQI.reverseDomain(client_id);
94         }
95         
96         public String client_id() {
97                 return client_id;
98         }
99         
100         /**
101          * This scope based on client_id... the App configured for call
102          * @return
103          */
104         public String defaultScope() {
105                 return default_scope;
106         }
107
108         public void client_creds(Access access) throws CadiException {
109                 if(okind=='A') {
110                         String alias = access.getProperty(Config.CADI_ALIAS, null);
111                         if(alias == null) {
112                                 client_creds(access.getProperty(Config.AAF_APPID, null),access.getProperty(Config.AAF_APPPASS, null));
113                         } else {
114                                 client_creds(alias,null);
115                         }
116                 } else {
117                         client_creds(access.getProperty(Config.AAF_ALT_CLIENT_ID, null),access.getProperty(Config.AAF_ALT_CLIENT_SECRET, null));
118                 }
119         }
120
121         /**
122          * Note: OAuth2 provides for normal Authentication parameters when getting tokens.  Basic Auth is one such valid
123          * way to get Credentials.  However, support is up to the OAuth2 Implementation
124          * 
125          * This method is for setting an App's creds (client) to another App.
126          * 
127          * @param client_id
128          * @param client_secret
129          * @throws IOException
130          */
131         public void client_creds(final String client_id, final String client_secret) throws CadiException {
132                 if(client_id==null) {
133                         throw new CadiException("client_creds:client_id is null");
134                 }
135                 this.client_id = client_id;
136                 default_scope = FQI.reverseDomain(client_id);
137
138                 if(client_secret!=null) {
139                         try {
140                                 if(client_secret.startsWith("enc:")) {
141                                         final String temp = factory.access.decrypt(client_secret, false); // this is a more powerful, but non-thread-safe encryption
142                                         hash = Hash.hashSHA256(temp.getBytes());
143                                         this.enc_client_secret = factory.symm.encode(temp.getBytes());
144                                         ss = new GetSetter() {
145                                                 @Override
146                                                 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
147                                                         return con.basicAuth(client_id, temp);// Base class encrypts password
148                                                 }
149                                         };
150                                 } else {
151                                         byte[] temp = client_secret.getBytes();
152                                         hash = Hash.hashSHA256(temp);
153                                         this.enc_client_secret = factory.symm.encode(temp);
154                                         ss = new GetSetter() {
155                                                 @Override
156                                                 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
157                                                         return con.basicAuth(client_id, client_secret);// Base class encrypts password
158                                                 }
159                                         };
160                                 }
161                                 authn_method = AUTHN_METHOD.client_credentials;
162                         } catch(IOException | NoSuchAlgorithmException e) {
163                                 throw new CadiException(e);
164                         }
165                 } else {
166                         ss = new GetSetter() {
167                                 @Override
168                                 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
169                                         try {
170                                                 return con.x509Alias(client_id);// no password, assume Cert
171                                         } catch (APIException e) {
172                                                 throw new CadiException(e);
173                                         } 
174                                 }                               
175                         };
176                         authn_method = AUTHN_METHOD.client_credentials;
177                 }
178         }
179         
180         public void username(String username) {
181                 this.username = username;
182         }
183
184         /**
185          * Note: OAuth2 provides for normal Authentication parameters when getting tokens.  Basic Auth is one such valid
186          * way to get Credentials.  However, support is up to the OAuth2 Implementation
187          * 
188          * This method is for setting the End-User's Creds
189          * 
190          * @param client_id
191          * @param client_secret
192          * @throws IOException
193          */
194         public void password(final String user, final String password) throws CadiException {
195                 this.username = user;
196                 if(password!=null) {
197                         try {
198                                 if(password.startsWith("enc:")) {
199                                         final String temp = factory.access.decrypt(password, false); // this is a more powerful, but non-thread-safe encryption
200                                         hash = Hash.hashSHA256(temp.getBytes());
201                                         this.enc_password = factory.symm.encode(temp.getBytes());
202                                         ss = new GetSetter() {
203                                                 @Override
204                                                 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
205                                                         return con.basicAuth(user, temp);// Base class encrypts password
206                                                 }
207                                         };
208                                 } else {
209                                         byte[] temp = password.getBytes();
210                                         hash = Hash.hashSHA256(temp);
211                                         this.enc_password = factory.symm.encode(temp);
212                                         ss = new GetSetter() {
213                                                 @Override
214                                                 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
215                                                         return con.basicAuth(user, password);// Base class encrypts password
216                                                 }
217                                         };
218                                 }
219                                 authn_method = AUTHN_METHOD.password;
220                         } catch (IOException | NoSuchAlgorithmException e) {
221                                 throw new CadiException(e);
222                         }
223                 }
224         }
225         
226         public void clearEndUser() {
227                 username = null;
228                 enc_password = null;
229                 if(client_id!=null && enc_client_secret!=null) {
230                         authn_method = AUTHN_METHOD.client_credentials;
231                 } else {
232                         authn_method = AUTHN_METHOD.password;
233                 }
234         }
235
236         public Result<TimedToken> getToken(final String ... scopes) throws LocatorException, CadiException, APIException {
237                 return getToken(Kind.OAUTH,scopes);
238         }
239
240         public void clearToken(final String ... scopes) throws CadiException {
241                 clearToken(Kind.OAUTH,scopes);
242         }
243
244         public void clearToken(final char kind, final String ... scopes) throws CadiException {
245                 final String scope = addScope(scopes);
246                 char c;
247                 if(kind==Kind.OAUTH) {
248                         c = okind;
249                 } else {
250                         c = kind;
251                 }
252                 final String key = TokenClientFactory.getKey(c,client_id,username,hash,scope);
253                 factory.delete(key);
254         }
255         /**
256          * Get AuthToken
257          * @throws APIException 
258          * @throws CadiException 
259          * @throws LocatorException 
260          */
261         public Result<TimedToken> getToken(final char kind, final String ... scopes) throws LocatorException, CadiException, APIException {
262                 final String scope = addScope(scopes);
263                 char c;
264                 if(kind==Kind.OAUTH) {
265                         c = okind;
266                 } else {
267                         c = kind;
268                 }
269                 final String key = TokenClientFactory.getKey(c,client_id,username,hash,scope);
270                 if(ss==null) {
271                         throw new APIException("client_creds(...) must be set before obtaining Access Tokens");
272                 }
273                 
274                 Result<TimedToken> rtt = factory.get(key,hash,new Loader<TimedToken>() {
275                         @Override
276                         public Result<TimedToken> load(final String key) throws APIException, CadiException, LocatorException {
277                                 final List<String> params = new ArrayList<>();
278                                 params.add(scope);
279                                 addSecurity(params,authn_method);
280                         
281                                 final String paramsa[] = new String[params.size()];
282                                 params.toArray(paramsa);
283                                 Result<Token> rt = tkCon.best(new Retryable<Result<Token>>() {
284                                         @Override
285                                         public Result<Token> code(Rcli<?> client) throws CadiException, ConnectException, APIException {
286                                                 // /token?grant_type=client_credential&scope=com.att.aaf+com.att.test
287                                                 Future<Token> f = client.postForm(null,tokenDF,paramsa);
288                                                 if(f.get(timeout)) {
289                                                         return Result.ok(f.code(),f.value);
290                                                 } else {
291                                                         return Result.err(f.code(), f.body());
292                                                 }
293                                         }
294                                 });
295                                 
296                                 if(rt.isOK()) {
297                                         try {
298                                                 return Result.ok(rt.code,factory.putTimedToken(key,rt.value, hash));
299                                         } catch (IOException e) {
300                                                 // TODO What to do here?
301                                                 e.printStackTrace();
302                                                 return Result.err(999,e.getMessage());
303                                         }
304                                 } else {
305                                         return Result.err(rt);
306                                 }
307                         }
308                 });
309                 if(rtt.isOK()) { // not validated for Expired
310                         TimedToken tt = rtt.value;
311                         if(tt.expired()) {
312                                 rtt = refreshToken(tt);
313                                 if(rtt.isOK()) {
314                                         tkCon.access.printf(Level.INFO, "Refreshed token %s to %s",tt.getAccessToken(),rtt.value.getAccessToken());
315                                         return Result.ok(200,rtt.value);
316                                 } else {
317                                         tkCon.access.printf(Level.INFO, "Expired token %s cannot be renewed %d %s",tt.getAccessToken(),rtt.code,rtt.error);
318                                         factory.delete(key);
319                                         tt=null;
320                                 }
321                         } else {
322                                 return Result.ok(200,tt);
323                         }
324                 } else {
325                         Result.err(rtt);
326                 }
327                 return Result.err(404,"Not Found");
328         }
329         
330         public Result<TimedToken> refreshToken(Token token) throws APIException, LocatorException, CadiException {
331                 if(ss==null) {
332                         throw new APIException("client_creds(...) must be set before obtaining Access Tokens");
333                 }
334                 final List<String> params = new ArrayList<>();
335                 params.add("refresh_token="+token.getRefreshToken());
336                 addSecurity(params,AUTHN_METHOD.refresh_token);
337                 final String scope="scope="+token.getScope().replace(' ', '+');
338                 params.add(scope);
339         
340                 final String paramsa[] = new String[params.size()];
341                 params.toArray(paramsa);
342                 Result<Token> rt = tkCon.best(new Retryable<Result<Token>>() {
343                         @Override
344                         public Result<Token> code(Rcli<?> client) throws CadiException, ConnectException, APIException {
345                                 // /token?grant_type=client_credential&scope=com.att.aaf+com.att.test
346                                 Future<Token> f = client.postForm(null,tokenDF,paramsa);
347                                 if(f.get(timeout)) {
348                                         return Result.ok(f.code(),f.value);
349                                 } else {
350                                         return Result.err(f.code(), f.body());
351                                 }
352                         }
353                 });
354                 String key =  TokenClientFactory.getKey(okind,client_id, username, hash, scope);
355                 if(rt.isOK()) {
356                         try {
357                                 return Result.ok(200,factory.putTimedToken(key, rt.value, hash));
358                         } catch (IOException e) {
359                                 //TODO what to do here?
360                                 return Result.err(999, e.getMessage());
361                         }
362                 } else if(rt.code==404) {
363                         factory.deleteFromDisk(key);
364                 }
365                 return Result.err(rt);
366         }
367
368         public Result<Introspect> introspect(final String token) throws APIException, LocatorException, CadiException {
369                 if(ss==null) {
370                         throw new APIException("client_creds(...) must be set before introspecting Access Tokens");
371                 }
372
373                 return tkCon.best(new Retryable<Result<Introspect>>() {
374                                 @Override
375                                 public Result<Introspect> code(Rcli<?> client) throws CadiException, ConnectException, APIException {
376                                         final List<String> params = new ArrayList<>();
377                                         params.add("token="+token);
378                                         addSecurity(params,AUTHN_METHOD.client_credentials);
379                                         final String paramsa[] = new String[params.size()];
380                                         params.toArray(paramsa);
381                                         // /token?grant_type=client_credential&scope=com.att.aaf+com.att.test
382                                         Future<Introspect> f = client.postForm(null,introspectDF,paramsa);
383                                         if(f.get(timeout)) {
384                                                 return Result.ok(f.code(),f.value);
385                                         } else {
386                                                 return Result.err(f.code(), f.body());
387                                         }
388                                 }
389                         }
390                 );
391         }
392         
393         private String addScope(String[] scopes) {
394                 String rv = null;
395                 StringBuilder scope=null;
396                 boolean first = true;
397                 for(String s : scopes) {
398                         if(first) {
399                                 scope = new StringBuilder();
400                                 scope.append("scope=");
401                                 first=false;
402                         } else {
403                                 scope.append('+');
404                         }
405                         scope.append(s);
406                 }
407                 if(scope!=null) {
408                         rv=scope.toString();
409                 }
410                 return rv;
411         }
412
413         private void addSecurity(List<String> params, AUTHN_METHOD authn) throws APIException {
414                 // Set GrantType... different than Credentials
415                 switch(authn) {
416                         case client_credentials:
417                                 params.add("grant_type=client_credentials");
418                                 break;
419                         case password:
420                                 params.add("grant_type=password");
421                                 break;
422                         case refresh_token:
423                                 params.add("grant_type=refresh_token");
424                                 break;
425                         case none:
426                                 break;
427                         default:
428                                 // Nothing to do
429                                 break;
430                 }
431                 
432                 // Set Credentials appropriate 
433                 switch(authn_method) {
434                         case client_credentials:
435                                 if(client_id!=null) {
436                                         params.add("client_id="+client_id);
437                                 }
438                 
439                                 if(enc_client_secret!=null) {
440                                         try {
441                                                 params.add("client_secret="+URLEncoder.encode(new String(factory.symm.decode(enc_client_secret)),UTF_8));
442                                         } catch (IOException e) {
443                                                 throw new APIException("Error Decrypting Password",e);
444                                         }
445                                 }
446                                 break;
447                         case refresh_token:
448                                 if(client_id!=null) {
449                                         params.add("client_id="+client_id);
450                                 }
451                 
452                                 if(enc_client_secret!=null) {
453                                         try {
454                                                 params.add("client_secret="+URLEncoder.encode(new String(factory.symm.decode(enc_client_secret)),UTF_8));
455                                         } catch (IOException e) {
456                                                 throw new APIException("Error Decrypting Password",e);
457                                         }
458                                 }
459                                 break;
460
461                         case password:
462                                 if(client_id!=null) {
463                                         params.add("client_id="+client_id);
464                                 }
465                 
466                                 if(enc_client_secret!=null) {
467                                         try {
468                                                 params.add("client_secret="+ URLEncoder.encode(new String(factory.symm.decode(enc_client_secret)),UTF_8));
469                                         } catch (IOException e) {
470                                                 throw new APIException("Error Decrypting Password",e);
471                                         }
472                                 }
473                                 if(username!=null) {
474                                         params.add("username="+username);
475                                 }
476                 
477                                 if(enc_password!=null) {
478                                         try {
479                                                 params.add("password="+ URLEncoder.encode(new String(factory.symm.decode(enc_password)),UTF_8));
480                                         } catch (IOException e) {
481                                                 throw new APIException("Error Decrypting Password",e);
482                                         }
483                                 }
484         
485                                 break;
486                         default:
487                                 // Nothing to do
488                                 break;
489                 }
490         }
491 }