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.listForAny;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject;
import javax.inject.Provider;
import com.getperka.flatpack.HasUuid;
import com.getperka.flatpack.ext.TypeContext;
import com.getperka.flatpack.security.GroupPermissions;
import com.getperka.flatpack.security.SecurityGroups;
import com.getperka.flatpack.security.SecurityPolicy;
import com.getperka.flatpack.security.SecurityTarget;
/**
* A {@link SecurityPolicy} implementation that uses policy rules defined in an external file.
*/
public class StaticPolicy implements SecurityPolicy {
private final Map<SecurityTarget, GroupPermissions> cache =
new ConcurrentHashMap<SecurityTarget, GroupPermissions>();
private final String contents;
private final AtomicReference<StaticPolicyImpl> impl = new AtomicReference<StaticPolicyImpl>();
@Inject
private Provider<StaticPolicyImpl> implProvider;
@Inject
private SecurityGroups securityGroups;
@Inject
private TypeContext typeContext;
public StaticPolicy(String contents) {
this.contents = contents;
}
/**
* Computes all permissions for the target, including those inherited from supertype and global
* decorations.
*/
@Override
public GroupPermissions getPermissions(SecurityTarget target) {
/*
* Since the policy file can't talk about specific entities, we'll generalize any incoming
* target. This will allow targets to be cached, without indefinitely retaining an entity.
*/
switch (target.getKind()) {
case ENTITY:
target = SecurityTarget.of(target.getEntityType());
break;
case ENTITY_PROPERTY:
target = SecurityTarget.of(target.getProperty());
break;
case PROPERTY:
case GLOBAL:
case TYPE:
// OK;
break;
default:
// Definitively break if a new kind is added
throw new UnsupportedOperationException(target.getKind().name());
}
// See if there's any cached data
GroupPermissions toReturn = cache.get(target);
if (toReturn != null) {
return toReturn;
}
// Determine which targets should be inherited from
List<SecurityTarget> targets = listForAny();
computeTargets(target, targets);
// Lazily construct the inner implementation, since it requires injection
StaticPolicyImpl policy = maybeParse();
// Pull the data from the policy tree
toReturn = new GroupPermissions();
for (SecurityTarget t : targets) {
policy.extractPermissions(toReturn, t);
}
// Cache and return
cache.put(target, toReturn);
return toReturn;
}
/**
* Returns {@link SecurityGroups#getPermissionsNone()}.
*/
protected GroupPermissions getDefaultPermissions() {
return securityGroups.getPermissionsNone();
}
private void computeTargets(SecurityTarget target, List<SecurityTarget> accumulator) {
// Look for inherited data first
switch (target.getKind()) {
case GLOBAL:
// Just add the global target
break;
case PROPERTY: {
// Inherit from the entity type that defines the property
Class<? extends HasUuid> enclosing =
target.getProperty().getEnclosingType().getEntityType();
computeTargets(SecurityTarget.of(enclosing), accumulator);
break;
}
case TYPE: {
// Inherit from the entity type's supertype or global rules
Class<?> superType = target.getEntityType().getSuperclass();
if (superType != null && HasUuid.class.isAssignableFrom(superType)) {
computeTargets(SecurityTarget.of(superType.asSubclass(HasUuid.class)), accumulator);
} else {
computeTargets(SecurityTarget.global(), accumulator);
}
break;
}
case ENTITY:
case ENTITY_PROPERTY:
// Entity-based targets should have been generalized
default:
throw new UnsupportedOperationException(target.getKind().name());
}
accumulator.add(target);
}
private StaticPolicyImpl maybeParse() {
StaticPolicyImpl toReturn = impl.get();
if (toReturn != null) {
return toReturn;
}
if (implProvider == null) {
throw new IllegalStateException("Not injected, must be initialized via FlatPack.create()");
}
toReturn = implProvider.get();
toReturn.parse(contents);
impl.compareAndSet(null, toReturn);
return impl.get();
}
}