Package org.apache.jackrabbit.mk.wrapper

Source Code of org.apache.jackrabbit.mk.wrapper.SecurityWrapper

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.jackrabbit.mk.wrapper;

import java.io.InputStream;
import org.apache.jackrabbit.mk.MicroKernelFactory;
import org.apache.jackrabbit.mk.api.MicroKernel;
import org.apache.jackrabbit.mk.api.MicroKernelException;
import org.apache.jackrabbit.mk.json.JsopReader;
import org.apache.jackrabbit.mk.json.JsopStream;
import org.apache.jackrabbit.mk.json.JsopTokenizer;
import org.apache.jackrabbit.mk.json.JsopWriter;
import org.apache.jackrabbit.mk.simple.NodeImpl;
import org.apache.jackrabbit.mk.simple.NodeMap;
import org.apache.jackrabbit.mk.util.ExceptionFactory;
import org.apache.jackrabbit.mk.util.PathUtils;
import org.apache.jackrabbit.mk.util.SimpleLRUCache;

/**
* A microkernel prototype implementation that filters nodes based on simple
* access rights. Each user has a password, and (optionally) a list of rights,
* stored as follows: /:user/x { password: "123", rights: "a" } Each node can
* require the user has a certain right: /data { ":right": "a" } Access rights
* are recursive. There is a special right "admin" which means everything is
* allowed, and "write" meaning a user can write.
* <p>
* This implementation is not meant for production, it is only used to find
* (performance and other) problems when using such an approach.
*/
public class SecurityWrapper extends MicroKernelWrapperBase implements MicroKernel {

    private final MicroKernelWrapper mk;
    private final boolean admin, write;
    private final String[] userRights;
    private final NodeMap map = new NodeMap();
    private final SimpleLRUCache<String, NodeImpl> cache = SimpleLRUCache.newInstance(100);
    private String rightsRevision;

    private SecurityWrapper(MicroKernel mk, String[] rights) {
        // TODO security for the index mechanism
        this.mk = MicroKernelWrapperBase.wrap(mk);
        this.userRights = rights;
        boolean isAdmin = false, canWrite = false;
        for (String r : rights) {
            if (r.equals("admin")) {
                isAdmin = true;
            } else if (r.equals("write")) {
                canWrite = true;
            }
        }
        admin = isAdmin;
        write = canWrite;
    }

    public static synchronized SecurityWrapper get(String url) {
        String userPassUrl = url.substring("sec:".length());
        int index = userPassUrl.indexOf(':');
        if (index < 0) {
            throw ExceptionFactory.get("Expected url format: sec:user@pass:<url>");
        }
        String u = userPassUrl.substring(index + 1);
        String userPass = userPassUrl.substring(0, index);
        index = userPass.indexOf('@');
        if (index < 0) {
            throw ExceptionFactory.get("Expected url format: sec:user@pass:<url>");
        }
        String user = userPass.substring(0, index);
        String pass = userPass.substring(index + 1);
        MicroKernel mk = MicroKernelFactory.getInstance(u);
        try {
            String role = mk.getNodes("/:user/" + user, mk.getHeadRevision());
            NodeMap map = new NodeMap();
            JsopReader t = new JsopTokenizer(role);
            t.read('{');
            NodeImpl n = NodeImpl.parse(map, t, 0);
            String password = JsopTokenizer.decodeQuoted(n.getProperty("password"));
            if (!pass.equals(password)) {
                throw ExceptionFactory.get("Wrong password");
            }
            String rights = JsopTokenizer.decodeQuoted(n.getProperty("rights"));
            return new SecurityWrapper(mk, rights.split(","));
        } catch (MicroKernelException e) {
            mk.dispose();
            throw e;
        }
    }

    public String commitStream(String rootPath, JsopReader jsonDiff, String revisionId, String message) {
        checkRights(rootPath, true);
        if (!admin) {
            verifyDiff(jsonDiff, revisionId, rootPath, null);
        }
        return mk.commitStream(rootPath, jsonDiff, revisionId, message);
    }

    public void dispose() {
        mk.dispose();
    }

    public String getHeadRevision() {
        return mk.getHeadRevision();
    }

    public JsopReader getJournalStream(String fromRevisionId, String toRevisionId, String filter) {
        rightsRevision = getHeadRevision();
        JsopReader t = mk.getJournalStream(fromRevisionId, toRevisionId, filter);
        if (admin) {
            return t;
        }
        t.read('[');
        if (t.matches(']')) {
            return new JsopTokenizer("[]");
        }
        JsopStream buff = new JsopStream();
        buff.array();
        String revision = fromRevisionId;
        do {
            t.read('{');
            buff.object();
            do {
                String key = t.readString();
                buff.key(key);
                t.read(':');
                if (key.equals("id")) {
                    t.read();
                    String value = t.getToken();
                    revision = value;
                    buff.value(value);
                } else if (key.equals("changes")) {
                    t.read();
                    String value = t.getToken();
                    value = filterDiff(new JsopTokenizer(value), revision).toString();
                    buff.value(value);
                } else {
                    String raw = t.readRawValue();
                    //System.out.println(key + ":" + raw);
                    buff.encodedValue(raw);
                }
            } while (t.matches(','));
            buff.endObject();
            t.read('}');
        } while (t.matches(','));
        buff.endArray();
        return buff;
    }

    public JsopReader diffStream(String fromRevisionId, String toRevisionId, String path) {
        rightsRevision = getHeadRevision();
        JsopReader diff = mk.diffStream(fromRevisionId, toRevisionId, path);
        if (admin) {
            return diff;
        }
        return filterDiff(diff, toRevisionId);
    }

    private JsopReader filterDiff(JsopReader t, String revisionId) {
        JsopStream buff = new JsopStream();
        verifyDiff(t, revisionId, null, buff);
        return buff;
    }

    private void verifyDiff(JsopReader t, String revisionId, String rootPath, JsopWriter diff) {
        while (true) {
            int r = t.read();
            if (r == JsopTokenizer.END) {
                break;
            }
            String path;
            if (rootPath == null) {
                path = t.readString();
            } else {
                path = PathUtils.concat(rootPath, t.readString());
            }
            switch (r) {
            case '+':
                t.read(':');
                if (t.matches('{')) {
                    NodeImpl n = NodeImpl.parse(map, t, 0);
                    if (checkDiff(path, diff)) {
                        diff.tag('+').key(path);
                        n = filterAccess(path, n);
                        n.append(diff, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, false);
                        diff.newline();
                    }
                } else {
                    String value = t.readRawValue().trim();
                    String nodeName = PathUtils.getParentPath(path);
                    if (checkDiff(nodeName, diff)) {
                        if (checkPropertyRights(path)) {
                            diff.tag('+').key(path);
                            diff.encodedValue(value);
                            diff.newline();
                        }
                    }
                }
                break;
            case '-':
                if (checkDiff(path, diff)) {
                    diff.tag('-').value(path);
                    diff.newline();
                }
                break;
            case '^':
                t.read(':');
                String value;
                if (t.matches(JsopTokenizer.NULL)) {
                    if (checkDiff(path, diff)) {
                        if (checkPropertyRights(path)) {
                            diff.tag('^').key(path).value(null);
                            diff.newline();
                        }
                    }
                } else {
                    value = t.readRawValue().trim();
                    String nodeName = PathUtils.getParentPath(path);
                    if (checkDiff(nodeName, diff)) {
                        if (checkPropertyRights(path)) {
                            diff.tag('^').key(path).encodedValue(value);
                            diff.newline();
                        }
                    }
                }
                break;
            case '>':
                t.read(':');
                checkDiff(path, diff);
                String name = PathUtils.getName(path);
                if (t.matches('{')) {
                    String position = t.readString();
                    t.read(':');
                    String to = t.readString();
                    String target;
                    t.read('}');
                    if (!PathUtils.isAbsolute(to)) {
                        to = PathUtils.concat(rootPath, to);
                    }
                    if ("last".equals(position) || "first".equals(position)) {
                        target = PathUtils.concat(to, name);
                    } else {
                        // before, after
                        target = PathUtils.getParentPath(to);
                    }
                    if (checkDiff(target, diff)) {
                        diff.tag('>').key(path);
                        diff.object().key(position);
                        diff.value(to).endObject();
                        diff.newline();
                    }
                } else {
                    String to = t.readString();
                    if (!PathUtils.isAbsolute(to)) {
                        to = PathUtils.concat(rootPath, to);
                    }
                    if (checkDiff(to, diff)) {
                        diff.tag('>').key(path);
                        diff.value(to);
                        diff.newline();
                    }
                }
                break;
            default:
                throw ExceptionFactory.get("token: " + (char) t.getTokenType());
            }
        }
    }

    private boolean checkDiff(String path, JsopWriter target) {
        if (checkRights(path, target == null)) {
            return target != null;
        } else if (target == null) {
            throw ExceptionFactory.get("Access denied");
        }
        return false;
    }

    public long getLength(String blobId) {
        return mk.getLength(blobId);
    }

    public JsopReader getNodesStream(String path, String revisionId) {
        return getNodesStream(path, revisionId, 1, 0, -1, null);
    }

    public JsopReader getNodesStream(String path, String revisionId, int depth, long offset, int count, String filter) {
        rightsRevision = getHeadRevision();
        if (!checkRights(path, false)) {
            throw ExceptionFactory.get("Node not found: " + path);
        }
        JsopReader t = mk.getNodesStream(path, revisionId, depth, offset, count, filter);
        if (admin) {
            return t;
        }
        t.read('{');
        NodeImpl n = NodeImpl.parse(map, t, 0);
        n = filterAccess(path, n);
        JsopStream buff = new JsopStream();
        if (n == null) {
            throw ExceptionFactory.get("Node not found: " + path);
        } else {
            // TODO childNodeCount properties might be wrong
            // when count and offset are used
            n.append(buff, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, true);
        }
        return buff;
    }

    public JsopReader getRevisionsStream(long since, int maxEntries) {
        return mk.getRevisionsStream(since, maxEntries);
    }

    public boolean nodeExists(String path, String revisionId) {
        rightsRevision = getHeadRevision();
        if (!checkRights(path, false)) {
            return false;
        }
        return mk.nodeExists(path, revisionId);
    }

    public long getChildNodeCount(String path, String revisionId) {
        rightsRevision = getHeadRevision();
        if (!checkRights(path, false)) {
            throw ExceptionFactory.get("Node not found: " + path);
        }
        return mk.getChildNodeCount(path, revisionId);
    }

    public int read(String blobId, long pos, byte[] buff, int off, int length) {
        return mk.read(blobId, pos, buff, off, length);
    }

    public String waitForCommit(String oldHeadRevision, long maxWaitMillis) throws InterruptedException {
        return mk.waitForCommit(oldHeadRevision, maxWaitMillis);
    }

    public String write(InputStream in) {
        rightsRevision = getHeadRevision();
        checkRights(null, true);
        return mk.write(in);
    }

    private NodeImpl filterAccess(String path, NodeImpl n) {
        if (!checkRights(path, false)) {
            return null;
        }
        if (!admin && n.hasProperty(":rights")) {
            n = n.cloneAndSetProperty(":rights", null, 0);
        }
        for (long pos = 0;; pos++) {
            String childName = n.getChildNodeName(pos);
            if (childName == null) {
                break;
            }
            NodeImpl c = n.getNode(childName);
            NodeImpl c2 = filterAccess(PathUtils.concat(path, childName), c);
            if (c2 != c) {
                if (c2 == null) {
                    n = n.cloneAndRemoveChildNode(childName, 0);
                } else {
                    n = n.setChild(childName, c2, 0);
                }
            }
        }
        return n;
    }

    private boolean checkPropertyRights(String path) {
        return !PathUtils.getName(path).equals(":rights");
    }

    private boolean checkRights(String path, boolean write) {
        if (admin) {
            return true;
        }
        if (write && !this.write) {
            return false;
        }
        if (path == null) {
            return true;
        }
        boolean access = false;
        while (true) {
            String key = path + "@" + rightsRevision;
            NodeImpl n = cache.get(key);
            if (n == null) {
                if (mk.nodeExists(path, rightsRevision)) {
                    String json = mk.getNodes(path, rightsRevision, 0, 0, 0, null);
                    JsopReader t = new JsopTokenizer(json);
                    t.read('{');
                    n = NodeImpl.parse(map, t, 0);
                } else {
                    n = new NodeImpl(map, 0);
                }
                cache.put(key, n);
            }
            Boolean b = hasRights(n);
            if (b != null) {
                if (b) {
                    access = true;
                } else {
                    return false;
                }
            }
            // check parent
            if (PathUtils.denotesRoot(path)) {
                break;
            }
            path = PathUtils.getParentPath(path);
        }
        return access;
    }

    private Boolean hasRights(NodeImpl n) {
        String rights = n.getProperty(":rights");
        if (rights == null) {
            return null;
        }
        rights = JsopTokenizer.decodeQuoted(rights);
        for (String r : rights.split(",")) {
            boolean got = false;
            for (String u : userRights) {
                if (u.equals(r)) {
                    got = true;
                    break;
                }
            }
            if (!got) {
                return false;
            }
        }
        return true;
    }

}
TOP

Related Classes of org.apache.jackrabbit.mk.wrapper.SecurityWrapper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.