You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ja...@apache.org on 2022/01/19 14:46:37 UTC
[solr] branch branch_9x updated: SOLR-15776 Make Admin UI play well with Authorization (#399)
This is an automated email from the ASF dual-hosted git repository.
janhoy pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/branch_9x by this push:
new 20bcad7 SOLR-15776 Make Admin UI play well with Authorization (#399)
20bcad7 is described below
commit 20bcad761fb8a830e58c0894ac66d8645573540e
Author: Jan Høydahl <ja...@users.noreply.github.com>
AuthorDate: Wed Jan 19 15:38:01 2022 +0100
SOLR-15776 Make Admin UI play well with Authorization (#399)
(cherry picked from commit a1406847ba256965e636e05b11e53c0ac696e2a9)
# Conflicts:
# solr/CHANGES.txt
---
solr/CHANGES.txt | 2 +
.../solr/handler/admin/SystemInfoHandler.java | 2 +-
.../security/RuleBasedAuthorizationPluginBase.java | 18 +++++++
.../BaseTestRuleBasedAuthorizationPlugin.java | 19 ++++++--
solr/webapp/web/index.html | 1 +
solr/webapp/web/js/angular/app.js | 22 +++++----
solr/webapp/web/js/angular/controllers/cloud.js | 28 ++++++-----
solr/webapp/web/js/angular/controllers/logging.js | 6 +++
solr/webapp/web/js/angular/controllers/security.js | 2 +-
solr/webapp/web/js/angular/permissions.js | 56 ++++++++++++++++++++++
solr/webapp/web/partials/collections.html | 4 +-
11 files changed, 133 insertions(+), 27 deletions(-)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index d347b03..7f13a02 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -28,6 +28,8 @@ Other Changes
---------------------
* SOLR-15897: Remove <jmx/> from all unit test solrconfig.xml files. (Eric Pugh)
+* SOLR-15776: Admin UI is now aware of logged-in user's permissions and can adapt accordingly (janhoy)
+
================== 9.0.0 ==================
Docker and contrib modules have separate CHANGES.md files.
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java
index 87c5d48..7589e8d 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java
@@ -65,7 +65,6 @@ import static org.apache.solr.common.params.CommonParams.NAME;
public class SystemInfoHandler extends RequestHandlerBase
{
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
- private static final String PARAM_NODE = "node";
public static String REDACT_STRING = RedactionUtils.getRedactString();
@@ -354,6 +353,7 @@ public class SystemInfoHandler extends RequestHandlerBase
RuleBasedAuthorizationPluginBase rbap = (RuleBasedAuthorizationPluginBase) auth;
Set<String> roles = rbap.getUserRoles(req.getUserPrincipal());
info.add("roles", roles);
+ info.add("permissions", rbap.getPermissionNamesForRoles(roles));
}
}
diff --git a/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPluginBase.java b/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPluginBase.java
index cdcee91..9045de6 100644
--- a/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPluginBase.java
+++ b/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPluginBase.java
@@ -27,6 +27,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
+import java.util.stream.Collectors;
import org.apache.solr.common.SpecProvider;
import org.apache.solr.common.util.Utils;
@@ -46,6 +47,7 @@ public abstract class RuleBasedAuthorizationPluginBase implements AuthorizationP
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final Map<String, WildCardSupportMap> mapping = new HashMap<>();
+ private final Map<String, Set<Permission>> roleToPermissionsMap = new HashMap<>();
// Doesn't implement Map because we violate the contracts of put() and get()
private static class WildCardSupportMap {
@@ -110,6 +112,16 @@ public abstract class RuleBasedAuthorizationPluginBase implements AuthorizationP
return flag.rsp;
}
+ /**
+ * Retrieves permission names for a given set of roles
+ */
+ public Set<String> getPermissionNamesForRoles(Set<String> roles) {
+ return roles.stream().filter(roleToPermissionsMap::containsKey)
+ .flatMap(r -> roleToPermissionsMap.get(r).stream())
+ .map(p -> p.name)
+ .collect(Collectors.toSet());
+ }
+
private MatchStatus checkCollPerm(WildCardSupportMap pathVsPerms, AuthorizationContext context) {
if (pathVsPerms == null) return MatchStatus.NO_PERMISSIONS_FOUND;
@@ -286,6 +298,12 @@ public abstract class RuleBasedAuthorizationPluginBase implements AuthorizationP
perms.add(permission);
}
}
+ if (permission.role != null) {
+ for (String r : permission.role) {
+ Set<Permission> rm = roleToPermissionsMap.computeIfAbsent(r, k -> new HashSet<>());
+ rm.add(permission);
+ }
+ }
}
/**
diff --git a/solr/core/src/test/org/apache/solr/security/BaseTestRuleBasedAuthorizationPlugin.java b/solr/core/src/test/org/apache/solr/security/BaseTestRuleBasedAuthorizationPlugin.java
index 57c2001..f1598ac 100644
--- a/solr/core/src/test/org/apache/solr/security/BaseTestRuleBasedAuthorizationPlugin.java
+++ b/solr/core/src/test/org/apache/solr/security/BaseTestRuleBasedAuthorizationPlugin.java
@@ -25,6 +25,7 @@ import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.apache.http.auth.BasicUserPrincipal;
import org.apache.solr.SolrTestCaseJ4;
@@ -48,9 +49,7 @@ import org.hamcrest.core.IsInstanceOf;
import org.junit.Before;
import org.junit.Test;
-import static java.util.Collections.emptyMap;
-import static java.util.Collections.singletonList;
-import static java.util.Collections.singletonMap;
+import static java.util.Collections.*;
import static org.apache.solr.common.util.CommandOperation.captureErrors;
import static org.apache.solr.common.util.Utils.getObjectByPath;
import static org.hamcrest.CoreMatchers.instanceOf;
@@ -449,6 +448,20 @@ public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
checkRules(values, STATUS_OK);
}
+ @Test
+ public void testGetPermissionNamesForRoles() {
+ // Tests the method that maps role(s) to permissions, used by SystemInfoHandler to provide UI with logged in user's permissions
+ try (RuleBasedAuthorizationPluginBase plugin = createPlugin()) {
+ plugin.init(rules);
+ assertEquals(Set.of("mycoll_update", "read"), plugin.getPermissionNamesForRoles(Set.of("dev")));
+ assertEquals(emptySet(), plugin.getPermissionNamesForRoles(Set.of("user")));
+ assertEquals(Set.of("schema-edit", "collection-admin-edit", "mycoll_update"), plugin.getPermissionNamesForRoles(Set.of("admin")));
+ assertEquals(Set.of("schema-edit", "collection-admin-edit", "mycoll_update", "read"), plugin.getPermissionNamesForRoles(Set.of("admin", "dev")));
+ } catch (IOException e) {
+ ; // swallow error, otherwise a you have to add a _lot_ of exceptions to methods.
+ }
+ }
+
void addPermission(String permissionName, String role, String path, Map<String, Object> params) {
((List)rules.get("permissions")).add(Map.of("name", permissionName, "role", role, "path", path, "params", params));
}
diff --git a/solr/webapp/web/index.html b/solr/webapp/web/index.html
index 64a5f8e..8fce6df 100644
--- a/solr/webapp/web/index.html
+++ b/solr/webapp/web/index.html
@@ -71,6 +71,7 @@ limitations under the License.
<script src="libs/angular-utf8-base64.min.js?_=${version}"></script>
<script src="js/angular/app.js?_=${version}"></script>
<script src="js/angular/services.js?_=${version}"></script>
+ <script src="js/angular/permissions.js?_=${version}"></script>
<script src="js/angular/controllers/index.js?_=${version}"></script>
<script src="js/angular/controllers/login.js?_=${version}"></script>
<script src="js/angular/controllers/logging.js?_=${version}"></script>
diff --git a/solr/webapp/web/js/angular/app.js b/solr/webapp/web/js/angular/app.js
index 437a7e2..d0de77a 100644
--- a/solr/webapp/web/js/angular/app.js
+++ b/solr/webapp/web/js/angular/app.js
@@ -492,6 +492,11 @@ solrAdminApp.controller('MainController', function($scope, $route, $rootScope, $
$scope.aliases = [];
}
+ $scope.permissions = permissions;
+ $scope.isPermitted = function (permissions) {
+ return hasAllRequiredPermissions(permissions, $scope.usersPermissions);
+ }
+
$scope.refresh();
$scope.resetMenu = function(page, pageType) {
Cores.list(function(data) {
@@ -512,9 +517,16 @@ solrAdminApp.controller('MainController', function($scope, $route, $rootScope, $
$scope.initFailures = data.initFailures;
});
- $scope.isSchemaDesignerEnabled = true;
System.get(function(data) {
$scope.isCloudEnabled = data.mode.match( /solrcloud/i );
+ $scope.usersPermissions = data.security.permissions;
+
+ $scope.isSchemaDesignerEnabled = $scope.isPermitted([
+ permissions.CONFIG_EDIT_PERM,
+ permissions.SCHEMA_EDIT_PERM,
+ permissions.READ_PERM,
+ permissions.UPDATE_PERM
+ ]);
var currentCollectionName = $route.current.params.core;
delete $scope.currentCollection;
@@ -550,14 +562,6 @@ solrAdminApp.controller('MainController', function($scope, $route, $rootScope, $
$scope.aliases_and_collections = $scope.aliases_and_collections.concat({name:'-----'});
}
$scope.aliases_and_collections = $scope.aliases_and_collections.concat($scope.collections);
-
- SchemaDesigner.get({path: "configs"}, function (ignore) {
- // no-op, just checking if we have access to this path
- }, function(e) {
- if (e.status === 401 || e.status === 403) {
- $scope.isSchemaDesignerEnabled = false;
- }
- });
});
});
}
diff --git a/solr/webapp/web/js/angular/controllers/cloud.js b/solr/webapp/web/js/angular/controllers/cloud.js
index 2d62989..2769b3a 100644
--- a/solr/webapp/web/js/angular/controllers/cloud.js
+++ b/solr/webapp/web/js/angular/controllers/cloud.js
@@ -560,17 +560,23 @@ var treeSubController = function($scope, Zookeeper) {
$scope.showTreeLink = function(link) {
var path = decodeURIComponent(link.replace(/.*[\\?&]path=([^&#]*).*/, "$1"));
- Zookeeper.detail({path: path}, function(data) {
- $scope.znode = data.znode;
- if (data.znode.path.endsWith("/managed-schema") || data.znode.path.endsWith(".xml.bak")) {
- $scope.lang = "xml";
- } else {
- var lastPathElement = data.znode.path.split( '/' ).pop();
- var lastDotAt = lastPathElement ? lastPathElement.lastIndexOf('.') : -1;
- $scope.lang = lastDotAt != -1 ? lastPathElement.substring(lastDotAt+1) : "txt";
- }
- $scope.showData = true;
- });
+ if (path === '/security.json' && !$scope.isPermitted(permissions.SECURITY_READ_PERM)) {
+ // TODO: Set proper data here to display a warning in right panel "You lack the required role to see this file"
+ $scope.znode = {};
+ $scope.showData = false;
+ } else {
+ Zookeeper.detail({path: path}, function(data) {
+ $scope.znode = data.znode;
+ if (data.znode.path.endsWith("/managed-schema") || data.znode.path.endsWith(".xml.bak")) {
+ $scope.lang = "xml";
+ } else {
+ var lastPathElement = data.znode.path.split( '/' ).pop();
+ var lastDotAt = lastPathElement ? lastPathElement.lastIndexOf('.') : -1;
+ $scope.lang = lastDotAt != -1 ? lastPathElement.substring(lastDotAt+1) : "txt";
+ }
+ $scope.showData = true;
+ });
+ }
};
$scope.hideData = function() {
diff --git a/solr/webapp/web/js/angular/controllers/logging.js b/solr/webapp/web/js/angular/controllers/logging.js
index 585c90a..6b38afe 100644
--- a/solr/webapp/web/js/angular/controllers/logging.js
+++ b/solr/webapp/web/js/angular/controllers/logging.js
@@ -134,6 +134,9 @@ solrAdminApp.controller('LoggingController',
};
$scope.toggleOptions = function(logger) {
+ if (!$scope.isPermitted(permissions.CONFIG_EDIT_PERM)) {
+ return;
+ }
if (logger.showOptions) {
logger.showOptions = false;
delete $scope.currentLogger;
@@ -147,6 +150,9 @@ solrAdminApp.controller('LoggingController',
};
$scope.setLevel = function(logger, newLevel) {
+ if (!$scope.isPermitted(permissions.CONFIG_EDIT_PERM)) {
+ return;
+ }
var setString = logger.name + ":" + newLevel;
logger.showOptions = false;
Logging.setLevel({set: setString}, function(data) {
diff --git a/solr/webapp/web/js/angular/controllers/security.js b/solr/webapp/web/js/angular/controllers/security.js
index c95469c..6c7223d 100644
--- a/solr/webapp/web/js/angular/controllers/security.js
+++ b/solr/webapp/web/js/angular/controllers/security.js
@@ -142,7 +142,7 @@ solrAdminApp.controller('SecurityController', function ($scope, $timeout, $cooki
return (!obj || (Array.isArray(obj) && obj.length === 0)) ? "null" : $scope.displayList(obj);
};
- // TODO: Read this list from Solr to avoid duplication
+ // TODO: Use new permissions.js
$scope.predefinedPermissions = ["collection-admin-edit", "collection-admin-read", "core-admin-read", "core-admin-edit", "zk-read",
"read", "update", "all", "config-edit", "config-read", "schema-read", "schema-edit", "security-edit", "security-read",
"metrics-read", "health", "filestore-read", "filestore-write", "package-edit", "package-read"].sort();
diff --git a/solr/webapp/web/js/angular/permissions.js b/solr/webapp/web/js/angular/permissions.js
new file mode 100644
index 0000000..2c28a49
--- /dev/null
+++ b/solr/webapp/web/js/angular/permissions.js
@@ -0,0 +1,56 @@
+/*
+ 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.
+*/
+
+const permissions = {
+ COLL_EDIT_PERM: "collection-admin-edit",
+ COLL_READ_PERM: "collection-admin-read",
+ CORE_READ_PERM: "core-admin-read",
+ CORE_EDIT_PERM: "core-admin-edit",
+ ZK_READ_PERM: "zk-read",
+ READ_PERM: "read",
+ UPDATE_PERM: "update",
+ CONFIG_EDIT_PERM: "config-edit",
+ CONFIG_READ_PERM: "config-read",
+ SCHEMA_READ_PERM: "schema-read",
+ SCHEMA_EDIT_PERM: "schema-edit",
+ SECURITY_EDIT_PERM: "security-edit",
+ SECURITY_READ_PERM: "security-read",
+ METRICS_READ_PERM: "metrics-read",
+ FILESTORE_READ_PERM: "filestore-read",
+ FILESTORE_WRITE_PERM: "filestore-write",
+ PACKAGE_EDIT_PERM: "package-edit",
+ PACKAGE_READ_PERM: "package-read",
+ ALL_PERM: "all"
+}
+
+/**
+ * Returns true if all required permissions are available. Also returns true if RBAC is not enabled
+ * @param requiredPermissions the permission(s) to check for, can be a single or array
+ * @param userPermissions the actual permissions of current user
+ * @returns {boolean}
+ */
+var hasAllRequiredPermissions = function (requiredPermissions, userPermissions) {
+ if (!Array.isArray(requiredPermissions)) {
+ requiredPermissions = [requiredPermissions];
+ }
+ if (userPermissions !== undefined) {
+ return requiredPermissions.every(elem => userPermissions.indexOf(elem) > -1);
+ } else {
+ // RBAC not enabled, always return true
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/solr/webapp/web/partials/collections.html b/solr/webapp/web/partials/collections.html
index 59104f0..b4d4c2b 100644
--- a/solr/webapp/web/partials/collections.html
+++ b/solr/webapp/web/partials/collections.html
@@ -61,7 +61,7 @@ limitations under the License.
</p>
<p class="clearfix buttons">
- <button type="submit" class="submit" ng-click="addCollection()"><span>Add Collection</span></button>
+ <button type="submit" class="submit" ng-disabled="!isPermitted(permissions.COLL_EDIT_PERM)" ng-click="addCollection()"><span>Add Collection</span></button>
<button type="reset" class="reset" ng-click="cancelAddCollection()"><span>Cancel</span></button>
</p>
@@ -372,7 +372,7 @@ limitations under the License.
</div>
<div id="navigation" class="requires-core clearfix">
- <button id="add" class="action" ng-click="showAddCollection()"><span>Add Collection</span></button>
+ <button id="add" class="action" ng-disabled="!isPermitted(permissions.COLL_EDIT_PERM)" ng-click="showAddCollection()"><span>Add Collection</span></button>
<ul>
<li ng-repeat="c in collections" ng-class="{current: collection.name == c.name && collection.type === 'collection'}"><a href="#~collections/{{c.name}}">{{c.name}}</a></li>
</ul>