CatalogService.java [src/csip] Revision:   Date:
/*
 * $Id: 2.7+12 CatalogService.java ba6f84354433 2022-09-29 od $
 *
 * This file is part of the Cloud Services Integration Platform (CSIP),
 * a Model-as-a-Service framework, API and application suite.
 *
 * 2012-2022, Olaf David and others, OMSLab, Colorado State University.
 *
 * OMSLab licenses this file to you under the MIT license.
 * See the LICENSE file in the project root for more information.
 */
package csip;

import csip.annotations.State;
import csip.utils.JSONUtils;
import csip.utils.Services;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;

import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;

/**
 * Catalog service
 *
 * @author wlloyd,od
 */
@Path("")
public class CatalogService {

  static final Logger LOG = Config.LOG;

  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public String getTextCatalog(@Context UriInfo uriInfo, @Context HttpServletRequest httpReq) {
    return getJSONCatalog(uriInfo, httpReq);
  }

  @GET
  @Produces(MediaType.APPLICATION_JSON)
  public String getJSONCatalog(@Context UriInfo uriInfo, @Context HttpServletRequest httpReq) {
    LOG.log(Level.INFO, "HTTP/GET {0}", httpReq.getRequestURL().toString());

    Registry r = Config.getRegistry();
    JSONArray services = new JSONArray();
    try {
      String host = Utils.getPublicRequestURL(httpReq);
      if (!host.endsWith("/"))
        host += "/";

      // Config parameter for requesting configuration info if:
      // '...?config=true'
      boolean requestConfig = isConfigInfoRequested(uriInfo);

      // check access if needed.
      boolean accessOK = false;
      if (requestConfig) {
        try {
          Utils.checkRemoteAccessACL(httpReq);
          accessOK = true;
        } catch (RuntimeException Ex) {
          // false
        }
      }

      for (Class<? extends ModelDataService> cl : r.getServices()) {
        State sta = cl.getAnnotation(State.class);
        if (sta != null && sta.value().equals(State.HIDDEN))
          continue;

        String path = host + Utils.getServicePath(cl);
        JSONObject service_info = Utils.getServiceInfo(cl, path);
        if (requestConfig)
          addConfigInfo(service_info, cl, accessOK);

        services.put(service_info);
      }
      return services.toString(2).replace("\\/", "/");
    } catch (JSONException ex) {
      services.put(JSONUtils.error(ex.getMessage()));
      LOG.log(Level.SEVERE, null, ex);
    }
    return services.toString().replace("\\/", "/");
  }

  private void addConfigInfo(JSONObject o, Class<? extends ModelDataService> cl, boolean accessOK) throws JSONException {
    Map<String, Object> vi = callGetConfigInfo(cl);
    if (vi != null) {
      if (accessOK) {
        for (Map.Entry<String, Object> e : vi.entrySet()) {
          o.put(e.getKey(), sanitize(e.getValue()));
        }
      } else {
        o.put("config", "Not authorized.");
      }
    }
  }

  @SuppressWarnings("unchecked")
  private Map<String, Object> callGetConfigInfo(Class<? extends ModelDataService> cl) {
    try {
      Method m = findGetConfigInfoMethod(cl);
      if (m == null)
        return null;
      m.setAccessible(true);
      return (Map<String, Object>) m.invoke(cl.getDeclaredConstructor().newInstance());
    } catch (Exception ex) {
      LOG.log(Level.SEVERE, null, ex);
      return null;
    }
  }

  private Method findGetConfigInfoMethod(Class<?> cl) {
    try {
      if (cl == ModelDataService.class)
        return null;
      return cl.getDeclaredMethod("getConfigInfo");
    } catch (NoSuchMethodException E) {
      return findGetConfigInfoMethod((Class<?>) cl.getSuperclass());
    } catch (Exception E) {
      return null;
    }
  }

  private static boolean isConfigInfoRequested(UriInfo uriInfo) {
    MultivaluedMap<String, String> qp = uriInfo.getQueryParameters();
    if (qp == null)
      return false;
    String val = qp.getFirst("config");
    return val != null && val.toLowerCase().equals("true");
  }

  /*
   * remove credentials from JDBC connect string if present. such as
   * "jdbc:sqlserver://129.82.20.129:1433;databaseName=conservation_resources;user=sdmro;password=emHBcNq3"
   * or "jdbc:postgresql://localhost/test?user=fred&password=secret&ssl=true"
   *
   */
  private static Object sanitize(Object s) {
    if (s == null)
      return "null";

    // only process strings here
    if (!(s instanceof String))
      return s;

    String s1 = (String) s;
    if (s1.contains("jdbc:sqlserver:")) {
      // remove all credentials
      s1 = s1.replaceAll(";user=[^;]*", "");
      s1 = s1.replaceAll(";password=[^;]*", "");
    } else if (s1.contains("jdbc:postgresql:")) {
      // remove all URL Path parameter
      s1 = s1 + "?"; // add this in case there is no authentication info
      s1 = s1.substring(0, s1.indexOf('?'));
    }
    return s1;
  }
}