From b6de402c0c1fbcaa2d6e95ebbc99baef9165bbe9 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 22 Nov 2017 12:29:58 -0800 Subject: [PATCH] GUACAMOLE-96: Add base support within JDBC auth for storage of arbitrary attributes from other extensions. --- .../auth/jdbc/base/ArbitraryAttributeMap.java | 162 ++++++++++++++++++ .../jdbc/base/ArbitraryAttributeModel.java | 104 +++++++++++ .../jdbc/base/ModeledDirectoryObject.java | 33 ++-- .../base/ModeledDirectoryObjectMapper.java | 25 ++- .../base/ModeledDirectoryObjectService.java | 12 ++ .../guacamole/auth/jdbc/base/ObjectModel.java | 49 ++++-- 6 files changed, 354 insertions(+), 31 deletions(-) create mode 100644 extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ArbitraryAttributeMap.java create mode 100644 extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ArbitraryAttributeModel.java diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ArbitraryAttributeMap.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ArbitraryAttributeMap.java new file mode 100644 index 000000000..e2cb43805 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ArbitraryAttributeMap.java @@ -0,0 +1,162 @@ +/* + * 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.guacamole.auth.jdbc.base; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; + +/** + * Map of arbitrary attribute name/value pairs which can alternatively be + * exposed as a collection of model objects. + */ +public class ArbitraryAttributeMap extends HashMap { + + /** + * Creates a new ArbitraryAttributeMap containing the name/value pairs + * within the given collection of model objects. + * + * @param models + * The model objects of all attributes which should be stored in the + * new map as name/value pairs. + * + * @return + * A new ArbitraryAttributeMap containing the name/value pairs within + * the given collection of model objects. + */ + public static ArbitraryAttributeMap fromModelCollection(Collection models) { + + // Add all name/value pairs from the given collection to the map + ArbitraryAttributeMap map = new ArbitraryAttributeMap(); + for (ArbitraryAttributeModel model : models) + map.put(model.getName(), model.getValue()); + + return map; + + } + + /** + * Returns a collection of model objects which mirrors the contents of this + * ArbitraryAttributeMap. Each name/value pair within the map is reflected + * by a corresponding model object within the returned collection. Removing + * a model object from the collection removes the corresponding name/value + * pair from the map. Adding a new model object to the collection adds a + * corresponding name/value pair to the map. Changes to a model object + * within the collection are NOT reflected on the map, however. + * + * @return + * A collection of model objects which mirrors the contents of this + * ArbitraryAttributeMap. + */ + public Collection toModelCollection() { + return new AbstractCollection() { + + @Override + public void clear() { + ArbitraryAttributeMap.this.clear(); + } + + @Override + public boolean remove(Object o) { + + // The Collection view of an ArbitraryAttributeMap can contain + // only ArbitraryAttributeModel objects + if (!(o instanceof ArbitraryAttributeModel)) + return false; + + // The attribute should be removed only if the value matches + ArbitraryAttributeModel model = (ArbitraryAttributeModel) o; + return ArbitraryAttributeMap.this.remove(model.getName(), + model.getValue()); + + } + + @Override + public boolean add(ArbitraryAttributeModel e) { + + String newValue = e.getValue(); + String oldValue = put(e.getName(), newValue); + + // If null value is being added, collection changed only if + // old value was non-null + if (newValue == null) + return oldValue != null; + + // Collection changed if value changed + return !newValue.equals(oldValue); + + } + + @Override + public boolean contains(Object o) { + + // The Collection view of an ArbitraryAttributeMap can contain + // only ArbitraryAttributeModel objects + if (!(o instanceof ArbitraryAttributeModel)) + return false; + + // No need to check the value of the attribute if the attribute + // is not even present + ArbitraryAttributeModel model = (ArbitraryAttributeModel) o; + String value = get(model.getName()); + if (value == null) + return false; + + // The name/value pair is present only if the value matches + return value.equals(model.getValue()); + + } + + @Override + public Iterator iterator() { + + // Get iterator over all string name/value entries + final Iterator> iterator = entrySet().iterator(); + + // Dynamically translate each string name/value entry into a + // corresponding attribute model object as iteration continues + return new Iterator() { + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public ArbitraryAttributeModel next() { + Entry entry = iterator.next(); + return new ArbitraryAttributeModel(entry.getKey(), + entry.getValue()); + } + + }; + + } + + @Override + public int size() { + return ArbitraryAttributeMap.this.size(); + } + + }; + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ArbitraryAttributeModel.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ArbitraryAttributeModel.java new file mode 100644 index 000000000..b064b5414 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ArbitraryAttributeModel.java @@ -0,0 +1,104 @@ +/* + * 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.guacamole.auth.jdbc.base; + +/** + * A single attribute name/value pair belonging to a object which implements + * the Attributes interface, such as a Connection or User. Attributes stored + * as raw name/value pairs are the attributes which are given to the database + * authentication extension for storage by other extensions. Attributes which + * are directly supported by the database authentication extension have defined + * columns and properties with proper types, constraints, etc. + */ +public class ArbitraryAttributeModel { + + /** + * The name of the attribute. + */ + private String name; + + /** + * The value the attribute is set to. + */ + private String value; + + /** + * Creates a new ArbitraryAttributeModel with its name and value both set + * to null. + */ + public ArbitraryAttributeModel() { + } + + /** + * Creates a new ArbitraryAttributeModel with its name and value + * initialized to the given values. + * + * @param name + * The name of the attribute. + * + * @param value + * The value the attribute is set to. + */ + public ArbitraryAttributeModel(String name, String value) { + this.name = name; + this.value = value; + } + + /** + * Returns the name of this attribute. + * + * @return + * The name of this attribute. + */ + public String getName() { + return name; + } + + /** + * Sets the name of this attribute. + * + * @param name + * The name of this attribute. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Returns the value of this attribute. + * + * @return + * The value of this attribute. + */ + public String getValue() { + return value; + } + + /** + * Sets the value of this attribute. + * + * @param value + * The value of this attribute. + */ + public void setValue(String value) { + this.value = value; + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ModeledDirectoryObject.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ModeledDirectoryObject.java index e13445bc7..ddeda92a6 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ModeledDirectoryObject.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ModeledDirectoryObject.java @@ -65,35 +65,32 @@ public abstract class ModeledDirectoryObject @Override public Map getAttributes() { - - // If no arbitrary attributes are defined, just return an empty map - Map arbitraryAttributes = getModel().getArbitraryAttributes(); - if (arbitraryAttributes == null) - return new HashMap(); - - // Otherwise include any defined arbitrary attributes - return new HashMap(arbitraryAttributes); - + return new HashMap(getModel().getArbitraryAttributeMap()); } @Override public void setAttributes(Map attributes) { + ArbitraryAttributeMap arbitraryAttributes = getModel().getArbitraryAttributeMap(); + // Get set of all supported attribute names Set supportedAttributes = getSupportedAttributeNames(); - // Initialize model with empty map if no such map is already present - Map arbitraryAttributes = getModel().getArbitraryAttributes(); - if (arbitraryAttributes == null) { - arbitraryAttributes = new HashMap(); - getModel().setArbitraryAttributes(arbitraryAttributes); - } - // Store remaining attributes only if not directly mapped to model for (Map.Entry attribute : attributes.entrySet()) { + String name = attribute.getKey(); - if (!supportedAttributes.contains(name)) - arbitraryAttributes.put(name, attribute.getValue()); + String value = attribute.getValue(); + + // Handle null attributes as explicit removal of that attribute, + // as the underlying model cannot store null attribute values + if (!supportedAttributes.contains(name)) { + if (value == null) + arbitraryAttributes.remove(name); + else + arbitraryAttributes.put(name, value); + } + } } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ModeledDirectoryObjectMapper.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ModeledDirectoryObjectMapper.java index ebd95d905..4431e8f42 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ModeledDirectoryObjectMapper.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ModeledDirectoryObjectMapper.java @@ -132,5 +132,28 @@ public interface ModeledDirectoryObjectMapper { * The number of rows updated. */ int update(@Param("object") ModelType object); - + + /** + * Deletes any arbitrary attributes currently associated with the given + * object in the database. + * + * @param object + * The object whose arbitrary attributes should be deleted. + * + * @return + * The number of rows deleted. + */ + int deleteAttributes(@Param("object") ModelType object); + + /** + * Inserts all arbitrary attributes associated with the given object. + * + * @param object + * The object whose arbitrary attributes should be inserted. + * + * @return + * The number of rows inserted. + */ + int insertAttributes(@Param("object") ModelType object); + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ModeledDirectoryObjectService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ModeledDirectoryObjectService.java index 2c1402eb6..21508c471 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ModeledDirectoryObjectService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ModeledDirectoryObjectService.java @@ -32,6 +32,7 @@ import org.apache.guacamole.auth.jdbc.user.UserModel; import org.apache.guacamole.net.auth.Identifiable; import org.apache.guacamole.net.auth.permission.ObjectPermission; import org.apache.guacamole.net.auth.permission.ObjectPermissionSet; +import org.mybatis.guice.transactional.Transactional; /** * Service which provides convenience methods for creating, retrieving, and @@ -446,6 +447,7 @@ public abstract class ModeledDirectoryObjectService arbitraryAttributes; + private ArbitraryAttributeMap arbitraryAttributes = + new ArbitraryAttributeMap(); /** * Creates a new, empty object. @@ -102,23 +103,47 @@ public abstract class ObjectModel { * with this model which do not otherwise have explicit mappings to * properties. */ - public Map getArbitraryAttributes() { + public ArbitraryAttributeMap getArbitraryAttributeMap() { return arbitraryAttributes; } /** - * Sets all arbitrary attribute name/value pairs associated with this - * model. The provided map should contain only attributes which are not - * explicitly supported by the model, as any explicitly-supported - * attributes should instead be mapped to corresponding properties. + * Returns whether at least one arbitrary attribute name/value pair has + * been associated with this object. + * + * @return + * true if this object has at least one arbitrary attribute set, false + * otherwise. + */ + public boolean hasArbitraryAttributes() { + return !arbitraryAttributes.isEmpty(); + } + + /** + * Returns a Collection view of the equivalent attribute model objects + * which make up the map of arbitrary attribute name/value pairs returned + * by getArbitraryAttributeMap(). Additions and removals on the returned + * Collection directly affect the attribute map. + * + * @return + * A Collection view of the map returned by + * getArbitraryAttributeMap(). + */ + public Collection getArbitraryAttributes() { + return arbitraryAttributes.toModelCollection(); + } + + /** + * Replaces all arbitrary attributes associated with this object with the + * attribute name/value pairs within the given collection of model objects. * * @param arbitraryAttributes - * A map of attribute name/value pairs for all attributes associated - * with this model which do not otherwise have explicit mappings to - * properties. + * The Collection of model objects containing the attribute name/value + * pairs which should replace all currently-stored arbitrary attributes, + * if any. */ - public void setArbitraryAttributes(Map arbitraryAttributes) { - this.arbitraryAttributes = arbitraryAttributes; + public void setArbitraryAttributes(Collection arbitraryAttributes) { + this.arbitraryAttributes = ArbitraryAttributeMap.fromModelCollection(arbitraryAttributes); } }