se.kth.iss.ug2.test.MockTransport Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of standalone-ugclient Show documentation
Show all versions of standalone-ugclient Show documentation
A stand-alone Maven UgClient package separated from the UG server.
/*
* 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