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);
161 ss = new GetSetter() {
163 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
165 return con.x509Alias(client_id);// no password, assume Cert
166 } catch (APIException e) {
167 throw new CadiException(e);
171 authn_method = AUTHN_METHOD.client_credentials;
175 public void username(String username) {
176 this.username = username;
180 * Note: OAuth2 provides for normal Authentication parameters when getting tokens. Basic Auth is one such valid
181 * way to get Credentials. However, support is up to the OAuth2 Implementation
183 * This method is for setting the End-User's Creds
186 * @param client_secret
187 * @throws IOException
189 public void password(final String user, final String password) throws CadiException {
190 this.username = user;
193 if(password.startsWith("enc:")) {
194 final String temp = factory.access.decrypt(password, false); // this is a more powerful, but non-thread-safe encryption
195 hash = Hash.hashSHA256(temp.getBytes());
196 this.enc_password = factory.symm.encode(temp.getBytes());
197 ss = new GetSetter() {
199 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
200 return con.basicAuth(user, temp);// Base class encrypts password
204 byte[] temp = password.getBytes();
205 hash = Hash.hashSHA256(temp);
206 this.enc_password = factory.symm.encode(temp);
207 ss = new GetSetter() {
209 public <CLIENT> SecuritySetter<CLIENT> get(AAFCon<CLIENT> con) throws CadiException {
210 return con.basicAuth(user, password);// Base class encrypts password
214 authn_method = AUTHN_METHOD.password;
215 } catch (IOException | NoSuchAlgorithmException e) {
216 throw new CadiException(e);
221 public void clearEndUser() {
224 if(client_id!=null && enc_client_secret!=null) {
225 authn_method = AUTHN_METHOD.client_credentials;
227 authn_method = AUTHN_METHOD.password;
231 public Result<TimedToken> getToken(final String ... scopes) throws LocatorException, CadiException, APIException {
232 return getToken(Kind.OAUTH,scopes);
235 public void clearToken(final String ... scopes) throws CadiException {
236 clearToken(Kind.OAUTH,scopes);
239 public void clearToken(final char kind, final String ... scopes) throws CadiException {
240 final String scope = addScope(scopes);
242 if(kind==Kind.OAUTH) {
247 final String key = TokenClientFactory.getKey(c,client_id,username,hash,scope);
252 * @throws APIException
253 * @throws CadiException
254 * @throws LocatorException
256 public Result<TimedToken> getToken(final char kind, final String ... scopes) throws LocatorException, CadiException, APIException {
257 final String scope = addScope(scopes);
259 if(kind==Kind.OAUTH) {
264 final String key = TokenClientFactory.getKey(c,client_id,username,hash,scope);
266 throw new APIException("client_creds(...) must be set before obtaining Access Tokens");
269 Result<TimedToken> rtt = factory.get(key,hash,new Loader<TimedToken>() {
271 public Result<TimedToken> load(final String key) throws APIException, CadiException, LocatorException {
272 final List<String> params = new ArrayList<String>();
274 addSecurity(params,authn_method);
276 final String paramsa[] = new String[params.size()];
277 params.toArray(paramsa);
278 Result<Token> rt = tkCon.best(new Retryable<Result<Token>>() {
280 public Result<Token> code(Rcli<?> client) throws CadiException, ConnectException, APIException {
281 // /token?grant_type=client_credential&scope=com.att.aaf+com.att.test
282 Future<Token> f = client.postForm(null,tokenDF,paramsa);
284 return Result.ok(f.code(),f.value);
286 return Result.err(f.code(), f.body());
293 return Result.ok(rt.code,factory.putTimedToken(key,rt.value, hash));
294 } catch (IOException e) {
295 // TODO What to do here?
297 return Result.err(999,e.getMessage());
300 return Result.err(rt);
304 if(rtt.isOK()) { // not validated for Expired
305 TimedToken tt = rtt.value;
307 rtt = refreshToken(tt);
309 tkCon.access.printf(Level.INFO, "Refreshed token %s to %s",tt.getAccessToken(),rtt.value.getAccessToken());
310 return Result.ok(200,rtt.value);
312 tkCon.access.printf(Level.INFO, "Expired token %s cannot be renewed %d %s",tt.getAccessToken(),rtt.code,rtt.error);
317 return Result.ok(200,tt);
322 return Result.err(404,"Not Found");
325 public Result<TimedToken> refreshToken(Token token) throws APIException, LocatorException, CadiException {
327 throw new APIException("client_creds(...) must be set before obtaining Access Tokens");
329 final List<String> params = new ArrayList<String>();
330 params.add("refresh_token="+token.getRefreshToken());
331 addSecurity(params,AUTHN_METHOD.refresh_token);
332 final String scope="scope="+token.getScope().replace(' ', '+');
335 final String paramsa[] = new String[params.size()];
336 params.toArray(paramsa);
337 Result<Token> rt = tkCon.best(new Retryable<Result<Token>>() {
339 public Result<Token> code(Rcli<?> client) throws CadiException, ConnectException, APIException {
340 // /token?grant_type=client_credential&scope=com.att.aaf+com.att.test
341 Future<Token> f = client.postForm(null,tokenDF,paramsa);
343 return Result.ok(f.code(),f.value);
345 return Result.err(f.code(), f.body());
349 String key = TokenClientFactory.getKey(okind,client_id, username, hash, scope);
352 return Result.ok(200,factory.putTimedToken(key, rt.value, hash));
353 } catch (IOException e) {
354 //TODO what to do here?
355 return Result.err(999, e.getMessage());
357 } else if(rt.code==404) {
358 factory.deleteFromDisk(key);
360 return Result.err(rt);
363 public Result<Introspect> introspect(final String token) throws APIException, LocatorException, CadiException {
365 throw new APIException("client_creds(...) must be set before introspecting Access Tokens");
368 return tkCon.best(new Retryable<Result<Introspect>>() {
370 public Result<Introspect> code(Rcli<?> client) throws CadiException, ConnectException, APIException {
371 final List<String> params = new ArrayList<String>();
372 params.add("token="+token);
373 addSecurity(params,AUTHN_METHOD.client_credentials);
374 final String paramsa[] = new String[params.size()];
375 params.toArray(paramsa);
376 // /token?grant_type=client_credential&scope=com.att.aaf+com.att.test
377 Future<Introspect> f = client.postForm(null,introspectDF,paramsa);
379 return Result.ok(f.code(),f.value);
381 return Result.err(f.code(), f.body());
388 private String addScope(String[] scopes) {
390 StringBuilder scope=null;
391 boolean first = true;
392 for(String s : scopes) {
394 scope = new StringBuilder();
395 scope.append("scope=");
408 private void addSecurity(List<String> params, AUTHN_METHOD authn) throws APIException {
409 // Set GrantType... different than Credentials
411 case client_credentials:
412 params.add("grant_type=client_credentials");
415 params.add("grant_type=password");
418 params.add("grant_type=refresh_token");
427 // Set Credentials appropriate
428 switch(authn_method) {
429 case client_credentials:
430 if(client_id!=null) {
431 params.add("client_id="+client_id);
434 if(enc_client_secret!=null) {
436 params.add("client_secret="+URLEncoder.encode(new String(factory.symm.decode(enc_client_secret)),UTF_8));
437 } catch (IOException e) {
438 throw new APIException("Error Decrypting Password",e);
443 if(client_id!=null) {
444 params.add("client_id="+client_id);
447 if(enc_client_secret!=null) {
449 params.add("client_secret="+URLEncoder.encode(new String(factory.symm.decode(enc_client_secret)),UTF_8));
450 } catch (IOException e) {
451 throw new APIException("Error Decrypting Password",e);
457 if(client_id!=null) {
458 params.add("client_id="+client_id);
461 if(enc_client_secret!=null) {
463 params.add("client_secret="+ URLEncoder.encode(new String(factory.symm.decode(enc_client_secret)),UTF_8));
464 } catch (IOException e) {
465 throw new APIException("Error Decrypting Password",e);
469 params.add("username="+username);
472 if(enc_password!=null) {
474 params.add("password="+ URLEncoder.encode(new String(factory.symm.decode(enc_password)),UTF_8));
475 } catch (IOException e) {
476 throw new APIException("Error Decrypting Password",e);