AclServiceImpl.java

/*
 * 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.authorization.xacml.manager.impl;

import org.opencastproject.authorization.xacml.manager.api.AclService;
import org.opencastproject.authorization.xacml.manager.api.AclServiceException;
import org.opencastproject.authorization.xacml.manager.api.ManagedAcl;
import org.opencastproject.elasticsearch.api.SearchIndexException;
import org.opencastproject.elasticsearch.api.SearchResult;
import org.opencastproject.elasticsearch.api.SearchResultItem;
import org.opencastproject.elasticsearch.index.ElasticsearchIndex;
import org.opencastproject.elasticsearch.index.objects.event.Event;
import org.opencastproject.elasticsearch.index.objects.event.EventSearchQuery;
import org.opencastproject.elasticsearch.index.objects.series.Series;
import org.opencastproject.elasticsearch.index.objects.series.SeriesSearchQuery;
import org.opencastproject.security.api.AccessControlList;
import org.opencastproject.security.api.Organization;
import org.opencastproject.security.api.SecurityService;
import org.opencastproject.security.api.User;
import org.opencastproject.util.NotFoundException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Optional;
import java.util.function.Function;

/** Organization bound impl. */
public final class AclServiceImpl implements AclService {
  /** Logging utility */
  private static final Logger logger = LoggerFactory.getLogger(AclServiceImpl.class);

  /** Context */
  private final Organization organization;

  /** Service dependencies */
  private final AclDb aclDb;
  private final SecurityService securityService;

  /** The Elasticsearch indices */
  protected ElasticsearchIndex index;

  public AclServiceImpl(Organization organization, AclDb aclDb, ElasticsearchIndex index,
          SecurityService securityService) {
    this.organization = organization;
    this.aclDb = aclDb;
    this.index = index;
    this.securityService = securityService;
  }

  @Override
  public List<ManagedAcl> getAcls() {
    return aclDb.getAcls(organization);
  }

  @Override
  public Optional<ManagedAcl> getAcl(long id) {
    return aclDb.getAcl(organization, id);
  }

  @Override
  public Optional<ManagedAcl> getAcl(String name) {
    return aclDb.getAcl(organization, name);
  }

  @Override
  public boolean updateAcl(ManagedAcl acl) {
    Optional<ManagedAcl> oldName = getAcl(acl.getId());
    boolean updateAcl = aclDb.updateAcl(acl);
    if (updateAcl) {
      if (oldName.isPresent() && !(oldName.get().getName().equals(acl.getName()))) {
        User user = securityService.getUser();
        updateAclInIndex(oldName.get().getName(), acl.getName(), index, organization.getId(), user);
      }
    }
    return updateAcl;
  }

  @Override
  public Optional<ManagedAcl> createAcl(AccessControlList acl, String name) {
    // we don't need to update the Elasticsearch indices in this case
    return aclDb.createAcl(organization, acl, name);
  }

  @Override
  public boolean deleteAcl(long id) throws AclServiceException, NotFoundException {
    Optional<ManagedAcl> deletedAcl = getAcl(id);
    if (aclDb.deleteAcl(organization, id)) {
      if (deletedAcl.isPresent()) {
        User user = securityService.getUser();
        removeAclFromIndex(deletedAcl.get().getName(), index, organization.getId(), user);
      }
      return true;
    }
    throw new NotFoundException("Managed acl with id " + id + " not found.");
  }

  /**
   * Update the Managed ACL in the events and series in the Elasticsearch index.
   *
   * @param currentAclName
   *         the current name of the managed acl
   * @param newAclName
   *         the new name of the managed acl
   * @param index
   *         the index to update
   * @param orgId
   *         the organization the managed acl belongs to
   * @param user
   *         the current user
   */
  private void updateAclInIndex(String currentAclName, String newAclName, ElasticsearchIndex index, String orgId,
          User user) {
    logger.debug("Update the events to change the managed acl name from '{}' to '{}'.", currentAclName, newAclName);
    updateManagedAclForEvents(currentAclName, Optional.of(newAclName), index, orgId, user);

    logger.debug("Update the series to change the managed acl name from '{}' to '{}'.", currentAclName, newAclName);
    updateManagedAclForSeries(currentAclName, Optional.of(newAclName), index, orgId, user);
  }

  /**
   * Remove the Managed ACL from the events and series in the Elasticsearch index.
   *
   * @param currentAclName
   *         the current name of the managed acl
   * @param index
   *         the index to update
   * @param orgId
   *         the organization the managed acl belongs to
   * @param user
   *         the current user
   */
  private void removeAclFromIndex(String currentAclName, ElasticsearchIndex index, String orgId,
          User user) {
    logger.debug("Update the events to remove the managed acl name '{}'.", currentAclName);
    updateManagedAclForEvents(currentAclName, Optional.empty(), index, orgId, user);

    logger.debug("Update the series to remove the managed acl name '{}'.", currentAclName);
    updateManagedAclForSeries(currentAclName, Optional.empty(), index, orgId, user);
  }

  /**
   * Update or remove the Managed Acl for the series in the Elasticsearch index.
   *
   * @param currentAclName
   *         the current name of the managed acl
   * @param newAclNameOpt
   * @param index
   *         the index to update
   * @param orgId
   *         the organization the managed acl belongs to
   * @param user
   *         the current user
   */
  private void updateManagedAclForSeries(String currentAclName, Optional<String> newAclNameOpt,
          ElasticsearchIndex index, String orgId, User user) {
    SearchResult<Series> result;
    try {
      result = index.getByQuery(new SeriesSearchQuery(orgId, user).withoutActions()
              .withManagedAcl(currentAclName));
    } catch (SearchIndexException e) {
      logger.error("Unable to find the series in org '{}' with current managed acl name '{}'", orgId, currentAclName,
              e);
      return;
    }

    for (SearchResultItem<Series> seriesItem : result.getItems()) {
      String seriesId = seriesItem.getSource().getIdentifier();

      Function<Optional<Series>, Optional<Series>> updateFunction = (Optional<Series> seriesOpt) -> {
        if (seriesOpt.isPresent() && seriesOpt.get().getManagedAcl().equals(currentAclName)) {
          Series series = seriesOpt.get();
          series.setManagedAcl(newAclNameOpt.orElse(null));
          return Optional.of(series);
        }
        return Optional.empty();
      };

      try {
        index.addOrUpdateSeries(seriesId, updateFunction, orgId, user);
      } catch (SearchIndexException e) {
        if (newAclNameOpt.isPresent()) {
          logger.warn("Unable to update series'{}' from current managed acl '{}' to new managed acl name '{}'",
                  seriesId, currentAclName, newAclNameOpt.get(), e);
        } else {
          logger.warn("Unable to update series '{}' to remove managed acl '{}'", seriesId, currentAclName, e);
        }
      }
    }
  }

  /**
   * Update or remove the Managed Acl for the events in the Elasticsearch index.
   *
   * @param currentAclName
   *         the current name of the managed acl
   * @param newAclNameOpt
   * @param index
   *         the index to update
   * @param orgId
   *         the organization the managed acl belongs to
   * @param user
   *         the current user
   */
  private void updateManagedAclForEvents(String currentAclName, Optional<String> newAclNameOpt,
          ElasticsearchIndex index, String orgId, User user) {
    SearchResult<Event> result;
    try {
      result = index.getByQuery(new EventSearchQuery(orgId, user).withoutActions()
              .withManagedAcl(currentAclName));
    } catch (SearchIndexException e) {
      logger.error("Unable to find the events in org '{}' with current managed acl name '{}' for event",
              orgId, currentAclName, e);
      return;
    }

    for (SearchResultItem<Event> eventItem : result.getItems()) {
      String eventId = eventItem.getSource().getIdentifier();

      Function<Optional<Event>, Optional<Event>> updateFunction = (Optional<Event> eventOpt) -> {
        if (eventOpt.isPresent() && eventOpt.get().getManagedAcl().equals(currentAclName)) {
          Event event = eventOpt.get();
          event.setManagedAcl(newAclNameOpt.orElse(null));
          return Optional.of(event);
        }
        return Optional.empty();
      };

      try {
        index.addOrUpdateEvent(eventId, updateFunction, orgId, user);
      } catch (SearchIndexException e) {
        if (newAclNameOpt.isPresent()) {
          logger.warn(
                  "Unable to update event '{}' from current managed acl '{}' to new managed acl name '{}'",
                  eventId, currentAclName, newAclNameOpt.get(), e);
        } else {
          logger.warn("Unable to update event '{}' to remove managed acl '{}'", eventId, currentAclName, e);
        }
      }
    }
  }
}