Merge "Add INFO.yaml file"
[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                         client_creds(access.getProperty(Config.AAF_APPID, null),access.getProperty(Config.AAF_APPPASS, null));
111                 } else {
112                         client_creds(access.getProperty(Config.AAF_ALT_CLIENT_ID, null),access.getProperty(Config.AAF_ALT_CLIENT_SECRET, null));
113                 }
114         }
115
116         /**
117          * Note: OAuth2 provides for normal Authentication parameters when getting tokens.  Basic Auth is one such valid
118          * way to get Credentials.  However, support is up to the OAuth2 Implementation
119          * 
120          * This method is for setting an App's creds (client) to another App.
121          * 
122          * @param client_id
123          * @param client_secret
124          * @throws IOException
125          */
126         public void client_creds(final String client_id, final String client_secret) throws CadiException {
127                 if(client_id==null) {
128                         throw new CadiException(Config.AAF_ALT_CLIENT_ID + " is null");
129                 }
130                 this.client_id = client_id;
131                 default_scope = FQI.reverseDomain(client_id);
132
133                 if(client_secret!=null) {
134                         try {
135                                 if(client_secret.startsWith("enc:")) {
136                                         final String temp = factory.access.decrypt(client_secret, false); // this is a more powerful, but non-thread-safe encryption
137                                         hash = Hash.hashSHA256(temp.getBytes());
138                                         this.enc_client_secret = factory.symm.encode(temp.getBytes());
139                                         ss = new GetSetter() {
140                                                 @Override
141                                                 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
142                                                         return con.basicAuth(client_id, temp);// Base class encrypts password
143                                                 }
144                                         };
145                                 } else {
146                                         byte[] temp = client_secret.getBytes();
147                                         hash = Hash.hashSHA256(temp);
148                                         this.enc_client_secret = factory.symm.encode(temp);
149                                         ss = new GetSetter() {
150                                                 @Override
151                                                 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
152                                                         return con.basicAuth(client_id, client_secret);// Base class encrypts password
153                                                 }
154                                         };
155                                 }
156                                 authn_method = AUTHN_METHOD.client_credentials;
157                         } catch(IOException | NoSuchAlgorithmException e) {
158                                 throw new CadiException(e);
159                         }
160                 }
161         }
162         
163         public void username(String username) {
164                 this.username = username;
165         }
166
167         /**
168          * Note: OAuth2 provides for normal Authentication parameters when getting tokens.  Basic Auth is one such valid
169          * way to get Credentials.  However, support is up to the OAuth2 Implementation
170          * 
171          * This method is for setting the End-User's Creds
172          * 
173          * @param client_id
174          * @param client_secret
175          * @throws IOException
176          */
177         public void password(final String user, final String password) throws CadiException {
178                 this.username = user;
179                 if(password!=null) {
180                         try {
181                                 if(password.startsWith("enc:")) {
182                                         final String temp = factory.access.decrypt(password, false); // this is a more powerful, but non-thread-safe encryption
183                                         hash = Hash.hashSHA256(temp.getBytes());
184                                         this.enc_password = factory.symm.encode(temp.getBytes());
185                                         ss = new GetSetter() {
186                                                 @Override
187                                                 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
188                                                         return con.basicAuth(user, temp);// Base class encrypts password
189                                                 }
190                                         };
191                                 } else {
192                                         byte[] temp = password.getBytes();
193                                         hash = Hash.hashSHA256(temp);
194                                         this.enc_password = factory.symm.encode(temp);
195                                         ss = new GetSetter() {
196                                                 @Override
197                                                 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
198                                                         return con.basicAuth(user, password);// Base class encrypts password
199                                                 }
200                                         };
201                                 }
202                                 authn_method = AUTHN_METHOD.password;
203                         } catch (IOException | NoSuchAlgorithmException e) {
204                                 throw new CadiException(e);
205                         }
206                 }
207         }
208         
209         public void clearEndUser() {
210                 username = null;
211                 enc_password = null;
212                 if(client_id!=null && enc_client_secret!=null) {
213                         authn_method = AUTHN_METHOD.client_credentials;
214                 } else {
215                         authn_method = AUTHN_METHOD.password;
216                 }
217         }
218
219         public Result<TimedToken> getToken(final String ... scopes) throws LocatorException, CadiException, APIException {
220                 return getToken(Kind.OAUTH,scopes);
221         }
222
223         public void clearToken(final String ... scopes) throws CadiException {
224                 clearToken(Kind.OAUTH,scopes);
225         }
226
227         public void clearToken(final char kind, final String ... scopes) throws CadiException {
228                 final String scope = addScope(scopes);
229                 char c;
230                 if(kind==Kind.OAUTH) {
231                         c = okind;
232                 } else {
233                         c = kind;
234                 }
235                 final String key = TokenClientFactory.getKey(c,client_id,username,hash,scope);
236                 factory.delete(key);
237         }
238         /**
239          * Get AuthToken
240          * @throws APIException 
241          * @throws CadiException 
242          * @throws LocatorException 
243          */
244         public Result<TimedToken> getToken(final char kind, final String ... scopes) throws LocatorException, CadiException, APIException {
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                 if(ss==null) {
254                         throw new APIException("client_creds(...) must be set before obtaining Access Tokens");
255                 }
256                 
257                 Result<TimedToken> rtt = factory.get(key,hash,new Loader<TimedToken>() {
258                         @Override
259                         public Result<TimedToken> load(final String key) throws APIException, CadiException, LocatorException {
260                                 final List<String> params = new ArrayList<String>();
261                                 params.add(scope);
262                                 addSecurity(params,authn_method);
263                         
264                                 final String paramsa[] = new String[params.size()];
265                                 params.toArray(paramsa);
266                                 Result<Token> rt = tkCon.best(new Retryable<Result<Token>>() {
267                                         @Override
268                                         public Result<Token> code(Rcli<?> client) throws CadiException, ConnectException, APIException {
269                                                 // /token?grant_type=client_credential&scope=com.att.aaf+com.att.test
270                                                 Future<Token> f = client.postForm(null,tokenDF,paramsa);
271                                                 if(f.get(timeout)) {
272                                                         return Result.ok(f.code(),f.value);
273                                                 } else {
274                                                         return Result.err(f.code(), f.body());
275                                                 }
276                                         }
277                                 });
278                                 
279                                 if(rt.isOK()) {
280                                         try {
281                                                 return Result.ok(rt.code,factory.putTimedToken(key,rt.value, hash));
282                                         } catch (IOException e) {
283                                                 // TODO What to do here?
284                                                 e.printStackTrace();
285                                                 return Result.err(999,e.getMessage());
286                                         }
287                                 } else {
288                                         return Result.err(rt);
289                                 }
290                         }
291                 });
292                 if(rtt.isOK()) { // not validated for Expired
293                         TimedToken tt = rtt.value;
294                         if(tt.expired()) {
295                                 rtt = refreshToken(tt);
296                                 if(rtt.isOK()) {
297                                         tkCon.access.printf(Level.INFO, "Refreshed token %s to %s",tt.getAccessToken(),rtt.value.getAccessToken());
298                                         return Result.ok(200,rtt.value);
299                                 } else {
300                                         tkCon.access.printf(Level.INFO, "Expired token %s cannot be renewed %d %s",tt.getAccessToken(),rtt.code,rtt.error);
301                                         factory.delete(key);
302                                         tt=null;
303                                 }
304                         } else {
305                                 return Result.ok(200,tt);
306                         }
307                 } else {
308                         Result.err(rtt);
309                 }
310                 return Result.err(404,"Not Found");
311         }
312         
313         public Result<TimedToken> refreshToken(Token token) throws APIException, LocatorException, CadiException {
314                 if(ss==null) {
315                         throw new APIException("client_creds(...) must be set before obtaining Access Tokens");
316                 }
317                 final List<String> params = new ArrayList<String>();
318                 params.add("refresh_token="+token.getRefreshToken());
319                 addSecurity(params,AUTHN_METHOD.refresh_token);
320                 final String scope="scope="+token.getScope().replace(' ', '+');
321                 params.add(scope);
322         
323                 final String paramsa[] = new String[params.size()];
324                 params.toArray(paramsa);
325                 Result<Token> rt = tkCon.best(new Retryable<Result<Token>>() {
326                         @Override
327                         public Result<Token> code(Rcli<?> client) throws CadiException, ConnectException, APIException {
328                                 // /token?grant_type=client_credential&scope=com.att.aaf+com.att.test
329                                 Future<Token> f = client.postForm(null,tokenDF,paramsa);
330                                 if(f.get(timeout)) {
331                                         return Result.ok(f.code(),f.value);
332                                 } else {
333                                         return Result.err(f.code(), f.body());
334                                 }
335                         }
336                 });
337                 String key =  TokenClientFactory.getKey(okind,client_id, username, hash, scope);
338                 if(rt.isOK()) {
339                         try {
340                                 return Result.ok(200,factory.putTimedToken(key, rt.value, hash));
341                         } catch (IOException e) {
342                                 //TODO what to do here?
343                                 return Result.err(999, e.getMessage());
344                         }
345                 } else if(rt.code==404) {
346                         factory.deleteFromDisk(key);
347                 }
348                 return Result.err(rt);
349         }
350
351         public Result<Introspect> introspect(final String token) throws APIException, LocatorException, CadiException {
352                 if(ss==null) {
353                         throw new APIException("client_creds(...) must be set before introspecting Access Tokens");
354                 }
355
356                 return tkCon.best(new Retryable<Result<Introspect>>() {
357                                 @Override
358                                 public Result<Introspect> code(Rcli<?> client) throws CadiException, ConnectException, APIException {
359                                         final List<String> params = new ArrayList<String>();
360                                         params.add("token="+token);
361                                         addSecurity(params,AUTHN_METHOD.client_credentials);
362                                         final String paramsa[] = new String[params.size()];
363                                         params.toArray(paramsa);
364                                         // /token?grant_type=client_credential&scope=com.att.aaf+com.att.test
365                                         Future<Introspect> f = client.postForm(null,introspectDF,paramsa);
366                                         if(f.get(timeout)) {
367                                                 return Result.ok(f.code(),f.value);
368                                         } else {
369                                                 return Result.err(f.code(), f.body());
370                                         }
371                                 }
372                         }
373                 );
374         }
375         
376         private String addScope(String[] scopes) {
377                 String rv = null;
378                 StringBuilder scope=null;
379                 boolean first = true;
380                 for(String s : scopes) {
381                         if(first) {
382                                 scope = new StringBuilder();
383                                 scope.append("scope=");
384                                 first=false;
385                         } else {
386                                 scope.append('+');
387                         }
388                         scope.append(s);
389                 }
390                 if(scope!=null) {
391                         rv=scope.toString();
392                 }
393                 return rv;
394         }
395
396         private void addSecurity(List<String> params, AUTHN_METHOD authn) throws APIException {
397                 // Set GrantType... different than Credentials
398                 switch(authn) {
399                         case client_credentials:
400                                 params.add("grant_type=client_credentials");
401                                 break;
402                         case password:
403                                 params.add("grant_type=password");
404                                 break;
405                         case refresh_token:
406                                 params.add("grant_type=refresh_token");
407                                 break;
408                         case none:
409                                 break;
410                         default:
411                                 // Nothing to do
412                                 break;
413                 }
414                 
415                 // Set Credentials appropriate 
416                 switch(authn_method) {
417                         case client_credentials:
418                                 if(client_id!=null) {
419                                         params.add("client_id="+client_id);
420                                 }
421                 
422                                 if(enc_client_secret!=null) {
423                                         try {
424                                                 params.add("client_secret="+URLEncoder.encode(new String(factory.symm.decode(enc_client_secret)),UTF_8));
425                                         } catch (IOException e) {
426                                                 throw new APIException("Error Decrypting Password",e);
427                                         }
428                                 }
429                                 break;
430                         case refresh_token:
431                                 if(client_id!=null) {
432                                         params.add("client_id="+client_id);
433                                 }
434                 
435                                 if(enc_client_secret!=null) {
436                                         try {
437                                                 params.add("client_secret="+URLEncoder.encode(new String(factory.symm.decode(enc_client_secret)),UTF_8));
438                                         } catch (IOException e) {
439                                                 throw new APIException("Error Decrypting Password",e);
440                                         }
441                                 }
442                                 break;
443
444                         case password:
445                                 if(client_id!=null) {
446                                         params.add("client_id="+client_id);
447                                 }
448                 
449                                 if(enc_client_secret!=null) {
450                                         try {
451                                                 params.add("client_secret="+ URLEncoder.encode(new String(factory.symm.decode(enc_client_secret)),UTF_8));
452                                         } catch (IOException e) {
453                                                 throw new APIException("Error Decrypting Password",e);
454                                         }
455                                 }
456                                 if(username!=null) {
457                                         params.add("username="+username);
458                                 }
459                 
460                                 if(enc_password!=null) {
461                                         try {
462                                                 params.add("password="+ URLEncoder.encode(new String(factory.symm.decode(enc_password)),UTF_8));
463                                         } catch (IOException e) {
464                                                 throw new APIException("Error Decrypting Password",e);
465                                         }
466                                 }
467         
468                                 break;
469                         default:
470                                 // Nothing to do
471                                 break;
472                 }
473         }
474 }