/**
* 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 static com.entwinemedia.fn.Stream.$;
import static org.opencastproject.assetmanager.api.AssetManager.DEFAULT_OWNER;
import static org.opencastproject.assetmanager.api.fn.Enrichments.enrich;
import static org.opencastproject.authorization.xacml.manager.impl.Util.toAcl;
import static org.opencastproject.util.data.Collections.list;
import static org.opencastproject.workflow.api.ConfiguredWorkflowRef.toConfiguredWorkflow;
import org.opencastproject.assetmanager.api.AssetManager;
import org.opencastproject.assetmanager.api.fn.Snapshots;
import org.opencastproject.assetmanager.api.query.AQueryBuilder;
import org.opencastproject.assetmanager.util.Workflows;
import org.opencastproject.authorization.xacml.manager.api.AclService;
import org.opencastproject.authorization.xacml.manager.api.AclServiceException;
import org.opencastproject.authorization.xacml.manager.api.EpisodeACLTransition;
import org.opencastproject.authorization.xacml.manager.api.ManagedAcl;
import org.opencastproject.authorization.xacml.manager.api.SeriesACLTransition;
import org.opencastproject.authorization.xacml.manager.api.TransitionQuery;
import org.opencastproject.authorization.xacml.manager.api.TransitionResult;
import org.opencastproject.mediapackage.MediaPackage;
import org.opencastproject.mediapackage.MediaPackageSupport;
import org.opencastproject.message.broker.api.MessageSender;
import org.opencastproject.message.broker.api.acl.AclItem;
import org.opencastproject.security.api.AccessControlList;
import org.opencastproject.security.api.AclScope;
import org.opencastproject.security.api.AuthorizationService;
import org.opencastproject.security.api.Organization;
import org.opencastproject.series.api.SeriesService;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.util.data.Option;
import org.opencastproject.workflow.api.ConfiguredWorkflow;
import org.opencastproject.workflow.api.ConfiguredWorkflowRef;
import org.opencastproject.workflow.api.WorkflowService;
import org.opencastproject.workspace.api.Workspace;
import com.entwinemedia.fn.data.Opt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/** 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 AclTransitionDb persistence;
private final AclDb aclDb;
private final SeriesService seriesService;
private final AssetManager assetManager;
private final AuthorizationService authorizationService;
private final WorkflowService workflowService;
private final Workspace workspace;
private final MessageSender messageSender;
public AclServiceImpl(Organization organization, AclDb aclDb, AclTransitionDb transitionDb,
SeriesService seriesService, AssetManager assetManager, WorkflowService workflowService,
AuthorizationService authorizationService, MessageSender messageSender, Workspace workspace) {
this.organization = organization;
this.persistence = transitionDb;
this.aclDb = aclDb;
this.seriesService = seriesService;
this.assetManager = assetManager;
this.workflowService = workflowService;
this.authorizationService = authorizationService;
this.messageSender = messageSender;
this.workspace = workspace;
}
@Override
public EpisodeACLTransition addEpisodeTransition(String episodeId, Option<Long> managedAclId, Date at,
Option<ConfiguredWorkflowRef> workflow) throws AclServiceException {
return persistence.storeEpisodeAclTransition(organization, episodeId, at, managedAclId, workflow);
}
@Override
public SeriesACLTransition addSeriesTransition(String seriesId, long managedAclId, Date at, boolean override,
Option<ConfiguredWorkflowRef> workflow) throws AclServiceException {
return persistence.storeSeriesAclTransition(organization, seriesId, at, managedAclId, override, workflow);
}
@Override
public EpisodeACLTransition updateEpisodeTransition(long transitionId, Option<Long> managedAclId, Date at,
Option<ConfiguredWorkflowRef> workflow) throws AclServiceException, NotFoundException {
return persistence.updateEpisodeAclTransition(organization, transitionId, at, managedAclId, workflow);
}
@Override
public SeriesACLTransition updateSeriesTransition(long transitionId, long managedAclId, Date at,
Option<ConfiguredWorkflowRef> workflow, boolean override) throws AclServiceException, NotFoundException {
return persistence.updateSeriesAclTransition(organization, transitionId, at, managedAclId, override, workflow);
}
@Override
public void deleteEpisodeTransition(long transitionId) throws NotFoundException, AclServiceException {
persistence.deleteEpisodeAclTransition(organization, transitionId);
}
@Override
public void deleteSeriesTransition(long transitionId) throws NotFoundException, AclServiceException {
persistence.deleteSeriesAclTransition(organization, transitionId);
}
@Override
public void deleteEpisodeTransitions(String episodeId) throws AclServiceException, NotFoundException {
List<EpisodeACLTransition> transitions = persistence.getEpisodeAclTransitions(organization, episodeId);
for (EpisodeACLTransition transition : transitions) {
persistence.deleteEpisodeAclTransition(organization, transition.getTransitionId());
}
}
@Override
public void deleteSeriesTransitions(String seriesId) throws AclServiceException, NotFoundException {
List<SeriesACLTransition> transitions = persistence.getSeriesAclTransitions(organization, seriesId);
for (SeriesACLTransition transition : transitions) {
persistence.deleteSeriesAclTransition(organization, transition.getTransitionId());
}
}
@Override
public TransitionResult getTransitions(TransitionQuery query) throws AclServiceException {
return persistence.getByQuery(organization, query);
}
@Override
public SeriesACLTransition markSeriesTransitionAsCompleted(long transitionId) throws AclServiceException,
NotFoundException {
return persistence.markSeriesTransitionAsCompleted(organization, transitionId);
}
@Override
public EpisodeACLTransition markEpisodeTransitionAsCompleted(long transitionId) throws AclServiceException,
NotFoundException {
return persistence.markEpisodeTransitionAsCompleted(organization, transitionId);
}
@Override
public boolean applyAclToEpisode(String episodeId, AccessControlList acl, Option<ConfiguredWorkflowRef> workflow)
throws AclServiceException {
try {
Option<MediaPackage> mediaPackage = Option.none();
if (assetManager != null)
mediaPackage = getFromAssetManagerByMpId(episodeId);
Option<AccessControlList> aclOpt = Option.option(acl);
// the episode service is the source of authority for the retrieval of media packages
for (final MediaPackage episodeSvcMp : mediaPackage) {
aclOpt.fold(new Option.EMatch<AccessControlList>() {
// set the new episode ACL
@Override
public void esome(final AccessControlList acl) {
// update in episode service
MediaPackage mp = authorizationService.setAcl(episodeSvcMp, AclScope.Episode, acl).getA();
if (assetManager != null)
assetManager.takeSnapshot(DEFAULT_OWNER, mp);
}
// if none EpisodeACLTransition#isDelete returns true so delete the episode ACL
@Override
public void enone() {
// update in episode service
MediaPackage mp = authorizationService.removeAcl(episodeSvcMp, AclScope.Episode);
if (assetManager != null)
assetManager.takeSnapshot(DEFAULT_OWNER, mp);
}
});
// apply optional workflow
for (ConfiguredWorkflowRef workflowRef : workflow)
applyWorkflow(list(episodeSvcMp), workflowRef);
return true;
}
// not found
return false;
} catch (Exception e) {
logger.error("Error applying episode ACL", e);
throw new AclServiceException(e);
}
}
@Override
public boolean applyAclToEpisode(String episodeId, Option<ManagedAcl> managedAcl,
Option<ConfiguredWorkflowRef> workflow) throws AclServiceException {
return applyAclToEpisode(episodeId, managedAcl.map(toAcl).getOrElseNull(), workflow);
}
/** Update the ACL of an episode. */
@Override
public void applyEpisodeAclTransition(final EpisodeACLTransition t) throws AclServiceException {
applyAclToEpisode(t.getEpisodeId(), t.getAccessControlList(), t.getWorkflow());
try {
// mark as done
// todo If acl application fails the transition will not be marked as done. Depending on the
// failure cause the application of the transition will be tried forever.
markEpisodeTransitionAsCompleted(t.getTransitionId());
} catch (NotFoundException e) {
throw new AclServiceException(e);
}
}
@Override
public boolean applyAclToSeries(String seriesId, AccessControlList acl, boolean override,
Option<ConfiguredWorkflowRef> workflow) throws AclServiceException {
try {
if (override) {
// delete acls before calling seriesService.updateAccessControl to avoid
// possible interference since a call to this method triggers update event handlers
// which run on a separate thread. This must be considered a design smell since it
// requires knowledge of the services implementation.
//
// delete in episode service
List<MediaPackage> mediaPackages = new ArrayList<>();
if (assetManager != null)
mediaPackages = getFromAssetManagerBySeriesId(seriesId);
for (MediaPackage mp : mediaPackages) {
// remove episode xacml and update in archive service
if (assetManager != null)
assetManager.takeSnapshot(DEFAULT_OWNER, authorizationService.removeAcl(mp, AclScope.Episode));
}
}
// update in series service
// this will in turn update the search service by the SeriesUpdatedEventHandler
// and the episode service by the EpisodesPermissionsUpdatedEventHandler
try {
seriesService.updateAccessControl(seriesId, acl);
} catch (NotFoundException e) {
return false;
}
return true;
} catch (Exception e) {
logger.error("Error applying series ACL", e);
throw new AclServiceException(e);
}
}
@Override
public boolean applyAclToSeries(String seriesId, ManagedAcl managedAcl, boolean override,
Option<ConfiguredWorkflowRef> workflow) throws AclServiceException {
return applyAclToSeries(seriesId, managedAcl.getAcl(), override, workflow);
}
/** Update the ACL of a series. */
@Override
public void applySeriesAclTransition(SeriesACLTransition t) throws AclServiceException {
applyAclToSeries(t.getSeriesId(), t.getAccessControlList(), t.isOverride(), t.getWorkflow());
try {
// mark as done
// todo If acl application fails the transition will not be marked as done. Depending on the
// failure cause the application of the transition will be tried forever.
markSeriesTransitionAsCompleted(t.getTransitionId());
} catch (NotFoundException e) {
throw new AclServiceException(e);
}
}
/**
* Return media package with id <code>mpId</code> from asset manager.
*
* @return single element list or empty list
*/
private Option<MediaPackage> getFromAssetManagerByMpId(String mpId) {
final AQueryBuilder q = assetManager.createQuery();
final Opt<MediaPackage> mp = enrich(
q.select(q.snapshot()).where(q.mediaPackageId(mpId).and(q.version().isLatest())).run()).getSnapshots()
.head().map(Snapshots.getMediaPackage);
return Option.fromOpt(mp);
}
/** Return all media packages of a series from the asset manager. */
private List<MediaPackage> getFromAssetManagerBySeriesId(String seriesId) {
final AQueryBuilder q = assetManager.createQuery();
return enrich(q.select(q.snapshot()).where(q.seriesId().eq(seriesId).and(q.version().isLatest())).run())
.getSnapshots().map(Snapshots.getMediaPackage).toList();
}
/** Apply a workflow to a list of media packages. */
private void applyWorkflow(final List<MediaPackage> mps, final ConfiguredWorkflowRef workflowRef) {
toConfiguredWorkflow(workflowService, workflowRef).fold(new Option.Match<ConfiguredWorkflow, Void>() {
@Override
public Void some(ConfiguredWorkflow workflow) {
logger.info("Apply optional workflow {}", workflow.getWorkflowDefinition().getId());
if (assetManager != null) {
new Workflows(assetManager, workspace, workflowService)
.applyWorkflowToLatestVersion($(mps).map(MediaPackageSupport.Fn.getId.toFn()), workflow);
}
return null;
}
@Override
public Void none() {
logger.warn("{} does not exist", workflowRef.getWorkflowId());
return null;
}
});
}
@Override
public List<ManagedAcl> getAcls() {
return aclDb.getAcls(organization);
}
@Override
public Option<ManagedAcl> getAcl(long id) {
return aclDb.getAcl(organization, id);
}
@Override
public boolean updateAcl(ManagedAcl acl) {
Option<ManagedAcl> oldName = getAcl(acl.getId());
boolean updateAcl = aclDb.updateAcl(acl);
if (updateAcl) {
if (oldName.isSome() && !(oldName.get().getName().equals(acl.getName()))) {
AclItem aclItem = AclItem.update(oldName.get().getName(), acl.getName());
messageSender.sendObjectMessage(AclItem.ACL_QUEUE, MessageSender.DestinationType.Queue, aclItem);
}
}
return updateAcl;
}
@Override
public Option<ManagedAcl> createAcl(AccessControlList acl, String name) {
Option<ManagedAcl> createAcl = aclDb.createAcl(organization, acl, name);
if (createAcl.isSome()) {
AclItem aclItem = AclItem.create(createAcl.get().getName());
messageSender.sendObjectMessage(AclItem.ACL_QUEUE, MessageSender.DestinationType.Queue, aclItem);
}
return createAcl;
}
@Override
public boolean deleteAcl(long id) throws AclServiceException, NotFoundException {
final TransitionQuery query = TransitionQuery.query().withDone(false).withAclId(id);
final TransitionResult result = persistence.getByQuery(organization, query);
if (result.getEpisodeTransistions().size() > 0 || result.getSeriesTransistions().size() > 0)
return false;
Option<ManagedAcl> deletedAcl = getAcl(id);
if (aclDb.deleteAcl(organization, id)) {
if (deletedAcl.isSome()) {
AclItem aclItem = AclItem.delete(deletedAcl.get().getName());
messageSender.sendObjectMessage(AclItem.ACL_QUEUE, MessageSender.DestinationType.Queue, aclItem);
}
return true;
}
throw new NotFoundException("Managed acl with id " + id + " not found.");
}
}