GUACAMOLE-96: Add base support within JDBC auth for storage of arbitrary attributes from other extensions.

This commit is contained in:
Michael Jumper
2017-11-22 12:29:58 -08:00
parent a3cee158cb
commit b6de402c0c
6 changed files with 354 additions and 31 deletions

View File

@@ -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<String, String> {
/**
* 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<ArbitraryAttributeModel> 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<ArbitraryAttributeModel> toModelCollection() {
return new AbstractCollection<ArbitraryAttributeModel>() {
@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<ArbitraryAttributeModel> iterator() {
// Get iterator over all string name/value entries
final Iterator<Entry<String, String>> iterator = entrySet().iterator();
// Dynamically translate each string name/value entry into a
// corresponding attribute model object as iteration continues
return new Iterator<ArbitraryAttributeModel>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public ArbitraryAttributeModel next() {
Entry<String, String> entry = iterator.next();
return new ArbitraryAttributeModel(entry.getKey(),
entry.getValue());
}
};
}
@Override
public int size() {
return ArbitraryAttributeMap.this.size();
}
};
}
}

View File

@@ -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;
}
}

View File

@@ -65,35 +65,32 @@ public abstract class ModeledDirectoryObject<ModelType extends ObjectModel>
@Override
public Map<String, String> getAttributes() {
// If no arbitrary attributes are defined, just return an empty map
Map<String, String> arbitraryAttributes = getModel().getArbitraryAttributes();
if (arbitraryAttributes == null)
return new HashMap<String, String>();
// Otherwise include any defined arbitrary attributes
return new HashMap<String, String>(arbitraryAttributes);
return new HashMap<String, String>(getModel().getArbitraryAttributeMap());
}
@Override
public void setAttributes(Map<String, String> attributes) {
ArbitraryAttributeMap arbitraryAttributes = getModel().getArbitraryAttributeMap();
// Get set of all supported attribute names
Set<String> supportedAttributes = getSupportedAttributeNames();
// Initialize model with empty map if no such map is already present
Map<String, String> arbitraryAttributes = getModel().getArbitraryAttributes();
if (arbitraryAttributes == null) {
arbitraryAttributes = new HashMap<String, String>();
getModel().setArbitraryAttributes(arbitraryAttributes);
}
// Store remaining attributes only if not directly mapped to model
for (Map.Entry<String, String> 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);
}
}
}

View File

@@ -132,5 +132,28 @@ public interface ModeledDirectoryObjectMapper<ModelType> {
* 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);
}

View File

@@ -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<InternalType extends Modeled
}
@Override
@Transactional
public InternalType createObject(ModeledAuthenticatedUser user, ExternalType object)
throws GuacamoleException {
@@ -461,6 +463,10 @@ public abstract class ModeledDirectoryObjectService<InternalType extends Modeled
// Add implicit permissions
getPermissionMapper().insert(getImplicitPermissions(user, model));
// Add any arbitrary attributes
if (model.hasArbitraryAttributes())
getObjectMapper().insertAttributes(model);
return getObjectInstance(user, model);
}
@@ -477,6 +483,7 @@ public abstract class ModeledDirectoryObjectService<InternalType extends Modeled
}
@Override
@Transactional
public void updateObject(ModeledAuthenticatedUser user, InternalType object)
throws GuacamoleException {
@@ -486,6 +493,11 @@ public abstract class ModeledDirectoryObjectService<InternalType extends Modeled
// Update object
getObjectMapper().update(model);
// Replace any existing arbitrary attributes
getObjectMapper().deleteAttributes(model);
if (model.hasArbitraryAttributes())
getObjectMapper().insertAttributes(model);
}
@Override

View File

@@ -19,7 +19,7 @@
package org.apache.guacamole.auth.jdbc.base;
import java.util.Map;
import java.util.Collection;
/**
* Object representation of a Guacamole object, such as a user or connection,
@@ -41,7 +41,8 @@ public abstract class ObjectModel {
* Map of all arbitrary attributes associated with this object but not
* directly mapped to a particular column.
*/
private Map<String, String> 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<String, String> 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<ArbitraryAttributeModel> 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<String, String> arbitraryAttributes) {
this.arbitraryAttributes = arbitraryAttributes;
public void setArbitraryAttributes(Collection<ArbitraryAttributeModel> arbitraryAttributes) {
this.arbitraryAttributes = ArbitraryAttributeMap.fromModelCollection(arbitraryAttributes);
}
}