blob: 9c3c807f40889cef456a3c8210d8038f1719291c [file] [log] [blame]
/*
* 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.hugegraph.auth;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.apache.hugegraph.type.Nameable;
import org.apache.hugegraph.type.Typeable;
import org.apache.hugegraph.util.JsonUtil;
import org.apache.tinkerpop.gremlin.structure.Graph.Hidden;
import org.apache.tinkerpop.gremlin.structure.Property;
import org.apache.tinkerpop.shaded.jackson.annotation.JsonProperty;
import org.apache.tinkerpop.shaded.jackson.core.JsonGenerator;
import org.apache.tinkerpop.shaded.jackson.core.JsonParser;
import org.apache.tinkerpop.shaded.jackson.core.JsonToken;
import org.apache.tinkerpop.shaded.jackson.core.type.TypeReference;
import org.apache.tinkerpop.shaded.jackson.databind.DeserializationContext;
import org.apache.tinkerpop.shaded.jackson.databind.SerializerProvider;
import org.apache.tinkerpop.shaded.jackson.databind.deser.std.StdDeserializer;
import org.apache.tinkerpop.shaded.jackson.databind.module.SimpleModule;
import org.apache.tinkerpop.shaded.jackson.databind.ser.std.StdSerializer;
import org.apache.hugegraph.HugeException;
import org.apache.hugegraph.auth.SchemaDefine.AuthElement;
import org.apache.hugegraph.structure.HugeElement;
import org.apache.hugegraph.traversal.optimize.TraversalUtil;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
public class HugeResource {
public static final String ANY = "*";
public static final HugeResource ALL = new HugeResource(ResourceType.ALL,
ANY, null);
public static final List<HugeResource> ALL_RES = ImmutableList.of(ALL);
private static final Set<ResourceType> CHECK_NAME_RESS = ImmutableSet.of(
ResourceType.META);
static {
SimpleModule module = new SimpleModule();
module.addSerializer(HugeResource.class, new HugeResourceSer());
module.addDeserializer(HugeResource.class, new HugeResourceDeser());
JsonUtil.registerModule(module);
}
@JsonProperty("type")
private ResourceType type = ResourceType.NONE;
@JsonProperty("label")
private String label = ANY;
@JsonProperty("properties")
private Map<String, Object> properties; // value can be predicate
public HugeResource() {
// pass
}
public HugeResource(ResourceType type, String label,
Map<String, Object> properties) {
this.type = type;
this.label = label;
this.properties = properties;
this.checkFormat();
}
public void checkFormat() {
if (this.properties == null) {
return;
}
for (Map.Entry<String, Object> entry : this.properties.entrySet()) {
String propName = entry.getKey();
Object propValue = entry.getValue();
if (propName.equals(ANY) && propValue.equals(ANY)) {
continue;
}
if (propValue instanceof String &&
((String) propValue).startsWith(TraversalUtil.P_CALL)) {
TraversalUtil.parsePredicate((String) propValue);
}
}
}
public boolean filter(ResourceObject<?> resourceObject) {
if (this.type == null) {
return false;
}
if (!this.type.match(resourceObject.type())) {
return false;
}
if (resourceObject.operated() != NameObject.ANY) {
ResourceType resType = resourceObject.type();
if (resType.isGraph()) {
return this.filter((HugeElement) resourceObject.operated());
}
if (resType.isAuth()) {
return this.filter((AuthElement) resourceObject.operated());
}
if (resType.isSchema() || CHECK_NAME_RESS.contains(resType)) {
return this.filter((Nameable) resourceObject.operated());
}
}
/*
* Allow any others resource if the type is matched:
* VAR, GREMLIN, GREMLIN_JOB, TASK
*/
return true;
}
private boolean filter(AuthElement element) {
assert this.type.match(element.type());
if (element instanceof Nameable) {
return this.filter((Nameable) element);
}
return true;
}
private boolean filter(Nameable element) {
assert !(element instanceof Typeable) || this.type.match(
ResourceType.from(((Typeable) element).type()));
return this.matchLabel(element.name());
}
private boolean filter(HugeElement element) {
assert this.type.match(ResourceType.from(element.type()));
if (!this.matchLabel(element.label())) {
return false;
}
if (this.properties == null) {
return true;
}
for (Map.Entry<String, Object> entry : this.properties.entrySet()) {
String propName = entry.getKey();
Object expected = entry.getValue();
if (propName.equals(ANY) && expected.equals(ANY)) {
return true;
}
Property<Object> prop = element.property(propName);
if (!prop.isPresent()) {
return false;
}
try {
if (!TraversalUtil.testProperty(prop, expected)) {
return false;
}
} catch (IllegalArgumentException e) {
throw new HugeException("Invalid resource '%s' for '%s': %s",
expected, propName, e.getMessage());
}
}
return true;
}
private boolean matchLabel(String other) {
// Label value may be vertex/edge label or schema name
if (this.label == null || other == null) {
return false;
}
// It's ok if wildcard match or regular match
return this.label.equals(ANY) || other.matches(this.label);
}
private boolean matchProperties(Map<String, Object> other) {
if (this.properties == null) {
// Any property is OK
return true;
}
if (other == null) {
return false;
}
for (Map.Entry<String, Object> p : other.entrySet()) {
Object value = this.properties.get(p.getKey());
if (!Objects.equals(value, p.getValue())) {
return false;
}
}
return true;
}
protected boolean contains(HugeResource other) {
if (this.equals(other)) {
return true;
}
if (this.type == null) {
return false;
}
if (!this.type.match(other.type)) {
return false;
}
if (!this.matchLabel(other.label)) {
return false;
}
return this.matchProperties(other.properties);
}
@Override
public boolean equals(Object object) {
if (!(object instanceof HugeResource)) {
return false;
}
HugeResource other = (HugeResource) object;
return this.type == other.type &&
Objects.equals(this.label, other.label) &&
Objects.equals(this.properties, other.properties);
}
@Override
public int hashCode() {
return Objects.hash(this.type, this.label, this.properties);
}
@Override
public String toString() {
return JsonUtil.toJson(this);
}
public static boolean allowed(ResourceObject<?> resourceObject) {
// Allowed to access system(hidden) schema by anyone
if (resourceObject.type().isSchema()) {
Nameable schema = (Nameable) resourceObject.operated();
return Hidden.isHidden(schema.name());
}
return false;
}
public static HugeResource parseResource(String resource) {
return JsonUtil.fromJson(resource, HugeResource.class);
}
public static List<HugeResource> parseResources(String resources) {
TypeReference<?> type = new TypeReference<List<HugeResource>>() {};
return JsonUtil.fromJson(resources, type);
}
public static class NameObject implements Nameable {
public static final NameObject ANY = new NameObject("*");
private final String name;
public static NameObject of(String name) {
return new NameObject(name);
}
private NameObject(String name) {
this.name = name;
}
@Override
public String name() {
return this.name;
}
@Override
public String toString() {
return this.name;
}
}
private static class HugeResourceSer extends StdSerializer<HugeResource> {
private static final long serialVersionUID = -138482122210181714L;
public HugeResourceSer() {
super(HugeResource.class);
}
@Override
public void serialize(HugeResource res, JsonGenerator generator,
SerializerProvider provider)
throws IOException {
generator.writeStartObject();
generator.writeObjectField("type", res.type);
generator.writeObjectField("label", res.label);
generator.writeObjectField("properties", res.properties);
generator.writeEndObject();
}
}
private static class HugeResourceDeser extends StdDeserializer<HugeResource> {
private static final long serialVersionUID = -2499038590503066483L;
public HugeResourceDeser() {
super(HugeResource.class);
}
@Override
public HugeResource deserialize(JsonParser parser,
DeserializationContext ctxt)
throws IOException {
HugeResource res = new HugeResource();
while (parser.nextToken() != JsonToken.END_OBJECT) {
String key = parser.getCurrentName();
if ("type".equals(key)) {
if (parser.nextToken() != JsonToken.VALUE_NULL) {
res.type = ctxt.readValue(parser, ResourceType.class);
} else {
res.type = null;
}
} else if ("label".equals(key)) {
if (parser.nextToken() != JsonToken.VALUE_NULL) {
res.label = parser.getValueAsString();
} else {
res.label = null;
}
} else if ("properties".equals(key)) {
if (parser.nextToken() != JsonToken.VALUE_NULL) {
@SuppressWarnings("unchecked")
Map<String, Object> prop = ctxt.readValue(parser,
Map.class);
res.properties = prop;
} else {
res.properties = null;
}
}
}
res.checkFormat();
return res;
}
}
}