2 * ============LICENSE_START====================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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====================================================
22 package org.onap.aaf.cadi.oauth;
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;
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;
50 import aafoauth.v2_0.Introspect;
51 import aafoauth.v2_0.Token;
53 public class TokenClient {
54 private static final String UTF_8 = "UTF-8";
56 public enum AUTHN_METHOD {client_credentials,password,payload,basic_auth,certificate,refresh_token, none}
58 private final TokenClientFactory factory;
59 private final AAFCon<?> tkCon;
60 private static RosettaDF<Token> tokenDF;
61 protected static RosettaDF<Introspect> introspectDF;
65 private String client_id, username;
66 private byte[] enc_client_secret, enc_password;
69 private AUTHN_METHOD authn_method;
71 private final char okind;
72 private String default_scope;
75 TokenClient(char okind, final TokenClientFactory tcf, final AAFCon<?> tkCon, final int timeout, AUTHN_METHOD am) throws CadiException, APIException {
79 this.timeout = timeout;
83 if(introspectDF==null) {
84 tokenDF = tkCon.env().newDataFactory(Token.class);
85 introspectDF = tkCon.env().newDataFactory(Introspect.class);
91 public void client_id(String client_id) {
92 this.client_id = client_id;
93 default_scope = FQI.reverseDomain(client_id);
96 public String client_id() {
101 * This scope based on client_id... the App configured for call
104 public String defaultScope() {
105 return default_scope;
108 public void client_creds(Access access) throws CadiException {
110 client_creds(access.getProperty(Config.AAF_APPID, null),access.getProperty(Config.AAF_APPPASS, null));
112 client_creds(access.getProperty(Config.AAF_ALT_CLIENT_ID, null),access.getProperty(Config.AAF_ALT_CLIENT_SECRET, null));
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
120 * This method is for setting an App's creds (client) to another App.
123 * @param client_secret
124 * @throws IOException
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");
130 this.client_id = client_id;
131 default_scope = FQI.reverseDomain(client_id);
133 if(client_secret!=null) {
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() {
141 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
142 return con.basicAuth(client_id, temp);// Base class encrypts password
146 byte[] temp = client_secret.getBytes();
147 hash = Hash.hashSHA256(temp);
148 this.enc_client_secret = factory.symm.encode(temp);
149 ss = new GetSetter() {
151 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
152 return con.basicAuth(client_id, client_secret);// Base class encrypts password
156 authn_method = AUTHN_METHOD.client_credentials;
157 } catch(IOException | NoSuchAlgorithmException e) {
158 throw new CadiException(e);
163 public void username(String username) {
164 this.username = username;
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
171 * This method is for setting the End-User's Creds
174 * @param client_secret
175 * @throws IOException
177 public void password(final String user, final String password) throws CadiException {
178 this.username = user;
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() {
187 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
188 return con.basicAuth(user, temp);// Base class encrypts password
192 byte[] temp = password.getBytes();
193 hash = Hash.hashSHA256(temp);
194 this.enc_password = factory.symm.encode(temp);
195 ss = new GetSetter() {
197 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
198 return con.basicAuth(user, password);// Base class encrypts password
202 authn_method = AUTHN_METHOD.password;
203 } catch (IOException | NoSuchAlgorithmException e) {
204 throw new CadiException(e);
209 public void clearEndUser() {
212 if(client_id!=null && enc_client_secret!=null) {
213 authn_method = AUTHN_METHOD.client_credentials;
215 authn_method = AUTHN_METHOD.password;
219 public Result<TimedToken> getToken(final String ... scopes) throws LocatorException, CadiException, APIException {
220 return getToken(Kind.OAUTH,scopes);
223 public void clearToken(final String ... scopes) throws CadiException {
224 clearToken(Kind.OAUTH,scopes);
227 public void clearToken(final char kind, final String ... scopes) throws CadiException {
228 final String scope = addScope(scopes);
230 if(kind==Kind.OAUTH) {
235 final String key = TokenClientFactory.getKey(c,client_id,username,hash,scope);
240 * @throws APIException
241 * @throws CadiException
242 * @throws LocatorException
244 public Result<TimedToken> getToken(final char kind, final String ... scopes) throws LocatorException, CadiException, APIException {
245 final String scope = addScope(scopes);
247 if(kind==Kind.OAUTH) {
252 final String key = TokenClientFactory.getKey(c,client_id,username,hash,scope);
254 throw new APIException("client_creds(...) must be set before obtaining Access Tokens");
257 Result<TimedToken> rtt = factory.get(key,hash,new Loader<TimedToken>() {
259 public Result<TimedToken> load(final String key) throws APIException, CadiException, LocatorException {
260 final List<String> params = new ArrayList<String>();
262 addSecurity(params,authn_method);
264 final String paramsa[] = new String[params.size()];
265 params.toArray(paramsa);
266 Result<Token> rt = tkCon.best(new Retryable<Result<Token>>() {
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);
272 return Result.ok(f.code(),f.value);
274 return Result.err(f.code(), f.body());
281 return Result.ok(rt.code,factory.putTimedToken(key,rt.value, hash));
282 } catch (IOException e) {
283 // TODO What to do here?
285 return Result.err(999,e.getMessage());
288 return Result.err(rt);
292 if(rtt.isOK()) { // not validated for Expired
293 TimedToken tt = rtt.value;
295 rtt = refreshToken(tt);
297 tkCon.access.printf(Level.INFO, "Refreshed token %s to %s",tt.getAccessToken(),rtt.value.getAccessToken());
298 return Result.ok(200,rtt.value);
300 tkCon.access.printf(Level.INFO, "Expired token %s cannot be renewed %d %s",tt.getAccessToken(),rtt.code,rtt.error);
305 return Result.ok(200,tt);
310 return Result.err(404,"Not Found");
313 public Result<TimedToken> refreshToken(Token token) throws APIException, LocatorException, CadiException {
315 throw new APIException("client_creds(...) must be set before obtaining Access Tokens");
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(' ', '+');
323 final String paramsa[] = new String[params.size()];
324 params.toArray(paramsa);
325 Result<Token> rt = tkCon.best(new Retryable<Result<Token>>() {
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);
331 return Result.ok(f.code(),f.value);
333 return Result.err(f.code(), f.body());
337 String key = TokenClientFactory.getKey(okind,client_id, username, hash, scope);
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());
345 } else if(rt.code==404) {
346 factory.deleteFromDisk(key);
348 return Result.err(rt);
351 public Result<Introspect> introspect(final String token) throws APIException, LocatorException, CadiException {
353 throw new APIException("client_creds(...) must be set before introspecting Access Tokens");
356 return tkCon.best(new Retryable<Result<Introspect>>() {
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);
367 return Result.ok(f.code(),f.value);
369 return Result.err(f.code(), f.body());
376 private String addScope(String[] scopes) {
378 StringBuilder scope=null;
379 boolean first = true;
380 for(String s : scopes) {
382 scope = new StringBuilder();
383 scope.append("scope=");
396 private void addSecurity(List<String> params, AUTHN_METHOD authn) throws APIException {
397 // Set GrantType... different than Credentials
399 case client_credentials:
400 params.add("grant_type=client_credentials");
403 params.add("grant_type=password");
406 params.add("grant_type=refresh_token");
415 // Set Credentials appropriate
416 switch(authn_method) {
417 case client_credentials:
418 if(client_id!=null) {
419 params.add("client_id="+client_id);
422 if(enc_client_secret!=null) {
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);
431 if(client_id!=null) {
432 params.add("client_id="+client_id);
435 if(enc_client_secret!=null) {
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);
445 if(client_id!=null) {
446 params.add("client_id="+client_id);
449 if(enc_client_secret!=null) {
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);
457 params.add("username="+username);
460 if(enc_password!=null) {
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);