From 7e5a955aff88b1efe1adec431faf26e4de10d19a Mon Sep 17 00:00:00 2001 From: Hiroyuki Wada Date: Thu, 28 Jan 2021 14:35:59 +0900 Subject: [PATCH] feat: support custom user attributes --- .../connector/guacamole/GuacamoleClient.java | 19 +++ .../GuacamoleConnectionGroupHandler.java | 4 +- .../guacamole/GuacamoleConnectionHandler.java | 4 +- .../guacamole/GuacamoleConnector.java | 47 ++--- .../connector/guacamole/GuacamoleSchema.java | 8 +- .../guacamole/GuacamoleUserGroupHandler.java | 4 +- .../guacamole/GuacamoleUserHandler.java | 160 ++++++++---------- .../connector/guacamole/GuacamoleUtils.java | 77 +++++++-- .../guacamole/rest/GuacamoleRESTClient.java | 23 +++ .../guacamole/testutil/MockClient.java | 6 + 10 files changed, 198 insertions(+), 154 deletions(-) diff --git a/src/main/java/jp/openstandia/connector/guacamole/GuacamoleClient.java b/src/main/java/jp/openstandia/connector/guacamole/GuacamoleClient.java index 97368bb..1fb1933 100644 --- a/src/main/java/jp/openstandia/connector/guacamole/GuacamoleClient.java +++ b/src/main/java/jp/openstandia/connector/guacamole/GuacamoleClient.java @@ -28,6 +28,13 @@ public interface GuacamoleClient { String getAuthToken(); + List schema(); + + + default String getSchemaEndpointURL(GuacamoleConfiguration configuration) { + String url = configuration.getGuacamoleURL(); + return String.format("%ssession/data/%s/schema/userAttributes", url, configuration.getGuacamoleDataSource()); + } default String getUserEndpointURL(GuacamoleConfiguration configuration) { String url = configuration.getGuacamoleURL(); @@ -339,6 +346,18 @@ public List toGuacamoleAttributes() { } } + @JsonIgnoreProperties(ignoreUnknown = true) + class GuacamoleSchemaRepresentation { + public String name; + public List fields; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + class GuacamoleSchemaFieldRepresentation { + public String name; + public String type; + } + @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) class GuacamoleUserGroupRepresentation { diff --git a/src/main/java/jp/openstandia/connector/guacamole/GuacamoleConnectionGroupHandler.java b/src/main/java/jp/openstandia/connector/guacamole/GuacamoleConnectionGroupHandler.java index 81dfbc0..8bac1de 100644 --- a/src/main/java/jp/openstandia/connector/guacamole/GuacamoleConnectionGroupHandler.java +++ b/src/main/java/jp/openstandia/connector/guacamole/GuacamoleConnectionGroupHandler.java @@ -49,10 +49,10 @@ public class GuacamoleConnectionGroupHandler implements GuacamoleObjectHandler { private final GuacamoleSchema schema; private final GuacamoleAssociationHandler associationHandler; - public GuacamoleConnectionGroupHandler(GuacamoleConfiguration configuration, GuacamoleClient client) { + public GuacamoleConnectionGroupHandler(GuacamoleConfiguration configuration, GuacamoleClient client, GuacamoleSchema schema) { this.configuration = configuration; this.client = client; - this.schema = new GuacamoleSchema(configuration, client); + this.schema = schema; this.associationHandler = new GuacamoleAssociationHandler(configuration, client); } diff --git a/src/main/java/jp/openstandia/connector/guacamole/GuacamoleConnectionHandler.java b/src/main/java/jp/openstandia/connector/guacamole/GuacamoleConnectionHandler.java index 0ea1785..0e4dcca 100644 --- a/src/main/java/jp/openstandia/connector/guacamole/GuacamoleConnectionHandler.java +++ b/src/main/java/jp/openstandia/connector/guacamole/GuacamoleConnectionHandler.java @@ -59,10 +59,10 @@ public class GuacamoleConnectionHandler implements GuacamoleObjectHandler { private final GuacamoleSchema schema; private final GuacamoleAssociationHandler associationHandler; - public GuacamoleConnectionHandler(GuacamoleConfiguration configuration, GuacamoleClient client) { + public GuacamoleConnectionHandler(GuacamoleConfiguration configuration, GuacamoleClient client, GuacamoleSchema schema) { this.configuration = configuration; this.client = client; - this.schema = new GuacamoleSchema(configuration, client); + this.schema = schema; this.associationHandler = new GuacamoleAssociationHandler(configuration, client); } diff --git a/src/main/java/jp/openstandia/connector/guacamole/GuacamoleConnector.java b/src/main/java/jp/openstandia/connector/guacamole/GuacamoleConnector.java index dd866a7..634de67 100644 --- a/src/main/java/jp/openstandia/connector/guacamole/GuacamoleConnector.java +++ b/src/main/java/jp/openstandia/connector/guacamole/GuacamoleConnector.java @@ -34,9 +34,7 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -53,7 +51,7 @@ public class GuacamoleConnector implements PoolableConnector, CreateOp, UpdateDe protected GuacamoleConfiguration configuration; protected GuacamoleClient client; - private Map userSchemaMap; + private GuacamoleSchema cachedSchema; private String instanceName; @Override @@ -121,42 +119,21 @@ public Response intercept(Chain chain) throws IOException { @Override public Schema schema() { try { - SchemaBuilder schemaBuilder = new SchemaBuilder(GuacamoleConnector.class); - - ObjectClassInfo userSchemaInfo = GuacamoleUserHandler.createSchema(); - schemaBuilder.defineObjectClass(userSchemaInfo); - - ObjectClassInfo userGroupSchemaInfo = GuacamoleUserGroupHandler.createSchema(); - schemaBuilder.defineObjectClass(userGroupSchemaInfo); - - ObjectClassInfo connectionSchemaInfo = GuacamoleConnectionHandler.createSchema(); - schemaBuilder.defineObjectClass(connectionSchemaInfo); - - ObjectClassInfo connectionGroupSchemaInfo = GuacamoleConnectionGroupHandler.createSchema(); - schemaBuilder.defineObjectClass(connectionGroupSchemaInfo); - - schemaBuilder.defineOperationOption(OperationOptionInfoBuilder.buildAttributesToGet(), SearchOp.class); - schemaBuilder.defineOperationOption(OperationOptionInfoBuilder.buildReturnDefaultAttributes(), SearchOp.class); - - userSchemaMap = new HashMap<>(); - userSchemaInfo.getAttributeInfo().stream() - .forEach(a -> userSchemaMap.put(a.getName(), a)); - userSchemaMap.put(Uid.NAME, AttributeInfoBuilder.define("username").build()); - userSchemaMap = Collections.unmodifiableMap(userSchemaMap); - - return schemaBuilder.build(); + List guacamoleSchema = this.client.schema(); + cachedSchema = new GuacamoleSchema(configuration, client, guacamoleSchema); + return cachedSchema.schema; } catch (RuntimeException e) { throw processRuntimeException(e); } } - private Map getUserSchemaMap() { + private GuacamoleSchema geSchema() { // Load schema map if it's not loaded yet - if (userSchemaMap == null) { + if (cachedSchema == null) { schema(); } - return userSchemaMap; + return cachedSchema; } protected GuacamoleObjectHandler createGuacamoleObjectHandler(ObjectClass objectClass) { @@ -165,16 +142,16 @@ protected GuacamoleObjectHandler createGuacamoleObjectHandler(ObjectClass object } if (objectClass.equals(USER_OBJECT_CLASS)) { - return new GuacamoleUserHandler(configuration, client, getUserSchemaMap()); + return new GuacamoleUserHandler(configuration, client, geSchema()); } else if (objectClass.equals(USER_GROUP_OBJECT_CLASS)) { - return new GuacamoleUserGroupHandler(configuration, client); + return new GuacamoleUserGroupHandler(configuration, client, geSchema()); } else if (objectClass.equals(CONNECTION_OBJECT_CLASS)) { - return new GuacamoleConnectionHandler(configuration, client); + return new GuacamoleConnectionHandler(configuration, client, geSchema()); } else if (objectClass.equals(CONNECTION_GROUP_OBJECT_CLASS)) { - return new GuacamoleConnectionGroupHandler(configuration, client); + return new GuacamoleConnectionGroupHandler(configuration, client, geSchema()); } else { throw new InvalidAttributeValueException("Unsupported object class " + objectClass); diff --git a/src/main/java/jp/openstandia/connector/guacamole/GuacamoleSchema.java b/src/main/java/jp/openstandia/connector/guacamole/GuacamoleSchema.java index bd636c6..85fa4d9 100644 --- a/src/main/java/jp/openstandia/connector/guacamole/GuacamoleSchema.java +++ b/src/main/java/jp/openstandia/connector/guacamole/GuacamoleSchema.java @@ -5,6 +5,7 @@ import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -23,13 +24,14 @@ public class GuacamoleSchema { public final Map connectionSchema; public final Map connectionGroupSchema; - public GuacamoleSchema(GuacamoleConfiguration configuration, GuacamoleClient client) { + public GuacamoleSchema(GuacamoleConfiguration configuration, GuacamoleClient client, + List guacamoleSchema) { this.configuration = configuration; this.client = client; SchemaBuilder schemaBuilder = new SchemaBuilder(GuacamoleConnector.class); - ObjectClassInfo userSchemaInfo = GuacamoleUserHandler.createSchema(); + ObjectClassInfo userSchemaInfo = GuacamoleUserHandler.createSchema(guacamoleSchema); schemaBuilder.defineObjectClass(userSchemaInfo); ObjectClassInfo groupSchemaInfo = GuacamoleUserGroupHandler.createSchema(); @@ -44,7 +46,7 @@ public GuacamoleSchema(GuacamoleConfiguration configuration, GuacamoleClient cli schemaBuilder.defineOperationOption(OperationOptionInfoBuilder.buildAttributesToGet(), SearchOp.class); schemaBuilder.defineOperationOption(OperationOptionInfoBuilder.buildReturnDefaultAttributes(), SearchOp.class); - schema = schemaBuilder.build(); + this.schema = schemaBuilder.build(); Map userSchemaMap = new HashMap<>(); for (AttributeInfo info : userSchemaInfo.getAttributeInfo()) { diff --git a/src/main/java/jp/openstandia/connector/guacamole/GuacamoleUserGroupHandler.java b/src/main/java/jp/openstandia/connector/guacamole/GuacamoleUserGroupHandler.java index 3a1b75b..cacbe24 100644 --- a/src/main/java/jp/openstandia/connector/guacamole/GuacamoleUserGroupHandler.java +++ b/src/main/java/jp/openstandia/connector/guacamole/GuacamoleUserGroupHandler.java @@ -60,10 +60,10 @@ public class GuacamoleUserGroupHandler implements GuacamoleObjectHandler { private final GuacamoleSchema schema; private final GuacamoleAssociationHandler associationHandler; - public GuacamoleUserGroupHandler(GuacamoleConfiguration configuration, GuacamoleClient client) { + public GuacamoleUserGroupHandler(GuacamoleConfiguration configuration, GuacamoleClient client, GuacamoleSchema schema) { this.configuration = configuration; this.client = client; - this.schema = new GuacamoleSchema(configuration, client); + this.schema = schema; this.associationHandler = new GuacamoleAssociationHandler(configuration, client); } diff --git a/src/main/java/jp/openstandia/connector/guacamole/GuacamoleUserHandler.java b/src/main/java/jp/openstandia/connector/guacamole/GuacamoleUserHandler.java index 076c29d..6818761 100644 --- a/src/main/java/jp/openstandia/connector/guacamole/GuacamoleUserHandler.java +++ b/src/main/java/jp/openstandia/connector/guacamole/GuacamoleUserHandler.java @@ -16,11 +16,15 @@ package jp.openstandia.connector.guacamole; import org.identityconnectors.common.logging.Log; +import org.identityconnectors.common.security.GuardedString; import org.identityconnectors.framework.common.exceptions.UnknownUidException; import org.identityconnectors.framework.common.objects.*; import java.time.ZonedDateTime; -import java.util.*; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -38,16 +42,7 @@ public class GuacamoleUserHandler implements GuacamoleObjectHandler { static final String ATTR_USERNAME = "username"; // Attributes - static final String ATTR_EMAIL = "guac-email-address"; - static final String ATTR_ORGANIZATIONAL_ROLE = "guac-organizational-role"; - static final String ATTR_ORGANIZATION = "guac-organization"; - static final String ATTR_FULL_NAME = "guac-full-name"; - static final String ATTR_PASSWORD_EXPIRED = "expired"; - static final String ATTR_TIMEZONE = "timezone"; - static final String ATTR_ACCESS_WINDOW_START = "access-window-start"; - static final String ATTR_ACCESS_WINDOW_END = "access-window-end"; - static final String ATTR_VALID_UNTIL = "valid-until"; - static final String ATTR_VALID_FROM = "valid-from"; + // Don't define here since guacamole provides user's schema endpoint // Permissions private static final String ATTR_USER_PERMISSIONS = "userPermissions"; @@ -67,14 +62,14 @@ public class GuacamoleUserHandler implements GuacamoleObjectHandler { private final GuacamoleSchema schema; public GuacamoleUserHandler(GuacamoleConfiguration configuration, GuacamoleClient client, - Map schema) { + GuacamoleSchema schema) { this.configuration = configuration; this.client = client; - this.schema = new GuacamoleSchema(configuration, client); + this.schema = schema; this.associationHandler = new GuacamoleAssociationHandler(configuration, client); } - public static ObjectClassInfo createSchema() { + public static ObjectClassInfo createSchema(List schema) { ObjectClassInfoBuilder builder = new ObjectClassInfoBuilder(); builder.setType(USER_OBJECT_CLASS.getObjectClassValue()); @@ -102,86 +97,63 @@ public static ObjectClassInfo createSchema() { builder.addAttributeInfo(OperationalAttributeInfos.PASSWORD); // Other attributes - builder.addAttributeInfo( - AttributeInfoBuilder.define(ATTR_FULL_NAME) - .setRequired(false) // Must be optional - .setCreateable(true) - .setUpdateable(true) - .build() - ); - builder.addAttributeInfo( - AttributeInfoBuilder.define(ATTR_EMAIL) - .setRequired(false) // Must be optional - .setCreateable(true) - .setUpdateable(true) - .build() - ); - builder.addAttributeInfo( - AttributeInfoBuilder.define(ATTR_ORGANIZATION) - .setRequired(false) // Must be optional - .setCreateable(true) - .setUpdateable(true) - .build() - ); - builder.addAttributeInfo( - AttributeInfoBuilder.define(ATTR_ORGANIZATIONAL_ROLE) - .setRequired(false) // Must be optional - .setCreateable(true) - .setUpdateable(true) - .build() - ); - builder.addAttributeInfo( - AttributeInfoBuilder.define(ATTR_PASSWORD_EXPIRED) - .setRequired(false) // Must be optional - .setCreateable(true) - .setUpdateable(true) - .setType(Boolean.class) - .build() - ); - builder.addAttributeInfo( - AttributeInfoBuilder.define(ATTR_TIMEZONE) - .setRequired(false) // Must be optional - .setCreateable(true) - .setUpdateable(true) - .build() - ); - builder.addAttributeInfo( - AttributeInfoBuilder.define(ATTR_ACCESS_WINDOW_START) - .setRequired(false) // Must be optional - .setCreateable(true) - .setUpdateable(true) - .build() - ); - builder.addAttributeInfo( - AttributeInfoBuilder.define(ATTR_ACCESS_WINDOW_END) - .setRequired(false) // Must be optional - .setCreateable(true) - .setUpdateable(true) - .build() - ); -// builder.addAttributeInfo( -// AttributeInfoBuilder.define(ATTR_DISABLED) -// .setRequired(false) // Must be optional -// .setCreateable(true) -// .setUpdateable(true) -// .build() -// ); - builder.addAttributeInfo( - AttributeInfoBuilder.define(ATTR_VALID_UNTIL) - .setRequired(false) // Must be optional - .setCreateable(true) - .setUpdateable(true) - .setType(ZonedDateTime.class) - .build() - ); - builder.addAttributeInfo( - AttributeInfoBuilder.define(ATTR_VALID_FROM) - .setRequired(false) // Must be optional - .setCreateable(true) - .setUpdateable(true) - .setType(ZonedDateTime.class) - .build() - ); + // Generate from fetched guacamole schema + schema.forEach(s -> { + s.fields.stream() + .filter(f -> !(s.name.equals("restrictions") && f.name.equals(ATTR_DISABLED))) + .forEach(f -> { + switch (f.type) { + case "NUMERIC": + builder.addAttributeInfo( + AttributeInfoBuilder.define(f.name) + .setRequired(false) // Must be optional + .setCreateable(true) + .setUpdateable(true) + .setType(Integer.class) // Guacamole NumericFiled is defined with Integer + .build()); + break; + case "BOOLEAN": + builder.addAttributeInfo( + AttributeInfoBuilder.define(f.name) + .setRequired(false) // Must be optional + .setCreateable(true) + .setUpdateable(true) + .setType(Boolean.class) + .build()); + break; + case "DATE": + builder.addAttributeInfo( + AttributeInfoBuilder.define(f.name) + .setRequired(false) // Must be optional + .setCreateable(true) + .setUpdateable(true) + .setType(ZonedDateTime.class) + .build()); + break; + case "PASSWORD": + builder.addAttributeInfo( + AttributeInfoBuilder.define(f.name) + .setRequired(false) // Must be optional + .setCreateable(true) + .setUpdateable(true) + .setType(GuardedString.class) + .build()); + break; + case "TEXT": + case "EMAIL": + case "TIMEZONE": + case "TIME": + default: // Define Other types as string + builder.addAttributeInfo( + AttributeInfoBuilder.define(f.name) + .setRequired(false) // Must be optional + .setCreateable(true) + .setUpdateable(true) + .build()); + break; + } + }); + }); // Permissions builder.addAttributeInfo( diff --git a/src/main/java/jp/openstandia/connector/guacamole/GuacamoleUtils.java b/src/main/java/jp/openstandia/connector/guacamole/GuacamoleUtils.java index 66b1f8b..e61a542 100644 --- a/src/main/java/jp/openstandia/connector/guacamole/GuacamoleUtils.java +++ b/src/main/java/jp/openstandia/connector/guacamole/GuacamoleUtils.java @@ -15,6 +15,7 @@ */ package jp.openstandia.connector.guacamole; +import org.identityconnectors.common.security.GuardedString; import org.identityconnectors.framework.common.exceptions.InvalidAttributeValueException; import org.identityconnectors.framework.common.objects.*; @@ -26,6 +27,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; /** @@ -65,6 +67,9 @@ public static Attribute toConnectorAttribute(AttributeInfo attributeInfo, Guacam if (attributeInfo.getType() == Boolean.class) { return AttributeBuilder.build(a.name, Boolean.parseBoolean(a.value)); } + if (attributeInfo.getType() == GuardedString.class) { + return AttributeBuilder.build(a.name, new GuardedString(a.value.toCharArray())); + } // String return AttributeBuilder.build(a.name, a.value); @@ -91,23 +96,43 @@ private static String toGuacamoleValue(Map schema, Attrib throw new InvalidAttributeValueException("Invalid attribute. name: " + delta.getName()); } + String rtn = null; + if (attributeInfo.getType() == Integer.class) { - return AttributeDeltaUtil.getAsStringValue(delta); - } - if (attributeInfo.getType() == ZonedDateTime.class) { + rtn = AttributeDeltaUtil.getAsStringValue(delta); + + } else if (attributeInfo.getType() == ZonedDateTime.class) { // The format must be YYYY-MM-DD in guacamole ZonedDateTime date = (ZonedDateTime) AttributeDeltaUtil.getSingleValue(delta); - return date.format(DateTimeFormatter.ISO_LOCAL_DATE); - } - if (attributeInfo.getType() == Boolean.class) { + rtn = date.format(DateTimeFormatter.ISO_LOCAL_DATE); + + } else if (attributeInfo.getType() == Boolean.class) { // Use "" for false value in Guacamole API if (Boolean.FALSE.equals(AttributeDeltaUtil.getBooleanValue(delta))) { return ""; } - return AttributeDeltaUtil.getAsStringValue(delta); + rtn = AttributeDeltaUtil.getAsStringValue(delta); + + } else if (attributeInfo.getType() == GuardedString.class) { + GuardedString gs = AttributeDeltaUtil.getGuardedStringValue(delta); + if (gs == null) { + return ""; + } + AtomicReference value = new AtomicReference<>(); + gs.access(v -> { + value.set(String.valueOf(v)); + }); + rtn = value.get(); + + } else { + rtn = AttributeDeltaUtil.getAsStringValue(delta); } - return AttributeDeltaUtil.getAsStringValue(delta); + if (rtn == null) { + // To remove, return empty string + return ""; + } + return rtn; } private static String toGuacamoleValue(Map schema, Attribute attr) { @@ -116,23 +141,43 @@ private static String toGuacamoleValue(Map schema, Attrib throw new InvalidAttributeValueException("Invalid attribute. name: " + attr.getName()); } + String rtn = null; + if (attributeInfo.getType() == Integer.class) { - return AttributeUtil.getAsStringValue(attr); - } - if (attributeInfo.getType() == ZonedDateTime.class) { + rtn = AttributeUtil.getAsStringValue(attr); + + } else if (attributeInfo.getType() == ZonedDateTime.class) { // The format must be YYYY-MM-DD in guacamole ZonedDateTime date = (ZonedDateTime) AttributeUtil.getSingleValue(attr); - return date.format(DateTimeFormatter.ISO_LOCAL_DATE); - } - if (attributeInfo.getType() == Boolean.class) { + rtn = date.format(DateTimeFormatter.ISO_LOCAL_DATE); + + } else if (attributeInfo.getType() == Boolean.class) { // Use "" for false value in Guacamole API if (Boolean.FALSE.equals(AttributeUtil.getBooleanValue(attr))) { return ""; } - return AttributeUtil.getAsStringValue(attr); + rtn = AttributeUtil.getAsStringValue(attr); + + } else if (attributeInfo.getType() == GuardedString.class) { + GuardedString gs = AttributeUtil.getGuardedStringValue(attr); + if (gs == null) { + return ""; + } + AtomicReference value = new AtomicReference<>(); + gs.access(v -> { + value.set(String.valueOf(v)); + }); + rtn = value.get(); + + } else { + rtn = AttributeUtil.getAsStringValue(attr); } - return AttributeUtil.getAsStringValue(attr); + if (rtn == null) { + // To remove, return empty string + return ""; + } + return rtn; } /** diff --git a/src/main/java/jp/openstandia/connector/guacamole/rest/GuacamoleRESTClient.java b/src/main/java/jp/openstandia/connector/guacamole/rest/GuacamoleRESTClient.java index a1484c5..b584c3a 100644 --- a/src/main/java/jp/openstandia/connector/guacamole/rest/GuacamoleRESTClient.java +++ b/src/main/java/jp/openstandia/connector/guacamole/rest/GuacamoleRESTClient.java @@ -98,6 +98,29 @@ public String getAuthToken() { return authToken; } + @Override + public List schema() { + try (Response response = get(getSchemaEndpointURL(configuration))) { + if (response.code() == 404) { + // Don't throw + return null; + } + + if (response.code() != 200) { + throw new ConnectorIOException(String.format("Failed to get guacamole schema. statusCode: %d", response.code())); + } + + // Success + List schema = MAPPER.readValue(response.body().byteStream(), + new TypeReference>() { + }); + return schema; + + } catch (IOException e) { + throw new ConnectorIOException("Failed to call guacamole get schema API", e); + } + } + @Override public void close() { } diff --git a/src/test/java/jp/openstandia/connector/guacamole/testutil/MockClient.java b/src/test/java/jp/openstandia/connector/guacamole/testutil/MockClient.java index 44df820..ab4ba98 100644 --- a/src/test/java/jp/openstandia/connector/guacamole/testutil/MockClient.java +++ b/src/test/java/jp/openstandia/connector/guacamole/testutil/MockClient.java @@ -22,6 +22,7 @@ import org.identityconnectors.framework.common.exceptions.UnknownUidException; import org.identityconnectors.framework.common.objects.*; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; @@ -58,6 +59,11 @@ public String getAuthToken() { return null; } + @Override + public List schema() { + return new ArrayList<>(); + } + @Override public void close() {