All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.revenj.RevenjPermissionManager Maven / Gradle / Ivy

The newest version!
package org.revenj;

import org.revenj.patterns.*;
import org.revenj.security.PermissionManager;
import org.revenj.security.GlobalPermission;
import org.revenj.security.RolePermission;
import org.revenj.security.UserPrincipal;
import rx.Observable;
import rx.Subscription;

import java.io.Closeable;
import java.security.Principal;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
import java.util.stream.Stream;

final class RevenjPermissionManager implements PermissionManager, Closeable {

	private final Callable>> globalRepository;
	private final Callable>> rolesRepository;
	private final boolean defaultPermissions;

	private final Subscription globalSubscription;
	private final Subscription roleSubscription;

	private Map globalPermissions = new HashMap<>();
	private Map> rolePermissions = new HashMap<>();

	private Map cache = new HashMap<>();
	private final Map, List> registeredFilters = new HashMap<>();

	private final class Pair {
		public final String name;
		public final boolean isAllowed;

		public Pair(String name, boolean isAllowed) {
			this.name = name;
			this.isAllowed = isAllowed;
		}
	}

	private final class Filter {
		public final Specification specification;
		public final String role;
		public final boolean inverse;

		public Filter(Specification specification, String role, boolean inverse) {
			this.specification = specification;
			this.role = role;
			this.inverse = inverse;
		}
	}

	private boolean permissionsChanged = true;

	public RevenjPermissionManager(ServiceLocator locator) {
		this(locator.resolve(Properties.class),
				new Generic>>() {
				}.resolve(locator),
				new Generic>>() {
				}.resolve(locator),
				new Generic>>>() {
				}.resolve(locator),
				new Generic>>>() {
				}.resolve(locator));
	}

	public RevenjPermissionManager(
			Properties properties,
			Observable> globalChanges,
			Observable> roleChanges,
			Callable>> globalRepository,
			Callable>> rolesRepository) {
		String permissions = properties.getProperty("revenj.permissions");
		if (permissions != null && permissions.length() > 0) {
			if (!permissions.equalsIgnoreCase("open") && !permissions.equalsIgnoreCase("closed")) {
				throw new RuntimeException("Invalid revenj.permission settings found: '" + permissions + "'.\n"
						+ "Allowed values are open and closed");
			}
		}
		defaultPermissions = "open".equals(permissions);
		globalSubscription = globalChanges.subscribe(c -> permissionsChanged = true);
		roleSubscription = roleChanges.subscribe(c -> permissionsChanged = true);
		this.globalRepository = globalRepository;
		this.rolesRepository = rolesRepository;
	}

	private void checkPermissions() {
		if (!permissionsChanged) {
			return;
		}
		Optional> global;
		Optional> roles;
		try {
			global = globalRepository.call();
			roles = rolesRepository.call();
		} catch (Exception ignore) {
			global = Optional.empty();
			roles = Optional.empty();
		}
		if (global.isPresent()) {
			globalPermissions =
					global.get().search().stream().collect(
							Collectors.toMap(GlobalPermission::getName, GlobalPermission::getIsAllowed));
		}
		if (roles.isPresent()) {
			rolePermissions =
					roles.get().search().stream().collect(
							Collectors.groupingBy(
									RolePermission::getName,
									Collectors.mapping(it -> new Pair(it.getRoleID(), it.getIsAllowed()), Collectors.toList())));
		}
		cache = new HashMap<>();
		permissionsChanged = false;
	}

	private boolean checkOpen(String[] parts, int len) {
		if (len < 0) {
			return defaultPermissions;
		}
		String name = String.join(".", Arrays.copyOf(parts, len));
		Boolean found = globalPermissions.get(name);
		return found != null ? found : checkOpen(parts, len - 1);
	}

	private boolean implies(Principal principal, String role) {
		return principal instanceof UserPrincipal
				? ((UserPrincipal) principal).implies(role)
				: role.equals(principal.getName());
	}

	@Override
	public boolean canAccess(String identifier, Principal user) {
		checkPermissions();
		String target = identifier != null ? identifier : "";
		String id = user != null ? user.getName() + ":" + target : target;
		Boolean exists = cache.get(id);
		if (exists != null) {
			return exists;
		}
		String[] parts = target.split("\\.");
		boolean isAllowed = checkOpen(parts, parts.length);
		if (user != null) {
			List permissions;
			for (int i = parts.length; i >= 0; i--) {
				String subName = String.join(".", Arrays.copyOf(parts, i));
				permissions = rolePermissions.get(subName);
				if (permissions != null) {
					Optional found = permissions.stream().filter(it -> implies(user, it.name)).findFirst();
					if (found.isPresent()) {
						isAllowed = found.get().isAllowed;
						break;
					}
				}
			}
		}
		Map newCache = new HashMap<>(cache);
		newCache.put(id, isAllowed);
		cache = newCache;
		return isAllowed;
	}

	@Override
	public  Query applyFilters(Class manifest, Principal user, Query data) {
		if (user == null) return data.filter(it -> defaultPermissions);
		List registered = registeredFilters.get(manifest);
		if (registered != null) {
			Query result = data;
			for (Filter r : registered) {
				if ((implies(user, r.role)) != r.inverse) {
					result = result.filter(r.specification);
				}
			}
			return result;
		}
		return data;
	}

	@Override
	public  List applyFilters(Class manifest, Principal user, List data) {
		if (user == null) return defaultPermissions ? data : Collections.EMPTY_LIST;
		List registered = registeredFilters.get(manifest);
		if (registered != null) {
			Stream result = data.stream();
			boolean filtered = false;
			for (Filter r : registered) {
				if (implies(user, r.role) != r.inverse) {
					result = result.filter(r.specification);
					filtered = true;
				}
			}
			return filtered ? result.collect(Collectors.toList()) : data;
		}
		return data;
	}

	@Override
	public  Closeable registerFilter(Class manifest, Specification filter, String role, boolean inverse) {
		List registered = registeredFilters.get(manifest);
		if (registered == null) {
			registered = new ArrayList<>();
			registeredFilters.put(manifest, registered);
		}
		Filter item = new Filter(filter, role, inverse);
		List reg = registered;
		reg.add(item);
		return () -> reg.remove(item);
	}

	public void close() {
		globalSubscription.unsubscribe();
		roleSubscription.unsubscribe();
	}
}