Skip to content

Commit

Permalink
Allow to use the variables '@request' and '@mongoPermissions' in read…
Browse files Browse the repository at this point in the history
…Filter, writeFilter and mongo.overriddenProps ACL permissions
  • Loading branch information
ujibang committed Jan 29, 2021
1 parent 25391b4 commit 21e4977
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 93 deletions.
Expand Up @@ -23,24 +23,19 @@
import static org.restheart.plugins.ConfigurablePlugin.argValue;
import static org.restheart.security.authorizers.MongoAclAuthorizer.MATCHING_ACL_PERMISSION;

import java.time.Instant;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import org.bson.BsonDocument;
import org.bson.BsonValue;
import org.restheart.ConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.undertow.attribute.ExchangeAttributes;
import io.undertow.predicate.Predicate;
import io.undertow.predicate.PredicateParser;
import io.undertow.server.HttpServerExchange;
Expand All @@ -50,7 +45,7 @@
* the request
*
* The request is authorized if AclPermission.resolve() to true
*
*
* @author Andrea Di Cesare {@literal <andrea@softinstigate.com>}
*/
public class AclPermission {
Expand Down Expand Up @@ -241,42 +236,4 @@ public boolean resolve(final HttpServerExchange exchange) {
return this.predicate.resolve(exchange);
}
}

/**
* resolves the a filter variables such as %USER, %ROLES, and %NOW
*
* @param exchange
* @param filter
* @return the filter with interpolated variables
*/
public static JsonObject interpolateFilterVars(final HttpServerExchange exchange, final BsonDocument filter) {
if (Objects.isNull(filter) || filter.isNull()) {
return null;
}

String ret = filter.toString();

String username = ExchangeAttributes.remoteUser().readAttribute(exchange);

if (username != null) {
ret = ret.replace("%USER", username);
}

// user roles
if (Objects.nonNull(exchange.getSecurityContext())
&& Objects.nonNull(exchange.getSecurityContext().getAuthenticatedAccount())
&& Objects.nonNull(exchange.getSecurityContext().getAuthenticatedAccount().getRoles())) {
String roles = exchange.getSecurityContext().getAuthenticatedAccount().getRoles().toString();

ret = ret.replace("%ROLES", roles);
} else {
ret = ret.replace("%ROLES", "[]");
}

// now
long now = Instant.now().getEpochSecond() * 1000;
ret = ret.replace("%NOW", "{'$date':" + now + "}");

return JsonParser.parseString(ret).getAsJsonObject();
}
}
Expand Up @@ -124,6 +124,19 @@ public static MongoPermissions from(BsonDocument args) throws ConfigurationExcep
}
}

public BsonDocument asBson() {
var map = new HashMap<String, Object>();

map.put("whitelistManagementRequests", this.whitelistManagementRequests);
map.put("whitelistBulkPatch", this.whitelistBulkPatch);
map.put("whitelistBulkDelete", this.whitelistBulkDelete);
map.put("allowAllWriteModes", this.allowAllWriteModes);
map.put("readFilter", this.readFilter);
map.put("writeFilter", this.writeFilter);

return JsonUtils.toBsonDocument(map);
}

public static MongoPermissions from(Map<String, Object> args) throws ConfigurationException {
if (args == null || args.isEmpty()) {
// return default values
Expand Down
Expand Up @@ -40,6 +40,7 @@
import org.restheart.exchange.MongoRequest;
import org.restheart.idm.FileRealmAccount;
import org.restheart.idm.MongoRealmAccount;
import org.restheart.security.authorizers.AclPermission;
import org.restheart.utils.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -52,12 +53,13 @@ public class MongoPermissionsUtils {
/**
* Interpolate values in doc like '@user', '@user.property', @now
*
* Supports accounts handled by MongoRealAuthenticator and FileRealmAuthenticator
* Supports accounts handled by MongoRealAuthenticator and
* FileRealmAuthenticator
*
* Legacy variable names %USER, %ROLES and %NOW are supported as well
*
* @param request
* @param doc
* @param bson
* @return bson with interpolated variables
*/
public static BsonValue interpolateBson(MongoRequest request, BsonValue bson) {
Expand All @@ -82,20 +84,23 @@ public static BsonValue interpolateBson(MongoRequest request, BsonValue bson) {
var ret = new BsonArray();
bson.asArray().stream().forEachOrdered(ae -> ret.add(interpolateBson(request, ae)));
return ret;
} else if (bson.isString()){
} else if (bson.isString()) {
return interpolatePropValue(request, null, bson.asString().getValue());
} else {
return bson;
}
}

/**
* If value is a '@user', '@user.property', @now, returns the interpolated value from
* authenticated account.
* If value is a '@user', '@user.property', '@request',
* '@request.remoteIp', '@mongoPermissions', '@mongoPermissions.readFilter',
* '@now', returns the interpolated value.
*
* Supports accounts handled by MongoRealAuthenticator and FileRealmAuthenticator
* For '@user' and '@mongoPermissions' supports accounts handled by
* MongoRealAuthenticator and FileRealmAuthenticator
*
* Legacy variable names %USER, %ROLES and %NOW are supported as well
*
* @param request
* @param key
* @param value
Expand Down Expand Up @@ -128,7 +133,64 @@ public static BsonValue interpolatePropValue(MongoRequest request, String key, S
} else {
return BsonNull.VALUE;
}
} else if (value.startsWith("@user.")) {
} else if (value.equals("@request")) {
return getRequestObject(request);
} else if (value.startsWith("@request.") && value.length() > 8) {
var requestObject = getRequestObject(request);
var prop = value.substring(9);

LOGGER.debug("request doc: {}", requestObject.toJson());

if (prop.contains(".")) {
try {
JsonElement v = JsonPath.read(requestObject.toJson(), "$.".concat(prop));

return JsonUtils.parse(v.toString());
} catch (Throwable pnfe) {
return BsonNull.VALUE;
}
} else {
if (requestObject.containsKey(prop)) {
return requestObject.get(prop);
} else {
return BsonNull.VALUE;
}
}
} else if (value.equals("@mongoPermissions")) {
var permission = AclPermission.from(request.getExchange());
if (permission != null && permission.getMongoPermissions() != null) {
return permission.getMongoPermissions().asBson();
} else {
return BsonNull.VALUE;
}
} else if (value.startsWith("@mongoPermissions.") && value.length() > 17) {
var permission = AclPermission.from(request.getExchange());
if (permission != null && permission.getMongoPermissions() != null) {

var doc = permission.getMongoPermissions().asBson();
var prop = value.substring(18);

LOGGER.debug("permission doc: {}", permission);

if (prop.contains(".")) {
try {
JsonElement v = JsonPath.read(doc.toJson(), "$.".concat(prop));

return JsonUtils.parse(v.toString());
} catch (Throwable pnfe) {
return BsonNull.VALUE;
}
} else {
if (doc.containsKey(prop)) {
return doc.get(prop);
} else {
return BsonNull.VALUE;
}
}
} else {
return BsonNull.VALUE;
}
} else if (value.startsWith("@user.") && value.length() > 5) {
if (request.getAuthenticatedAccount() instanceof MongoRealmAccount) {
var maccount = (MongoRealmAccount) request.getAuthenticatedAccount();
var accountDoc = maccount.getAccountDocument();
Expand Down Expand Up @@ -167,11 +229,11 @@ public static BsonValue interpolatePropValue(MongoRequest request, String key, S
private static BsonValue fromProperties(Map<String, Object> properties, String key) {
if (key.contains(".")) {
var first = key.substring(0, key.indexOf("."));
var last = key.substring(key.indexOf(".")+1);
var last = key.substring(key.indexOf(".") + 1);
var subProperties = properties.get(first);

if (subProperties != null && subProperties instanceof Map<?,?>) {
return fromProperties((Map<String,Object>)subProperties, last);
if (subProperties != null && subProperties instanceof Map<?, ?>) {
return fromProperties((Map<String, Object>) subProperties, last);
} else if (subProperties != null && subProperties instanceof List<?>) {
List<Object> list = (List<Object>) subProperties;

Expand All @@ -182,13 +244,14 @@ private static BsonValue fromProperties(Map<String, Object> properties, String k
try {
var idx = Integer.parseInt(next);
var elementAtIdx = list.get(idx);
var afterNext = last.substring(last.indexOf(".")+1);
var afterNext = last.substring(last.indexOf(".") + 1);

if (elementAtIdx instanceof Map<?,?>) {
return fromProperties((Map<String,Object>)elementAtIdx, afterNext);
if (elementAtIdx instanceof Map<?, ?>) {
return fromProperties((Map<String, Object>) elementAtIdx, afterNext);
} else {
LOGGER.warn("Key {} at {} matches an array but selected element is not an object", key, first);
return BsonNull.VALUE;
LOGGER.warn("Key {} at {} matches an array but selected element is not an object", key,
first);
return BsonNull.VALUE;
}
} catch (Throwable t) {
LOGGER.warn("Key {} at {} matches an array but following part is not a number", key, first);
Expand Down Expand Up @@ -219,8 +282,8 @@ private static BsonValue fromProperties(Map<String, Object> properties, String k
private static BsonValue toBson(Object obj) {
if (obj instanceof String) {
return new BsonString((String) obj);
} else if (obj instanceof Map<?,?>) {
var map = (Map<String,Object>) obj;
} else if (obj instanceof Map<?, ?>) {
var map = (Map<String, Object>) obj;
var ret = new BsonDocument();
map.entrySet().stream().forEachOrdered(e -> ret.put(e.getKey(), toBson(e.getValue())));
return ret;
Expand All @@ -240,4 +303,49 @@ private static BsonValue toBson(Object obj) {
return BsonNull.VALUE;
}
}

private static BsonDocument getRequestObject(final MongoRequest request) {
var exchange = request.getExchange();

var properties = new BsonDocument();

var _userName = ExchangeAttributes.remoteUser().readAttribute(exchange);

var userName = _userName != null ? new BsonString(_userName) : BsonNull.VALUE;

// remote user
properties.put("userName", userName);

// dateTime
properties.put("epochTimeStamp", new BsonDateTime(Instant.now().getEpochSecond() * 1000));

// dateTime
properties.put("dateTime", new BsonString(ExchangeAttributes.dateTime().readAttribute(exchange)));

// local ip
properties.put("localIp", new BsonString(ExchangeAttributes.localIp().readAttribute(exchange)));

// local port
properties.put("localPort", new BsonString(ExchangeAttributes.localPort().readAttribute(exchange)));

// local server name
properties.put("localServerName", new BsonString(ExchangeAttributes.localServerName().readAttribute(exchange)));

// request query string
properties.put("queryString", new BsonString(ExchangeAttributes.queryString().readAttribute(exchange)));

// request relative path
properties.put("relativePath", new BsonString(ExchangeAttributes.relativePath().readAttribute(exchange)));

// remote ip
properties.put("remoteIp", new BsonString(ExchangeAttributes.remoteIp().readAttribute(exchange)));

// request method
properties.put("requestMethod", new BsonString(ExchangeAttributes.requestMethod().readAttribute(exchange)));

// request protocol
properties.put("requestProtocol", new BsonString(ExchangeAttributes.requestProtocol().readAttribute(exchange)));

return properties;
}
}
Expand Up @@ -73,7 +73,7 @@ public boolean resolve(MongoRequest request, MongoResponse response) {
var permission = AclPermission.from(request.getExchange());

if (permission != null && permission.getMongoPermissions() != null) {
return !permission.getMongoPermissions().getProtectedProps().isEmpty();
return !permission.getMongoPermissions().getOverriddenProps().isEmpty();
} else {
return false;
}
Expand Down

0 comments on commit 21e4977

Please sign in to comment.