/*- * ============LICENSE_START======================================================= * org.onap.dmaap * ================================================================================ * Copyright (C) 2017 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.dmaap.dbcapi.service; import static com.att.eelf.configuration.Configuration.MDC_BEGIN_TIMESTAMP; import static com.att.eelf.configuration.Configuration.MDC_ELAPSED_TIME; import static com.att.eelf.configuration.Configuration.MDC_END_TIMESTAMP; import static com.att.eelf.configuration.Configuration.MDC_KEY_REQUEST_ID; import static com.att.eelf.configuration.Configuration.MDC_PARTNER_NAME; import static com.att.eelf.configuration.Configuration.MDC_RESPONSE_CODE; import static com.att.eelf.configuration.Configuration.MDC_RESPONSE_DESC; import static com.att.eelf.configuration.Configuration.MDC_SERVICE_NAME; import static com.att.eelf.configuration.Configuration.MDC_STATUS_CODE; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.xml.bind.DatatypeConverter; import org.onap.dmaap.dbcapi.aaf.DmaapPerm; import org.onap.dmaap.dbcapi.aaf.authentication.ApiPolicy; import org.onap.dmaap.dbcapi.aaf.authentication.AuthenticationErrorException; import org.onap.dmaap.dbcapi.logging.BaseLoggingClass; import org.onap.dmaap.dbcapi.model.ApiError; import org.onap.dmaap.dbcapi.model.Dmaap; import org.onap.dmaap.dbcapi.resources.RequiredFieldException; import org.onap.dmaap.dbcapi.util.DmaapConfig; import org.onap.dmaap.dbcapi.util.RandomString; import org.slf4j.MDC; public class ApiService extends BaseLoggingClass { private class StopWatch { private long clock = 0; private long elapsed = 0; public StopWatch() { clock = 0; elapsed = 0; } public void reset() { clock = System.currentTimeMillis(); elapsed = 0; } public void stop() { Long stopTime = System.currentTimeMillis(); elapsed += stopTime - clock; clock = 0; MDC.put( MDC_END_TIMESTAMP, isoFormatter.format(new Date(stopTime))); MDC.put( MDC_ELAPSED_TIME, String.valueOf(elapsed)); } public void start() { if ( clock != 0 ) { //not stopped return; } clock = System.currentTimeMillis(); MDC.put( MDC_BEGIN_TIMESTAMP, isoFormatter.format(new Date(clock))); } private long getElapsed() { return elapsed; } } private String apiNamespace; private boolean usePE; private String uri; private String uriPath; private String method; private String authorization; private String requestId; private ApiError err; private StopWatch stopwatch; private ApiPolicy apiPolicy; public static final String ISO_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; public final static TimeZone utc = TimeZone.getTimeZone("UTC"); public final static SimpleDateFormat isoFormatter = new SimpleDateFormat(ISO_FORMAT); static { isoFormatter.setTimeZone(utc); } public ApiService() { stopwatch = new StopWatch(); stopwatch.start(); err = new ApiError(); requestId = (new RandomString(10)).nextString(); if (apiNamespace == null) { DmaapConfig p = (DmaapConfig)DmaapConfig.getConfig(); usePE = "true".equalsIgnoreCase(p.getProperty("UsePE", "false")); apiNamespace = p.getProperty("ApiNamespace", "org.openecomp.dmaapBC.api"); } apiPolicy = new ApiPolicy(); logger.info( "usePE=" + usePE + " apiNamespace=" + apiNamespace); } public ApiService setAuth( String auth ) { this.authorization = auth; logger.info( "setAuth: authorization={} ", authorization); return this; } private void setServiceName(){ String svcRequest = new String( this.method + " " + this.uriPath ); MDC.put(MDC_SERVICE_NAME, svcRequest ); } public ApiService setHttpMethod( String httpMethod ) { this.method = httpMethod; logger.info( "setHttpMethod: method={} ", method); setServiceName(); return this; } public ApiService setUriPath( String uriPath ) { this.uriPath = uriPath; this.uri = setUriFromPath( uriPath ); logger.info( "setUriPath: uriPath={} uri={}", uriPath, uri); setServiceName(); return this; } private String setUriFromPath( String uriPath ) { int ch = uriPath.indexOf("/"); if ( ch > 0 ) { return( (String) uriPath.subSequence(0, ch ) ); } else { return uriPath; } } public ApiError getErr() { return err; } public void setErr(ApiError err) { this.err = err; } // test for presence of a required field public void required( String name, Object val, String expr ) throws RequiredFieldException { if ( val == null ) { err.setCode(Status.BAD_REQUEST.getStatusCode()); err.setMessage("missing required field"); err.setFields( name ); throw new RequiredFieldException(); } if ( expr != null && ! expr.isEmpty() ) { Pattern pattern = Pattern.compile(expr); Matcher matcher = pattern.matcher((CharSequence) val); if ( ! matcher.find() ) { err.setCode(Status.BAD_REQUEST.getStatusCode()); err.setMessage( "value '" + val + "' violates regexp check '" + expr + "'"); err.setFields( name ); throw new RequiredFieldException(); } } } // utility to serialize ApiErr object public String toString() { return String.format( "code=%d msg=%s fields=%s", err.getCode(), err.getMessage(), err.getFields() ); } public void setCode(int statusCode) { err.setCode(statusCode); } public void setMessage(String string) { err.setMessage(string); } public void setFields(String string) { err.setFields(string); } private Response buildResponse( Object obj ) { stopwatch.stop(); MDC.put( MDC_RESPONSE_CODE, String.valueOf(err.getCode()) ); auditLogger.auditEvent( "" ); return Response.status( err.getCode()) .entity(obj) .build(); } private Response buildSuccessResponse(Object d) { MDC.put( MDC_STATUS_CODE, "COMPLETE"); MDC.put( MDC_RESPONSE_DESC, ""); return buildResponse( d ); } private Response buildErrResponse() { MDC.put( MDC_STATUS_CODE, "ERROR"); MDC.put( MDC_RESPONSE_DESC, err.getMessage()); return buildResponse(getErr()); } public Response success( Object d ) { err.setCode(Status.OK.getStatusCode()); return buildSuccessResponse(d); } public Response success( int code, Object d ) { err.setCode(code); return buildSuccessResponse(d); } public Response unauthorized( String msg ) { err.setCode(Status.UNAUTHORIZED.getStatusCode()); err.setFields( "Authorization"); err.setMessage( msg ); return buildErrResponse(); } public Response unauthorized() { err.setCode(Status.UNAUTHORIZED.getStatusCode()); err.setFields( "Authorization"); err.setMessage( "User credentials in HTTP Header field Authorization are not authorized for the requested action"); return buildErrResponse(); } public Response unavailable() { err.setCode(Status.SERVICE_UNAVAILABLE.getStatusCode()); err.setMessage( "Request is unavailable due to unexpected condition"); return buildErrResponse(); } public Response notFound() { err.setCode(Status.NOT_FOUND.getStatusCode()); err.setMessage( "Requested object not found"); return buildErrResponse(); } public Response error() { return buildErrResponse(); } public void checkAuthorization( String auth, String uriPath, String httpMethod ) throws AuthenticationErrorException, Exception { authorization = auth; setUriFromPath( uriPath ); method = httpMethod; checkAuthorization(); } public void checkAuthorization() throws AuthenticationErrorException, Exception { MDC.put(MDC_KEY_REQUEST_ID, requestId); logger.info("request: uri={} method={} auth={}", uri, method, authorization ); if ( uri == null || uri.isEmpty()) { String errmsg = "No URI value provided "; err.setMessage(errmsg); logger.info( errmsg ); throw new AuthenticationErrorException( ); } if ( method == null || method.isEmpty()) { String errmsg = "No method value provided "; err.setMessage(errmsg); logger.info( errmsg ); throw new AuthenticationErrorException( ); } DmaapService dmaapService = new DmaapService(); Dmaap dmaap = dmaapService.getDmaap(); String env = dmaap.getDmaapName(); // special case during bootstrap of app when DMaaP environment may not be set. // this allows us to authorize certain APIs used for initialization during this window. if ( env == null || env.isEmpty() ) { env = "boot"; } if ( ! usePE ) return; // skip authorization if not enabled if ( authorization == null || authorization.isEmpty()) { String errmsg = "No basic authorization value provided "; err.setMessage(errmsg); logger.info( errmsg ); throw new AuthenticationErrorException( ); } String credentials = authorization.substring("Basic".length()).trim(); byte[] decoded = DatatypeConverter.parseBase64Binary(credentials); String decodedString = new String(decoded); String[] actualCredentials = decodedString.split(":"); String ID = actualCredentials[0]; String Password = actualCredentials[1]; MDC.put(MDC_PARTNER_NAME, ID); try { DmaapPerm p = new DmaapPerm( apiNamespace + "." + uri, env, method ); apiPolicy.check( ID, Password, p); } catch ( AuthenticationErrorException ae ) { String errmsg = "User " + ID + " failed authentication/authorization for " + apiNamespace + "." + uriPath + " " + env + " " + method; logger.info( errmsg ); err.setMessage(errmsg); throw ae; } } public String getRequestId() { return requestId; } public ApiService setRequestId(String requestId) { if ( requestId == null || requestId.isEmpty()) { this.requestId = (new RandomString(10)).nextString(); logger.warn( "X-ECOMP-RequestID not set in HTTP Header. Setting RequestId value to: " + this.requestId ); } else { this.requestId = requestId; } MDC.put(MDC_KEY_REQUEST_ID, this.requestId); return this; } }