All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opencastproject.uiconfig.UIConfigRest Maven / Gradle / Ivy

/**
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 *
 * The Apereo Foundation licenses this file to you under the Educational
 * Community 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://opensource.org/licenses/ecl2.txt
 *
 * 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.
 *
 */

package org.opencastproject.uiconfig;

import static org.opencastproject.security.api.DefaultOrganization.DEFAULT_ORGANIZATION_ID;

import org.opencastproject.security.api.SecurityService;
import org.opencastproject.util.ConfigurationException;
import org.opencastproject.util.MimeTypes;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.util.doc.rest.RestParameter;
import org.opencastproject.util.doc.rest.RestQuery;
import org.opencastproject.util.doc.rest.RestResponse;
import org.opencastproject.util.doc.rest.RestService;

import org.apache.commons.lang3.StringUtils;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.AccessDeniedException;
import java.nio.file.Paths;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

/**
 * Serves UI configuration files via REST
 */
@Path("/")
@RestService(name = "UIConfigEndpoint",
    title = "UI Config Endpoint",
    abstractText = "Serves the configuration of the UI",
    notes = {
        "All paths above are relative to the REST endpoint base (something like http://your.server/files)",
        "If the service is down or not working it will return a status 503, this means the the "
            + "underlying service is not working and is either restarting or has failed",
        "A status code 500 means a general failure has occurred which is not recoverable and was "
            + "not anticipated.In other words, there is a bug! You should file an error report "
            + "with your server logs from the timewhen the error occurred: "
            + "Opencast Issue Tracker"
    }
)
@Component(
    property = {
        "service.description=UI Config REST Endpoint",
        "opencast.service.type=org.opencastproject.uiconfig",
        "opencast.service.path=/ui/config"
    },
    immediate = true,
    service = UIConfigRest.class
)
public class UIConfigRest {
  /** The logger */
  private static final Logger logger = LoggerFactory.getLogger(UIConfigRest.class);

  /** Configuration key for the ui config folder */
  static final String UI_CONFIG_FOLDER_PROPERTY = "org.opencastproject.uiconfig.folder";
  static final String X_ACCEL_REDIRECT_PROPERTY = "X-Accel-Redirect";

  /** Default Path for the ui configuration folder (relative to ${karaf.etc}) */
  private static final String UI_CONFIG_FOLDER_DEFAULT = "ui-config";

  /** The currently used path to the configuration folder */
  private String uiConfigFolder = null;
  private String xAccelRedirect = null;

  /** The used SecurityService */
  private SecurityService securityService;

  @Reference
  protected void setSecurityService(SecurityService securityService) {
    this.securityService = securityService;
  }

  /**
   * OSGI callback for activating this component
   *
   * @param cc
   *          the osgi component context
   */
  @Activate
  @Modified
  public void activate(BundleContext context, Map properties)
          throws ConfigurationException, IOException {
    uiConfigFolder = properties.get(UI_CONFIG_FOLDER_PROPERTY);
    logger.debug("UI configuration folder configured as '{}'", uiConfigFolder);
    if (StringUtils.isNotEmpty(uiConfigFolder)) {
      uiConfigFolder = new File(uiConfigFolder).getCanonicalPath();
    } else {
      final String karafEtc = context.getProperty("karaf.etc");
      if (StringUtils.isBlank(karafEtc)) {
        throw new ConfigurationException(String.format(
            "%s not set and unable to fall back to default location based on ${karaf.etc}",
            UI_CONFIG_FOLDER_PROPERTY));
      }
      uiConfigFolder = new File(karafEtc, UI_CONFIG_FOLDER_DEFAULT).getCanonicalPath();
    }
    logger.debug("UI configuration folder set to '{}'", uiConfigFolder);

    xAccelRedirect = properties.get(X_ACCEL_REDIRECT_PROPERTY);
    logger.debug("X-Accel-Redirect path set to {}", xAccelRedirect);
  }

  @GET
  @Path("{component}/{filename}")
  @Produces(MediaType.WILDCARD)
  @RestQuery(name = "getConfigFile",
      description = "Returns the requested configuration file (json, css, etc..)",
      pathParameters = {
          @RestParameter(description = "Name of the component, which the configuration file belongs to",
              isRequired = true, name = "component", type = RestParameter.Type.STRING),
          @RestParameter(description = "Name of the configuration file", isRequired = true,
              name = "filename", type = RestParameter.Type.STRING)
      },
      responses = {
          @RestResponse(
              description = "the requested configuration file",
              responseCode = HttpServletResponse.SC_OK
          ),
          @RestResponse(
              description = "if the configuration file doesn't exist",
              responseCode = HttpServletResponse.SC_NOT_FOUND
          ),
      },
      returnDescription = ""
  )
  public Response getConfigFile(@PathParam("component") String component, @PathParam("filename") String filename)
          throws IOException, NotFoundException {
    final String orgId = securityService.getOrganization().getId();
    File configFile = Paths.get(uiConfigFolder, orgId, component, filename).toFile();

    try {
      final String basePath = new File(uiConfigFolder, orgId).getCanonicalPath();
      final String configFileCanPath = configFile.getCanonicalPath();

      // is configFile a subdirectory of basePath (additional directory traversal protection), if not stop
      if (!configFileCanPath.startsWith(basePath)) {
        logger.debug("Directory traversal prevented (trying to access '{}')", configFile.getPath());
        throw new AccessDeniedException(configFileCanPath);
      }

      // Falling back to default organization if file does not exist
      if (!configFile.exists()) {
        logger.debug("Falling back to default organization");
        configFile = Paths.get(uiConfigFolder, DEFAULT_ORGANIZATION_ID, component, filename).toFile();
      }

      if (xAccelRedirect != null) {
        final String relative = Paths.get(uiConfigFolder).relativize(configFile.toPath()).toString();
        return Response.noContent()
            .header("X-Accel-Redirect", Paths.get(xAccelRedirect, relative).toString())
            .build();
      }

      // It is safe to pass the InputStream without closing it, JAX-RS takes care of that
      return Response.ok(new FileInputStream(configFile))
              .header("Content-Length", configFile.length())
              .header("Content-Type", MimeTypes.getMimeType(filename))
              .build();
    } catch (FileNotFoundException e) {
      logger.debug("Could not find requested configuration file '{}'", configFile.getPath(), e);
      throw new NotFoundException();
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy