examples.baku.io.permissions.PermissionManager.java Source code

Java tutorial

Introduction

Here is the source code for examples.baku.io.permissions.PermissionManager.java

Source

// 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 examples.baku.io.permissions;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.ValueEventListener;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import examples.baku.io.permissions.util.Utils;

/**
 * Created by phamilton on 6/28/16.
 */
public class PermissionManager {

    public static final String EXTRA_TIMEOUT = "extraTimeout";
    public static final String EXTRA_COLOR = "extraColor";
    private DatabaseReference mDatabaseRef;
    private DatabaseReference mBlessingsRef;
    private DatabaseReference mRequestsRef;

    public static final int FLAG_DEFAULT = 0;
    public static final int FLAG_WRITE = 1 << 0;
    public static final int FLAG_READ = 1 << 1;
    public static final int FLAG_SUGGEST = 1 << 2;
    public static final int FLAG_ROOT = Integer.MAX_VALUE;

    static final String KEY_PERMISSIONS = "_permissions";
    static final String KEY_REQUESTS = "_requests";
    static final String KEY_BLESSINGS = "_blessings";

    private static final String KEY_ROOT = "root";

    private String mId;
    private Blessing rootBlessing;

    //<blessing id, blessing>
    private final Map<String, Blessing> mBlessings = new HashMap<>();
    //<source, target, blessing>
    private final Table<String, String, Blessing> mBlessingsTable = HashBasedTable.create();
    private final Set<String> mBlessingTargets = new HashSet();

    private final Map<String, PermissionRequest> mRequests = new HashMap<>();
    private final Table<String, String, PermissionRequest.Builder> mActiveRequests = HashBasedTable.create();

    private final Multimap<String, OnRequestListener> mRequestListeners = HashMultimap.create(); //<path,, >
    private final Multimap<String, OnRequestListener> mSubscribedRequests = HashMultimap.create(); //<requestDialog id, >

    private Blessing.PermissionTree mPermissionTree = new Blessing.PermissionTree();
    private final Multimap<String, OnPermissionChangeListener> mPermissionValueEventListeners = HashMultimap
            .create();
    private final Multimap<String, String> mNearestAncestors = HashMultimap.create();

    //TODO: replace string ownerId with Auth
    public PermissionManager(final DatabaseReference databaseReference, String owner) {
        this.mDatabaseRef = databaseReference;
        this.mId = owner;

        mRequestsRef = databaseReference.child(KEY_REQUESTS);
        //TODO: only consider requests from sources within the constellation
        mRequestsRef.addChildEventListener(requestListener);
        mBlessingsRef = mDatabaseRef.child(KEY_BLESSINGS);

        this.mId = owner;
        initRootBlessing();
        join(mId);

    }

    public void join(String group) {
        mBlessingsRef.orderByChild("target").equalTo(group).addChildEventListener(blessingListener);
        mBlessingTargets.add(group);
    }

    public void leave(String group) {
        mBlessingsRef.orderByChild("target").equalTo(group).removeEventListener(blessingListener);
        mBlessingTargets.remove(group);
    }

    public void initRootBlessing() {
        rootBlessing = Blessing.createRoot(this, mId);
    }

    //TODO: optimize this mess. Currently, recalculating entire permission tree.
    void refreshPermissions() {
        Blessing.PermissionTree updatedPermissionTree = new Blessing.PermissionTree();
        //received blessings
        for (Blessing blessing : getReceivedBlessings()) {
            if (blessing.isSynched()) {
                updatedPermissionTree.merge(blessing.getPermissionTree());
            }
        }

        //re-associate listeners with rules
        mNearestAncestors.clear();
        for (String path : mPermissionValueEventListeners.keySet()) {
            String nearestAncestor = Utils.getNearestCommonAncestor(path, updatedPermissionTree.keySet());
            if (nearestAncestor != null) {
                mNearestAncestors.put(nearestAncestor, path);
            }
        }

        Set<String> changedPermissions = new HashSet<>();

        //determine removed permissions
        Sets.SetView<String> removedPermissions = Sets.difference(mPermissionTree.keySet(),
                updatedPermissionTree.keySet());
        for (String path : removedPermissions) {
            String newPath = Utils.getNearestCommonAncestor(path, updatedPermissionTree.keySet());
            int previous = mPermissionTree.getPermissions(path);
            int current = updatedPermissionTree.getPermissions(newPath);
            if (previous != current) {
                changedPermissions.add(newPath);
            }
        }

        //compare previous tree
        for (Blessing.Permission permission : updatedPermissionTree.values()) {
            int previous = mPermissionTree.getPermissions(permission.path);
            int current = updatedPermissionTree.getPermissions(permission.path);
            if (previous != current) {
                changedPermissions.add(permission.path);
            }
        }

        mPermissionTree = updatedPermissionTree;

        //notify listeners
        for (String path : changedPermissions) {
            onPermissionsChange(path);
        }

    }

    //call all the listeners effected by a permission change at this path
    void onPermissionsChange(String path) {
        int permission = getPermissions(path);
        if (mNearestAncestors.containsKey(path)) {
            for (String listenerPath : mNearestAncestors.get(path)) {
                if (mPermissionValueEventListeners.containsKey(listenerPath)) {
                    for (OnPermissionChangeListener listener : mPermissionValueEventListeners.get(listenerPath)) {
                        listener.onPermissionChange(permission);
                    }
                }
            }
        }
    }

    public Set<PermissionRequest> getRequests(String path) {
        Set<PermissionRequest> result = new HashSet<>();
        for (PermissionRequest request : mRequests.values()) {
            if (getAllPaths(request.getPath()).contains(path)) {
                result.add(request);
            }
        }
        return result;
    }

    public PermissionRequest getRequest(String rId) {
        return mRequests.get(rId);
    }

    public Blessing getRootBlessing() {
        return rootBlessing;
    }

    public Set<Blessing> getReceivedBlessings() {
        Set<Blessing> result = new HashSet<>();
        for (String target : mBlessingTargets) {
            result.addAll(mBlessingsTable.column(target).values());
        }
        return result;
    }

    public Set<Blessing> getGrantedBlessings(String src) {
        return new HashSet<>(mBlessingsTable.row(src).values());
    }

    public Blessing putBlessing(Blessing blessing) {
        String source = blessing.getSource();
        String target = blessing.getTarget();
        mBlessings.put(blessing.getId(), blessing);
        if (source == null) {
            source = KEY_ROOT;
        }
        return mBlessingsTable.put(source, target, blessing);
    }

    public Blessing getBlessing(String id) {
        return mBlessings.get(id);
    }

    public Blessing getBlessing(String source, String target) {
        if (source == null) {
            source = KEY_ROOT;
        }
        return mBlessingsTable.get(source, target);
    }

    public void removeBlessing(String rId) {
        Blessing removedBlessing = mBlessings.remove(rId);
        if (removedBlessing != null) {
            mBlessingsTable.remove(removedBlessing.getSource(), removedBlessing.getTarget());

        }
    }

    //return a blessing interface for granting/revoking permissions
    //uses local device blessing as root
    public Blessing bless(String target) {
        return rootBlessing.bless(target);
    }

    public DatabaseReference getBlessingsRef() {
        return mBlessingsRef;
    }

    private ChildEventListener requestListener = new ChildEventListener() {
        @Override
        public void onChildAdded(DataSnapshot dataSnapshot, String s) {
            onRequestUpdated(dataSnapshot);
        }

        @Override
        public void onChildChanged(DataSnapshot dataSnapshot, String s) {
            onRequestUpdated(dataSnapshot);
        }

        @Override
        public void onChildRemoved(DataSnapshot dataSnapshot) {
            onRequestRemoved(dataSnapshot);
        }

        @Override
        public void onChildMoved(DataSnapshot dataSnapshot, String s) {

        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }
    };

    public void finishRequest(String rId) {
        //TODO: notify source entity and ignore instead of removing
        mRequestsRef.child(rId).removeValue();
    }

    public void grantRequest(PermissionRequest request) {
        Blessing blessing = bless(request.getSource());
        blessing.setPermissions(request.getPath(), request.getPermissions());
        finishRequest(request.getId());
    }

    private void onRequestUpdated(DataSnapshot snapshot) {
        if (!snapshot.exists()) {
            return;
        }

        PermissionRequest request = snapshot.getValue(PermissionRequest.class);
        if (request == null) {
            return;
        }

        String requestPath = request.getPath();
        if (requestPath == null) {
            return;
        }

        //ignore local requests
        if (mId.equals(request.getSource())) {
            return;
        }

        //Check if request permissions can be granted by this instance
        if ((getPermissions(requestPath) & request.getPermissions()) != request.getPermissions()) {
            return;
        }

        String rId = request.getId();
        String source = request.getSource();
        mRequests.put(rId, request);

        if (mSubscribedRequests.containsKey(rId)) {
            for (OnRequestListener listener : new HashSet<>(mSubscribedRequests.get(rId))) {
                if (!listener.onRequest(request, bless(source))) {
                    //cancel subscription
                    mSubscribedRequests.remove(rId, listener);
                }
            }
        } else {
            for (String path : getAllPaths(request.getPath())) {
                for (OnRequestListener listener : mRequestListeners.get(path)) {
                    if (listener.onRequest(request, bless(source))) {
                        //add subscription
                        mSubscribedRequests.put(request.getId(), listener);
                    }
                }
            }
        }
    }

    private void onRequestRemoved(DataSnapshot snapshot) {
        mRequests.remove(snapshot.getKey());
        PermissionRequest request = snapshot.getValue(PermissionRequest.class);
        String source = request.getSource();
        if (request != null && !mId.equals(source)) { //ignore local requests
            for (OnRequestListener listener : mSubscribedRequests.removeAll(request.getId())) {
                listener.onRequestRemoved(request, bless(source));
            }
        }
    }

    //allows
    private Set<String> getAllPaths(String path) {
        Set<String> result = new HashSet<>();
        result.add(path);
        result.add("*");
        String subpath = path;
        int index;
        while ((index = subpath.lastIndexOf("/")) != -1) {
            subpath = subpath.substring(0, index);
            result.add(subpath + "/*");
        }
        return result;
    }

    private ChildEventListener blessingListener = new ChildEventListener() {
        @Override
        public void onChildAdded(DataSnapshot snapshot, String s) {
            Blessing receivedBlessing = Blessing.fromSnapshot(PermissionManager.this, snapshot);
            receivedBlessing.addListener(blessingChangedListner);
        }

        @Override
        public void onChildChanged(DataSnapshot dataSnapshot, String s) {
        }

        @Override
        public void onChildRemoved(DataSnapshot dataSnapshot) {
            Blessing removedBlessing = mBlessings.remove(dataSnapshot.getKey());
            if (removedBlessing != null) {
                removedBlessing.removeListener(blessingChangedListner);
                mBlessingsTable.remove(removedBlessing.getSource(), removedBlessing.getTarget());
                refreshPermissions();
            }
        }

        @Override
        public void onChildMoved(DataSnapshot dataSnapshot, String s) {

        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }
    };

    private Blessing.OnBlessingUpdatedListener blessingChangedListner = new Blessing.OnBlessingUpdatedListener() {
        @Override
        public void onBlessingUpdated(Blessing blessing) {
            refreshPermissions();
        }

        @Override
        public void onBlessingRemoved(Blessing blessing) {
            refreshPermissions();
        }
    };

    public int getPermissions(String path) {
        return mPermissionTree.getPermissions(path);
    }

    public OnPermissionChangeListener addPermissionEventListener(String path, OnPermissionChangeListener listener) {
        int current = FLAG_DEFAULT;
        mPermissionValueEventListeners.put(path, listener);

        String nearestAncestor = Utils.getNearestCommonAncestor(path, mPermissionTree.keySet());
        if (nearestAncestor != null) {
            current = getPermissions(nearestAncestor);
            mNearestAncestors.put(nearestAncestor, path);
        }
        listener.onPermissionChange(current);
        return listener;
    }

    public void removePermissionEventListener(String path, OnPermissionChangeListener listener) {
        mPermissionValueEventListeners.remove(path, listener);
        String nca = Utils.getNearestCommonAncestor(path, mPermissionTree.keySet());
        mNearestAncestors.remove(nca, path);

    }

    public void removeOnRequestListener(String path, OnRequestListener requestListener) {
        mRequestListeners.remove(path, requestListener);
        if (mRequestListeners.values().contains(requestListener)) {
            //TODO: this doesn't catch cases where one request listener unsubscribed
            for (Map.Entry<String, OnRequestListener> entry : mSubscribedRequests.entries()) {
                if (entry.getValue().equals(requestListener)) {
                    String rId = entry.getKey();
                    PermissionRequest request = mRequests.get(rId);
                    if (getAllPaths(request.getPath()).contains(path)) {
                        mSubscribedRequests.remove(rId, requestListener);
                    }
                }
            }

        } else {
            mSubscribedRequests.values().remove(requestListener);
        }
    }

    public OnRequestListener addOnRequestListener(String path, OnRequestListener requestListener) {
        mRequestListeners.put(path, requestListener);
        for (Map.Entry<String, PermissionRequest> entry : mRequests.entrySet()) {
            String rId = entry.getKey();
            PermissionRequest request = entry.getValue();
            Set<String> requestPaths = getAllPaths(request.getPath());
            if (requestPaths.contains(path)) {
                String source = request.getSource();
                if (requestListener.onRequest(request, bless(source))) {
                    mSubscribedRequests.put(rId, requestListener);
                }
            }
        }
        return requestListener;
    }

    public PermissionRequest.Builder request(String path, String group) {
        PermissionRequest.Builder builder = mActiveRequests.get(group, path);
        if (builder == null) {
            builder = new PermissionRequest.Builder(mRequestsRef.push(), path, mId);
            mActiveRequests.put(group, path, builder);
        }
        return builder;
    }

    public void cancelRequests(String group) {
        Collection<PermissionRequest.Builder> builders = mActiveRequests.row(group).values();
        for (PermissionRequest.Builder builder : builders) {
            builder.cancel();
        }
        builders.clear();
    }

    public void cancelRequest(String group, String path) {
        PermissionRequest.Builder builder = mActiveRequests.remove(group, path);
        if (builder != null) {
            builder.cancel();
        }
    }

    public void onDestroy() {
        mBlessingsRef.removeEventListener(blessingListener);
        mRequestsRef.removeEventListener(requestListener);
        for (Blessing blessing : new HashSet<Blessing>(mBlessings.values())) {
            blessing.revoke();
        }
    }

    public interface OnRequestListener {
        boolean onRequest(PermissionRequest request, Blessing blessing);

        void onRequestRemoved(PermissionRequest request, Blessing blessing);
    }

    public interface OnPermissionChangeListener {
        void onPermissionChange(int current);

        void onCancelled(DatabaseError databaseError);
    }
}