diff options
Diffstat (limited to 'auth/auth-oauth/src/main')
23 files changed, 2173 insertions, 0 deletions
diff --git a/auth/auth-oauth/src/main/config/.gitignore b/auth/auth-oauth/src/main/config/.gitignore new file mode 100644 index 00000000..e53ef90a --- /dev/null +++ b/auth/auth-oauth/src/main/config/.gitignore @@ -0,0 +1 @@ +/log4j.properties diff --git a/auth/auth-oauth/src/main/config/oauth.props b/auth/auth-oauth/src/main/config/oauth.props new file mode 100644 index 00000000..cdd382d1 --- /dev/null +++ b/auth/auth-oauth/src/main/config/oauth.props @@ -0,0 +1,26 @@ +## +## AAF OAUTH2 API (authz-oauth) Properties +## + +# Standard AFT for this box +hostname=_HOSTNAME_ + +## DISCOVERY (DME2) Parameters on the Command Line +AFT_LATITUDE=_AFT_LATITUDE_ +AFT_LONGITUDE=_AFT_LONGITUDE_ +AFT_ENVIRONMENT=_AFT_ENVIRONMENT_ +DEPLOYED_VERSION=_ARTIFACT_VERSION_ + +## Pull in common/security properties + +cadi_prop_files=_COMMON_DIR_/com.att.aaf.common.props:_COMMON_DIR_/com.att.aaf.props + +##DME2 related parameters + +DMEServiceName=service=com.att.authz.oauth/version=_MAJOR_VER_._MINOR_VER_._PATCH_VER_/envContext=_ENV_CONTEXT_/routeOffer=_ROUTE_OFFER_ +AFT_DME2_PORT_RANGE=_AUTHZ_OAUTH_PORT_RANGE_ + + + + + diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/AAF_OAuth.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/AAF_OAuth.java new file mode 100644 index 00000000..1dac22fc --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/AAF_OAuth.java @@ -0,0 +1,196 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + + +package org.onap.aaf.auth.oauth; + +import java.util.Map; + +import javax.servlet.Filter; + +import org.onap.aaf.auth.cache.Cache; +import org.onap.aaf.auth.cache.Cache.Dated; +import org.onap.aaf.auth.dao.CassAccess; +import org.onap.aaf.auth.dao.hl.Question; +import org.onap.aaf.auth.direct.DirectLocatorCreator; +import org.onap.aaf.auth.direct.DirectRegistrar; +import org.onap.aaf.auth.env.AuthzEnv; +import org.onap.aaf.auth.env.AuthzTrans; +import org.onap.aaf.auth.env.AuthzTransFilter; +import org.onap.aaf.auth.oauth.api.API_Token; +import org.onap.aaf.auth.oauth.facade.OAFacade; +import org.onap.aaf.auth.oauth.facade.OAFacade1_0; +import org.onap.aaf.auth.oauth.facade.OAFacadeFactory; +import org.onap.aaf.auth.oauth.mapper.Mapper.API; +import org.onap.aaf.auth.oauth.service.OAuthService; +import org.onap.aaf.auth.rserv.HttpCode; +import org.onap.aaf.auth.rserv.HttpMethods; +import org.onap.aaf.auth.server.AbsService; +import org.onap.aaf.auth.server.JettyServiceStarter; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.LocatorException; +import org.onap.aaf.cadi.PropAccess; +import org.onap.aaf.cadi.aaf.v2_0.AAFAuthn; +import org.onap.aaf.cadi.aaf.v2_0.AAFLurPerm; +import org.onap.aaf.cadi.aaf.v2_0.AAFTrustChecker; +import org.onap.aaf.cadi.aaf.v2_0.AbsAAFLocator; +import org.onap.aaf.cadi.config.Config; +import org.onap.aaf.cadi.oauth.TokenMgr; +import org.onap.aaf.cadi.oauth.TokenMgr.TokenPermLoader; +import org.onap.aaf.cadi.register.Registrant; +import org.onap.aaf.misc.env.APIException; +import org.onap.aaf.misc.env.Env; +import org.onap.aaf.misc.env.Data.TYPE; + +import com.datastax.driver.core.Cluster; + +import aafoauth.v2_0.Introspect; + +public class AAF_OAuth extends AbsService<AuthzEnv,AuthzTrans> { + private static final String DOT_OAUTH = ".oauth"; + public Map<String, Dated> cacheUser; + public AAFAuthn<?> aafAuthn; + public AAFLurPerm aafLurPerm; + private final OAuthService service; + private OAFacade1_0 facade1_0; + private final Question question; + private TokenPermLoader tpLoader; + private final Cluster cluster; + + /** + * Construct AuthzAPI with all the Context Supporting Routes that Authz needs + * + * @param env + * @param si + * @param dm + * @param decryptor + * @throws APIException + */ + public AAF_OAuth(final AuthzEnv env) throws Exception { + super(env.access(),env); + + String aaf_env = env.getProperty(Config.AAF_ENV); + if(aaf_env==null) { + throw new APIException("aaf_env needs to be set"); + } + + // Initialize Facade for all uses + AuthzTrans trans = env.newTrans(); + cluster = org.onap.aaf.auth.dao.CassAccess.cluster(env,null); + + aafLurPerm = aafCon().newLur(); + // Note: If you need both Authn and Authz construct the following: + aafAuthn = aafCon().newAuthn(aafLurPerm); + + // Start Background Processing + // Question question = + question = new Question(trans, cluster, CassAccess.KEYSPACE, true); + + // Have AAFLocator object Create DirectLocators for Location needs + AbsAAFLocator.setCreator(new DirectLocatorCreator(env, question.locateDAO)); + + + service = new OAuthService(env.access(),trans,question); + facade1_0 = OAFacadeFactory.v1_0(this, trans, service, TYPE.JSON); + StringBuilder sb = new StringBuilder(); + trans.auditTrail(2, sb); + trans.init().log(sb); + + API_Token.init(this, facade1_0); + } + + /** + * Setup XML and JSON implementations for each supported Version type + * + * We do this by taking the Code passed in and creating clones of these with the appropriate Facades and properties + * to do Versions and Content switches + * + */ + public void route(HttpMethods meth, String path, API api, HttpCode<AuthzTrans, OAFacade<Introspect>> code) throws Exception { + String version = "1.0"; + // Get Correct API Class from Mapper + Class<?> respCls = facade1_0.mapper().getClass(api); + if(respCls==null) throw new Exception("Unknown class associated with " + api.getClass().getName() + ' ' + api.name()); + // setup Application API HTML ContentTypes for JSON and Route + String application = applicationJSON(respCls, version); + if(meth.equals(HttpMethods.POST)) { + route(env,meth,path,code,application,"application/json;version="+version,"application/x-www-form-urlencoded","*/*"); + } else { + route(env,meth,path,code,application,"application/json;version="+version,"*/*"); + } + } + + @Override + public Filter[] filters() throws CadiException, LocatorException { + try { + DirectOAuthTAF doat; + return new Filter[] {new AuthzTransFilter(env,aafCon(), + new AAFTrustChecker((Env)env), + doat = new DirectOAuthTAF(env,question,facade1_0), + doat.directUserPass() + )}; + } catch (NumberFormatException | APIException e) { + throw new CadiException("Invalid Property information", e); + } + } + + + @SuppressWarnings("unchecked") + @Override + public Registrant<AuthzEnv>[] registrants(final int port) throws CadiException { + return new Registrant[] { + new DirectRegistrar(access,question.locateDAO,app_name,app_version,port), + new DirectRegistrar(access,question.locateDAO,app_name.replace(DOT_OAUTH, ".token"),app_version,port), + new DirectRegistrar(access,question.locateDAO,app_name.replace(DOT_OAUTH, ".introspect"),app_version,port) + + }; + } + + + @Override + public void destroy() { + Cache.stopTimer(); + if(service!=null) { + service.close(); + } + if(cluster!=null) { + cluster.close(); + } + super.destroy(); + } + + // For use in CADI ONLY + public TokenMgr.TokenPermLoader tpLoader() { + return tpLoader; + } + + public static void main(final String[] args) { + PropAccess propAccess = new PropAccess(args); + try { + AAF_OAuth service = new AAF_OAuth(new AuthzEnv(propAccess)); +// env.setLog4JNames("log4j.properties","authz","oauth","audit","init","trace"); + JettyServiceStarter<AuthzEnv,AuthzTrans> jss = new JettyServiceStarter<AuthzEnv,AuthzTrans>(service); + jss.start(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/DirectOAuthTAF.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/DirectOAuthTAF.java new file mode 100644 index 00000000..74c9947d --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/DirectOAuthTAF.java @@ -0,0 +1,224 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.auth.dao.hl.Question; +import org.onap.aaf.auth.direct.DirectAAFUserPass; +import org.onap.aaf.auth.env.AuthzEnv; +import org.onap.aaf.auth.env.AuthzTrans; +import org.onap.aaf.auth.layer.Result; +import org.onap.aaf.auth.oauth.facade.DirectIntrospect; +import org.onap.aaf.auth.rserv.TransFilter; +import org.onap.aaf.cadi.CachedPrincipal; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.Hash; +import org.onap.aaf.cadi.LocatorException; +import org.onap.aaf.cadi.PropAccess; +import org.onap.aaf.cadi.CachedPrincipal.Resp; +import org.onap.aaf.cadi.CredVal.Type; +import org.onap.aaf.cadi.Taf.LifeForm; +import org.onap.aaf.cadi.config.Config; +import org.onap.aaf.cadi.oauth.OAuth2HttpTafResp; +import org.onap.aaf.cadi.oauth.OAuth2Principal; +import org.onap.aaf.cadi.oauth.TokenClient; +import org.onap.aaf.cadi.oauth.TokenClientFactory; +import org.onap.aaf.cadi.oauth.TokenMgr; +import org.onap.aaf.cadi.oauth.TokenPerm; +import org.onap.aaf.cadi.oauth.TokenMgr.TokenPermLoader; +import org.onap.aaf.cadi.principal.OAuth2FormPrincipal; +import org.onap.aaf.cadi.taf.HttpTaf; +import org.onap.aaf.cadi.taf.TafResp; +import org.onap.aaf.cadi.taf.TafResp.RESP; +import org.onap.aaf.cadi.util.Split; +import org.onap.aaf.misc.env.APIException; + +import aafoauth.v2_0.Introspect; + +public class DirectOAuthTAF implements HttpTaf { + private PropAccess access; + private DirectIntrospect<Introspect> oaFacade; + private TokenMgr tkMgr; + private final DirectAAFUserPass directUserPass; + private TokenClient altIntrospectClient; + + public DirectOAuthTAF(AuthzEnv env, Question q, DirectIntrospect<Introspect> facade) throws APIException, CadiException { + access = env.access(); + oaFacade = facade; + tkMgr = TokenMgr.getInstance(access,"dbToken","dbIntrospect"); + String alt_url = access.getProperty(Config.AAF_ALT_OAUTH2_INTROSPECT_URL,null); + TokenClientFactory tcf; + if(alt_url!=null) { + try { + tcf = TokenClientFactory.instance(access); + String[] split = Split.split(',', alt_url); + int timeout = split.length>1?Integer.parseInt(split[1]):3000; + altIntrospectClient = tcf.newClient(split[0], timeout); + altIntrospectClient.client_creds(access.getProperty(Config.AAF_ALT_CLIENT_ID,null), + access.getProperty(Config.AAF_ALT_CLIENT_SECRET,null)); + } catch (GeneralSecurityException | IOException | LocatorException e) { + throw new CadiException(e); + } + } + + directUserPass = new DirectAAFUserPass(env,q); + } + + @Override + public TafResp validate(LifeForm reading, HttpServletRequest req, HttpServletResponse resp) { + String value; + String token; + if((value=req.getHeader("Authorization"))!=null && value.startsWith("Bearer ")) { + token = value.substring(7); + } else { + token = null; + } + + if("application/x-www-form-urlencoded".equals(req.getContentType())) { + Map<String, String[]> map = req.getParameterMap(); + String client_id=null,client_secret=null,username=null,password=null; + for(Map.Entry<String, String[]> es : map.entrySet()) { + switch(es.getKey()) { + case "client_id": + for(String s : es.getValue()) { + client_id=s; + } + break; + case "client_secret": + for(String s : es.getValue()) { + client_secret=s; + } + break; + case "username": + for(String s : es.getValue()) { + username=s; + } + break; + case "password": + for(String s : es.getValue()) { + password=s; + } + break; + case "token": + if(token!=null) { // Defined as both Bearer and Form Encoded - Error + return new OAuth2HttpTafResp(access, null, "Token Info found as both Bearer Token and Form Info", RESP.FAIL, resp, true); + } + for(String s : es.getValue()) { + token=s; + } + break; + // Ignore others + } + } + + if(client_id==null && client_secret==null) { + return new OAuth2HttpTafResp(access, null, "client_id and client_secret required", RESP.TRY_ANOTHER_TAF, resp, false); + } + + if(token==null) { // No Token to work with, use only Client_ID and Client_Secret + AuthzTrans trans = (AuthzTrans)req.getAttribute(TransFilter.TRANS_TAG); + + if(directUserPass.validate(client_id, Type.PASSWORD, client_secret.getBytes(), trans)) { + // Client_ID is valid + if(username==null) { // Validating just the Client_ID + return new OAuth2FormHttpTafResp(access,new OAuth2FormPrincipal(client_id,client_id),"OAuth client_id authenticated",RESP.IS_AUTHENTICATED,resp,false); + } else { + //TODO - Does a clientID need specific Authorization to pair authentication with user name? At the moment, no. + // username is ok. + if(password!=null) { + if(directUserPass.validate(username, Type.PASSWORD, password.getBytes(), trans)) { + return new OAuth2FormHttpTafResp(access,new OAuth2FormPrincipal(client_id, username),"OAuth username authenticated",RESP.IS_AUTHENTICATED,resp,false); + } else { + return new OAuth2HttpTafResp(access,null,"OAuth username " + username + " not authenticated ",RESP.FAIL,resp,true); + } + } else { // no Password + //TODO Check for Trust Permission, which requires looking up Perms? + return new OAuth2HttpTafResp(access,null,"OAuth username " + username + " not authenticated ",RESP.FAIL,resp,true); + } + } + } else { + return new OAuth2HttpTafResp(access,null,"OAuth client_id " + client_id + " not authenticated ",RESP.FAIL,resp,true); + } + } + } + + // OK, have only a Token to validate + if(token!=null) { + AuthzTrans trans = (AuthzTrans)req.getAttribute(TransFilter.TRANS_TAG); + + try { + Result<Introspect> ri = oaFacade.mappedIntrospect(trans, token); + if(ri.isOK()) { + TokenPerm tp = tkMgr.putIntrospect(ri.value, Hash.hashSHA256(token.getBytes())); + if(tp==null) { + return new OAuth2HttpTafResp(access, null, "TokenPerm persistence failure", RESP.FAIL, resp, false); + } else { + return new OAuth2HttpTafResp(access,new OAuth2Principal(tp,Hash.hashSHA256(token.getBytes())),"Token Authenticated",RESP.IS_AUTHENTICATED,resp,false); + } + } else { + return new OAuth2HttpTafResp(access, null, ri.errorString(), RESP.FAIL, resp, false); + } + } catch (APIException e) { + trans.error().log(e,"Error getting token"); + return new OAuth2HttpTafResp(access, null, "Error getting token: " + e.getMessage(), RESP.TRY_ANOTHER_TAF, resp, false); + } catch (NoSuchAlgorithmException e) { + return new OAuth2HttpTafResp(access, null, "Error in security algorithm: " + e.getMessage(), RESP.TRY_ANOTHER_TAF, resp, false); + } + } + return new OAuth2HttpTafResp(access, null, "No OAuth2 Credentials in OAuthForm", RESP.TRY_ANOTHER_TAF, resp, false); + } + + @Override + public Resp revalidate(CachedPrincipal prin, Object state) { + // TODO Auto-generated method stub + return null; + } + + class ServiceTPL implements TokenPermLoader { + private final AuthzTrans trans; + public ServiceTPL(AuthzTrans atrans) { + trans = atrans; + } + + @Override + public org.onap.aaf.cadi.client.Result<TokenPerm> load(String accessToken, byte[] cred) throws APIException, CadiException, LocatorException { + Result<Introspect> ri = oaFacade.mappedIntrospect(trans, accessToken); + if(ri.notOK()) { + //TODO what should the status mapping be? + return org.onap.aaf.cadi.client.Result.err(ri.status,ri.errorString()); + } + return org.onap.aaf.cadi.client.Result.ok(200,tkMgr.putIntrospect(ri.value, cred)); + } + } + + public DirectAAFUserPass directUserPass() { + return directUserPass; + } +} + diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/OACode.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/OACode.java new file mode 100644 index 00000000..f60c689b --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/OACode.java @@ -0,0 +1,45 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth; +import org.onap.aaf.auth.env.AuthzTrans; +import org.onap.aaf.auth.oauth.facade.OAFacade; +import org.onap.aaf.auth.rserv.HttpCode; + +import aafoauth.v2_0.Introspect; + +public abstract class OACode extends HttpCode<AuthzTrans, OAFacade<Introspect>> implements Cloneable { + public boolean useJSON; + + public OACode(OAFacade<Introspect> facade, String description, boolean useJSON, String ... roles) { + super(facade, description, roles); + this.useJSON = useJSON; + } + + public <D extends OACode> D clone(OAFacade<Introspect> facade, boolean useJSON) throws Exception { + @SuppressWarnings("unchecked") + D d = (D)clone(); + d.useJSON = useJSON; + d.context = facade; + return d; + } + +}
\ No newline at end of file diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/OAuth2Filter.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/OAuth2Filter.java new file mode 100644 index 00000000..4442e36f --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/OAuth2Filter.java @@ -0,0 +1,64 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth; + +import java.io.IOException; +import java.security.Principal; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import org.onap.aaf.cadi.principal.BearerPrincipal; +import org.onap.aaf.cadi.util.Split; + +public class OAuth2Filter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest hreq = (HttpServletRequest)request; + Principal p = hreq.getUserPrincipal(); + if(request.getContentType().equals("application/x-www-form-urlencoded")) { + + } else if(p instanceof BearerPrincipal) { + for(String authz : Split.splitTrim(';', hreq.getHeader("Authorization"))) { + if(authz.startsWith("Bearer ")) { + ((BearerPrincipal)p).setBearer(authz.substring(7)); + } + } + } + chain.doFilter(request, response); + } + + @Override + public void destroy() { + } + +} diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/OAuth2FormHttpTafResp.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/OAuth2FormHttpTafResp.java new file mode 100644 index 00000000..23d87e3e --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/OAuth2FormHttpTafResp.java @@ -0,0 +1,65 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth; + +import java.io.IOException; + +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.principal.OAuth2FormPrincipal; +import org.onap.aaf.cadi.principal.TrustPrincipal; +import org.onap.aaf.cadi.taf.AbsTafResp; +import org.onap.aaf.cadi.taf.TafResp; + +public class OAuth2FormHttpTafResp extends AbsTafResp implements TafResp { + private HttpServletResponse httpResp; + private RESP status; + private final boolean wasFailed; + + public OAuth2FormHttpTafResp(Access access, OAuth2FormPrincipal principal, String desc, RESP status, HttpServletResponse resp, boolean wasFailed) { + super(access,principal, desc); + httpResp = resp; + this.status = status; + this.wasFailed = wasFailed; + } + + public OAuth2FormHttpTafResp(Access access, TrustPrincipal principal, String desc, RESP status,HttpServletResponse resp) { + super(access,principal, desc); + httpResp = resp; + this.status = status; + wasFailed = true; // if Trust Principal added, must be good + } + + public RESP authenticate() throws IOException { + httpResp.setStatus(401); // Unauthorized + return RESP.HTTP_REDIRECT_INVOKED; + } + + public RESP isAuthenticated() { + return status; + } + + public boolean isFailedAttempt() { + return wasFailed; + } +} diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/api/API_Token.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/api/API_Token.java new file mode 100644 index 00000000..f2836a7b --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/api/API_Token.java @@ -0,0 +1,82 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth.api; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.auth.env.AuthzTrans; +import org.onap.aaf.auth.layer.Result; +import org.onap.aaf.auth.oauth.AAF_OAuth; +import org.onap.aaf.auth.oauth.OACode; +import org.onap.aaf.auth.oauth.facade.OAFacade; +import org.onap.aaf.auth.oauth.mapper.Mapper.API; +import org.onap.aaf.auth.rserv.HttpMethods; + +import aafoauth.v2_0.Introspect; + +/** + * API Apis + * @author Jonathan + * + */ +public class API_Token { + // Hide Public Constructor + private API_Token() {} + + /** + * Normal Init level APIs + * + * @param authzAPI + * @param facade + * @throws Exception + */ + public static void init(final AAF_OAuth authzAPI, OAFacade<Introspect> facade) throws Exception { + //////// + // Overall APIs + /////// + authzAPI.route(HttpMethods.POST,"/token",API.TOKEN,new OACode(facade,"OAuth Token", true) { + @Override + public void handle(AuthzTrans trans, HttpServletRequest req, HttpServletResponse resp) throws Exception { + Result<Void> r = context.createBearerToken(trans,req, resp); + if(r.isOK()) { + resp.setStatus(201/*HttpStatus.CREATED_201*/); + } else { + context.error(trans,resp,r); + } + } + }); + + authzAPI.route(HttpMethods.POST,"/introspect",API.INTROSPECT,new OACode(facade,"AAF Token Information", true) { + @Override + public void handle(AuthzTrans trans, HttpServletRequest req, HttpServletResponse resp) throws Exception { + Result<Void> r = context.introspect(trans,req, resp); + if(r.isOK()) { + resp.setStatus(200 /*HttpStatus.OK_200*/); + } else { + context.error(trans,resp,r); + } + } + }); + + } +} diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/DirectIntrospect.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/DirectIntrospect.java new file mode 100644 index 00000000..91423cef --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/DirectIntrospect.java @@ -0,0 +1,29 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth.facade; + +import org.onap.aaf.auth.env.AuthzTrans; +import org.onap.aaf.auth.layer.Result; + +public interface DirectIntrospect<INTROSPECT> { + Result<INTROSPECT> mappedIntrospect(AuthzTrans trans, String token); +} diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/DirectIntrospectImpl.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/DirectIntrospectImpl.java new file mode 100644 index 00000000..91431c34 --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/DirectIntrospectImpl.java @@ -0,0 +1,57 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth.facade; + +import org.onap.aaf.auth.dao.cass.OAuthTokenDAO; +import org.onap.aaf.auth.env.AuthzTrans; +import org.onap.aaf.auth.layer.FacadeImpl; +import org.onap.aaf.auth.layer.Result; +import org.onap.aaf.auth.oauth.mapper.MapperIntrospect; +import org.onap.aaf.auth.oauth.service.OAuthService; + +public class DirectIntrospectImpl<INTROSPECT> extends FacadeImpl implements DirectIntrospect<INTROSPECT> { + protected OAuthService service; + private MapperIntrospect<INTROSPECT> mapper; + + public DirectIntrospectImpl(OAuthService service, MapperIntrospect<INTROSPECT> mapper) { + this.service = service; + this.mapper = mapper; + } + + /* (non-Javadoc) + * @see org.onap.aaf.auth.oauth.facade.OAFacade#mappedIntrospect(org.onap.aaf.auth.env.test.AuthzTrans, java.lang.String) + */ + @Override + public Result<INTROSPECT> mappedIntrospect(AuthzTrans trans, String token) { + Result<INTROSPECT> rti; + Result<OAuthTokenDAO.Data> rs = service.introspect(trans,token); + if(rs.notOK()) { + rti = Result.err(rs); + } else if(rs.isEmpty()) { + rti = Result.err(Result.ERR_NotFound,"No Token %s found",token); + } else { + rti = mapper.introspect(rs); + } + return rti; + } + +} diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/DirectOAFacadeImpl.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/DirectOAFacadeImpl.java new file mode 100644 index 00000000..f71f7c15 --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/DirectOAFacadeImpl.java @@ -0,0 +1,28 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth.facade; + +import org.onap.aaf.auth.layer.FacadeImpl; + +public class DirectOAFacadeImpl extends FacadeImpl { + +} diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/OAFacade.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/OAFacade.java new file mode 100644 index 00000000..52ff38b7 --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/OAFacade.java @@ -0,0 +1,67 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth.facade; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.auth.env.AuthzTrans; +import org.onap.aaf.auth.layer.Result; +import org.onap.aaf.auth.oauth.service.OAuthService; + + +/** + * + * @author Jonathan + * + */ +public interface OAFacade<INTROSPECT> { + +///////////////////// STANDARD ELEMENTS ////////////////// + /** + * @param trans + * @param response + * @param result + */ + public void error(AuthzTrans trans, HttpServletResponse response, Result<?> result); + + /** + * + * @param trans + * @param response + * @param status + */ + public void error(AuthzTrans trans, HttpServletResponse response, int status, String msg, String ... detail); + + public Result<Void> createBearerToken(AuthzTrans trans, HttpServletRequest req, HttpServletResponse resp); + + public Result<Void> introspect(AuthzTrans trans, HttpServletRequest req, HttpServletResponse resp); + + public OAuthService service(); + + +///////////////////// STANDARD ELEMENTS ////////////////// + + + + +}
\ No newline at end of file diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/OAFacade1_0.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/OAFacade1_0.java new file mode 100644 index 00000000..204a104a --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/OAFacade1_0.java @@ -0,0 +1,47 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth.facade; + +import org.onap.aaf.auth.oauth.AAF_OAuth; +import org.onap.aaf.auth.oauth.mapper.Mapper; +import org.onap.aaf.auth.oauth.service.OAuthService; +import org.onap.aaf.misc.env.APIException; +import org.onap.aaf.misc.env.Data; + +import aaf.v2_0.Error; +import aafoauth.v2_0.Introspect; +import aafoauth.v2_0.Token; +import aafoauth.v2_0.TokenRequest; + +/** + * @author Jonathan + * + */ +public class OAFacade1_0 extends OAFacadeImpl<TokenRequest,Token,Introspect,Error> { + public OAFacade1_0(AAF_OAuth api, + OAuthService service, + Mapper<TokenRequest,Token,Introspect,Error> mapper, + Data.TYPE type) throws APIException { + super(api, service, mapper, type); + } + +} diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/OAFacadeFactory.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/OAFacadeFactory.java new file mode 100644 index 00000000..ff586007 --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/OAFacadeFactory.java @@ -0,0 +1,47 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth.facade; + +import org.onap.aaf.auth.env.AuthzTrans; +import org.onap.aaf.auth.oauth.AAF_OAuth; +import org.onap.aaf.auth.oauth.mapper.Mapper1_0; +import org.onap.aaf.auth.oauth.mapper.MapperIntrospect1_0; +import org.onap.aaf.auth.oauth.service.OAuthService; +import org.onap.aaf.misc.env.APIException; +import org.onap.aaf.misc.env.Data; + +import aafoauth.v2_0.Introspect; + + +public class OAFacadeFactory { + public static OAFacade1_0 v1_0(AAF_OAuth certman, AuthzTrans trans, OAuthService service, Data.TYPE type) throws APIException { + return new OAFacade1_0( + certman, + service, + new Mapper1_0(), + type); + } + + public static DirectIntrospect<Introspect> directV1_0(OAuthService service) { + return new DirectIntrospectImpl<Introspect>(service, new MapperIntrospect1_0()); + } +} diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/OAFacadeImpl.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/OAFacadeImpl.java new file mode 100644 index 00000000..ee35b8bf --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/facade/OAFacadeImpl.java @@ -0,0 +1,333 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth.facade; + +import static org.onap.aaf.auth.layer.Result.ERR_ActionNotCompleted; +import static org.onap.aaf.auth.layer.Result.ERR_BadData; +import static org.onap.aaf.auth.layer.Result.ERR_ConflictAlreadyExists; +import static org.onap.aaf.auth.layer.Result.ERR_Denied; +import static org.onap.aaf.auth.layer.Result.ERR_NotFound; +import static org.onap.aaf.auth.layer.Result.ERR_NotImplemented; +import static org.onap.aaf.auth.layer.Result.ERR_Policy; +import static org.onap.aaf.auth.layer.Result.ERR_Security; +import static org.onap.aaf.auth.layer.Result.OK; + +import java.security.Principal; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.auth.dao.cass.OAuthTokenDAO; +import org.onap.aaf.auth.dao.cass.Status; +import org.onap.aaf.auth.dao.hl.Question; +import org.onap.aaf.auth.env.AuthzEnv; +import org.onap.aaf.auth.env.AuthzTrans; +import org.onap.aaf.auth.layer.Result; +import org.onap.aaf.auth.oauth.AAF_OAuth; +import org.onap.aaf.auth.oauth.mapper.Mapper; +import org.onap.aaf.auth.oauth.mapper.Mapper.API; +import org.onap.aaf.auth.oauth.service.OAuthService; +import org.onap.aaf.auth.oauth.service.OAuthService.GRANT_TYPE; +import org.onap.aaf.cadi.client.Holder; +import org.onap.aaf.cadi.oauth.OAuth2Principal; +import org.onap.aaf.cadi.principal.OAuth2FormPrincipal; +import org.onap.aaf.misc.env.APIException; +import org.onap.aaf.misc.env.Data; +import org.onap.aaf.misc.env.Env; +import org.onap.aaf.misc.env.TimeTaken; +import org.onap.aaf.misc.rosetta.env.RosettaDF; +import org.onap.aaf.misc.rosetta.env.RosettaData; + +import aaf.v2_0.Perms; + +/** + * AuthzFacade + * + * This Service Facade encapsulates the essence of the API Service can do, and provides + * a single created object for elements such as RosettaDF. + * + * The Responsibilities of this class are to: + * 1) Interact with the Service Implementation (which might be supported by various kinds of Backend Storage) + * 2) Validate incoming data (if applicable) + * 3) Convert the Service response into the right Format, and mark the Content Type + * a) In the future, we may support multiple Response Formats, aka JSON or XML, based on User Request. + * 4) Log Service info, warnings and exceptions as necessary + * 5) When asked by the API layer, this will create and write Error content to the OutputStream + * + * Note: This Class does NOT set the HTTP Status Code. That is up to the API layer, so that it can be + * clearly coordinated with the API Documentation + * + * @author Jonathan + * + */ +public abstract class OAFacadeImpl<TOKEN_REQ,TOKEN,INTROSPECT,ERROR> + extends DirectIntrospectImpl<INTROSPECT> implements OAFacade<INTROSPECT> { + private static final String INVALID_INPUT = "Invalid Input"; + private final RosettaDF<TOKEN> tokenDF; + private final RosettaDF<TOKEN_REQ> tokenReqDF; + private final RosettaDF<INTROSPECT> introspectDF; + private final RosettaDF<ERROR> errDF; + public final RosettaDF<Perms> permsDF; + private final Mapper<TOKEN_REQ, TOKEN, INTROSPECT, ERROR> mapper; + + public OAFacadeImpl(AAF_OAuth api, + OAuthService service, + Mapper<TOKEN_REQ,TOKEN,INTROSPECT,ERROR> mapper, + Data.TYPE dataType) throws APIException { + super(service, mapper); + this.mapper = mapper; + AuthzEnv env = api.env; + (tokenReqDF = env.newDataFactory(mapper.getClass(API.TOKEN_REQ))).in(dataType).out(dataType); + (tokenDF = env.newDataFactory(mapper.getClass(API.TOKEN))).in(dataType).out(dataType); + (introspectDF = env.newDataFactory(mapper.getClass(API.INTROSPECT))).in(dataType).out(dataType); + (permsDF = env.newDataFactory(Perms.class)).in(dataType).out(dataType); + (errDF = env.newDataFactory(mapper.getClass(API.ERROR))).in(dataType).out(dataType); + } + + /////////////////////////// + // Tokens + /////////////////////////// + public static final String CREATE_TOKEN = "createToken"; + public static final String INTROSPECT = "introspect"; + + /* (non-Javadoc) + * @see org.onap.aaf.auth.oauth.facade.OAFacade#getToken(org.onap.aaf.auth.env.test.AuthzTrans, javax.servlet.http.HttpServletResponse, org.onap.aaf.auth.oauth.service.OAuthAPI) + */ + @Override + public Result<Void> createBearerToken(AuthzTrans trans, HttpServletRequest req, HttpServletResponse resp) { + TimeTaken tt = trans.start(CREATE_TOKEN, Env.SUB|Env.ALWAYS); + try { + TOKEN_REQ request; + try { + request = mapper.tokenReqFromParams(req); + if(request==null) { + Data<TOKEN_REQ> rd = tokenReqDF.newData().load(req.getInputStream()); + if(Question.willSpecialLog(trans, trans.user())) { + Question.logEncryptTrace(trans,rd.asString()); + } + request = rd.asObject(); + } + } catch(APIException e) { + trans.error().log(INVALID_INPUT,IN,CREATE_TOKEN); + return Result.err(Status.ERR_BadData,INVALID_INPUT); + } + + // Already validated for Oauth2FormPrincipal +// Result<Void> rv = service.validate(trans,mapper.credsFromReq(request)); +// if(rv.notOK()) { +// return rv; +// } + Holder<GRANT_TYPE> hgt = new Holder<GRANT_TYPE>(GRANT_TYPE.unknown); + Result<OAuthTokenDAO.Data> rs = service.createToken(trans,req,mapper.clientTokenReq(request,hgt),hgt); + Result<TOKEN> rp; + if(rs.isOKhasData()) { + rp = mapper.tokenFromData(rs); + } else { + rp = Result.err(rs); + } + switch(rp.status) { + case OK: + RosettaData<TOKEN> data = tokenDF.newData(trans).load(rp.value); + if(Question.willSpecialLog(trans, trans.user())) { + Question.logEncryptTrace(trans,data.asString()); + } + data.to(resp.getOutputStream()); + resp.getOutputStream().print('\n'); + setContentType(resp,tokenDF.getOutType()); + return Result.ok(); + default: + return Result.err(rp); + } + } catch (Exception e) { + trans.error().log(e,IN,CREATE_TOKEN); + return Result.err(e); + } finally { + tt.done(); + } + + } + +/* (non-Javadoc) + * @see org.onap.aaf.auth.oauth.facade.OAFacade#Introspect(org.onap.aaf.auth.env.test.AuthzTrans, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + @Override + public Result<Void> introspect(AuthzTrans trans, HttpServletRequest req, HttpServletResponse resp) { + TimeTaken tt = trans.start(INTROSPECT, Env.SUB|Env.ALWAYS); + try { + Principal p = req.getUserPrincipal(); + String token=null; + if(p != null) { + if(p instanceof OAuth2Principal) { + RosettaData<INTROSPECT> data = introspectDF.newData(trans).load(mapper.fromPrincipal((OAuth2Principal)p)); + if(Question.willSpecialLog(trans, trans.user())) { + Question.logEncryptTrace(trans,data.asString()); + } + data.to(resp.getOutputStream()); + resp.getOutputStream().print('\n'); + setContentType(resp,tokenDF.getOutType()); + return Result.ok(); + } else if(p instanceof OAuth2FormPrincipal) { + token = req.getParameter("token"); + } + } + + if(token==null) { + token = req.getParameter("access_token"); + if(token==null || token.isEmpty()) { + token = req.getHeader("Authorization"); + if(token != null && token.startsWith("Bearer ")) { + token = token.substring(7); + } else { + token = req.getParameter("token"); + if(token==null) { + return Result.err(Result.ERR_Security,"token is required"); + } + } + } + } + + Result<INTROSPECT> rti = mappedIntrospect(trans,token); + switch(rti.status) { + case OK: + RosettaData<INTROSPECT> data = introspectDF.newData(trans).load(rti.value); + if(Question.willSpecialLog(trans, trans.user())) { + Question.logEncryptTrace(trans,data.asString()); + } + data.to(resp.getOutputStream()); + resp.getOutputStream().print('\n'); + setContentType(resp,tokenDF.getOutType()); + return Result.ok(); + default: + return Result.err(rti); + } + } catch (Exception e) { + trans.error().log(e,IN,INTROSPECT); + return Result.err(e); + } finally { + tt.done(); + } + } + + + /* (non-Javadoc) + * @see com.att.authz.facade.AuthzFacade#error(org.onap.aaf.auth.env.test.AuthzTrans, javax.servlet.http.HttpServletResponse, int) + * + * Note: Conforms to AT&T TSS RESTful Error Structure + */ + @Override + public void error(AuthzTrans trans, HttpServletResponse response, Result<?> result) { + error(trans, response, result.status, + result.details==null?"":result.details.trim(), + result.variables==null?new String[0]:result.variables); + } + + @Override + public void error(AuthzTrans trans, HttpServletResponse response, int status, final String _msg, final String ... _detail) { + String msgId; + String prefix; + boolean hidemsg=false; + switch(status) { + case 202: + case ERR_ActionNotCompleted: + msgId = "SVC1202"; + prefix = "Accepted, Action not complete"; + response.setStatus(/*httpstatus=*/202); + break; + + case 403: + case ERR_Policy: + case ERR_Security: + case ERR_Denied: + msgId = "SVC1403"; + prefix = "Forbidden"; + response.setStatus(/*httpstatus=*/403); + break; + + case 404: + case ERR_NotFound: + msgId = "SVC1404"; + prefix = "Not Found"; + response.setStatus(/*httpstatus=*/404); + break; + + case 406: + case ERR_BadData: + msgId="SVC1406"; + prefix = "Not Acceptable"; + response.setStatus(/*httpstatus=*/406); + break; + + case 409: + case ERR_ConflictAlreadyExists: + msgId = "SVC1409"; + prefix = "Conflict Already Exists"; + response.setStatus(/*httpstatus=*/409); + break; + + case 501: + case ERR_NotImplemented: + msgId = "SVC1501"; + prefix = "Not Implemented"; + response.setStatus(/*httpstatus=*/501); + break; + + + default: + msgId = "SVC1500"; + prefix = "General Service Error"; + response.setStatus(/*httpstatus=*/500); + hidemsg=true; + break; + } + + try { + StringBuilder holder = new StringBuilder(); + ERROR em = mapper.errorFromMessage(holder, msgId,prefix + ": " + _msg,_detail); + trans.checkpoint( + "ErrResp [" + + msgId + + "] " + + holder.toString(), + Env.ALWAYS); + if(hidemsg) { + holder.setLength(0); + em = mapper.errorFromMessage(holder, msgId, "Server had an issue processing this request"); + } + errDF.newData(trans).load(em).to(response.getOutputStream()); + + } catch (Exception e) { + trans.error().log(e,"unable to send response for",_msg); + } + } + + public Mapper<TOKEN_REQ,TOKEN,INTROSPECT,ERROR> mapper() { + return mapper; + } + + /* (non-Javadoc) + * @see org.onap.aaf.auth.oauth.facade.OAFacade#service() + */ + @Override + public OAuthService service() { + return service; + } +}
\ No newline at end of file diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/mapper/Mapper.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/mapper/Mapper.java new file mode 100644 index 00000000..55100e21 --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/mapper/Mapper.java @@ -0,0 +1,47 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth.mapper; + +import javax.servlet.http.HttpServletRequest; + +import org.onap.aaf.auth.dao.cass.OAuthTokenDAO; +import org.onap.aaf.auth.layer.Result; +import org.onap.aaf.auth.oauth.service.OCreds; +import org.onap.aaf.auth.oauth.service.OAuthService.GRANT_TYPE; +import org.onap.aaf.cadi.client.Holder; +import org.onap.aaf.cadi.oauth.OAuth2Principal; + +public interface Mapper<TOKEN_REQ,TOKEN,INTROSPECT,ERROR> extends MapperIntrospect<INTROSPECT> +{ + public enum API{TOKEN_REQ, TOKEN,INTROSPECT, ERROR,VOID}; + + public Class<?> getClass(API api); + public<A> A newInstance(API api); + + public ERROR errorFromMessage(StringBuilder holder, String msgID, String text, String... detail); + public TOKEN_REQ tokenReqFromParams(HttpServletRequest req); + public OCreds credsFromReq(TOKEN_REQ tokReq); + + public OAuthTokenDAO.Data clientTokenReq(TOKEN_REQ tokReq, Holder<GRANT_TYPE> hgt); + public Result<TOKEN> tokenFromData(Result<OAuthTokenDAO.Data> rs); + public INTROSPECT fromPrincipal(OAuth2Principal p); +} diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/mapper/Mapper1_0.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/mapper/Mapper1_0.java new file mode 100644 index 00000000..ee4237c8 --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/mapper/Mapper1_0.java @@ -0,0 +1,225 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth.mapper; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; + +import org.onap.aaf.auth.dao.cass.OAuthTokenDAO; +import org.onap.aaf.auth.dao.cass.OAuthTokenDAO.Data; +import org.onap.aaf.auth.layer.Result; +import org.onap.aaf.auth.oauth.service.OAuthService; +import org.onap.aaf.auth.oauth.service.OCreds; +import org.onap.aaf.auth.oauth.service.OAuthService.CLIENT_TYPE; +import org.onap.aaf.auth.oauth.service.OAuthService.GRANT_TYPE; +import org.onap.aaf.cadi.client.Holder; +import org.onap.aaf.cadi.oauth.OAuth2Principal; +import org.onap.aaf.cadi.util.Vars; +import org.onap.aaf.misc.env.util.Split; + +import aaf.v2_0.Error; +import aafoauth.v2_0.Introspect; +import aafoauth.v2_0.Token; +import aafoauth.v2_0.TokenRequest; + + +public class Mapper1_0 extends MapperIntrospect1_0 implements Mapper<TokenRequest,Token,Introspect,Error> { + @Override + public Class<?> getClass(API api) { + switch(api) { + case TOKEN_REQ: return TokenRequest.class; + case TOKEN: return Token.class; + case INTROSPECT: return Introspect.class; + case ERROR: return Error.class; + case VOID: return Void.class; + } + return null; + } + + @SuppressWarnings("unchecked") + @Override + public <A> A newInstance(API api) { + switch(api) { + case TOKEN_REQ: return (A)new TokenRequest(); + case TOKEN: return (A)new Token(); + case INTROSPECT: return (A)new Introspect(); + case ERROR: return (A)new Error(); + case VOID: return null; + } + return null; + } + + ////////////// Mapping Functions ///////////// + @Override + public Error errorFromMessage(StringBuilder holder, String msgID, String text, String... var) { + Error err = new Error(); + err.setMessageId(msgID); + // AT&T Restful Error Format requires numbers "%" placements + err.setText(Vars.convert(holder, text, var)); + for(String s : var) { + err.getVariables().add(s); + } + return err; + } + + @Override + public TokenRequest tokenReqFromParams(HttpServletRequest req) { + TokenRequest tr = new TokenRequest(); + boolean data = false; + @SuppressWarnings("unchecked") + Map<String, String[]> map = req.getParameterMap(); + for(Entry<String, String[]> es : map.entrySet()) { + switch(es.getKey()) { + case "client_id": + if(es.getValue().length==1) { + tr.setClientId(es.getValue()[0]); + data = true; + } + break; + case "client_secret": + if(es.getValue().length==1) { + tr.setClientSecret(es.getValue()[0]); + data = true; + } + break; + case "username": + if(es.getValue().length==1) { + tr.setUsername(es.getValue()[0]); + data = true; + } + break; + case "password": + if(es.getValue().length==1) { + tr.setPassword(es.getValue()[0]); + data = true; + } + break; + case "scope": + if(es.getValue().length==1) { + tr.setScope(es.getValue()[0]); + data = true; + } + break; + case "grant_type": + if(es.getValue().length==1) { + tr.setGrantType(es.getValue()[0]); + data = true; + } + break; + case "refresh_token": + if(es.getValue().length==1) { + tr.setRefreshToken(es.getValue()[0]); + data = true; + } + break; + + } + } + return data?tr:null; + } + + + + /* (non-Javadoc) + * @see org.onap.aaf.auth.oauth.mapper.Mapper#credsFromReq(javax.servlet.http.HttpServletRequest) + */ + @Override + public OCreds credsFromReq(TokenRequest tokReq) { + return new OCreds(tokReq.getClientId(),tokReq.getClientSecret(), + tokReq.getUsername(),tokReq.getPassword()); + } + + /* (non-Javadoc) + * @see org.onap.aaf.auth.oauth.mapper.Mapper#tokenReq(java.lang.Object) + */ + @Override + public Data clientTokenReq(TokenRequest tokReq, Holder<GRANT_TYPE> hgt) { + OAuthTokenDAO.Data tdd = new OAuthTokenDAO.Data(); + tdd.client_id = tokReq.getClientId(); + tdd.user = tokReq.getUsername(); + if(tokReq.getRefreshToken()!=null) { + tdd.refresh=tokReq.getRefreshToken(); + } + + for(GRANT_TYPE ttt : GRANT_TYPE.values()) { + if(ttt.name().equals(tokReq.getGrantType())) { + hgt.set(ttt); + break; + } + } + + switch(hgt.get()) { + case client_credentials: + case password: + case refresh_token: + tdd.type = CLIENT_TYPE.confidential.ordinal(); + break; + default: + tdd.type = CLIENT_TYPE.unknown.ordinal(); + break; + } + String scopes=tokReq.getScope(); + if(scopes!=null) { + Set<String> ss = tdd.scopes(true); + for(String s: Split.split(' ', tokReq.getScope())) { + ss.add(s); + } + } + + tdd.state = tokReq.getState(); + return tdd; + } + + @Override + public Result<Token> tokenFromData(Result<Data> rd) { + if(rd.notOK()) { + return Result.err(rd); + } + Data d = rd.value; + Token token = new Token(); + if(OAuthService.TOKEN_TYPE.values().length>d.type) { + token.setTokenType(OAuthService.TOKEN_TYPE.values()[d.type].name()); + } else { + token.setTokenType("Invalid"); + } + token.setAccessToken(d.id); + token.setRefreshToken(d.refresh); + token.setExpiresIn((int)(d.exp_sec-(System.currentTimeMillis())/1000)); + token.setScope(getScopes(d.scopes(false))); + token.setState(d.state); + return Result.ok(token); + } + + + + /* (non-Javadoc) + * @see org.onap.aaf.auth.oauth.mapper.Mapper#fromPrincipal(org.onap.aaf.cadi.oauth.OAuth2Principal) + */ + @Override + public Introspect fromPrincipal(OAuth2Principal p) { + return p.tokenPerm().getIntrospect(); + } + +}
\ No newline at end of file diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/mapper/MapperIntrospect.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/mapper/MapperIntrospect.java new file mode 100644 index 00000000..bf558799 --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/mapper/MapperIntrospect.java @@ -0,0 +1,29 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth.mapper; + +import org.onap.aaf.auth.dao.cass.OAuthTokenDAO; +import org.onap.aaf.auth.layer.Result; + +public interface MapperIntrospect<INTROSPECT> { + public Result<INTROSPECT> introspect(Result<OAuthTokenDAO.Data> rs); +} diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/mapper/MapperIntrospect1_0.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/mapper/MapperIntrospect1_0.java new file mode 100644 index 00000000..00a94fdf --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/mapper/MapperIntrospect1_0.java @@ -0,0 +1,74 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth.mapper; + +import java.util.Set; + +import org.onap.aaf.auth.dao.cass.OAuthTokenDAO.Data; +import org.onap.aaf.auth.layer.Result; +import org.onap.aaf.auth.oauth.service.OAuthService.CLIENT_TYPE; + +import aafoauth.v2_0.Introspect; + +public class MapperIntrospect1_0 implements MapperIntrospect<Introspect> { + + public Result<Introspect> introspect(Result<Data> rs) { + if(rs.isOKhasData()) { + Data data = rs.value; + Introspect ti = new Introspect(); + ti.setAccessToken(data.id); + ti.setActive(data.active); + ti.setClientId(data.client_id); + for(CLIENT_TYPE ct : CLIENT_TYPE.values()) { + if(data.type==ct.ordinal()) { + ti.setClientType(ct.name()); + break; + } + } + if(ti.getClientType()==null) { + ti.setClientType(CLIENT_TYPE.unknown.name()); + } + ti.setActive(data.active); + ti.setScope(getScopes(data.scopes(false))); + ti.setContent(data.content); + ti.setUsername(data.user); + ti.setExp(data.exp_sec); // want seconds from Jan 1, 1970 + return Result.ok(ti); + } + return Result.err(rs); + } + + protected static String getScopes(Set<String> scopes) { + StringBuilder sb = new StringBuilder(); + boolean start = true; + for(String s : scopes) { + if(start) { + start = false; + } else { + sb.append(' '); + } + sb.append(s); + } + return sb.toString(); + } + +} diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/service/JSONPermLoader.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/service/JSONPermLoader.java new file mode 100644 index 00000000..bf04472b --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/service/JSONPermLoader.java @@ -0,0 +1,34 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth.service; + +import java.util.Set; + +import org.onap.aaf.auth.env.AuthzTrans; +import org.onap.aaf.auth.layer.Result; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.misc.env.APIException; + +public interface JSONPermLoader { + public Result<String> loadJSONPerms(AuthzTrans trans, String user, Set<String> scopes) throws APIException, CadiException; + +} diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/service/JSONPermLoaderFactory.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/service/JSONPermLoaderFactory.java new file mode 100644 index 00000000..ea5c595c --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/service/JSONPermLoaderFactory.java @@ -0,0 +1,119 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth.service; + +import java.util.List; +import java.util.Set; + +import org.onap.aaf.auth.dao.cass.PermDAO; +import org.onap.aaf.auth.dao.hl.Question; +import org.onap.aaf.auth.env.AuthzTrans; +import org.onap.aaf.auth.layer.Result; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.aaf.v2_0.AAFCon; +import org.onap.aaf.cadi.client.Future; +import org.onap.aaf.cadi.client.Rcli; +import org.onap.aaf.cadi.config.Config; +import org.onap.aaf.misc.env.APIException; +import org.onap.aaf.misc.env.Env; +import org.onap.aaf.misc.env.TimeTaken; + +public class JSONPermLoaderFactory { + /** + * Load JSON Perms from AAF Service (Remotely) + * @param aafcon + * @param timeout + * @return + */ + public static JSONPermLoader remote(final AAFCon<?> aafcon, final int timeout) { + return new JSONPermLoader() { + public Result<String> loadJSONPerms(AuthzTrans trans, String user, Set<String> scopes) throws APIException, CadiException { + Rcli<?> c = aafcon.clientAs(Config.AAF_DEFAULT_VERSION,trans.getUserPrincipal()); + StringBuilder pathinfo = new StringBuilder("/authz/perms/user/"); + pathinfo.append(user); + pathinfo.append("?scopes="); + boolean first = true; + for(String s : scopes) { + if(first) { + first = false; + } else { + pathinfo.append(':'); + } + pathinfo.append(s); + } + TimeTaken tt = trans.start("Call AAF Service", Env.REMOTE); + try { + Future<String> fs = c.read(pathinfo.toString(), "application/Perms+json;charset=utf-8;version=2.0"); + if(fs.get(timeout)) { + return Result.ok(fs.body()); + } else if(fs.code()==404) { + return Result.err(Result.ERR_NotFound,fs.body()); + } else { + return Result.err(Result.ERR_Backend,"Error accessing AAF %s: %s",Integer.toString(fs.code()),fs.body()); + } + } finally { + tt.done(); + } + } + }; + } + public static JSONPermLoader direct(final Question question) { + return new JSONPermLoader() { + public Result<String> loadJSONPerms(AuthzTrans trans, String user, Set<String> scopes) throws APIException, CadiException { + TimeTaken tt = trans.start("Cached DB Perm lookup", Env.SUB); + Result<List<PermDAO.Data>> pd; + try { + pd = question.getPermsByUser(trans, user, false); + } finally { + tt.done(); + } + if(pd.notOK()) { + return Result.err(pd); + } + // Since we know it is + StringBuilder sb = new StringBuilder("{\"perm\":["); + boolean first = true; + for(PermDAO.Data d : pd.value) { + if(scopes.contains(d.ns)) { + if(first) { + first = false; + } else { + sb.append(','); + } + sb.append("{\"type\":\""); + sb.append(d.ns); + sb.append('.'); + sb.append(d.type); + sb.append("\",\"instance\":\""); + sb.append(d.instance); + sb.append("\",\"action\":\""); + sb.append(d.action); + sb.append("\"}"); + } + } + sb.append("]}"); + return Result.ok(sb.toString()); + } + }; + } + +} diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/service/OAuthService.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/service/OAuthService.java new file mode 100644 index 00000000..052b292e --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/service/OAuthService.java @@ -0,0 +1,301 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth.service; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import javax.servlet.http.HttpServletRequest; + +import org.onap.aaf.auth.dao.DAO; +import org.onap.aaf.auth.dao.cass.OAuthTokenDAO; +import org.onap.aaf.auth.dao.cass.Status; +import org.onap.aaf.auth.dao.cass.OAuthTokenDAO.Data; +import org.onap.aaf.auth.dao.hl.Question; +import org.onap.aaf.auth.direct.DirectAAFUserPass; +import org.onap.aaf.auth.env.AuthzTrans; +import org.onap.aaf.auth.env.NullTrans; +import org.onap.aaf.auth.layer.Result; +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.LocatorException; +import org.onap.aaf.cadi.CredVal.Type; +import org.onap.aaf.cadi.client.Holder; +import org.onap.aaf.cadi.config.Config; +import org.onap.aaf.cadi.oauth.AAFToken; +import org.onap.aaf.cadi.oauth.TokenClient; +import org.onap.aaf.cadi.oauth.TokenClientFactory; +import org.onap.aaf.cadi.util.Split; +import org.onap.aaf.misc.env.APIException; + +import aafoauth.v2_0.Introspect; + +public class OAuthService { + + private static final int TOK_EXP = 60*60*1000; // 1 hour, millis. + + public enum TOKEN_TYPE {unknown,bearer,refresh} + public enum GRANT_TYPE {unknown,password,client_credentials,refresh_token}; + public enum CLIENT_TYPE {unknown,confidential}; + + // Additional Expires + private final DAO<AuthzTrans, ?>[] daos; + public final OAuthTokenDAO tokenDAO; + private final DirectAAFUserPass directUserPass; + private final TokenClientFactory tcf; + private TokenClient altIntrospectClient; + private String altDomain; + private final JSONPermLoader permLoader; + + + // If we add more CAs, may want to parameterize + + @SuppressWarnings("unchecked") + public OAuthService(final Access access, final AuthzTrans trans, final Question q) throws APIException, IOException { + permLoader = JSONPermLoaderFactory.direct(q); + tokenDAO = new OAuthTokenDAO(trans, q.historyDAO); + daos =(DAO<AuthzTrans, ?>[]) new DAO<?,?>[] { + tokenDAO + }; + try { + String alt_url = access.getProperty(Config.AAF_ALT_OAUTH2_INTROSPECT_URL,null); + if(alt_url!=null) { + tcf = TokenClientFactory.instance(access); + String[] split = Split.split(',', alt_url); + int timeout = split.length>1?Integer.parseInt(split[1]):3000; + altIntrospectClient = tcf.newClient(split[0], timeout); + altIntrospectClient.client_creds(access.getProperty(Config.AAF_ALT_CLIENT_ID,null), + access.getProperty(Config.AAF_ALT_CLIENT_SECRET,null)); + altDomain = '@'+access.getProperty(Config.AAF_ALT_OAUTH2_DOMAIN,null); + } else { + tcf = null; + } + directUserPass = new DirectAAFUserPass(trans.env(), q); + } catch (GeneralSecurityException | CadiException | LocatorException e) { + throw new APIException("Could not construct TokenClientFactory",e); + } + + } + + public Result<Void> validate(AuthzTrans trans, OCreds creds) { + if(directUserPass.validate(creds.username, Type.PASSWORD, creds.password, trans)) { + return Result.ok(); + } else { + return Result.err(Result.ERR_Security, "Invalid Credential for ",creds.username); + } + } + + public Result<Data> createToken(AuthzTrans trans, HttpServletRequest req, OAuthTokenDAO.Data odd, Holder<GRANT_TYPE> hgt) { + switch(hgt.get()) { + case client_credentials: + case password: + return createBearerToken(trans, odd); + case refresh_token: + return refreshBearerToken(trans, odd); + default: + return Result.err(Result.ERR_BadData, "Unknown Grant Type"); + } + } + + private Result<Data> createBearerToken(AuthzTrans trans, OAuthTokenDAO.Data odd) { + if(odd.user==null) { + odd.user = trans.user(); + } + odd.id = AAFToken.toToken(UUID.randomUUID()); + odd.refresh = AAFToken.toToken(UUID.randomUUID()); + odd.active = true; + long exp; + odd.expires = new Date(exp=(System.currentTimeMillis()+TOK_EXP)); + odd.exp_sec = exp/1000; + odd.req_ip = trans.ip(); + + try { + Result<Data> rd = loadToken(trans, odd); + if(rd.notOK()) { + return rd; + } + } catch (APIException | CadiException e) { + return Result.err(e); + } + return tokenDAO.create(trans, odd); + } + + private Result<Data> loadToken(AuthzTrans trans, Data odd) throws APIException, CadiException { + Result<String> rs = permLoader.loadJSONPerms(trans,odd.user,odd.scopes(false)); + if(rs.isOK()) { + odd.content = rs.value; + odd.type = TOKEN_TYPE.bearer.ordinal(); + return Result.ok(odd); + } else if(rs.status == Result.ERR_NotFound || rs.status==Status.ERR_UserRoleNotFound) { + odd.type = TOKEN_TYPE.bearer.ordinal(); + return Result.ok(odd); + } else { + return Result.err(Result.ERR_Backend,"Error accessing AAF Info: %s",rs.errorString()); + } + } + + + + private Result<Data> refreshBearerToken(AuthzTrans trans, Data odd) { + Result<List<Data>> rld = tokenDAO.readByUser(trans, trans.user()); + if(rld.notOK()) { + return Result.err(rld); + } + if(rld.isEmpty()) { + return Result.err(Result.ERR_NotFound,"Data not Found for %1 %2",trans.user(),odd.refresh==null?"":odd.refresh.toString()); + } + Data token = null; + for(Data d : rld.value) { + if(d.refresh.equals(odd.refresh)) { + token = d; + boolean scopesNE = false; + Set<String> scopes = odd.scopes(false); + if(scopes.size()>0) { // only check if Scopes listed, RFC 6749, Section 6 + if(scopesNE=!(scopes.size() == d.scopes(false).size())) { + for(String s : odd.scopes(false)) { + if(!d.scopes(false).contains(s)) { + scopesNE=true; + break; + } + } + } + if(scopesNE) { + return Result.err(Result.ERR_BadData,"Requested Scopes do not match existing Token"); + } + } + break; + } + } + + if(token==null) { + trans.audit().printf("Duplicate Refresh Token (%s) attempted for %s. Possible Replay Attack",odd.refresh.toString(),trans.user()); + return Result.err(Result.ERR_Security,"Invalid Refresh Token"); + } else { + // Got the Result + Data deleteMe = new Data(); + deleteMe.id = token.id; + token.id = AAFToken.toToken(UUID.randomUUID()); + token.client_id = trans.user(); + token.refresh = AAFToken.toToken(UUID.randomUUID()); + long exp; + token.expires = new Date(exp=(System.currentTimeMillis()+TOK_EXP)); + token.exp_sec = exp/1000; + token.req_ip = trans.ip(); + Result<Data> rd = tokenDAO.create(trans, token); + if(rd.notOK()) { + return Result.err(rd); + } + Result<Void> rv = tokenDAO.delete(trans, deleteMe,false); + if(rv.notOK()) { + trans.error().log("Unable to delete token", token); + } + } + return Result.ok(token); + } + + public Result<OAuthTokenDAO.Data> introspect(AuthzTrans trans, String token) { + Result<List<Data>> rld; + try { + UUID uuid = AAFToken.fromToken(token); + if(uuid==null) { // not an AAF Token + // Attempt to get Alternative Token + if(altIntrospectClient!=null) { + org.onap.aaf.cadi.client.Result<Introspect> rai = altIntrospectClient.introspect(token); + if(rai.isOK()) { + Introspect in = rai.value; + if(in.getExp()==null) { + trans.audit().printf("Alt OAuth sent back inactive, empty token: requesting_id,%s,access_token=%s,ip=%s\n",trans.user(),token,trans.ip()); + } + long expires = in.getExp()*1000; + if(in.isActive() && expires>System.currentTimeMillis()) { + // We have a good Token, modify to be Fully Qualified + String fqid = in.getUsername()+altDomain; + // read contents + rld = tokenDAO.read(trans, token); + if(rld.isOKhasData()) { + Data td = rld.value.get(0); + in.setContent(td.content); + } else { + Data td = new Data(); + td.id = token; + td.client_id = in.getClientId(); + td.user = fqid; + td.active=true; + td.type = TOKEN_TYPE.bearer.ordinal(); + td.expires = new Date(expires); + td.exp_sec = in.getExp(); + Set<String> scopes = td.scopes(true); + if(in.getScope()!=null) { + for(String s : Split.split(' ', in.getScope())) { + scopes.add(s); + } + } + // td.state = nothing to add at this point + td.req_ip = trans.ip(); + trans.checkpoint(td.user + ':' + td.client_id + ", " + td.id); + return loadToken(trans, td); + } + } +// System.out.println(rai.value.getClientId()); + } else { + 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); + } + } else { + trans.audit().printf("Bad Token: requesting_id,%s,access_token=%s,ip=%s\n",trans.user(),token,trans.ip()); + } + return Result.err(Result.ERR_Denied,"Bad Token"); + } else { + return dbIntrospect(trans,token); + } + } catch (CadiException | APIException | LocatorException e) { + return Result.err(e); + } + } + + public Result<Data> dbIntrospect(final AuthzTrans trans, final String token) { + Result<List<Data>> rld = tokenDAO.read(trans, token); + if(rld.notOKorIsEmpty()) { + return Result.err(rld); + } + OAuthTokenDAO.Data odd = rld.value.get(0); + trans.checkpoint(odd.user + ':' + odd.client_id + ", " + odd.id); + if(odd.active) { + if(odd.expires.before(trans.now())) { + return Result.err(Result.ERR_Policy,"Token %1 has expired",token); + } + return Result.ok(rld.value.get(0)); // ok keyed on id/token. + } else { + return Result.err(Result.ERR_Denied,"Token %1 is inactive",token); + } + } + + public void close() { + for(DAO<AuthzTrans,?> dao : daos) { + dao.close(NullTrans.singleton()); + } + } + +} diff --git a/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/service/OCreds.java b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/service/OCreds.java new file mode 100644 index 00000000..becb746a --- /dev/null +++ b/auth/auth-oauth/src/main/java/org/onap/aaf/auth/oauth/service/OCreds.java @@ -0,0 +1,33 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.auth.oauth.service; + +public class OCreds { + public final String client_id, username; + public final byte[] client_secret, password; + public OCreds(String client_id, String client_secret, String username, String password) { + this.client_id = client_id; + this.client_secret = client_secret==null?null:client_secret.getBytes(); + this.username = username; + this.password = password==null?null:password.getBytes(); + } +} |