Skip to content

Commit

Permalink
Refactor MongoPermissions
Browse files Browse the repository at this point in the history
  • Loading branch information
ujibang committed Feb 19, 2021
1 parent 9aa67ca commit 23938a2
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 68 deletions.
Expand Up @@ -399,6 +399,23 @@ private static BsonDocument getRequestObject(final MongoRequest request) {

var properties = new BsonDocument();

// the name of the db
properties.put("db", request.getDBName() == null ? BsonNull.VALUE : new BsonString(request.getDBName()));

// the name of the collection
properties.put("collection", request.getDBName() == null ? BsonNull.VALUE : new BsonString(request.getCollectionName()));

// the _id of the document
properties.put("_id", request.getDocumentId() == null ? BsonNull.VALUE : request.getDocumentId());

// the TYPE of the resource:
// - INVALID, ROOT, ROOT_SIZE, DB, DB_SIZE, DB_META, CHANGE_STREAM, COLLECTION,
// - COLLECTION_SIZE, COLLECTION_META, DOCUMENT, COLLECTION_INDEXES, INDEX,
// - FILES_BUCKET, FILES_BUCKET_SIZE, FILES_BUCKET_META, FILE, FILE_BINARY,
// - AGGREGATION, SCHEMA, SCHEMA_STORE, SCHEMA_STORE_SIZE, SCHEMA_STORE_META,
// - BULK_DOCUMENTS, METRICS, SESSION, SESSIONS, TRANSACTIONS, TRANSACTION
properties.put("resourceType", new BsonString(request.getType().name()));

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

var userName = _userName != null ? new BsonString(_userName) : BsonNull.VALUE;
Expand Down
60 changes: 32 additions & 28 deletions commons/src/main/java/org/restheart/security/MongoPermissions.java
Expand Up @@ -50,10 +50,14 @@ public class MongoPermissions {
private final BsonDocument readFilter;
private final BsonDocument writeFilter;

final Set<String> hiddenProps = Sets.newHashSet();
final Set<String> protectedProps = Sets.newHashSet();
final Map<String, BsonValue> overriddenProps = Maps.newHashMap();
final Set<String> forbiddenQueryParams = Sets.newHashSet();
// an hidden property is removed from the response a GET requests
final Set<String> hideProps = Sets.newHashSet();
// a protected property cannot be in the body of a write request, otherwise 403 FORBBIDEN is returned
final Set<String> protectProps = Sets.newHashSet();
// the value of an overridden property is set by the server
final Map<String, BsonValue> overrideProps = Maps.newHashMap();
// a forbidden query parameter cannot be specified in the request URL, otherwise 403 FORBBIDEN is returned
final Set<String> forbidQueryParams = Sets.newHashSet();

public MongoPermissions() {
this.allowManagementRequests = false;
Expand All @@ -66,7 +70,7 @@ public MongoPermissions() {

MongoPermissions(BsonDocument readFilter, BsonDocument writeFilter, boolean allowManagementRequests,
boolean allowBulkPatch, boolean allowBulkDelete, boolean allowWriteMode,
Set<String> hiddenProps, Set<String> protectedProps, Map<String, BsonValue> overriddenProps, Set<String> forbiddenQueryParams) {
Set<String> hideProps, Set<String> protectProps, Map<String, BsonValue> overrideProps, Set<String> forbidQueryParams) {
this.readFilter = readFilter == null ? null
: readFilter.isNull() ? null : BsonUtils.escapeKeys(readFilter.asDocument(), true).asDocument();

Expand All @@ -78,20 +82,20 @@ public MongoPermissions() {
this.allowBulkDelete = allowBulkDelete;
this.allowWriteMode = allowWriteMode;

if (hiddenProps != null) {
this.hiddenProps.addAll(hiddenProps);
if (hideProps != null) {
this.hideProps.addAll(hideProps);
}

if (protectedProps != null) {
this.protectedProps.addAll(protectedProps);
if (protectProps != null) {
this.protectProps.addAll(protectProps);
}

if (overriddenProps != null) {
this.overriddenProps.putAll(overriddenProps);
if (overrideProps != null) {
this.overrideProps.putAll(overrideProps);
}

if (overriddenProps != null) {
this.forbiddenQueryParams.addAll(forbiddenQueryParams);
if (overrideProps != null) {
this.forbidQueryParams.addAll(forbidQueryParams);
}
}

Expand Down Expand Up @@ -139,8 +143,8 @@ public static MongoPermissions from(BsonDocument args) throws ConfigurationExcep

return new MongoPermissions(readFilter, writeFilter, parseBooleanArg(args, "allowManagementRequests"),
parseBooleanArg(args, "allowBulkPatch"), parseBooleanArg(args, "allowBulkDelete"),
parseBooleanArg(args, "allowWriteMode"), parseSetArg(args, "hiddenProps"),
parseSetArg(args, "protectedProps"), parseMapArg(args, "overriddenProps"), parseSetArg(args, "forbiddenQueryParams"));
parseBooleanArg(args, "allowWriteMode"), parseSetArg(args, "hideProps"),
parseSetArg(args, "protectProps"), parseMapArg(args, "overrideProps"), parseSetArg(args, "forbidQueryParams"));
}
}

Expand Down Expand Up @@ -213,8 +217,8 @@ public static MongoPermissions from(Map<String, Object> args) throws Configurati

return new MongoPermissions(readFilter, writeFilter, parseBooleanArg(args, "allowManagementRequests"),
parseBooleanArg(args, "allowBulkPatch"), parseBooleanArg(args, "allowBulkDelete"),
parseBooleanArg(args, "allowWriteMode"), parseSetArg(args, "hiddenProps"),
parseSetArg(args, "protectedProps"), parseMapArg(args, "overriddenProps"), parseSetArg(args, "forbiddenQueryParams"));
parseBooleanArg(args, "allowWriteMode"), parseSetArg(args, "hideProps"),
parseSetArg(args, "protectProps"), parseMapArg(args, "overrideProps"), parseSetArg(args, "forbidQueryParams"));
}
}

Expand Down Expand Up @@ -284,21 +288,21 @@ private static Map<String, BsonValue> parseMapArg(Map<String, Object> args, Stri
ret.put((String) ikey, BsonUtils.parse(svalue));
} catch (Throwable t) {
var ex = new ConfigurationException("Wrong permission: mongo." + key
+ " must be an object. A valid example is:\n\toverriddenProps:\n\t\tfoo: '\"bar\"'\n\t\tfoo: '{\"bar\": 1}'\n\t\tuser: \"@user._id\"",
+ " must be an object. A valid example is:\n\toverrideProps:\n\t\tfoo: '\"bar\"'\n\t\tfoo: '{\"bar\": 1}'\n\t\tuser: \"@user._id\"",
t);
LambdaUtils.throwsSneakyException(ex);
}
} else {
var ex = new ConfigurationException("Wrong permission: mongo." + key
+ " must be an object. A valid example is:\n\toverriddenProps:\n\t\tfoo: '\"bar\"'\n\t\tfoo: '{\"bar\": 1}'\n\t\tuser: \"@user._id\"");
+ " must be an object. A valid example is:\n\toverrideProps:\n\t\tfoo: '\"bar\"'\n\t\tfoo: '{\"bar\": 1}'\n\t\tuser: \"@user._id\"");
LambdaUtils.throwsSneakyException(ex);
}
});

return ret;
} else {
throw new ConfigurationException("Wrong permission: mongo." + key
+ " must be an object. A valid example is:\n\toverriddenProps:\n\t\tfoo: '\"bar\"'\n\t\tfoo: '{\"bar\": 1}'\n\t\tuser: \"@user._id\"");
+ " must be an object. A valid example is:\n\toverrideProps:\n\t\tfoo: '\"bar\"'\n\t\tfoo: '{\"bar\": 1}'\n\t\tuser: \"@user._id\"");
}
} else {
return Maps.newHashMap();
Expand Down Expand Up @@ -413,19 +417,19 @@ public boolean isAllowWriteMode() {
return this.allowWriteMode;
}

public Set<String> getHiddenProps() {
return this.hiddenProps;
public Set<String> getHideProps() {
return this.hideProps;
}

public Set<String> getProtectedProps() {
return this.protectedProps;
public Set<String> getProtectProps() {
return this.protectProps;
}

public Map<String, BsonValue> getOverriddenProps() {
return this.overriddenProps;
public Map<String, BsonValue> getOverrideProps() {
return this.overrideProps;
}

public Set<String> getForbiddenQueryParams() {
return this.forbiddenQueryParams;
public Set<String> getForbidQueryParams() {
return this.forbidQueryParams;
}
}
Expand Up @@ -33,26 +33,26 @@
import org.restheart.exchange.MongoResponse;
import org.restheart.plugins.InterceptPoint;

@RegisterPlugin(name = "mongoPermissionForbiddenQueryParams",
description = "Forbids query parameters according to the mongo.forbiddenQueryParams ACL permission",
@RegisterPlugin(name = "mongoPermissionForbidQueryParams",
description = "Forbids query parameters according to the mongo.forbidQueryParams ACL permission",
interceptPoint = InterceptPoint.REQUEST_AFTER_AUTH,
enabledByDefault = true,
priority = 10)
public class ForbiddenQueryParams implements MongoInterceptor {
public class ForbidQueryParams implements MongoInterceptor {

@Override
public void handle(MongoRequest request, MongoResponse response) throws Exception {
var forbiddenQueryParams = MongoPermissions.of(request).getForbiddenQueryParams();
var forbidQueryParams = MongoPermissions.of(request).getForbidQueryParams();

if (contains(request.getQueryParameters(), forbiddenQueryParams)) {
if (contains(request.getQueryParameters(), forbidQueryParams)) {
response.setStatusCode(HttpStatus.SC_FORBIDDEN);
request.setInError(true);
}
}

private boolean contains(Map<String, Deque<String>> queryParams, Set<String> forbiddenQueryParams) {
private boolean contains(Map<String, Deque<String>> queryParams, Set<String> forbidQueryParams) {
return queryParams != null
&& queryParams.keySet().stream().anyMatch(qp -> forbiddenQueryParams.contains(qp));
&& queryParams.keySet().stream().anyMatch(qp -> forbidQueryParams.contains(qp));
}

@Override
Expand All @@ -66,7 +66,7 @@ public boolean resolve(MongoRequest request, MongoResponse response) {
var mongoPermission = MongoPermissions.of(request);

if (mongoPermission != null) {
return !mongoPermission.getForbiddenQueryParams().isEmpty();
return !mongoPermission.getForbidQueryParams().isEmpty();
} else {
return false;
}
Expand Down
Expand Up @@ -31,15 +31,15 @@
import org.restheart.exchange.MongoResponse;
import org.restheart.plugins.InterceptPoint;

@RegisterPlugin(name = "mongoPermissionHiddenProps",
description = "Hides properties from the response according to the mongo.hiddenProps ACL permission",
@RegisterPlugin(name = "mongoPermissionHideProps",
description = "Hides properties from the response according to the mongo.hideProps ACL permission",
interceptPoint = InterceptPoint.RESPONSE,
enabledByDefault = true)
public class HiddenProps implements MongoInterceptor {
public class HideProps implements MongoInterceptor {

@Override
public void handle(MongoRequest request, MongoResponse response) throws Exception {
var hiddendProps = MongoPermissions.of(request).getHiddenProps();
var hiddendProps = MongoPermissions.of(request).getHideProps();

if (response.getContent().isDocument()) {
hide(response.getContent().asDocument(), hiddendProps);
Expand All @@ -48,8 +48,8 @@ public void handle(MongoRequest request, MongoResponse response) throws Exceptio
}
}

private void hide(BsonDocument doc, Set<String> hiddenProps) {
hiddenProps.stream().forEachOrdered(hiddenProp -> hide(doc, hiddenProp));
private void hide(BsonDocument doc, Set<String> hideProps) {
hideProps.stream().forEachOrdered(hiddenProp -> hide(doc, hiddenProp));
}

private void hide(BsonDocument doc, String hiddenProp) {
Expand All @@ -72,7 +72,7 @@ public boolean resolve(MongoRequest request, MongoResponse response) {
var mongoPermission = MongoPermissions.of(request);

if (mongoPermission != null) {
return !mongoPermission.getHiddenProps().isEmpty();
return !mongoPermission.getHideProps().isEmpty();
} else {
return false;
}
Expand Down
Expand Up @@ -32,31 +32,31 @@
import org.restheart.security.MongoPermissions;
import org.restheart.utils.BsonUtils;

@RegisterPlugin(name = "mongoPermissionOverriddenProps",
description = "Override properties's values in write requests according to the mongo.overriddenProps ACL permission",
@RegisterPlugin(name = "mongoPermissionOverrideProps",
description = "Override properties's values in write requests according to the mongo.overrideProps ACL permission",
interceptPoint = InterceptPoint.REQUEST_AFTER_AUTH,
enabledByDefault = true,
// must be lesser priority than mongoProtectedProps
// must be lesser priority than mongoprotectProps
priority = 11)
public class OverriddenProps implements MongoInterceptor {
public class OverrideProps implements MongoInterceptor {
@Override
public void handle(MongoRequest request, MongoResponse response) throws Exception {
var overriddenProps = MongoPermissions.of(request).getOverriddenProps();
var overrideProps = MongoPermissions.of(request).getOverrideProps();

if (request.getContent().isDocument()) {
override(request, overriddenProps);
override(request, overrideProps);
} else if (request.getContent().isArray()) {
request.getContent().asArray().stream().map(doc -> doc.asDocument())
.forEachOrdered(doc -> override(request, overriddenProps));
.forEachOrdered(doc -> override(request, overrideProps));
}

if (request.isPost()) {
request.setContent(BsonUtils.unflatten(request.getContent()));
}
}

private void override(MongoRequest request, Map<String, BsonValue> overriddenProps) {
overriddenProps.entrySet().stream()
private void override(MongoRequest request, Map<String, BsonValue> overrideProps) {
overrideProps.entrySet().stream()
.filter(e -> e.getValue() != null && e.getValue().isString())
.forEachOrdered(e -> override(request, e.getKey(), e.getValue().asString().getValue()));
}
Expand All @@ -74,7 +74,7 @@ public boolean resolve(MongoRequest request, MongoResponse response) {
var mongoPermission = MongoPermissions.of(request);

if (mongoPermission != null) {
return !mongoPermission.getOverriddenProps().isEmpty();
return !mongoPermission.getOverrideProps().isEmpty();
} else {
return false;
}
Expand Down
Expand Up @@ -34,23 +34,23 @@
import org.restheart.exchange.MongoResponse;
import org.restheart.plugins.InterceptPoint;

@RegisterPlugin(name = "mongoPermissionProtectedProps",
description = "Forbids writing properties according to the mongo.protectedProps ACL permission",
@RegisterPlugin(name = "mongoPermissionProtectProps",
description = "Forbids writing properties according to the mongo.protectProps ACL permission",
interceptPoint = InterceptPoint.REQUEST_AFTER_AUTH,
enabledByDefault = true,
priority = 10)
public class ProtectedProps implements MongoInterceptor {
public class ProtectProps implements MongoInterceptor {

@Override
public void handle(MongoRequest request, MongoResponse response) throws Exception {
var protectedProps = MongoPermissions.of(request).getProtectedProps();
var protectProps = MongoPermissions.of(request).getProtectProps();

boolean contains;

if (request.getContent().isDocument()) {
contains = contains(request.getContent().asDocument(), protectedProps);
contains = contains(request.getContent().asDocument(), protectProps);
} else if (request.getContent().isArray()) {
contains = request.getContent().asArray().stream().map(doc -> doc.asDocument()).anyMatch(doc -> contains(doc, protectedProps));
contains = request.getContent().asArray().stream().map(doc -> doc.asDocument()).anyMatch(doc -> contains(doc, protectProps));
} else {
contains = false;
}
Expand All @@ -61,13 +61,13 @@ public void handle(MongoRequest request, MongoResponse response) throws Exceptio
}
}

private boolean contains(BsonDocument doc, Set<String> protectedProps) {
private boolean contains(BsonDocument doc, Set<String> protectProps) {
var ufdoc = BsonUtils.unflatten(doc).asDocument();

return protectedProps.stream().anyMatch(hiddenProp -> contains(ufdoc, hiddenProp));
return protectProps.stream().anyMatch(hiddenProp -> contains(ufdoc, hiddenProp));
}

private boolean contains(BsonDocument doc, String protectedProps) {
private boolean contains(BsonDocument doc, String protectProps) {
// let's check update operators first, since doc can look like:
// {
// <operator1>: { <field1>: <value1>, ... },
Expand All @@ -78,18 +78,18 @@ private boolean contains(BsonDocument doc, String protectedProps) {
if (BsonUtils.containsUpdateOperators(doc)) {
var updateOperators = doc.keySet().stream().filter(k -> k.startsWith("$")).collect(Collectors.toList());

return updateOperators.stream().anyMatch(uo -> contains(BsonUtils.unflatten(doc.get(uo)).asDocument(), protectedProps));
return updateOperators.stream().anyMatch(uo -> contains(BsonUtils.unflatten(doc.get(uo)).asDocument(), protectProps));
}

if (protectedProps.contains(".")) {
var first = protectedProps.substring(0, protectedProps.indexOf("."));
if (protectProps.contains(".")) {
var first = protectProps.substring(0, protectProps.indexOf("."));
if (first.length() > 0 && doc.containsKey(first) && doc.get(first).isDocument()) {
return contains(doc.get(first).asDocument(), protectedProps.substring(protectedProps.indexOf(".")+1));
return contains(doc.get(first).asDocument(), protectProps.substring(protectProps.indexOf(".")+1));
} else {
return false;
}
} else {
return protectedProps.length() > 0 && doc.containsKey(protectedProps);
return protectProps.length() > 0 && doc.containsKey(protectProps);
}
}

Expand All @@ -102,7 +102,7 @@ public boolean resolve(MongoRequest request, MongoResponse response) {
var mongoPermission = MongoPermissions.of(request);

if (mongoPermission != null) {
return !mongoPermission.getProtectedProps().isEmpty();
return !mongoPermission.getProtectProps().isEmpty();
} else {
return false;
}
Expand Down

0 comments on commit 23938a2

Please sign in to comment.