package com.getperka.flatpack.policy;
/*
* #%L
* FlatPack Security Policy
* %%
* Copyright (C) 2012 - 2013 Perka Inc.
* %%
* Licensed under the Apache 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://www.apache.org/licenses/LICENSE-2.0
*
* 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.
* #L%
*/
import static com.getperka.flatpack.util.FlatPackCollections.mapForLookup;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.security.Principal;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Provider;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.getperka.flatpack.BaseHasUuid;
import com.getperka.flatpack.Configuration;
import com.getperka.flatpack.FlatPack;
import com.getperka.flatpack.HasUuid;
import com.getperka.flatpack.TypeSource;
import com.getperka.flatpack.ext.NoPack;
import com.getperka.flatpack.ext.Property;
import com.getperka.flatpack.ext.TypeContext;
import com.getperka.flatpack.inject.HasInjector;
import com.getperka.flatpack.inject.PackScope;
import com.getperka.flatpack.security.CrudOperation;
import com.getperka.flatpack.security.PrincipalMapper;
import com.getperka.flatpack.security.Security;
import com.getperka.flatpack.security.SecurityAction;
import com.getperka.flatpack.security.SecurityGroups;
import com.getperka.flatpack.security.SecurityPolicy;
import com.getperka.flatpack.security.SecurityTarget;
/**
* Test data-driven access policies.
*/
public class PrincipalSecurityTest extends PolicyTestBase {
static class MyPrincipal implements Principal {
private final Person person;
public MyPrincipal(Person person) {
this.person = person;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof MyPrincipal)) {
return false;
}
MyPrincipal o = (MyPrincipal) obj;
return person.equals(o.person);
}
@Override
public String getName() {
return person.getUuid().toString();
}
public Person getPerson() {
return person;
}
@Override
public int hashCode() {
return person.hashCode();
}
}
static class MyPrincipalMapper implements PrincipalMapper {
@Override
public List<String> getGlobalSecurityGroups(Principal principal) {
return ((MyPrincipal) principal).getPerson().getGlobalGroups();
}
@Override
public List<Principal> getPrincipals(HasUuid entity) {
if (entity instanceof Person) {
return Collections.<Principal> singletonList(new MyPrincipal((Person) entity));
}
return null;
}
@Override
public boolean isAccessEnforced(Principal principal, SecurityTarget target) {
return true;
}
}
static class Person extends BaseHasUuid {
private Person boss;
private List<String> globalGroups;
private String name;
private List<Person> peers;
public Person getBoss() {
return boss;
}
@NoPack
public List<String> getGlobalGroups() {
return globalGroups;
}
public String getName() {
return name;
}
public List<Person> getPeers() {
return peers;
}
public void setBoss(Person boss) {
this.boss = boss;
}
@NoPack
public void setGlobalGroups(List<String> globalGroups) {
this.globalGroups = globalGroups;
}
public void setName(String name) {
this.name = name;
}
public void setPeers(List<Person> peers) {
this.peers = peers;
}
}
@Inject
private SecurityGroups groups;
@Inject
private Provider<Security> securities;
@Inject
private SecurityPolicy securityPolicy;
@Inject
private PackScope packScope;
private Map<String, Property> personProps = mapForLookup();
@Inject
private TypeContext typeContext;
private static final CrudOperation[] ALL_OPS = {
CrudOperation.CREATE, CrudOperation.DELETE, CrudOperation.READ, CrudOperation.UPDATE };
@After
public void after() {
packScope.exit();
}
@Before
public void before() {
String policy = loadTestPolicyContents("PrincipalSecurityTest.policy");
assertNotNull(policy);
FlatPack flatpack = FlatPack.create(new Configuration()
.addTypeSource(new TypeSource() {
@Override
public Set<Class<?>> getTypes() {
return Collections.<Class<?>> singleton(Person.class);
}
})
.withPrincipalMapper(new MyPrincipalMapper())
.withSecurityPolicy(new StaticPolicy(policy)));
((HasInjector) flatpack).getInjector().injectMembers(this);
for (Property prop : typeContext.describe(Person.class).getProperties()) {
personProps.put(prop.getName(), prop);
}
packScope.enter();
}
@Test
public void testBoss() {
Person b = new Person();
Person p = new Person();
p.setBoss(b);
// Verify the boss can edit the child object's property
check(new MyPrincipal(b), p, personProps.get("boss"), true, ALL_OPS);
// But that it cannot edit its own boss property
checkMayNot(b, personProps.get("boss"), ALL_OPS);
}
@Test
public void testBossPeers() {
Person bPeer = new Person();
Person b = new Person();
b.setPeers(Collections.singletonList(bPeer));
Person pPeer = new Person();
Person p = new Person();
p.setBoss(b);
p.setPeers(Collections.singletonList(pPeer));
// Verify the boss's peer can edit the child object's property
check(new MyPrincipal(bPeer), p, personProps.get("boss"), true, ALL_OPS);
// Verify that the person's peers cannot edit the property
check(new MyPrincipal(pPeer), p, personProps.get("boss"), false, ALL_OPS);
}
@Test
public void testGlobalGroup() {
Person p = new Person();
p.setGlobalGroups(Collections.singletonList("global"));
// Verify that it can edit its own boss property
checkMay(p, personProps.get("boss"), ALL_OPS);
checkMayNot(new Person(), personProps.get("boss"), ALL_OPS);
}
@Test
public void testNobody() {
Person p = new Person();
check(new MyPrincipal(new Person()), p, null, true, CrudOperation.READ);
check(new MyPrincipal(new Person()), p, null, false, CrudOperation.UPDATE);
}
@Test
public void testSelf() {
Person p = new Person();
checkMay(p, ALL_OPS);
// Check property with no particular annotation
checkMay(p, personProps.get("name"), ALL_OPS);
// Check property inherited from BaseHasUuid
checkMay(p, personProps.get("uuid"), ALL_OPS);
// Verify that it can't edit its own boss property
checkMayNot(p, personProps.get("boss"), ALL_OPS);
}
private void check(MyPrincipal principal, Person p, Property property, boolean expect,
CrudOperation... ops) {
Security security = securities.get();
for (CrudOperation op : ops) {
boolean may;
if (property == null) {
may = security.may(principal, SecurityTarget.of(p), SecurityAction.of(op));
} else {
may = security.may(principal, SecurityTarget.of(p, property), SecurityAction.of(op));
}
assertEquals(p.getUuid() + (expect ? " could not " : " should not ") + op, expect, may);
}
}
private void checkMay(Person p, CrudOperation... ops) {
check(new MyPrincipal(p), p, null, true, ops);
}
private void checkMay(Person p, Property property, CrudOperation... ops) {
assertNotNull(property);
check(new MyPrincipal(p), p, property, true, ops);
}
private void checkMayNot(Person p, Property property, CrudOperation... ops) {
assertNotNull(property);
check(new MyPrincipal(p), p, property, false, ops);
}
}