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

se.kth.iss.ug2.test.MockTransport Maven / Gradle / Ivy

There is a newer version: 1.0.5
Show newest version
/*
 * MIT License
 *
 * Copyright (c) 2017 Kungliga Tekniska högskolan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package se.kth.iss.ug2.test;

import static java.util.Arrays.asList;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;

import se.kth.iss.ug2.Transport;
import se.kth.iss.ug2.Ug2ChangeLogEntry;
import se.kth.iss.ug2.Ug2Exception;
import se.kth.iss.ug2.Ug2Msg;
import se.kth.iss.ug2.Ug2ObjectHandle;
import se.kth.iss.ug2.Ug2Protocol;
import se.kth.iss.ug2.Ug2SessionInformation;
import se.kth.iss.ug2.UgAttribute;
import se.kth.iss.ug2.UgObject;

public class MockTransport implements Transport {

    private byte[] systemkey;

    private List ugObjects;

    private List changeLogEntries;
    private Map> groupMemberships = new HashMap<>();

    private final String theOperator;
    private final String theFacility;
    static Long sessionId = 0L;

    private static final char[] kthidChars = "0123456789abcdefghijklmnopqrstuvwxyz".toCharArray();

    public MockTransport(List ugObjects,
                  List changeLogEntries) throws Ug2Exception {
        this(ugObjects, changeLogEntries, null, null);
    }

    MockTransport(List ugObjects, List changeLogEntries,
                  String theOperator, String theFacility) throws Ug2Exception {
        this.theOperator = theOperator;
        this.theFacility = theFacility;
        this.systemkey = MockUgClient.stringToKey("");
        this.ugObjects = ugObjects;
        this.changeLogEntries = changeLogEntries;
        ugObjects.stream().filter(UgObject::isUser).forEach(object -> {
            if (object.hasAttribute(UgObject.USERGROUPPSEUDOATTRIBUTE)) {
                List values = object
                        .getUgAttribute(UgObject.USERGROUPPSEUDOATTRIBUTE)
                        .getValues();
                groupMemberships.put(object.getKthid(), values);
            }
        });
    }

    public void close() {}

    public enum Operations {
    	ping, prePing, changeData, createGroup, createSession, getSchema, getData, allObjectsHaving, getChangeLogEntries,
        currentVersion, findObjects, objectsMatching, accumulateGroupData, membership, enableRole, getSessionInfo
    }

    public Ug2Msg rpc(Ug2Msg request) throws Ug2Exception {
        byte key[];

        String operation = request.toMap().get("operation");

        Ug2Msg reply = Ug2Msg.makeEmptyMsg();

        switch (Operations.valueOf(operation)) {
        	case ping:
        		reply.addString(Ug2Protocol.STATUS, Ug2Protocol.STATUS_OK);
            break;
            case prePing:
                reply.addString(Ug2Protocol.STATUS, Ug2Protocol.STATUS_OK);
                break;
            case createSession:
                sessionId++;
                reply.addStringNoCheck(Ug2Protocol.SESSIONID, sessionId.toString());
                reply.addLongNoCheck(Ug2Protocol.SESSION_KEY_AGE_MAX, 36000);
                reply.addString(Ug2Protocol.STATUS, Ug2Protocol.STATUS_OK);
                break;
            case getSchema:
                reply.addSchema(TestSchema.createSchema());
                reply.addString(Ug2Protocol.STATUS, Ug2Protocol.STATUS_OK);
                break;
            case getChangeLogEntries:
                reply.addChangeLogEntryArray(getChangeLogEntryArray(Long
                                .parseLong(request.toMap().get("minVersion")))
                );
                reply.addString(Ug2Protocol.STATUS, Ug2Protocol.STATUS_OK);
                break;

            case createGroup:
                createGroup(request, reply);
                reply.addString(Ug2Protocol.STATUS, Ug2Protocol.STATUS_OK);
                break;

            case getData:
                getData(request, reply);

                reply.addString(Ug2Protocol.STATUS, Ug2Protocol.STATUS_OK);
                break;
            case changeData:
                changeOrSetData(request, reply);
                reply.addString(Ug2Protocol.STATUS, Ug2Protocol.STATUS_OK);
                break;
            case accumulateGroupData:
                accumulateGroupData(request, reply);
                reply.addString(Ug2Protocol.STATUS, Ug2Protocol.STATUS_OK);
                break;
            case findObjects:
                findGroupMemberships(request, reply);

                reply.addString(Ug2Protocol.STATUS, Ug2Protocol.STATUS_OK);
                break;
            case currentVersion:
                reply.addLong("version", getMaxVersion());

                reply.addString(Ug2Protocol.STATUS, Ug2Protocol.STATUS_OK);
                break;
            case allObjectsHaving:
                getKthids(request, reply);

                reply.addString(Ug2Protocol.STATUS, Ug2Protocol.STATUS_OK);
                break;
            case membership:
                reply.addArray(new String[]{}, "group");
                reply.addString(Ug2Protocol.STATUS, Ug2Protocol.STATUS_OK);
                break;
            case objectsMatching:
                objectsMatching(request, reply);
                reply.addString(Ug2Protocol.STATUS, Ug2Protocol.STATUS_OK);
                break;

            case enableRole: // TODO: Do we need to actually fake role handling?
                reply.addString(Ug2Protocol.STATUS, Ug2Protocol.STATUS_OK);
                break;

            case getSessionInfo:
                addSessionInfo(reply);
                reply.addString(Ug2Protocol.STATUS, Ug2Protocol.STATUS_OK);
                break;

            default:
                reply.addString(Ug2Protocol.STATUS, Ug2Protocol.STATUS_SERVERERROR);
        }

        key = systemkey;

        reply.setChallenge(request.challenge() + 1);
        reply.setTime();
        reply.addMessageDigest();
        reply.addAuthenticator(key);

        return reply;
    }

    private void accumulateGroupData(Ug2Msg request, Ug2Msg reply) throws Ug2Exception {
        String attribute = request.toMap().get(Ug2Protocol.ATTRIBUTE + "-0");
        List result = new LinkedList<>();
        ugObjects.stream().filter(entry -> entry.getUgClass().equals("group")).forEach(entry -> {
            if (entry.hasAttribute(attribute)) {
                result.addAll(entry.getUgAttribute(attribute).getValues());
            }
        });

        reply.addArray(result.toArray(new String[result.size()]), "value-0");
    }

    private void objectsMatching(Ug2Msg request, Ug2Msg reply) throws Ug2Exception {
        String ugClass = request.toMap().get("class");
        String attribute = request.toMap().get("attribute");
        String value = request.toMap().get("value");
        List result = ugObjects.stream().filter(entry -> entry.getUgClass().equals(ugClass)
                && attributeMatches(entry.getUgAttribute(attribute), value))
                .map(UgObject::getKthid).collect(Collectors.toCollection(LinkedList::new));

        reply.addArray(result.toArray(new String[result.size()]), "object");
    }

    private boolean attributeMatches(UgAttribute attr, String pattern) {
        boolean matches = false;
        if (attr != null) {
            for (Object value : attr.getValues()) {
                if (pattern.equals(value) || ((String) value).matches(pattern.replace("%", ".*"))) {
                    matches = true;
                }
            }
        }
        return matches;
    }

    private long getMaxVersion() {
        long result = 0;
        for (Ug2ChangeLogEntry e : changeLogEntries) {
            if (e.getDbVersion() > result) {
                result = e.getDbVersion();
            }
        }
        return result;
    }

    private void getKthids(Ug2Msg request, Ug2Msg reply) throws Ug2Exception {
        String ugClass = request.toMap().get("class");
        List result = ugObjects.stream().filter(entry -> entry.getUgClass()
                .equals(ugClass))
                .map(UgObject::getKthid)
                .collect(Collectors.toCollection(LinkedList::new));
        //		reply.add
        reply.addArray(result.toArray(new String[result.size()]), "object");

    }

    private Ug2ChangeLogEntry[] getChangeLogEntryArray(long version) {
        LinkedList result = new LinkedList<>();
        changeLogEntries.stream().filter(entry -> entry.getDbVersion() >= version).forEach(result::addLast);
        return result.toArray(new Ug2ChangeLogEntry[result.size()]);
    }

    private void createGroup(Ug2Msg request, Ug2Msg reply) throws Ug2Exception {
        String ug1name = request.readStringMandatory(Ug2Protocol.UG1NAME);
        String nameSv = request.readStringNoThrow(Ug2Protocol.NAME_SV);
        String nameEn = request.readStringNoThrow(Ug2Protocol.NAME_EN);
        boolean inheritRights = request.readBool(Ug2Protocol.INHERIT_RIGHTS);

        // TODO Check that the group does not allready exist.
        /*
         * Calculate the parent of this group from his ug1name and get the KTH-ID
         * of that group. The parent MUST exist.
         */
        String parentUg1name = findParentUg1name(ug1name);
        UgObject parent = findObject("group", "ug1name", parentUg1name);
        if (parent == null) {
            handleNotFoundError(reply, "No parent group found for requested new group " + ug1name);
            return;
        }
        String parentKthid = parent.getKthid();

        // TODO Check permissions.
        //if (!db.hasPermission("group", parentKthid, null, "create", request)) {
        //    request.handleNotPermittedError("Session not allowed to create new group named " + ug1name);
        //    return;
        //}

        /*
         * Now we try to create the new group. 
         */
        String kthid = generateKthid("group");
        Map> attributes = new HashMap<>();
        attributes.put("kthid", asList(kthid));
        attributes.put("ug1name", asList(ug1name));
        attributes.put("parent", asList(parentKthid));
        String operator = theOperator;
        if (operator != null) {
            attributes.put("creator", asList(operator));
        }

        if (inheritRights) {
            for (String attr : asList("editor", "admin", "setpwd", "watcher")) {
                UgAttribute value = parent.getUgAttribute(attr);
                if (value != null) {
                    attributes.put(attr, value.getValues());
                }
            }
        }

        if (nameSv != null && !nameSv.isEmpty()) {
            attributes.put("name_sv", asList(nameSv));
        }
        if (nameEn != null && !nameEn.isEmpty()) {
            attributes.put("name_en", asList(nameEn));
        }
        UgObject newGroup = new UgObject("group", kthid, attributes, TestSchema.createSchema());
        ugObjects.add(newGroup);

        reply.addObjectHandle(new Ug2ObjectHandle("group", kthid), Ug2Protocol.PRINCIPAL);

        //return reply.readObjectHandleMandatory(Ug2Protocol.PRINCIPAL);
    }

    private void handleNotFoundError(Ug2Msg reply, String msgdetails) {
        reply.addStringNoCheck(Ug2Protocol.STATUS, Ug2Protocol.STATUS_NOTFOUND);
        reply.addStringNoCheck(Ug2Protocol.DETAILS, msgdetails);
    }

    private void handleNotUniqueError(Ug2Msg reply, String msgdetails) {
        reply.addStringNoCheck(Ug2Protocol.STATUS, Ug2Protocol.STATUS_NOTUNIQUE);
        reply.addStringNoCheck(Ug2Protocol.DETAILS, msgdetails);
    }

    private UgObject findObject(String ugClass, String key, String value) {
        if ("kthid".equals(key)) {
            return findObjectByKthid(ugClass, value);
        } else {
            for (UgObject entry : ugObjects) {
                if (entry.getUgClass().equals(ugClass) && attributeMatches(entry.getUgAttribute(key), value)) {
                    return entry;
                }
            }
        }
        return null;
    }

    private UgObject findObjectByKthid(String ugClass, String value) {
        for (UgObject entry : ugObjects) {
            if (entry.getUgClass().equals(ugClass) && entry.getKthid().equals(value)) {
                return entry;
            }
        }
        return null;
    }

    private static String findParentUg1name(String ug1name) {
        if (ug1name == null)
            return null;

        int pos = ug1name.lastIndexOf('.');
        return (pos == -1) ? "ROOT" : ug1name.substring(0, pos);
    }

    public String generateKthid(String ug2class) throws Ug2Exception {
        String prefix = null;
        String kthid = null;

        switch (ug2class) {
            case "user":
                prefix = "u1";
                break;
            case "group":
                prefix = "u2";
                break;
            case "directory":
                prefix = "u3";
                break;
        }

        if (prefix == null)
            throw new Ug2Exception("Unknown class to generate kthid for: " + ug2class);

        Random rand = new Random();

        boolean found = false;
        int retries = 0;
        for (; !found && retries < 100; retries++) {
            int number = rand.nextInt();
            StringBuilder buf = new StringBuilder(prefix);

            /*
             * Convert random number to base36.
             */
            for (int i = 0; i < 6; i++) {
                int residual = Math.abs(number % 36);
                number /= 36;

                buf.append(kthidChars[residual]);
            }

            if (number != 0)
                throw new Ug2Exception("Error while base-36 encoding the kthid.");

            kthid = buf.toString();

            if (findObjectByKthid(ug2class, kthid) != null)
                continue;

            // I don't think the mock server handles deleted objects
            //if (objectIsDeleted(ug2class, kthid, request))
            //    continue;

            found = true;
        }
        if (retries >= 100)
            throw new Ug2Exception("Unable to generate new unique kthid (too many retries," +
                    " all either busy or deleted)");

        //addData(ug2class, kthid, "kthid", kthid, request);

        //log.debug("Generated kthid: " + kthid);
        return kthid;
    }


    private void getData(Ug2Msg request, Ug2Msg reply) throws Ug2Exception {
        String ug2class = request.readStringMandatory(Ug2Protocol.CLASS);
        getData(request, reply, ug2class);
    }

    @SuppressWarnings("serial")
	private static class NotUniqueException extends Exception {}

    private void getData(Ug2Msg request, Ug2Msg reply, String ug2class) throws Ug2Exception {
        String keyAttr = request.readStringNoThrow(Ug2Protocol.KEY);
        String[] objects = request.readArrayMandatory(Ug2Protocol.OBJECT);
        String[] attributes = request.readArrayMandatory(Ug2Protocol.ATTRIBUTE);

        List result = new LinkedList<>();
        List objectStatus = new LinkedList<>();

        for (String key : objects) {
            try {
                UgObject object = findUniqueUgObject(ug2class, keyAttr, key);
                objectStatus.add(object != null ? Ug2Protocol.STATUS_OK : Ug2Protocol.STATUS_NOTFOUND);
                result.add(object);
            } catch (NotUniqueException ignored) {
                objectStatus.add(Ug2Protocol.STATUS_NOTUNIQUE);
                result.add(null);
            }
        }

        for (int attrIndex = 0; attrIndex < attributes.length; attrIndex++) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            DataOutputStream dos = new DataOutputStream(baos);

            for (UgObject ugObject : result) {
                if (ugObject != null) {
                    UgAttribute attribute = ugObject.getUgAttribute(attributes[attrIndex]);
                    if (attribute != null) {
                        List values = attribute.getValues();
                        writeListToDos(dos, values);
                    } else {
                        writeListToDos(dos, Collections.emptyList());
                    }
                }
            }

            closeDos(dos);

            reply.addDataResult(attrIndex, baos.toByteArray());
        }

        reply.addArray(objectStatus.toArray(new String[objectStatus.size()]),
                Ug2Protocol.OBJECTSTATUS);
    }

    private UgObject findUniqueUgObject(String ug2class, String keyAttr, String key) throws NotUniqueException {
        if ("kthid".equals(keyAttr)) {
            return findObjectByKthid(ug2class, key); // kthid is allways unique
        }
        UgObject object = null;
        for (UgObject ugObject : ugObjects) {
            if (!ugObject.getUgClass().equals(ug2class))
                continue;
            UgAttribute attribute = ugObject.getUgAttribute(keyAttr);
            if(attribute == null || !attribute.getValues().contains(key)) {
                    continue;
            }
            if (object != null) {
                throw new NotUniqueException();
            }
            object = ugObject;
        }
        return object;
    }

    private void changeOrSetData(Ug2Msg request, Ug2Msg reply) throws Ug2Exception {
        String ug2class = request.readStringMandatory(Ug2Protocol.CLASS);
        String[] objects = request.readArrayMandatory(Ug2Protocol.OBJECT);
        String keyAttribute = request.readStringNoThrow(Ug2Protocol.KEY);

        if (keyAttribute == null)
            keyAttribute = "kthid";
        
        /* TODO Check permissions
        if (!keyAttribute.equals("kthid") && !db.hasPermission(ug2class, null, keyAttribute, "find", request))
        {
            request.handleNotPermittedError("No search access for key attribute " + ug2class + "/" + keyAttribute);
            return;
        }
        */
        
        /*
         * Map the given object references to kthids. If keyAttribute is
         * kthid, this operation only validates that the objects really do
         * exist in the db.
         */
        String[] objKthids = new String[objects.length];
        
        /*
         * Make sure that the object references are unique and lock the objects
         */
        for (int iObj = 0; iObj < objKthids.length; iObj++) {
            UgObject obj;
            try {
                obj = findUniqueUgObject(ug2class, keyAttribute, objects[iObj]);
            } catch (NotUniqueException ignored) {
                handleNotUniqueError(reply, "Object reference not unique:  " + objects[iObj]);
                return;
            }
            if (obj == null) {
                handleNotFoundError(reply, "Object not found:  " + objects[iObj]);
                return;
            }
            objKthids[iObj] = obj.getKthid();

            // Locking not considered in mock
            //request.lockObject(ug2class, objKthids[iObj][0]);

            // TODO Handle autodeletion
            /*
            for (String attr : attributes) {
                if (db.schema().isAutoDelete(ug2class, attr)) {
                    String[] potentialAutoDeleteObjects = db.values(ug2class, attr, objKthids[iObj][0], false, request);
                    for (String potentialAutoDeleteObject : potentialAutoDeleteObjects)
                        request.lockObject(db.schema().domainClass(ug2class, attr), potentialAutoDeleteObject);
                }
            }
            */
        }

        // Locking not considered in mock
        // request.lockUnlockedObjects();


    }

    private void findGroupMemberships(Ug2Msg request, Ug2Msg reply) throws Ug2Exception {
        String[] objects = request.readArrayMandatory(Ug2Protocol.LOOKUPVALUE);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);
        List objectStatus = new LinkedList<>();
        int i = 0;
        for (String kthid : objects) { //assuming kthid as key
            if (groupMemberships.containsKey(kthid)) {
                reply.addLinkedList(new LinkedList<>(getGroupMemberships().get(kthid)), "object-" + i);
                objectStatus.add(Ug2Protocol.STATUS_OK);
            } else {
                reply.addLinkedList(new LinkedList<>(), "object-" + i);
                objectStatus.add(Ug2Protocol.STATUS_NOTFOUND);
            }
            i++;
        }

        closeDos(dos);
        reply.addDataResult(0, baos.toByteArray());
        reply.addArray(objects, Ug2Protocol.OBJECT);
        reply.addArray(objectStatus.toArray(new String[objectStatus.size()]),
                Ug2Protocol.OBJECTSTATUS);
        reply.addInt(Ug2Protocol.NUMLOOKUPVALS, objects.length);
    }

    private void closeDos(DataOutputStream dos) throws Ug2Exception {
        try {
            dos.flush();
            dos.close();
        } catch (IOException e) {
            throw new Ug2Exception("Failed to compress result data", e);
        }
    }

    private void writeListToDos(DataOutputStream dos, Collection values) throws Ug2Exception {
        try {
            dos.writeInt(values.size());
            for (String value : values) {
                dos.writeUTF(value);
            }
        } catch (IOException e) {
            throw new Ug2Exception("Failed to compress result data", e);
        }
    }

    private void addSessionInfo(Ug2Msg reply) {
        Date now = new Date();
        reply.addSessionInfoNoCheck(new Ug2SessionInformation("x", theOperator, theFacility,
                now, now, now, new String[0]));
    }

    @Override
    public String server() {
        // TODO Auto-generated method stub
        return null;
    }

    public Map> getGroupMemberships() {
        return Collections.unmodifiableMap(groupMemberships);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy