/** * 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.workflow.impl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.opencastproject.workflow.impl.SecurityServiceStub.DEFAULT_ORG_ADMIN; import org.opencastproject.mediapackage.MediaPackage; import org.opencastproject.mediapackage.MediaPackageBuilderFactory; import org.opencastproject.message.broker.api.MessageSender; import org.opencastproject.metadata.api.MediaPackageMetadataService; 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.DefaultOrganization; import org.opencastproject.security.api.JaxbOrganization; import org.opencastproject.security.api.JaxbRole; import org.opencastproject.security.api.JaxbUser; import org.opencastproject.security.api.Organization; import org.opencastproject.security.api.OrganizationDirectoryService; import org.opencastproject.security.api.Permissions; 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.security.api.UserDirectoryService; import org.opencastproject.serviceregistry.api.IncidentService; import org.opencastproject.serviceregistry.api.ServiceRegistryInMemoryImpl; import org.opencastproject.util.data.Tuple; import org.opencastproject.workflow.api.WorkflowDefinitionImpl; import org.opencastproject.workflow.api.WorkflowInstance; import org.opencastproject.workflow.api.WorkflowOperationDefinitionImpl; import org.opencastproject.workspace.api.Workspace; import org.apache.commons.io.FileUtils; import org.easymock.EasyMock; import org.easymock.IAnswer; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.File; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class WorkflowServiceImplAuthzTest { private Organization defaultOrganization = null; private Organization otherOrganization = null; private User instructor1 = null; private User instructor2 = null; private User instructorFromDifferentOrg = null; private User globalAdmin = null; protected Map<String, User> users = null; private Responder<User> userResponder; private Responder<Organization> organizationResponder; private WorkflowServiceImpl service = null; private WorkflowDefinitionScanner scanner = null; private WorkflowServiceSolrIndex dao = null; private Workspace workspace = null; private ServiceRegistryInMemoryImpl serviceRegistry = null; private SecurityService securityService = null; private File sRoot = null; protected static final String getStorageRoot() { return "." + File.separator + "target" + File.separator + System.currentTimeMillis(); } private static class Responder<A> implements IAnswer<A> { private A response; Responder(A response) { this.response = response; } public void setResponse(A response) { this.response = response; } @Override public A answer() throws Throwable { return response; } } @Before public void setUp() throws Exception { Map<String, Integer> servers = new HashMap<String, Integer>(); servers.put("http://somewhere", 80); defaultOrganization = new DefaultOrganization(); otherOrganization = new JaxbOrganization("other_org", "Another organization", servers, defaultOrganization.getAdminRole(), defaultOrganization.getAnonymousRole(), null); JaxbOrganization jaxbOrganization = JaxbOrganization.fromOrganization(defaultOrganization); instructor1 = new JaxbUser("instructor1", "test", jaxbOrganization, new JaxbRole("ROLE_INSTRUCTOR", jaxbOrganization)); instructor2 = new JaxbUser("instructor2", "test", jaxbOrganization, new JaxbRole("ROLE_INSTRUCTOR", jaxbOrganization)); JaxbOrganization differentOrg = new JaxbOrganization("differentorg"); instructorFromDifferentOrg = new JaxbUser("instructor3", "test", differentOrg, new JaxbRole("ROLE_INSTRUCTOR", differentOrg)); JaxbOrganization doesntMatterOrg = new JaxbOrganization("org doesn't matter"); globalAdmin = new JaxbUser("global_admin", "test", doesntMatterOrg, new JaxbRole( SecurityConstants.GLOBAL_ADMIN_ROLE, doesntMatterOrg)); users = new HashMap<String, User>(); users.put(instructor1.getUsername(), instructor1); users.put(instructor2.getUsername(), instructor2); users.put(instructorFromDifferentOrg.getUsername(), instructorFromDifferentOrg); users.put(DEFAULT_ORG_ADMIN.getUsername(), DEFAULT_ORG_ADMIN); users.put(globalAdmin.getUsername(), globalAdmin); service = new WorkflowServiceImpl() { @Override public Set<HandlerRegistration> getRegisteredHandlers() { return new HashSet<WorkflowServiceImpl.HandlerRegistration>(); } }; scanner = new WorkflowDefinitionScanner(); service.addWorkflowDefinitionScanner(scanner); // Organization Service List<Organization> organizationList = new ArrayList<Organization>(); organizationList.add(defaultOrganization); OrganizationDirectoryService organizationDirectoryService = EasyMock.createMock(OrganizationDirectoryService.class); EasyMock.expect(organizationDirectoryService.getOrganization((String) EasyMock.anyObject())) .andAnswer(new IAnswer<Organization>() { @Override public Organization answer() throws Throwable { String orgId = (String) EasyMock.getCurrentArguments()[0]; Map<String, Integer> servers = new HashMap<String, Integer>(); servers.put("http://" + orgId, 80); defaultOrganization = new DefaultOrganization(); return new JaxbOrganization(orgId, orgId, servers, "ROLE_ADMIN", "ROLE_ANONYMOUS", null); } }).anyTimes(); EasyMock.expect(organizationDirectoryService.getOrganizations()).andReturn(organizationList).anyTimes(); EasyMock.replay(organizationDirectoryService); service.setOrganizationDirectoryService(organizationDirectoryService); // Metadata Service MediaPackageMetadataService mds = EasyMock.createNiceMock(MediaPackageMetadataService.class); EasyMock.replay(mds); service.addMetadataService(mds); // Workspace workspace = EasyMock.createNiceMock(Workspace.class); EasyMock.expect(workspace.getCollectionContents((String) EasyMock.anyObject())).andReturn(new URI[0]); EasyMock.replay(workspace); // User Directory UserDirectoryService userDirectoryService = EasyMock.createMock(UserDirectoryService.class); EasyMock.expect(userDirectoryService.loadUser((String) EasyMock.anyObject())).andAnswer(new IAnswer<User>() { @Override public User answer() throws Throwable { String userName = (String) EasyMock.getCurrentArguments()[0]; return users.get(userName); } }).anyTimes(); EasyMock.replay(userDirectoryService); service.setUserDirectoryService(userDirectoryService); // security service userResponder = new Responder<User>(DEFAULT_ORG_ADMIN); organizationResponder = new Responder<Organization>(defaultOrganization); securityService = EasyMock.createNiceMock(SecurityService.class); EasyMock.expect(securityService.getUser()).andAnswer(userResponder).anyTimes(); EasyMock.expect(securityService.getOrganization()).andAnswer(organizationResponder).anyTimes(); EasyMock.replay(securityService); service.setSecurityService(securityService); // Authorization Service AuthorizationService authzService = EasyMock.createNiceMock(AuthorizationService.class); EasyMock.replay(authzService); service.setAuthorizationService(authzService); MessageSender messageSender = EasyMock.createNiceMock(MessageSender.class); EasyMock.replay(messageSender); // Service Registry serviceRegistry = new ServiceRegistryInMemoryImpl(service, securityService, userDirectoryService, organizationDirectoryService, EasyMock.createNiceMock(IncidentService.class)); service.setServiceRegistry(serviceRegistry); // Search Index sRoot = new File(getStorageRoot()); FileUtils.forceMkdir(sRoot); dao = new WorkflowServiceSolrIndex(); dao.setServiceRegistry(serviceRegistry); dao.setAuthorizationService(authzService); dao.setSecurityService(securityService); dao.setOrgDirectory(organizationDirectoryService); dao.solrRoot = sRoot + File.separator + "solr." + System.currentTimeMillis(); dao.activate("System Admin"); service.setDao(dao); service.setMessageSender(messageSender); // Activate service.activate(null); } @After public void tearDown() throws Exception { serviceRegistry.deactivate(); dao.deactivate(); service.deactivate(); } @Test public void testWorkflowWithSecurityPolicy() throws Exception { // Create an ACL for the authorization service to return AccessControlList acl = new AccessControlList(); acl.getEntries().add(new AccessControlEntry("ROLE_INSTRUCTOR", Permissions.Action.READ.toString(), true)); acl.getEntries().add(new AccessControlEntry("ROLE_INSTRUCTOR", Permissions.Action.WRITE.toString(), true)); // Mock up an authorization service that always returns "true" for hasPermission() AuthorizationService authzService = EasyMock.createNiceMock(AuthorizationService.class); EasyMock.expect(authzService.getActiveAcl((MediaPackage) EasyMock.anyObject())) .andReturn(Tuple.tuple(acl, AclScope.Series)).anyTimes(); EasyMock.expect(authzService.hasPermission((MediaPackage) EasyMock.anyObject(), (String) EasyMock.anyObject())) .andReturn(true).anyTimes(); EasyMock.replay(authzService); service.setAuthorizationService(authzService); dao.setAuthorizationService(authzService); // Create the workflow and its dependent object graph WorkflowDefinitionImpl def = new WorkflowDefinitionImpl(); def.add(new WorkflowOperationDefinitionImpl("op1", "op1", null, true)); MediaPackage mp = MediaPackageBuilderFactory.newInstance().newMediaPackageBuilder().createNew(); // As an instructor, create a workflow. We don't care if it passes or fails. We just care about access to it. userResponder.setResponse(instructor1); WorkflowInstance workflow = service.start(def, mp); service.suspend(workflow.getId()); // Ensure that this instructor can access the workflow try { service.getWorkflowById(workflow.getId()); assertEquals(1, service.countWorkflowInstances()); } catch (Exception e) { fail(e.getMessage()); } // Ensure the organization admin can access that workflow userResponder.setResponse(DEFAULT_ORG_ADMIN); try { service.getWorkflowById(workflow.getId()); assertEquals(1, service.countWorkflowInstances()); } catch (Exception e) { fail(e.getMessage()); } // Ensure the global admin can access that workflow userResponder.setResponse(globalAdmin); try { service.getWorkflowById(workflow.getId()); assertEquals(1, service.countWorkflowInstances()); } catch (Exception e) { fail(e.getMessage()); } // Ensure the other instructor from this organization can also see the workflow, since this is specified in the // security policy userResponder.setResponse(instructor2); try { service.getWorkflowById(workflow.getId()); assertEquals(1, service.countWorkflowInstances()); } catch (Exception e) { fail(e.getMessage()); } // TODO change to answer show in episode or series how to do it. Cool stuff // Ensure the instructor from a different org can not see the workflow, even though they share the same role organizationResponder.setResponse(otherOrganization); userResponder.setResponse(instructorFromDifferentOrg); try { service.getWorkflowById(workflow.getId()); fail(); } catch (Exception e) { // expected } assertEquals(0, service.countWorkflowInstances()); } @Test public void testWorkflowWithoutSecurityPolicy() throws Exception { // Mock up an authorization service that always returns "false" for hasPermission() AuthorizationService authzService = EasyMock.createNiceMock(AuthorizationService.class); EasyMock.expect(authzService.getActiveAcl((MediaPackage) EasyMock.anyObject())) .andReturn(Tuple.tuple(new AccessControlList(), AclScope.Series)).anyTimes(); EasyMock.expect(authzService.hasPermission((MediaPackage) EasyMock.anyObject(), (String) EasyMock.anyObject())) .andReturn(false).anyTimes(); EasyMock.replay(authzService); service.setAuthorizationService(authzService); dao.setAuthorizationService(authzService); // Create the workflow and its dependent object graph WorkflowDefinitionImpl def = new WorkflowDefinitionImpl(); def.add(new WorkflowOperationDefinitionImpl("op1", "op1", null, true)); MediaPackage mp = MediaPackageBuilderFactory.newInstance().newMediaPackageBuilder().createNew(); // As an instructor, create a workflow userResponder.setResponse(instructor1); WorkflowInstance workflow = service.start(def, mp); service.suspend(workflow.getId()); // Ensure that this instructor can access the workflow try { service.getWorkflowById(workflow.getId()); assertEquals(1, service.countWorkflowInstances()); } catch (Exception e) { fail(e.getMessage()); } // Ensure the organization admin can access that workflow userResponder.setResponse(DEFAULT_ORG_ADMIN); try { service.getWorkflowById(workflow.getId()); assertEquals(1, service.countWorkflowInstances()); } catch (Exception e) { fail(e.getMessage()); } // Ensure the global admin can access that workflow userResponder.setResponse(globalAdmin); try { service.getWorkflowById(workflow.getId()); assertEquals(1, service.countWorkflowInstances()); } catch (Exception e) { fail(e.getMessage()); } // Ensure the other instructor can not see the workflow, since there is no security policy granting access userResponder.setResponse(instructor2); try { service.getWorkflowById(workflow.getId()); fail(); } catch (UnauthorizedException e) { // expected } assertEquals(0, service.countWorkflowInstances()); // Ensure the instructor from a different org can not see the workflow, even though they share a role organizationResponder.setResponse(otherOrganization); userResponder.setResponse(instructorFromDifferentOrg); try { service.getWorkflowById(workflow.getId()); fail(); } catch (Exception e) { // expected } assertEquals(0, service.countWorkflowInstances()); } }