blob: 2494529e103ac776c295de0a15c909c2c75ffb1e [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package io.v.syncbase;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import io.v.syncbase.core.Permissions;
/**
* Specifies access levels for a set of users. Each user has an associated access level: read-only,
* read-write, or read-write-admin.
*/
public class AccessList {
public enum AccessLevel {
READ,
READ_WRITE,
READ_WRITE_ADMIN
}
private Map<String, AccessLevel> users;
/**
* @throws IllegalArgumentException if accessList is not valid
*/
private static Set<String> parsedAccessListToUserIds(Map<String, Set<String>> accessList) {
Set<String> res = new HashSet<>();
if (accessList.containsKey(Permissions.NOT_IN) &&
!accessList.get(Permissions.NOT_IN).isEmpty()) {
throw new IllegalArgumentException("Non-empty not-in section: " + accessList);
}
for (String blessingPattern : accessList.get(Permissions.IN)) {
// TODO(alexfandrianto): What if the blessing pattern is actually "..."?
// TODO(sadovsky): Ignore cloud peer's blessing pattern?
res.add(Syncbase.getAliasFromBlessingPattern(blessingPattern));
}
return res;
}
/**
* Creates an empty access list.
*/
public AccessList() {
this.users = new HashMap<>();
}
/**
* Gets the user's access level.
*/
public AccessLevel getAccessLevelForUser(User user) {
return users.get(user.getAlias());
}
/**
* Changes the user's access level and returns their previous access level.
*
* @param user The user whose access level is changing.
* @param newLevel The user's new access level.
*/
public AccessLevel setAccessLevel(User user, AccessLevel newLevel) {
if (newLevel == null) {
return removeAccessLevel(user);
}
AccessLevel oldLevel = getAccessLevelForUser(user);
users.put(user.getAlias(), newLevel);
return oldLevel;
}
/**
* Removes access from the given user.
*/
public AccessLevel removeAccessLevel(User user) {
return users.remove(user);
}
/**
* Obtains the users that match the given access level.
* Filters out the cloud, which always has READ_WRITE_ADMIN access.
*/
public java.util.Collection<User> getByAccessLevel(AccessLevel level) {
Set<User> matchingUsers = new HashSet<>();
for (Map.Entry<String, AccessLevel> entry : users.entrySet()) {
String alias = entry.getKey();
boolean isCloud = Syncbase.sOpts.mCloudAdmin != null &&
alias.equals(Syncbase.getAliasFromBlessingPattern(Syncbase.sOpts.mCloudAdmin));
if (!isCloud && entry.getValue().equals(level)) {
matchingUsers.add(new User(alias));
}
}
return matchingUsers;
}
/**
* TODO(alexfandrianto): Vary implementation if this constructor needs to be called with non-
* collection permissions. The current simplification allows us to know that read/write/admin
* are available for parsing.
* @throws IllegalArgumentException if corePermissions are not valid
*/
AccessList(Permissions corePermissions) {
Map<String, Map<String, Set<String>>> parsedPermissions = corePermissions.parse();
Set<String> readers = parsedAccessListToUserIds(parsedPermissions.get(Permissions.Tags.READ));
Set<String> writers = parsedAccessListToUserIds(parsedPermissions.get(Permissions.Tags.WRITE));
Set<String> admins = parsedAccessListToUserIds(parsedPermissions.get(Permissions.Tags.ADMIN));
// Sanity checks. Readers must contain writers, which must contain admins.
if (!readers.containsAll(writers)) {
throw new IllegalArgumentException("Some writers are not readers: " + writers + ", " + readers);
}
if (!writers.containsAll(admins)) {
throw new IllegalArgumentException("Some admins are not writers: " + admins + ", " + writers);
}
// Compute the access level of each user.
// Note: This is only correct for collection permissions.
this.users = new HashMap<>();
for (String userId : readers) {
users.put(userId, AccessLevel.READ);
}
for (String userId : writers) {
users.put(userId, AccessLevel.READ_WRITE);
}
for (String userId : admins) {
users.put(userId, AccessLevel.READ_WRITE_ADMIN);
}
}
private static void addToVAccessList(Map<String, Set<String>> accessList, String blessing) {
if (!accessList.get(Permissions.IN).contains(blessing)) {
accessList.get(Permissions.IN).add(blessing);
}
}
private static void removeFromVAccessList(Map<String, Set<String>> accessList, String blessing) {
accessList.get(Permissions.IN).remove(blessing);
}
static Permissions applyDeltaForCollection(Permissions corePermissions,
AccessList delta) {
Map<String, Map<String, Set<String>>> parsedPermissions =
applyDeltaParsed(corePermissions, delta);
Map<String, Map<String, Set<String>>> filteredPermissions = new HashMap<>();
filteredPermissions.put(Permissions.Tags.READ,
parsedPermissions.get(Permissions.Tags.READ));
filteredPermissions.put(Permissions.Tags.WRITE,
parsedPermissions.get(Permissions.Tags.WRITE));
filteredPermissions.put(Permissions.Tags.ADMIN,
parsedPermissions.get(Permissions.Tags.ADMIN));
return new Permissions(filteredPermissions);
}
/**
* Computes a new Permissions object based on delta, allowing only the valid tags.
*/
static Permissions applyDeltaForSyncgroup(Permissions corePermissions,
AccessList delta) {
Map<String, Map<String, Set<String>>> parsedPermissions =
applyDeltaParsed(corePermissions, delta);
Map<String, Map<String, Set<String>>> filteredPermissions = new HashMap<>();
filteredPermissions.put(Permissions.Tags.READ,
parsedPermissions.get(Permissions.Tags.READ));
filteredPermissions.put(Permissions.Tags.ADMIN,
parsedPermissions.get(Permissions.Tags.ADMIN));
return new Permissions(filteredPermissions);
}
/**
* Computes a new parsed permissions map from the original Permissions object based on delta.
*/
private static Map<String, Map<String, Set<String>>> applyDeltaParsed(
Permissions corePermissions, AccessList delta){
Map<String, Map<String, Set<String>>> parsedPermissions = corePermissions.parse();
for (String userId : delta.users.keySet()) {
AccessLevel level = delta.users.get(userId);
String blessing = Syncbase.getBlessingStringFromAlias(userId);
if (level == null) {
removeFromVAccessList(parsedPermissions.get(Permissions.Tags.RESOLVE), blessing);
removeFromVAccessList(parsedPermissions.get(Permissions.Tags.READ), blessing);
removeFromVAccessList(parsedPermissions.get(Permissions.Tags.WRITE), blessing);
removeFromVAccessList(parsedPermissions.get(Permissions.Tags.ADMIN), blessing);
continue;
}
switch (level) {
case READ:
addToVAccessList(parsedPermissions.get(Permissions.Tags.RESOLVE), blessing);
addToVAccessList(parsedPermissions.get(Permissions.Tags.READ), blessing);
removeFromVAccessList(parsedPermissions.get(Permissions.Tags.WRITE), blessing);
removeFromVAccessList(parsedPermissions.get(Permissions.Tags.ADMIN), blessing);
break;
case READ_WRITE:
addToVAccessList(parsedPermissions.get(Permissions.Tags.RESOLVE), blessing);
addToVAccessList(parsedPermissions.get(Permissions.Tags.READ), blessing);
addToVAccessList(parsedPermissions.get(Permissions.Tags.WRITE), blessing);
removeFromVAccessList(parsedPermissions.get(Permissions.Tags.ADMIN), blessing);
break;
case READ_WRITE_ADMIN:
addToVAccessList(parsedPermissions.get(Permissions.Tags.RESOLVE), blessing);
addToVAccessList(parsedPermissions.get(Permissions.Tags.READ), blessing);
addToVAccessList(parsedPermissions.get(Permissions.Tags.WRITE), blessing);
addToVAccessList(parsedPermissions.get(Permissions.Tags.ADMIN), blessing);
break;
}
}
return parsedPermissions;
}
}