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 String alias = access.getProperty(Config.CADI_ALIAS, null);
112 client_creds(access.getProperty(Config.AAF_APPID, null),access.getProperty(Config.AAF_APPPASS, null));
114 client_creds(alias,null);
117 client_creds(access.getProperty(Config.AAF_ALT_CLIENT_ID, null),access.getProperty(Config.AAF_ALT_CLIENT_SECRET, null));
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
125 * This method is for setting an App's creds (client) to another App.
128 * @param client_secret
129 * @throws IOException
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");
135 this.client_id = client_id;
136 default_scope = FQI.reverseDomain(client_id);
138 if (client_secret!=null) {
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() {
146 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
147 return con.basicAuth(client_id, temp);// Base class encrypts password
151 byte[] temp = client_secret.getBytes();
152 hash = Hash.hashSHA256(temp);
153 this.enc_client_secret = factory.symm.encode(temp);
154 ss = new GetSetter() {
156 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
157 return con.basicAuth(client_id, client_secret);// Base class encrypts password
161 authn_method = AUTHN_METHOD.client_credentials;
162 } catch (IOException | NoSuchAlgorithmException e) {
163 throw new CadiException(e);
166 ss = new GetSetter() {
168 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
170 return con.x509Alias(client_id);// no password, assume Cert
171 } catch (APIException e) {
172 throw new CadiException(e);
176 authn_method = AUTHN_METHOD.client_credentials;
180 public void username(String username) {
181 this.username = username;
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
188 * This method is for setting the End-User's Creds
191 * @param client_secret
192 * @throws IOException
194 public void password(final String user, final String password) throws CadiException {
195 this.username = user;
196 if (password!=null) {
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() {
204 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
205 return con.basicAuth(user, temp);// Base class encrypts password
209 byte[] temp = password.getBytes();
210 hash = Hash.hashSHA256(temp);
211 this.enc_password = factory.symm.encode(temp);
212 ss = new GetSetter() {
214 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
215 return con.basicAuth(user, password);// Base class encrypts password
219 authn_method = AUTHN_METHOD.password;
220 } catch (IOException | NoSuchAlgorithmException e) {
221 throw new CadiException(e);
226 public void clearEndUser() {
229 if (client_id!=null && enc_client_secret!=null) {
230 authn_method = AUTHN_METHOD.client_credentials;
232 authn_method = AUTHN_METHOD.password;
236 public Result<TimedToken> getToken(final String ... scopes) throws LocatorException, CadiException, APIException {
237 return getToken(Kind.OAUTH,scopes);
240 public void clearToken(final String ... scopes) throws CadiException {
241 clearToken(Kind.OAUTH,scopes);
244 public void clearToken(final char kind, final String ... scopes) throws CadiException {
245 final String scope = addScope(scopes);
247 if (kind==Kind.OAUTH) {
252 final String key = TokenClientFactory.getKey(c,client_id,username,hash,scope);
257 * @throws APIException
258 * @throws CadiException
259 * @throws LocatorException
261 public Result<TimedToken> getToken(final char kind, final String ... scopes) throws LocatorException, CadiException, APIException {
262 final String scope = addScope(scopes);
264 if (kind==Kind.OAUTH) {
269 final String key = TokenClientFactory.getKey(c,client_id,username,hash,scope);
271 throw new APIException("client_creds(...) must be set before obtaining Access Tokens");
274 Result<TimedToken> rtt = factory.get(key,hash,new Loader<TimedToken>() {
276 public Result<TimedToken> load(final String key) throws APIException, CadiException, LocatorException {
277 final List<String> params = new ArrayList<>();
279 addSecurity(params,authn_method);
281 final String paramsa[] = new String[params.size()];
282 params.toArray(paramsa);
283 Result<Token> rt = tkCon.best(new Retryable<Result<Token>>() {
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);
291 return Result.err(f.code(), f.body());
298 return Result.ok(rt.code,factory.putTimedToken(key,rt.value, hash));
299 } catch (IOException e) {
300 // TODO What to do here?
302 return Result.err(999,e.getMessage());
305 return Result.err(rt);
309 if (rtt.isOK()) { // not validated for Expired
310 TimedToken tt = rtt.value;
312 rtt = refreshToken(tt);
314 tkCon.access.printf(Level.INFO, "Refreshed token %s to %s",tt.getAccessToken(),rtt.value.getAccessToken());
315 return Result.ok(200,rtt.value);
317 tkCon.access.printf(Level.INFO, "Expired token %s cannot be renewed %d %s",tt.getAccessToken(),rtt.code,rtt.error);
322 return Result.ok(200,tt);
327 return Result.err(404,"Not Found");
330 public Result<TimedToken> refreshToken(Token token) throws APIException, LocatorException, CadiException {
332 throw new APIException("client_creds(...) must be set before obtaining Access Tokens");
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(' ', '+');
340 final String paramsa[] = new String[params.size()];
341 params.toArray(paramsa);
342 Result<Token> rt = tkCon.best(new Retryable<Result<Token>>() {
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);
350 return Result.err(f.code(), f.body());
354 String key = TokenClientFactory.getKey(okind,client_id, username, hash, scope);
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());
362 } else if (rt.code==404) {
363 factory.deleteFromDisk(key);
365 return Result.err(rt);
368 public Result<Introspect> introspect(final String token) throws APIException, LocatorException, CadiException {
370 throw new APIException("client_creds(...) must be set before introspecting Access Tokens");
373 return tkCon.best(new Retryable<Result<Introspect>>() {
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);
386 return Result.err(f.code(), f.body());
393 private String addScope(String[] scopes) {
395 StringBuilder scope=null;
396 boolean first = true;
397 for (String s : scopes) {
399 scope = new StringBuilder();
400 scope.append("scope=");
413 private void addSecurity(List<String> params, AUTHN_METHOD authn) throws APIException {
414 // Set GrantType... different than Credentials
416 case client_credentials:
417 params.add("grant_type=client_credentials");
420 params.add("grant_type=password");
423 params.add("grant_type=refresh_token");
432 // Set Credentials appropriate
433 switch(authn_method) {
434 case client_credentials:
435 if (client_id!=null) {
436 params.add("client_id="+client_id);
439 if (enc_client_secret!=null) {
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);
447 if (username!=null) {
448 params.add("username="+username);
453 if (client_id!=null) {
454 params.add("client_id="+client_id);
457 if (enc_client_secret!=null) {
459 params.add("client_secret="+URLEncoder.encode(new String(factory.symm.decode(enc_client_secret)),UTF_8));
460 } catch (IOException e) {
461 throw new APIException("Error Decrypting Password",e);
467 if (client_id!=null) {
468 params.add("client_id="+client_id);
471 if (enc_client_secret!=null) {
473 params.add("client_secret="+ URLEncoder.encode(new String(factory.symm.decode(enc_client_secret)),UTF_8));
474 } catch (IOException e) {
475 throw new APIException("Error Decrypting Password",e);
478 if (username!=null) {
479 params.add("username="+username);
482 if (enc_password!=null) {
484 params.add("password="+ URLEncoder.encode(new String(factory.symm.decode(enc_password)),UTF_8));
485 } catch (IOException e) {
486 throw new APIException("Error Decrypting Password",e);