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.auth.oauth.service;
24 import java.io.IOException;
25 import java.security.GeneralSecurityException;
26 import java.util.Date;
27 import java.util.List;
29 import java.util.UUID;
31 import javax.servlet.http.HttpServletRequest;
33 import org.onap.aaf.auth.dao.DAO;
34 import org.onap.aaf.auth.dao.cass.OAuthTokenDAO;
35 import org.onap.aaf.auth.dao.cass.Status;
36 import org.onap.aaf.auth.dao.cass.OAuthTokenDAO.Data;
37 import org.onap.aaf.auth.dao.hl.Question;
38 import org.onap.aaf.auth.direct.DirectAAFUserPass;
39 import org.onap.aaf.auth.env.AuthzTrans;
40 import org.onap.aaf.auth.env.NullTrans;
41 import org.onap.aaf.auth.layer.Result;
42 import org.onap.aaf.cadi.Access;
43 import org.onap.aaf.cadi.CadiException;
44 import org.onap.aaf.cadi.LocatorException;
45 import org.onap.aaf.cadi.CredVal.Type;
46 import org.onap.aaf.cadi.client.Holder;
47 import org.onap.aaf.cadi.config.Config;
48 import org.onap.aaf.cadi.oauth.AAFToken;
49 import org.onap.aaf.cadi.oauth.TokenClient;
50 import org.onap.aaf.cadi.oauth.TokenClientFactory;
51 import org.onap.aaf.cadi.util.Split;
52 import org.onap.aaf.misc.env.APIException;
54 import aafoauth.v2_0.Introspect;
56 public class OAuthService {
58 private static final int TOK_EXP = 60*60*1000; // 1 hour, millis.
60 public enum TOKEN_TYPE {unknown,bearer,refresh}
61 public enum GRANT_TYPE {unknown,password,client_credentials,refresh_token};
62 public enum CLIENT_TYPE {unknown,confidential};
65 private final DAO<AuthzTrans, ?>[] daos;
66 public final OAuthTokenDAO tokenDAO;
67 private final DirectAAFUserPass directUserPass;
68 private final TokenClientFactory tcf;
69 private TokenClient altIntrospectClient;
70 private String altDomain;
71 private final JSONPermLoader permLoader;
74 // If we add more CAs, may want to parameterize
76 @SuppressWarnings("unchecked")
77 public OAuthService(final Access access, final AuthzTrans trans, final Question q) throws APIException, IOException {
78 permLoader = JSONPermLoaderFactory.direct(q);
79 tokenDAO = new OAuthTokenDAO(trans, q.historyDAO());
80 daos =(DAO<AuthzTrans, ?>[]) new DAO<?,?>[] {
84 String alt_url = access.getProperty(Config.AAF_ALT_OAUTH2_INTROSPECT_URL,null);
86 tcf = TokenClientFactory.instance(access);
87 String[] split = Split.split(',', alt_url);
88 int timeout = split.length>1?Integer.parseInt(split[1]):3000;
89 altIntrospectClient = tcf.newClient(split[0], timeout);
90 altIntrospectClient.client_creds(access.getProperty(Config.AAF_ALT_CLIENT_ID,null),
91 access.getProperty(Config.AAF_ALT_CLIENT_SECRET,null));
92 altDomain = '@'+access.getProperty(Config.AAF_ALT_OAUTH2_DOMAIN,null);
96 directUserPass = new DirectAAFUserPass(trans.env(), q);
97 } catch (GeneralSecurityException | CadiException | LocatorException e) {
98 throw new APIException("Could not construct TokenClientFactory",e);
103 public Result<Void> validate(AuthzTrans trans, OCreds creds) {
104 if (directUserPass.validate(creds.username, Type.PASSWORD, creds.password, trans)) {
107 return Result.err(Result.ERR_Security, "Invalid Credential for ",creds.username);
111 public Result<Data> createToken(AuthzTrans trans, HttpServletRequest req, OAuthTokenDAO.Data odd, Holder<GRANT_TYPE> hgt) {
113 case client_credentials:
115 return createBearerToken(trans, odd);
117 return refreshBearerToken(trans, odd);
119 return Result.err(Result.ERR_BadData, "Unknown Grant Type");
123 private Result<Data> createBearerToken(AuthzTrans trans, OAuthTokenDAO.Data odd) {
124 if (odd.user==null) {
125 odd.user = trans.user();
127 odd.id = AAFToken.toToken(UUID.randomUUID());
128 odd.refresh = AAFToken.toToken(UUID.randomUUID());
131 exp=(System.currentTimeMillis()+TOK_EXP);
132 odd.expires = new Date(exp);
133 odd.exp_sec = exp/1000;
134 odd.req_ip = trans.ip();
137 Result<Data> rd = loadToken(trans, odd);
141 } catch (APIException | CadiException e) {
142 return Result.err(e);
144 return tokenDAO.create(trans, odd);
147 private Result<Data> loadToken(AuthzTrans trans, Data odd) throws APIException, CadiException {
148 Result<String> rs = permLoader.loadJSONPerms(trans,odd.user,odd.scopes(false));
150 odd.content = rs.value;
151 odd.type = TOKEN_TYPE.bearer.ordinal();
152 return Result.ok(odd);
153 } else if (rs.status == Result.ERR_NotFound || rs.status==Status.ERR_UserRoleNotFound) {
154 odd.type = TOKEN_TYPE.bearer.ordinal();
155 return Result.ok(odd);
157 return Result.err(Result.ERR_Backend,"Error accessing AAF Info: %s",rs.errorString());
163 private Result<Data> refreshBearerToken(AuthzTrans trans, Data odd) {
164 Result<List<Data>> rld = tokenDAO.readByUser(trans, trans.user());
166 return Result.err(rld);
169 return Result.err(Result.ERR_NotFound,"Data not Found for %1 %2",trans.user(),odd.refresh==null?"":odd.refresh.toString());
172 for (Data d : rld.value) {
173 if (d.refresh.equals(odd.refresh)) {
175 boolean scopesNE = false;
176 Set<String> scopes = odd.scopes(false);
177 if (scopes.size()>0) { // only check if Scopes listed, RFC 6749, Section 6
178 if (scopesNE=!(scopes.size() == d.scopes(false).size())) {
179 for (String s : odd.scopes(false)) {
180 if (!d.scopes(false).contains(s)) {
187 return Result.err(Result.ERR_BadData,"Requested Scopes do not match existing Token");
195 trans.audit().printf("Duplicate Refresh Token (%s) attempted for %s. Possible Replay Attack",odd.refresh.toString(),trans.user());
196 return Result.err(Result.ERR_Security,"Invalid Refresh Token");
199 Data deleteMe = new Data();
200 deleteMe.id = token.id;
201 token.id = AAFToken.toToken(UUID.randomUUID());
202 token.client_id = trans.user();
203 token.refresh = AAFToken.toToken(UUID.randomUUID());
205 token.expires = new Date(exp=(System.currentTimeMillis()+TOK_EXP));
206 token.exp_sec = exp/1000;
207 token.req_ip = trans.ip();
208 Result<Data> rd = tokenDAO.create(trans, token);
210 return Result.err(rd);
212 Result<Void> rv = tokenDAO.delete(trans, deleteMe,false);
214 trans.error().log("Unable to delete token", token);
217 return Result.ok(token);
220 public Result<OAuthTokenDAO.Data> introspect(AuthzTrans trans, String token) {
221 Result<List<Data>> rld;
223 UUID uuid = AAFToken.fromToken(token);
224 if (uuid==null) { // not an AAF Token
225 // Attempt to get Alternative Token
226 if (altIntrospectClient!=null) {
227 org.onap.aaf.cadi.client.Result<Introspect> rai = altIntrospectClient.introspect(token);
229 Introspect in = rai.value;
230 if (in.getExp()==null) {
231 trans.audit().printf("Alt OAuth sent back inactive, empty token: requesting_id,%s,access_token=%s,ip=%s\n",trans.user(),token,trans.ip());
233 long expires = in.getExp()*1000;
234 if (in.isActive() && expires>System.currentTimeMillis()) {
235 // We have a good Token, modify to be Fully Qualified
236 String fqid = in.getUsername()+altDomain;
238 rld = tokenDAO.read(trans, token);
239 if (rld.isOKhasData()) {
240 Data td = rld.value.get(0);
241 in.setContent(td.content);
243 Data td = new Data();
245 td.client_id = in.getClientId();
248 td.type = TOKEN_TYPE.bearer.ordinal();
249 td.expires = new Date(expires);
250 td.exp_sec = in.getExp();
251 Set<String> scopes = td.scopes(true);
252 if (in.getScope()!=null) {
253 for (String s : Split.split(' ', in.getScope())) {
257 // td.state = nothing to add at this point
258 td.req_ip = trans.ip();
259 trans.checkpoint(td.user + ':' + td.client_id + ", " + td.id);
260 return loadToken(trans, td);
263 // System.out.println(rai.value.getClientId());
265 trans.audit().printf("Alt OAuth rejects: requesting_id,%s,access_token=%s,ip=%s,code=%d,error=%s\n",trans.user(),token,trans.ip(),rai.code,rai.error);
268 trans.audit().printf("Bad Token: requesting_id,%s,access_token=%s,ip=%s\n",trans.user(),token,trans.ip());
270 return Result.err(Result.ERR_Denied,"Bad Token");
272 return dbIntrospect(trans,token);
274 } catch (CadiException | APIException | LocatorException e) {
275 return Result.err(e);
279 public Result<Data> dbIntrospect(final AuthzTrans trans, final String token) {
280 Result<List<Data>> rld = tokenDAO.read(trans, token);
281 if (rld.notOKorIsEmpty()) {
282 return Result.err(rld);
284 OAuthTokenDAO.Data odd = rld.value.get(0);
285 trans.checkpoint(odd.user + ':' + odd.client_id + ", " + odd.id);
287 if (odd.expires.before(trans.now())) {
288 return Result.err(Result.ERR_Policy,"Token %1 has expired",token);
290 return Result.ok(rld.value.get(0)); // ok keyed on id/token.
292 return Result.err(Result.ERR_Denied,"Token %1 is inactive",token);
296 public void close() {
297 for (DAO<AuthzTrans,?> dao : daos) {
298 dao.close(NullTrans.singleton());