/**
* 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.assetmanager.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.opencastproject.assetmanager.impl.AssetManagerWithSecurity.READ_ACTION;
import static org.opencastproject.assetmanager.impl.AssetManagerWithSecurity.WRITE_ACTION;
import static org.opencastproject.util.data.Tuple.tuple;
import org.opencastproject.assetmanager.api.Availability;
import org.opencastproject.assetmanager.api.Property;
import org.opencastproject.assetmanager.api.PropertyId;
import org.opencastproject.assetmanager.api.Snapshot;
import org.opencastproject.assetmanager.api.Value;
import org.opencastproject.assetmanager.impl.util.TestOrganization;
import org.opencastproject.assetmanager.impl.util.TestUser;
import org.opencastproject.mediapackage.MediaPackage;
import org.opencastproject.security.api.AccessControlEntry;
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.security.api.Role;
import org.opencastproject.security.api.SecurityConstants;
import org.opencastproject.security.api.SecurityService;
import org.opencastproject.security.api.UnauthorizedException;
import org.opencastproject.security.api.User;
import org.opencastproject.util.data.Tuple;
import com.entwinemedia.fn.P1;
import com.entwinemedia.fn.P1Lazy;
import com.entwinemedia.fn.Prelude;
import com.entwinemedia.fn.ProductBuilder;
import com.entwinemedia.fn.Unit;
import com.entwinemedia.fn.data.Opt;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.HashSet;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
@RunWith(JUnitParamsRunner.class)
public class AssetManagerWithSecurityTest extends AssetManagerTestBase<AssetManagerWithSecurity> {
public static final String ROLE_TEACHER = "ROLE_TEACHER";
public static final String ROLE_USER = "ROLE_USER";
public static final String ROLE_STUDENT = "ROLE_STUDENT";
public static final String ROLE_ANONYMOUS = "ROLE_ANONYMOUS";
public static final String ROLE_ORG_ADMIN = "ROLE_ORG_ADMIN";
private static ProductBuilder p = com.entwinemedia.fn.Products.E;
private SecurityService secSvc;
// Yikes, mutable state! Watch out...
private User currentUser = TestUser.mk(TestOrganization.mkDefault(), new HashSet<Role>());
private AccessControlList currentMediaPackageAcl = acl();
@Before
public void setUp() throws Exception {
setUp(mkTestEnvironment());
}
/**
* Setup the test environment.
*/
public AssetManagerWithSecurity mkTestEnvironment() throws Exception {
final AuthorizationService authSvc = EasyMock.createNiceMock(AuthorizationService.class);
EasyMock.expect(authSvc.getActiveAcl(EasyMock.<MediaPackage>anyObject())).andAnswer(new IAnswer<Tuple<AccessControlList, AclScope>>() {
@Override
public Tuple<AccessControlList, AclScope> answer() throws Throwable {
return tuple(currentMediaPackageAcl, AclScope.Episode);
}
}).anyTimes();
EasyMock.replay(authSvc);
//
secSvc = EasyMock.createNiceMock(SecurityService.class);
EasyMock.expect(secSvc.getUser()).andAnswer(new IAnswer<User>() {
@Override public User answer() throws Throwable {
return currentUser;
}
}).anyTimes();
EasyMock.expect(secSvc.getOrganization()).andAnswer(new IAnswer<Organization>() {
@Override public Organization answer() throws Throwable {
return currentUser.getOrganization();
}
}).anyTimes();
EasyMock.replay(secSvc);
//
return new AssetManagerWithSecurity(mkAbstractAssetManager(), authSvc, secSvc);
}
@Override public AbstractAssetManager getAbstractAssetManager() {
return (AbstractAssetManager) am.delegate;
}
@Override public String getCurrentOrgId() {
return secSvc.getOrganization().getId();
}
/**
* Evaluate product <code>p</code> in a given security context.
*
* @param user
* the user under whose roles and organization <code>p</code> shall be evaluated
* @param assertAccess
* if true assert that the current user is authorized to evaluate <code>p</code>,
* if false assert that the current user is prohibited to evaluate <code>p</code>
* @param p
* the product that contains the calls to the asset manager
* @return the result of the evaluation of <code>p</code>
*/
private <A> A runWith(User user, boolean assertAccess, P1<A> p) {
final User stashedUser = currentUser;
currentUser = user;
A result = null;
try {
result = p.get1();
if (!assertAccess) {
fail("Access should be prohibited");
}
} catch (Exception e) {
if ((e instanceof UnauthorizedException) && assertAccess) {
fail("Access should be granted");
} else if (!(e instanceof UnauthorizedException)) {
Prelude.chuck(e);
}
}
currentUser = stashedUser;
return result;
}
/* ------------------------------------------------------------------------------------------------------------------ */
@Test
@Parameters
public void testTakeSnapshot(final AccessControlList acl, User user,
final boolean assertAccess) throws Exception {
createSnapshot(acl, user, assertAccess);
}
private Object parametersForTestTakeSnapshot() {
final Organization org = TestOrganization.mkDefault();
return $a($a(acl(ace(ROLE_TEACHER, WRITE_ACTION)),
TestUser.mk(org, ROLE_USER),
false),
$a(acl(ace(ROLE_TEACHER, WRITE_ACTION)),
TestUser.mk(org, ROLE_USER, ROLE_TEACHER),
true),
$a(acl(ace(ROLE_TEACHER, WRITE_ACTION)),
TestUser.mk(org),
false),
$a(acl(),
TestUser.mk(org, SecurityConstants.GLOBAL_ADMIN_ROLE),
true));
}
/* ------------------------------------------------------------------------------------------------------------------ */
/**
* Media package is created under the admin of the default organization.
*/
@Test
@Parameters
public void testSetAvailabilityAndSetProperty(
final AccessControlList acl,
User user,
boolean assertGrant) throws Exception {
// create a snapshot
final Snapshot snapshot = createSnapshot(acl);
runWith(user, assertGrant, new P1Lazy<Unit>() {
@Override public Unit get1() {
// set availability
am.setAvailability(
snapshot.getVersion(),
snapshot.getMediaPackage().getIdentifier().toString(),
Availability.OFFLINE);
// set a property
assertTrue(am.setProperty(Property.mk(
PropertyId.mk(
snapshot.getMediaPackage().getIdentifier().toString(),
"namespace",
"property-name"),
Value.mk("value"))));
return Unit.unit;
}
});
}
private Object parametersForTestSetAvailabilityAndSetProperty() {
final Organization org = TestOrganization.mkDefault();
return $a($a(acl(ace(ROLE_TEACHER, WRITE_ACTION)),
TestUser.mk(org, ROLE_USER),
false),
$a(acl(ace(ROLE_TEACHER, WRITE_ACTION)),
TestUser.mk(org, ROLE_USER, ROLE_STUDENT),
false),
$a(acl(ace(ROLE_TEACHER, WRITE_ACTION)),
TestUser.mk(org, ROLE_USER, ROLE_TEACHER),
true),
$a(acl(ace(ROLE_TEACHER, WRITE_ACTION)),
TestUser.mk(org, ROLE_TEACHER),
true),
$a(acl(ace(ROLE_TEACHER, READ_ACTION)),
TestUser.mk(org, ROLE_TEACHER),
false),
$a(acl(),
TestUser.mk(org, SecurityConstants.GLOBAL_ADMIN_ROLE),
true),
$a(acl(),
TestUser.mk(org, org.getAdminRole()),
true));
}
/* ------------------------------------------------------------------------------------------------------------------ */
/**
* Media package is created under the admin of the default organization.
*/
@Test
@Parameters
public void testGetAsset(final AccessControlList acl, User user,
boolean assertAccess) throws Exception {
// create a snapshot
final Snapshot snapshot = createSnapshot(acl);
// get an asset of the snapshot
runWith(user, assertAccess, new P1Lazy<Unit>() {
@Override public Unit get1() {
assertTrue(am.getAsset(
snapshot.getVersion(),
snapshot.getMediaPackage().getIdentifier().toString(),
snapshot.getMediaPackage().getElements()[0].getIdentifier()).isSome());
return Unit.unit;
}
});
}
private Object parametersForTestGetAsset() {
final Organization org = TestOrganization.mkDefault();
return $a($a(acl(ace(ROLE_TEACHER, WRITE_ACTION)),
TestUser.mk(org, ROLE_TEACHER),
false),
$a(acl(ace(ROLE_TEACHER, READ_ACTION)),
TestUser.mk(org, ROLE_USER, ROLE_STUDENT),
false),
$a(acl(ace(ROLE_TEACHER, READ_ACTION)),
TestUser.mk(org, ROLE_USER, ROLE_TEACHER),
true),
$a(acl(ace(ROLE_TEACHER, READ_ACTION)),
TestUser.mk(org, ROLE_TEACHER),
true),
$a(acl(ace(ROLE_TEACHER, READ_ACTION)),
mkGlobalAdmin(org),
true),
$a(acl(),
mkOrgAdmin(org),
true),
$a(acl(),
mkDefaultOrgGlobalAdmin(),
true));
}
/* ------------------------------------------------------------------------------------------------------------------ */
@Test
@Parameters
public void testQuery(
AccessControlList acl,
User writeUser, User queryUser,
final boolean assertReadAccess, final boolean assertWriteAccess)
throws Exception {
// create a snapshot -> should always work (set assertAccess to true)
final Snapshot snapshot = createSnapshot(acl, writeUser, true);
// Set assertAccess to true since querying does not yield a security exception.
// Restricted records are simply filtered out.
runWith(queryUser, true, new P1Lazy<Unit>() {
@Override public Unit get1() {
// if read access is granted the result contains one record
assertEquals("Snapshot should be retrieved: " + assertReadAccess,
assertReadAccess,
q.select(q.snapshot()).run().getSize() == 1);
return Unit.unit;
}
});
runWith(queryUser, true, new P1Lazy<Unit>() {
@Override public Unit get1() {
// if write access is granted one snapshot should be deleted
assertEquals("Snapshots should be deleted: " + assertWriteAccess,
assertWriteAccess,
q.delete(OWNER, q.snapshot()).run() == 1);
return Unit.unit;
}
});
}
private Object parametersForTestQuery() {
final Organization org1 = TestOrganization.mk("org1", ROLE_ANONYMOUS, ROLE_ORG_ADMIN);
final Organization org2 = TestOrganization.mk("org2", ROLE_ANONYMOUS, ROLE_ORG_ADMIN);
return $a(
// make sure that a role with read rights can access its episodes
$a(acl(ace(ROLE_TEACHER, READ_ACTION), ace(ROLE_TEACHER, WRITE_ACTION)),
TestUser.mk(org1, ROLE_TEACHER),
TestUser.mk(org1, ROLE_TEACHER),
true,
true),
// make sure that roles without read rights cannot read
$a(acl(ace(ROLE_TEACHER, WRITE_ACTION)),
TestUser.mk(org1, ROLE_TEACHER),
TestUser.mk(org1, ROLE_TEACHER),
false,
true),
// make sure that a different role cannot read
$a(acl(ace(ROLE_USER, READ_ACTION), ace(ROLE_USER, WRITE_ACTION)),
TestUser.mk(org1, ROLE_USER),
TestUser.mk(org1, ROLE_TEACHER),
false,
false),
// make sure that the organization's admin can always read the episodes of her organization
$a(acl(ace(ROLE_TEACHER, READ_ACTION), ace(ROLE_TEACHER, WRITE_ACTION)),
TestUser.mk(org1, ROLE_TEACHER),
TestUser.mk(org1, org1.getAdminRole()),
true,
true),
// make sure that the global admin is always allowed to read
$a(acl(ace(ROLE_TEACHER, READ_ACTION), ace(ROLE_TEACHER, WRITE_ACTION)),
TestUser.mk(org1, ROLE_TEACHER),
TestUser.mk(org1, SecurityConstants.GLOBAL_ADMIN_ROLE),
true,
true),
// make sure that the global admin is always allowed to read, no matter what organization she is from
$a(acl(ace(ROLE_TEACHER, READ_ACTION), ace(ROLE_TEACHER, WRITE_ACTION)),
TestUser.mk(org1, ROLE_TEACHER),
TestUser.mk(org2, SecurityConstants.GLOBAL_ADMIN_ROLE),
true,
true),
// make sure that even if the admin roles are named the same, an admin from one organization
// cannot read the episodes from a another one.
$a(acl(ace(ROLE_TEACHER, READ_ACTION), ace(ROLE_TEACHER, WRITE_ACTION)),
TestUser.mk(org1, ROLE_TEACHER),
TestUser.mk(org2, org2.getAdminRole()),
false,
false)
);
}
/* ------------------------------------------------------------------------------------------------------------------ */
/**
* Create a test media package and take a snapshot under the rights of the admin of the default organization.
*
* @param acl
* ACL of the media package to snapshot
*/
private Snapshot createSnapshot(final AccessControlList acl) {
return createSnapshot(acl, mkDefaultOrgAdmin(), true);
}
private Snapshot createSnapshot(final AccessControlList acl, final User user,
boolean assertAccess) {
return runWith(user, assertAccess, new P1Lazy<Snapshot>() {
@Override public Snapshot get1() {
final AccessControlList stashedAcl = currentMediaPackageAcl;
currentMediaPackageAcl = acl;
final Snapshot[] snapshot = createAndAddMediaPackages(1, 1, 1, Opt.<String>none());
currentMediaPackageAcl = stashedAcl;
return snapshot[0];
}
});
}
/** Create a user with admin rights who belongs to the default organization. */
private User mkDefaultOrgAdmin() {
final Organization org = TestOrganization.mkDefault();
return TestUser.mk(org, org.getAdminRole());
}
/** Create a user with admin rights who belongs to the given organization. */
private User mkOrgAdmin(Organization org) {
return TestUser.mk(org, org.getAdminRole());
}
/** Create a user with global admin rights who belongs to the default organization. */
private User mkDefaultOrgGlobalAdmin() {
final Organization org = TestOrganization.mkDefault();
return TestUser.mk(org, SecurityConstants.GLOBAL_ADMIN_ROLE);
}
/** Create a user with global admin rights who belongs to the given organization. */
private User mkGlobalAdmin(Organization org) {
return TestUser.mk(org, SecurityConstants.GLOBAL_ADMIN_ROLE);
}
private AccessControlList acl(AccessControlEntry... ace) {
return new AccessControlList(ace);
}
/**
* Create an allow rule (Opencast only support allow!)
*/
private AccessControlEntry ace(String role, String action) {
return new AccessControlEntry(role, action, true);
}
}