/*
* Copyright © 2016 Cask Data, 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.
*/
package co.cask.cdap.gateway.handlers;
import co.cask.cdap.proto.id.EntityId;
import co.cask.cdap.proto.security.Action;
import co.cask.cdap.proto.security.Principal;
import co.cask.cdap.proto.security.Privilege;
import co.cask.cdap.proto.security.Role;
import co.cask.cdap.security.spi.authorization.AbstractAuthorizer;
import co.cask.cdap.security.spi.authorization.AuthorizationContext;
import co.cask.cdap.security.spi.authorization.Authorizer;
import co.cask.cdap.security.spi.authorization.RoleAlreadyExistsException;
import co.cask.cdap.security.spi.authorization.RoleNotFoundException;
import co.cask.cdap.security.spi.authorization.UnauthorizedException;
import com.google.common.base.Splitter;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.annotation.concurrent.NotThreadSafe;
/**
* In-memory implementation of {@link Authorizer}.
*/
@NotThreadSafe
public class InMemoryAuthorizer extends AbstractAuthorizer {
private final Table<EntityId, Principal, Set<Action>> table = HashBasedTable.create();
private final Map<Role, Set<Principal>> roleToPrincipals = new HashMap<>();
private final Set<Principal> superUsers = new HashSet<>();
// Bypass enforcement for tests that want to simulate every user as a super user
private final Principal allSuperUsers = new Principal("*", Principal.PrincipalType.USER);
@Override
public void initialize(AuthorizationContext context) throws Exception {
Properties properties = context.getExtensionProperties();
if (properties.containsKey("superusers")) {
for (String superuser : Splitter.on(",").trimResults().omitEmptyStrings()
.split(properties.getProperty("superusers"))) {
superUsers.add(new Principal(superuser, Principal.PrincipalType.USER));
}
}
}
@Override
public void enforce(EntityId entity, Principal principal, Action action) throws UnauthorizedException {
// super users do not have any enforcement
if (superUsers.contains(principal) || superUsers.contains(allSuperUsers)) {
return;
}
// actions allowed to this principal
Set<Action> allowed = Sets.newHashSet(getActions(entity, principal));
// actions allowed to any of the roles to which this principal belongs if its not a role
if (principal.getType() != Principal.PrincipalType.ROLE) {
for (Role role : listRoles(principal)) {
allowed.addAll(getActions(entity, role));
}
}
if (!(allowed.contains(Action.ALL) || allowed.contains(action))) {
throw new UnauthorizedException(principal, action, entity);
}
}
@Override
public void grant(EntityId entity, Principal principal, Set<Action> actions) {
getActions(entity, principal).addAll(actions);
}
@Override
public void revoke(EntityId entity, Principal principal, Set<Action> actions) {
getActions(entity, principal).removeAll(actions);
}
@Override
public void revoke(EntityId entity) {
for (Principal principal : table.row(entity).keySet()) {
getActions(entity, principal).clear();
}
}
@Override
public void createRole(Role role) throws RoleAlreadyExistsException {
if (roleToPrincipals.containsKey(role)) {
throw new RoleAlreadyExistsException(role);
}
roleToPrincipals.put(role, new HashSet<Principal>());
}
@Override
public void dropRole(Role role) throws RoleNotFoundException {
if (!roleToPrincipals.containsKey(role)) {
throw new RoleNotFoundException(role);
}
roleToPrincipals.remove(role);
}
@Override
public void addRoleToPrincipal(Role role, Principal principal) throws RoleNotFoundException {
if (!roleToPrincipals.containsKey(role)) {
throw new RoleNotFoundException(role);
}
roleToPrincipals.get(role).add(principal);
}
@Override
public void removeRoleFromPrincipal(Role role, Principal principal) throws RoleNotFoundException {
if (!roleToPrincipals.containsKey(role)) {
throw new RoleNotFoundException(role);
}
roleToPrincipals.get(role).remove(principal);
}
@Override
public Set<Role> listRoles(Principal principal) {
Set<Role> roles = new HashSet<>();
for (Map.Entry<Role, Set<Principal>> roleSetEntry : roleToPrincipals.entrySet()) {
if (roleSetEntry.getValue().contains(principal)) {
roles.add(roleSetEntry.getKey());
}
}
return roles;
}
@Override
public Set<Role> listAllRoles() {
return roleToPrincipals.keySet();
}
@Override
public Set<Privilege> listPrivileges(Principal principal) {
Set<Privilege> privileges = new HashSet<>();
// privileges for this principal
privileges.addAll(getPrivileges(principal));
// privileges for the role to which this principal belongs to if its not a role
if (principal.getType() != Principal.PrincipalType.ROLE) {
for (Role role : listRoles(principal)) {
privileges.addAll(getPrivileges(role));
}
}
return privileges;
}
private Set<Privilege> getPrivileges(Principal principal) {
Set<Privilege> privileges = new HashSet<>();
Set<Map.Entry<EntityId, Set<Action>>> entries = table.column(principal).entrySet();
for (Map.Entry<EntityId, Set<Action>> entry : entries) {
for (Action action : entry.getValue()) {
privileges.add(new Privilege(entry.getKey(), action));
}
}
return privileges;
}
private Set<Action> getActions(EntityId entity, Principal principal) {
if (!table.contains(entity, principal)) {
table.put(entity, principal, new HashSet<Action>());
}
return table.get(entity, principal);
}
}