From 23938a29f8b002ef103d56fb24dcb1e8cb6a3856 Mon Sep 17 00:00:00 2001 From: Andrea Di Cesare Date: Fri, 19 Feb 2021 11:11:59 +0100 Subject: [PATCH] Refactor MongoPermissions --- .../security/AclVarsInterpolator.java | 17 ++++++ .../restheart/security/MongoPermissions.java | 60 ++++++++++--------- ...ueryParams.java => ForbidQueryParams.java} | 16 ++--- .../{HiddenProps.java => HideProps.java} | 14 ++--- ...verriddenProps.java => OverrideProps.java} | 20 +++---- ...{ProtectedProps.java => ProtectProps.java} | 30 +++++----- 6 files changed, 89 insertions(+), 68 deletions(-) rename mongodb/src/main/java/org/restheart/mongodb/security/{ForbiddenQueryParams.java => ForbidQueryParams.java} (82%) rename mongodb/src/main/java/org/restheart/mongodb/security/{HiddenProps.java => HideProps.java} (85%) rename mongodb/src/main/java/org/restheart/mongodb/security/{OverriddenProps.java => OverrideProps.java} (84%) rename mongodb/src/main/java/org/restheart/mongodb/security/{ProtectedProps.java => ProtectProps.java} (80%) diff --git a/commons/src/main/java/org/restheart/security/AclVarsInterpolator.java b/commons/src/main/java/org/restheart/security/AclVarsInterpolator.java index 6ffb8a8af..f1c1727a8 100644 --- a/commons/src/main/java/org/restheart/security/AclVarsInterpolator.java +++ b/commons/src/main/java/org/restheart/security/AclVarsInterpolator.java @@ -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; diff --git a/commons/src/main/java/org/restheart/security/MongoPermissions.java b/commons/src/main/java/org/restheart/security/MongoPermissions.java index fa671570a..ba93f3d04 100644 --- a/commons/src/main/java/org/restheart/security/MongoPermissions.java +++ b/commons/src/main/java/org/restheart/security/MongoPermissions.java @@ -50,10 +50,14 @@ public class MongoPermissions { private final BsonDocument readFilter; private final BsonDocument writeFilter; - final Set hiddenProps = Sets.newHashSet(); - final Set protectedProps = Sets.newHashSet(); - final Map overriddenProps = Maps.newHashMap(); - final Set forbiddenQueryParams = Sets.newHashSet(); + // an hidden property is removed from the response a GET requests + final Set hideProps = Sets.newHashSet(); + // a protected property cannot be in the body of a write request, otherwise 403 FORBBIDEN is returned + final Set protectProps = Sets.newHashSet(); + // the value of an overridden property is set by the server + final Map overrideProps = Maps.newHashMap(); + // a forbidden query parameter cannot be specified in the request URL, otherwise 403 FORBBIDEN is returned + final Set forbidQueryParams = Sets.newHashSet(); public MongoPermissions() { this.allowManagementRequests = false; @@ -66,7 +70,7 @@ public MongoPermissions() { MongoPermissions(BsonDocument readFilter, BsonDocument writeFilter, boolean allowManagementRequests, boolean allowBulkPatch, boolean allowBulkDelete, boolean allowWriteMode, - Set hiddenProps, Set protectedProps, Map overriddenProps, Set forbiddenQueryParams) { + Set hideProps, Set protectProps, Map overrideProps, Set forbidQueryParams) { this.readFilter = readFilter == null ? null : readFilter.isNull() ? null : BsonUtils.escapeKeys(readFilter.asDocument(), true).asDocument(); @@ -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); } } @@ -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")); } } @@ -213,8 +217,8 @@ public static MongoPermissions from(Map 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")); } } @@ -284,13 +288,13 @@ private static Map parseMapArg(Map 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); } }); @@ -298,7 +302,7 @@ private static Map parseMapArg(Map args, Stri 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(); @@ -413,19 +417,19 @@ public boolean isAllowWriteMode() { return this.allowWriteMode; } - public Set getHiddenProps() { - return this.hiddenProps; + public Set getHideProps() { + return this.hideProps; } - public Set getProtectedProps() { - return this.protectedProps; + public Set getProtectProps() { + return this.protectProps; } - public Map getOverriddenProps() { - return this.overriddenProps; + public Map getOverrideProps() { + return this.overrideProps; } - public Set getForbiddenQueryParams() { - return this.forbiddenQueryParams; + public Set getForbidQueryParams() { + return this.forbidQueryParams; } } diff --git a/mongodb/src/main/java/org/restheart/mongodb/security/ForbiddenQueryParams.java b/mongodb/src/main/java/org/restheart/mongodb/security/ForbidQueryParams.java similarity index 82% rename from mongodb/src/main/java/org/restheart/mongodb/security/ForbiddenQueryParams.java rename to mongodb/src/main/java/org/restheart/mongodb/security/ForbidQueryParams.java index c40f87f38..5594fa3a3 100644 --- a/mongodb/src/main/java/org/restheart/mongodb/security/ForbiddenQueryParams.java +++ b/mongodb/src/main/java/org/restheart/mongodb/security/ForbidQueryParams.java @@ -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> queryParams, Set forbiddenQueryParams) { + private boolean contains(Map> queryParams, Set forbidQueryParams) { return queryParams != null - && queryParams.keySet().stream().anyMatch(qp -> forbiddenQueryParams.contains(qp)); + && queryParams.keySet().stream().anyMatch(qp -> forbidQueryParams.contains(qp)); } @Override @@ -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; } diff --git a/mongodb/src/main/java/org/restheart/mongodb/security/HiddenProps.java b/mongodb/src/main/java/org/restheart/mongodb/security/HideProps.java similarity index 85% rename from mongodb/src/main/java/org/restheart/mongodb/security/HiddenProps.java rename to mongodb/src/main/java/org/restheart/mongodb/security/HideProps.java index ad8a31a84..8173cba06 100644 --- a/mongodb/src/main/java/org/restheart/mongodb/security/HiddenProps.java +++ b/mongodb/src/main/java/org/restheart/mongodb/security/HideProps.java @@ -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); @@ -48,8 +48,8 @@ public void handle(MongoRequest request, MongoResponse response) throws Exceptio } } - private void hide(BsonDocument doc, Set hiddenProps) { - hiddenProps.stream().forEachOrdered(hiddenProp -> hide(doc, hiddenProp)); + private void hide(BsonDocument doc, Set hideProps) { + hideProps.stream().forEachOrdered(hiddenProp -> hide(doc, hiddenProp)); } private void hide(BsonDocument doc, String hiddenProp) { @@ -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; } diff --git a/mongodb/src/main/java/org/restheart/mongodb/security/OverriddenProps.java b/mongodb/src/main/java/org/restheart/mongodb/security/OverrideProps.java similarity index 84% rename from mongodb/src/main/java/org/restheart/mongodb/security/OverriddenProps.java rename to mongodb/src/main/java/org/restheart/mongodb/security/OverrideProps.java index f4c42df9f..09f5a10d6 100644 --- a/mongodb/src/main/java/org/restheart/mongodb/security/OverriddenProps.java +++ b/mongodb/src/main/java/org/restheart/mongodb/security/OverrideProps.java @@ -32,22 +32,22 @@ 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()) { @@ -55,8 +55,8 @@ public void handle(MongoRequest request, MongoResponse response) throws Exceptio } } - private void override(MongoRequest request, Map overriddenProps) { - overriddenProps.entrySet().stream() + private void override(MongoRequest request, Map overrideProps) { + overrideProps.entrySet().stream() .filter(e -> e.getValue() != null && e.getValue().isString()) .forEachOrdered(e -> override(request, e.getKey(), e.getValue().asString().getValue())); } @@ -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; } diff --git a/mongodb/src/main/java/org/restheart/mongodb/security/ProtectedProps.java b/mongodb/src/main/java/org/restheart/mongodb/security/ProtectProps.java similarity index 80% rename from mongodb/src/main/java/org/restheart/mongodb/security/ProtectedProps.java rename to mongodb/src/main/java/org/restheart/mongodb/security/ProtectProps.java index 0b62e086a..a02f324c7 100644 --- a/mongodb/src/main/java/org/restheart/mongodb/security/ProtectedProps.java +++ b/mongodb/src/main/java/org/restheart/mongodb/security/ProtectProps.java @@ -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; } @@ -61,13 +61,13 @@ public void handle(MongoRequest request, MongoResponse response) throws Exceptio } } - private boolean contains(BsonDocument doc, Set protectedProps) { + private boolean contains(BsonDocument doc, Set 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: // { // : { : , ... }, @@ -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); } } @@ -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; }