diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/AclService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/AclService.java index b507a4b8f3..3b717f1c0c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/AclService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/AclService.java @@ -39,20 +39,26 @@ public class AclService { this.groupService = groupService; } - public Mono evaluateAcl(HttpMethod httpMethod, String resource) { + public Mono evaluateAcl(HttpMethod httpMethod, String resource, String requestUrl) { JSONObject requestBody = new JSONObject(); JSONObject input = new JSONObject(); JSONObject jsonUser = new JSONObject(); input.put("user", jsonUser); input.put("method", httpMethod.name()); input.put("resource", resource); + input.put("url", requestUrl); // The url is required for OPA to gate keep only public URLs for anonymous users requestBody.put("input", input); Mono user = sessionUserService.getCurrentUser(); return user - .map(u -> { + // This is when the user doesn't have an existing session. The user is anonymous + .switchIfEmpty(Mono.defer(() -> { + User anonymous = new User(); + return Mono.just(anonymous); + })) + .flatMap(u -> { Set globalPermissions = new HashSet<>(); Set groupSet = u.getGroupIds(); globalPermissions.addAll(u.getPermissions()); @@ -62,7 +68,6 @@ public class AclService { .collectList() .thenReturn(globalPermissions); }) - .flatMap(obj -> obj) .flatMap(permissions -> { jsonUser.put("permissions", permissions); String finalUrl = url + pkgName; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SecurityConfig.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SecurityConfig.java index 5e4dda8350..e11df39307 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SecurityConfig.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SecurityConfig.java @@ -82,6 +82,8 @@ public class SecurityConfig { .cors().and() .csrf().disable() .authorizeExchange() + // All public URLs that should be served to anonymous users should be defined in acl.rego file + // This list of matchers is only the list of URLs that shouldn't return 401 unauthorized .matchers(ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/login")) .permitAll() .pathMatchers("/public/**").permitAll() diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/filters/AclFilter.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/filters/AclFilter.java index d79dcb11a7..6338358023 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/filters/AclFilter.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/filters/AclFilter.java @@ -44,7 +44,8 @@ public class AclFilter implements WebFilter { public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); HttpMethod httpMethod = request.getMethod(); - String[] urlParts = request.getPath().value().split("/"); + String url = request.getPath().value(); + String[] urlParts = url.split("/"); // This is because all the urls are of the form /api/v1/{resource}. When we split by "/", the resource is always // the 4th element in the result array @@ -55,7 +56,7 @@ public class AclFilter implements WebFilter { String resource = urlParts[3]; - Mono aclResponse = aclService.evaluateAcl(httpMethod, resource); + Mono aclResponse = aclService.evaluateAcl(httpMethod, resource, url); return aclResponse .map(acl -> { log.debug("Got ACL response: {}", acl); diff --git a/app/server/appsmith-server/src/main/resources/public/appsmith/authz/acl.rego b/app/server/appsmith-server/src/main/resources/public/appsmith/authz/acl.rego index 1a69acfcc1..e28ec299fc 100644 --- a/app/server/appsmith-server/src/main/resources/public/appsmith/authz/acl.rego +++ b/app/server/appsmith-server/src/main/resources/public/appsmith/authz/acl.rego @@ -5,18 +5,34 @@ default resource_allow = false # This rule allows the user to access endpoints based on the permissions that they are assigned. # The httpMethod and resource are also an integral part of accessing this ACL +# We overload the rule url_allow thereby making it an OR condition. Either the user is able to access the resource +# via their authenticated session, or it's a public url that is accessible to everybody. url_allow = true { - op = allowed_operations[_] + op = authenticated_operations[_] input.method = op.method input.resource = op.resource p = input.user.permissions[_] p = op.permission } +url_allow = true { + op = public_operations[_] + input.url = op.url + input.method = op.method +} + +# All public URLs must go into this list. Anything not in this list requires an authenticated session to access +public_operations = [ + {"method" : "POST", "url" : "/api/v1/users/forgotPassword" }, + {"method" : "POST", "url" : "/api/v1/users" }, + {"method" : "POST", "url" : "/api/v1/users/verifyPasswordResetToken" }, + {"method" : "POST", "url" : "/api/v1/users/resetPassword" }, +] + # This is a global list of all the routes for all controllers. Any new controller that is written must # carry an entry in this array. OPA performs ACL based on an intersection of these entries and permissions # for a user + permissions inherited via the groups that the user is a part of. -allowed_operations = [ +authenticated_operations = [ {"method": "POST", "resource": "users", "permission": "create:users"}, {"method": "GET", "resource": "users", "permission": "read:users"}, {"method": "PUT", "resource": "users", "permission": "update:users"}, diff --git a/app/server/appsmith-server/src/main/resources/public/bundle.tar.gz b/app/server/appsmith-server/src/main/resources/public/bundle.tar.gz index 930e45db7c..68b8ba98b9 100644 Binary files a/app/server/appsmith-server/src/main/resources/public/bundle.tar.gz and b/app/server/appsmith-server/src/main/resources/public/bundle.tar.gz differ