This is an automated email from the ASF dual-hosted git repository.

smiklosovic pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/trunk by this push:
     new b35ad427c5 Add LIST SUPERUSERS CQL statement
b35ad427c5 is described below

commit b35ad427c5e9282730682553b6dcf5d70b603e22
Author: Shailaja Koppu <s_ko...@apple.com>
AuthorDate: Tue Mar 12 12:27:58 2024 +0000

    Add LIST SUPERUSERS CQL statement
    
    patch by Shailaja Koppu; reviewed by Stefan Miklosovic and Benjamin Lerer 
for CASSANDRA-19417
---
 CHANGES.txt                                        |   1 +
 doc/cql3/CQL.textile                               |  16 ++-
 .../examples/BNF/list_superusers_statement.bnf     |   1 +
 .../cassandra/pages/developing/cql/security.adoc   |  12 ++
 .../pages/reference/cql-commands/commands-toc.adoc |   3 +
 pylib/cqlshlib/cql3handling.py                     |   4 +
 pylib/cqlshlib/test/test_cqlsh_completion.py       |   2 +-
 src/antlr/Lexer.g                                  |   1 +
 src/antlr/Parser.g                                 |  11 ++
 .../apache/cassandra/audit/AuditLogEntryType.java  |   3 +-
 src/java/org/apache/cassandra/auth/Roles.java      |  14 +++
 .../cql3/statements/ListSuperUsersStatement.java   | 102 ++++++++++++++++
 .../cassandra/audit/AuditLoggerAuthTest.java       |  10 ++
 test/unit/org/apache/cassandra/auth/RolesTest.java |  22 ++++
 .../statements/ListSuperUsersStatementTest.java    | 131 +++++++++++++++++++++
 15 files changed, 330 insertions(+), 3 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 296d4cb2d9..b6b9ab86ba 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 5.1
+ * Add LIST SUPERUSERS CQL statement (CASSANDRA-19417)
  * Modernize CQLSH datetime conversions (CASSANDRA-18879)
  * Harry model and in-JVM tests for partition-restricted 2i queries 
(CASSANDRA-18275)
  * Refactor cqlshmain global constants (CASSANDRA-19201)
diff --git a/doc/cql3/CQL.textile b/doc/cql3/CQL.textile
index 959533f771..c85fef5ab5 100644
--- a/doc/cql3/CQL.textile
+++ b/doc/cql3/CQL.textile
@@ -1414,7 +1414,7 @@ REVOKE report_writer FROM alice;
 
 This statement revokes the @report_writer@ role from @alice@. Any permissions 
that @alice@ has acquired via the @report_writer@ role are also revoked. 
 
-h4(#listRolesStmt). LIST ROLES
+h3(#listRolesStmt). LIST ROLES
 
 __Syntax:__
 
@@ -1438,6 +1438,20 @@ LIST ROLES OF @bob@ NORECURSIVE
 
 List all roles directly granted to @bob@.
 
+h3(#listSuperusersStmt). LIST SUPERUSERS
+
+__Syntax:__
+
+bc(syntax).
+<list-superusers-stmt> ::= LIST SUPERUSERS;
+
+__Sample:__
+
+bc(sample).
+LIST SUPERUSERS;
+
+Returns roles with the superuser privilege (this includes roles with 
transitively acquired superuser privilege), this command requires `DESCRIBE` 
permission on all roles of the database.
+
 h3(#createUserStmt). CREATE USER 
 
 Prior to the introduction of roles in Cassandra 2.2, authentication and 
authorization were based around the concept of a @USER@. For backward 
compatibility, the legacy syntax has been preserved with @USER@ centric 
statments becoming synonyms for the @ROLE@ based equivalents.
diff --git a/doc/modules/cassandra/examples/BNF/list_superusers_statement.bnf 
b/doc/modules/cassandra/examples/BNF/list_superusers_statement.bnf
new file mode 100644
index 0000000000..ae21466e19
--- /dev/null
+++ b/doc/modules/cassandra/examples/BNF/list_superusers_statement.bnf
@@ -0,0 +1 @@
+list_superusers_statement ::= LIST SUPERUSERS
diff --git a/doc/modules/cassandra/pages/developing/cql/security.adoc 
b/doc/modules/cassandra/pages/developing/cql/security.adoc
index f751a1658d..2d438b9815 100644
--- a/doc/modules/cassandra/pages/developing/cql/security.adoc
+++ b/doc/modules/cassandra/pages/developing/cql/security.adoc
@@ -261,6 +261,18 @@ transitively acquired ones:
 include::cassandra:example$CQL/list_roles_nonrecursive.cql[]
 ----
 
+[[list-superusers-statement]]
+== LIST SUPERUSERS
+
+All the known roles (including transitively acquired) with superuser privilege 
can be listed using the `LIST SUPERUSERS` statement:
+
+[source, bnf]
+----
+include::cassandra:example$BNF/list_superusers_statement.bnf[]
+----
+
+This command requires `DESCRIBE` permission on all roles of the database.
+
 == Users
 
 Prior to the introduction of roles in Cassandra 2.2, authentication and
diff --git 
a/doc/modules/cassandra/pages/reference/cql-commands/commands-toc.adoc 
b/doc/modules/cassandra/pages/reference/cql-commands/commands-toc.adoc
index e1fe0fe0fd..0924e63ac7 100644
--- a/doc/modules/cassandra/pages/reference/cql-commands/commands-toc.adoc
+++ b/doc/modules/cassandra/pages/reference/cql-commands/commands-toc.adoc
@@ -95,6 +95,9 @@ Lists permissions on resources.
 xref:reference:cql-commands/list-roles.adoc[LIST ROLES] ::     
 Lists roles and shows superuser and login status.
 
+xref:reference:cql-commands/list-superusers.adoc[LIST SUPERUSERS] ::
+Lists roles with the superuser privilege.
+
 xref:reference:cql-commands/list-users.adoc[LIST USERS (Deprecated)] ::        
 Lists existing internal authentication users and their superuser status.
 
diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py
index 252ebabe30..f795ee7dd0 100644
--- a/pylib/cqlshlib/cql3handling.py
+++ b/pylib/cqlshlib/cql3handling.py
@@ -302,6 +302,7 @@ JUNK ::= /([ 
\t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ;
                             | <alterRoleStatement>
                             | <dropRoleStatement>
                             | <listRolesStatement>
+                            | <listSuperUsersStatement>
                             ;
 
 <authorizationStatement> ::= <grantStatement>
@@ -1529,6 +1530,9 @@ syntax_rules += r'''
 <listRolesStatement> ::= "LIST" "ROLES"
                               ( "OF" <rolename> )? "NORECURSIVE"?
                        ;
+
+<listSuperUsersStatement> ::= "LIST" "SUPERUSERS"
+                       ;
 '''
 
 syntax_rules += r'''
diff --git a/pylib/cqlshlib/test/test_cqlsh_completion.py 
b/pylib/cqlshlib/test/test_cqlsh_completion.py
index c2a99dc7b5..793dcd7a8b 100644
--- a/pylib/cqlshlib/test/test_cqlsh_completion.py
+++ b/pylib/cqlshlib/test/test_cqlsh_completion.py
@@ -1053,7 +1053,7 @@ class TestCqlshCompletion(CqlshCompletionCase):
 
 
     def test_complete_in_list(self):
-        self.trycompletions('LIST ', choices=['ALL', 'AUTHORIZE', 'DESCRIBE', 
'EXECUTE', 'ROLES', 'USERS', 'ALTER', 'CREATE', 'DROP', 'MODIFY', 'SELECT', 
'UNMASK', 'SELECT_MASKED'])
+        self.trycompletions('LIST ', choices=['ALL', 'AUTHORIZE', 'DESCRIBE', 
'EXECUTE', 'ROLES', 'USERS', 'ALTER', 'CREATE', 'DROP', 'MODIFY', 'SELECT', 
'UNMASK', 'SELECT_MASKED', 'SUPERUSERS'])
 
 
     # Non-CQL Shell Commands
diff --git a/src/antlr/Lexer.g b/src/antlr/Lexer.g
index 17045b2578..f259307311 100644
--- a/src/antlr/Lexer.g
+++ b/src/antlr/Lexer.g
@@ -147,6 +147,7 @@ K_USER:        U S E R;
 K_USERS:       U S E R S;
 K_ROLE:        R O L E;
 K_ROLES:       R O L E S;
+K_SUPERUSERS:  S U P E R U S E R S;
 K_SUPERUSER:   S U P E R U S E R;
 K_NOSUPERUSER: N O S U P E R U S E R;
 K_PASSWORD:    P A S S W O R D;
diff --git a/src/antlr/Parser.g b/src/antlr/Parser.g
index e33e61faa9..cb5b90c062 100644
--- a/src/antlr/Parser.g
+++ b/src/antlr/Parser.g
@@ -235,6 +235,7 @@ cqlStatement returns [CQLStatement.Raw stmt]
     | st41=describeStatement               { $stmt = st41; }
     | st42=addIdentityStatement            { $stmt = st42; }
     | st43=dropIdentityStatement           { $stmt = st43; }
+    | st44=listSuperUsersStatement         { $stmt = st44; }
     ;
 
 /*
@@ -1357,6 +1358,15 @@ listRolesStatement returns [ListRolesStatement stmt]
       { $stmt = new ListRolesStatement(grantee, recursive); }
     ;
 
+/**
+ * LIST SUPERUSERS
+ */
+listSuperUsersStatement returns [ListSuperUsersStatement stmt]
+    @init {
+    }
+    : K_LIST K_SUPERUSERS { $stmt = new ListSuperUsersStatement(); }
+    ;
+
 roleOptions[RoleOptions opts, DCPermissions.Builder dcperms, 
CIDRPermissions.Builder cidrperms]
     : roleOption[opts, dcperms, cidrperms] (K_AND roleOption[opts, dcperms, 
cidrperms])*
     ;
@@ -1961,6 +1971,7 @@ basic_unreserved_keyword returns [String str]
         | K_ROLES
         | K_IDENTITY
         | K_SUPERUSER
+        | K_SUPERUSERS
         | K_NOSUPERUSER
         | K_LOGIN
         | K_NOLOGIN
diff --git a/src/java/org/apache/cassandra/audit/AuditLogEntryType.java 
b/src/java/org/apache/cassandra/audit/AuditLogEntryType.java
index 1055f875e0..17d4c98fea 100644
--- a/src/java/org/apache/cassandra/audit/AuditLogEntryType.java
+++ b/src/java/org/apache/cassandra/audit/AuditLogEntryType.java
@@ -70,7 +70,8 @@ public enum AuditLogEntryType
     REQUEST_FAILURE(AuditLogEntryCategory.ERROR),
     LOGIN_ERROR(AuditLogEntryCategory.AUTH),
     UNAUTHORIZED_ATTEMPT(AuditLogEntryCategory.AUTH),
-    LOGIN_SUCCESS(AuditLogEntryCategory.AUTH);
+    LOGIN_SUCCESS(AuditLogEntryCategory.AUTH),
+    LIST_SUPERUSERS(AuditLogEntryCategory.DCL);
 
     private final AuditLogEntryCategory category;
 
diff --git a/src/java/org/apache/cassandra/auth/Roles.java 
b/src/java/org/apache/cassandra/auth/Roles.java
index f18851af84..c4070aaff3 100644
--- a/src/java/org/apache/cassandra/auth/Roles.java
+++ b/src/java/org/apache/cassandra/auth/Roles.java
@@ -20,6 +20,7 @@ package org.apache.cassandra.auth;
 import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 import org.slf4j.Logger;
@@ -82,6 +83,19 @@ public class Roles
         return cache.getAllRoles();
     }
 
+    /**
+     * Gets all roles which pass the predicate.
+     *
+     * @param predicate a predicate to filter roles with
+     * @return unmodifiable set of role resources passing the predicate
+     */
+    public static Set<RoleResource> getAllRoles(Predicate<RoleResource> 
predicate)
+    {
+        return getAllRoles().stream()
+                            .filter(predicate)
+                            .collect(Collectors.toUnmodifiableSet());
+    }
+
     /**
      * Returns true if the supplied role or any other role granted to it
      * (directly or indirectly) has superuser status.
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/ListSuperUsersStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/ListSuperUsersStatement.java
new file mode 100644
index 0000000000..3c94a14195
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/ListSuperUsersStatement.java
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+package org.apache.cassandra.cql3.statements;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import org.apache.cassandra.audit.AuditLogContext;
+import org.apache.cassandra.audit.AuditLogEntryType;
+import org.apache.cassandra.auth.AuthKeyspace;
+import org.apache.cassandra.auth.Permission;
+import org.apache.cassandra.auth.RoleResource;
+import org.apache.cassandra.auth.Roles;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.cql3.ColumnSpecification;
+import org.apache.cassandra.cql3.ResultSet;
+import org.apache.cassandra.db.marshal.UTF8Type;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.exceptions.RequestExecutionException;
+import org.apache.cassandra.exceptions.RequestValidationException;
+import org.apache.cassandra.exceptions.UnauthorizedException;
+import org.apache.cassandra.schema.SchemaConstants;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.transport.messages.ResultMessage;
+
+/**
+ * LIST SUPERUSERS cql command returns list of roles with superuser privileges
+ * This includes superusers and all roles who have superuser role granted in 
the roles hierarchy
+ */
+public class ListSuperUsersStatement extends AuthorizationStatement
+{
+    private static final List<ColumnSpecification> metadata =
+    List.of(new ColumnSpecification(SchemaConstants.AUTH_KEYSPACE_NAME, 
AuthKeyspace.ROLES,
+                                    new ColumnIdentifier("role", true), 
UTF8Type.instance));
+
+    public ListSuperUsersStatement()
+    {
+        // nothing to do
+    }
+
+    public void validate(ClientState state) throws UnauthorizedException, 
InvalidRequestException
+    {
+        state.ensureNotAnonymous();
+    }
+
+    public void authorize(ClientState state) throws InvalidRequestException
+    {
+        // Allow listing superuser privileged users only if the caller has 
DESCRIBE permission on 'all roles'
+        if (!DatabaseDescriptor.getAuthorizer()
+                               .authorize(state.getUser(), RoleResource.root())
+                               .contains(Permission.DESCRIBE))
+        {
+            throw new UnauthorizedException("You are not authorized to view 
superuser details");
+        }
+    }
+
+    public ResultMessage execute(ClientState state) throws 
RequestValidationException, RequestExecutionException
+    {
+        Set<RoleResource> superUsers = 
Roles.getAllRoles(Roles::hasSuperuserStatus);
+        if (superUsers == null || superUsers.isEmpty())
+            return new ResultMessage.Void();
+
+        ResultSet result = new ResultSet(new 
ResultSet.ResultMetadata(metadata));
+
+        superUsers.stream()
+                  .sorted(RoleResource::compareTo)
+                  .forEach(role -> 
result.addColumnValue(UTF8Type.instance.decompose(role.getRoleName())));
+
+        return new ResultMessage.Rows(result);
+    }
+
+    @Override
+    public String toString()
+    {
+        return ToStringBuilder.reflectionToString(this, 
ToStringStyle.SHORT_PREFIX_STYLE);
+    }
+
+    @Override
+    public AuditLogContext getAuditLogContext()
+    {
+        return new AuditLogContext(AuditLogEntryType.LIST_SUPERUSERS);
+    }
+}
diff --git a/test/unit/org/apache/cassandra/audit/AuditLoggerAuthTest.java 
b/test/unit/org/apache/cassandra/audit/AuditLoggerAuthTest.java
index 50d20ea883..c4a9819f56 100644
--- a/test/unit/org/apache/cassandra/audit/AuditLoggerAuthTest.java
+++ b/test/unit/org/apache/cassandra/audit/AuditLoggerAuthTest.java
@@ -212,6 +212,16 @@ public class AuditLoggerAuthTest
         assertLogEntry(logEntry, AuditLogEntryType.LIST_ROLES, cql, CASS_USER, 
"");
     }
 
+    @Test
+    public void testCqlLISTSUPERUSERSAuditing()
+    {
+        String cql = "LIST SUPERUSERS";
+        executeWithCredentials(Arrays.asList(cql), CASS_USER, CASS_PW, 
AuditLogEntryType.LOGIN_SUCCESS);
+        assertTrue(getInMemAuditLogger().size() > 0);
+        AuditLogEntry logEntry = getInMemAuditLogger().poll();
+        assertLogEntry(logEntry, AuditLogEntryType.LIST_SUPERUSERS, cql, 
CASS_USER, "");
+    }
+
     @Test
     public void testCqlLISTPERMISSIONSAuditing()
     {
diff --git a/test/unit/org/apache/cassandra/auth/RolesTest.java 
b/test/unit/org/apache/cassandra/auth/RolesTest.java
index 7cdbaf2856..0a64d1ffc7 100644
--- a/test/unit/org/apache/cassandra/auth/RolesTest.java
+++ b/test/unit/org/apache/cassandra/auth/RolesTest.java
@@ -18,7 +18,10 @@
 
 package org.apache.cassandra.auth;
 
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import com.google.common.collect.Iterables;
 import org.junit.Assert;
@@ -32,6 +35,8 @@ import org.apache.cassandra.db.ConsistencyLevel;
 import static org.apache.cassandra.auth.AuthTestUtils.ALL_ROLES;
 import static org.apache.cassandra.auth.AuthTestUtils.ROLE_A;
 import static org.apache.cassandra.auth.AuthTestUtils.ROLE_B;
+import static org.apache.cassandra.auth.AuthTestUtils.ROLE_B_1;
+import static org.apache.cassandra.auth.AuthTestUtils.ROLE_B_2;
 import static org.apache.cassandra.auth.AuthTestUtils.ROLE_C;
 import static org.apache.cassandra.auth.AuthTestUtils.getRolesReadCount;
 import static org.apache.cassandra.auth.AuthTestUtils.grantRolesTo;
@@ -56,6 +61,13 @@ public class RolesTest
             roleManager.createRole(AuthenticatedUser.ANONYMOUS_USER, role, new 
RoleOptions());
         grantRolesTo(roleManager, ROLE_A, ROLE_B, ROLE_C);
 
+        RoleOptions roleOptions = new RoleOptions();
+        roleOptions.setOption(IRoleManager.Option.SUPERUSER, true);
+        RoleResource testSuperUser = RoleResource.role("testSuperuser");
+        roleManager.createRole(AuthenticatedUser.ANONYMOUS_USER, 
testSuperUser, roleOptions);
+        grantRolesTo(roleManager, ROLE_B_1, testSuperUser);
+        grantRolesTo(roleManager, ROLE_B_2, ROLE_B_1);
+
         roleManager.setup();
         AuthCacheService.initializeAndRegisterCaches();
     }
@@ -117,4 +129,14 @@ public class RolesTest
         ConsistencyLevel nonPrivWriteLevel = 
CassandraRoleManager.consistencyForRoleWrite("non-privilaged");
         Assert.assertEquals(nonPrivWriteLevel, 
DatabaseDescriptor.getAuthWriteConsistencyLevel());
     }
+
+    @Test
+    public void testSuperUsers()
+    {
+        Assert.assertEquals(new HashSet<>(Arrays.asList("testSuperuser", 
"role_b_1", "role_b_2")),
+                            Roles.getAllRoles(Roles::hasSuperuserStatus)
+                                 .stream()
+                                 .map(RoleResource::getRoleName)
+                                 .collect(Collectors.toSet()));
+    }
 }
diff --git 
a/test/unit/org/apache/cassandra/cql3/statements/ListSuperUsersStatementTest.java
 
b/test/unit/org/apache/cassandra/cql3/statements/ListSuperUsersStatementTest.java
new file mode 100644
index 0000000000..4b4c48b2a4
--- /dev/null
+++ 
b/test/unit/org/apache/cassandra/cql3/statements/ListSuperUsersStatementTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.
+ */
+
+package org.apache.cassandra.cql3.statements;
+
+import java.util.Collections;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.exceptions.UnauthorizedException;
+import org.apache.cassandra.auth.Roles;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.transport.messages.ResultMessage;
+import org.mockito.MockedStatic;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+public class ListSuperUsersStatementTest extends CQLTester
+{
+    @BeforeClass
+    public static void defineSchema() throws ConfigurationException
+    {
+        DatabaseDescriptor.setPermissionsValidity(0);
+        DatabaseDescriptor.setRolesValidity(0);
+        DatabaseDescriptor.setCredentialsValidity(0);
+
+        requireAuthentication();
+        requireNetwork();
+    }
+
+    @Test
+    public void testAcquiredSuperUsers() throws InterruptedException
+    {
+        useSuperUser();
+        assertRowsNet(executeNet("list superusers"), row("cassandra"));
+
+        executeNet("create role role1 with login=true and password='role1'");
+        executeNet("create role role11 with login=true and password='role11'");
+        executeNet("create role role2 with login=true and password='role2'");
+        assertRowsNet(executeNet("list superusers"), row("cassandra"));
+
+        executeNet("grant cassandra to role1");
+        executeNet("grant role1 to role11");
+        Roles.cache.invalidate();
+        assertRowsNet(executeNet("list superusers"), row("cassandra"), 
row("role1"), row("role11"));
+
+        useUser("role1", "role1");
+        assertRowsNet(executeNet("list superusers"), row("cassandra"), 
row("role1"), row("role11"));
+
+        useUser("role11", "role11");
+        assertRowsNet(executeNet("list superusers"), row("cassandra"), 
row("role1"), row("role11"));
+    }
+
+    @Test
+    public void testNoRoles()
+    {
+        try (MockedStatic<Roles> roles = mockStatic(Roles.class))
+        {
+            roles.when(Roles::getAllRoles).thenReturn(Collections.emptySet());
+            ClientState state = ClientState.forInternalCalls("system_auth");
+            ListSuperUsersStatement listSuperUsersStatement = new 
ListSuperUsersStatement();
+            ResultMessage result = listSuperUsersStatement.execute(state);
+            assertEquals("EMPTY RESULT", result.toString());
+        }
+    }
+
+    @Test
+    public void testGetAllRolesReturnsNull()
+    {
+        try (MockedStatic<Roles> roles = mockStatic(Roles.class))
+        {
+            roles.when(Roles::getAllRoles).thenReturn(null);
+            ClientState state = ClientState.forInternalCalls("system_auth");
+            ListSuperUsersStatement listSuperUsersStatement = new 
ListSuperUsersStatement();
+            ResultMessage result = listSuperUsersStatement.execute(state);
+            assertEquals("EMPTY RESULT", result.toString());
+        }
+    }
+
+    @Test
+    public void testNonSuperUserDescribePermission()
+    {
+        useSuperUser();
+        executeNet("create role nonsuper with login=true and 
password='nonsuper'");
+        Roles.cache.invalidate();
+
+        useUser("nonsuper", "nonsuper");
+        assertThatThrownBy(() -> executeNet("list superusers"))
+        .isInstanceOf(UnauthorizedException.class)
+        .hasMessage("You are not authorized to view superuser details");
+
+        useSuperUser();
+        executeNet("GRANT DESCRIBE ON ALL ROLES to nonsuper");
+        Roles.cache.invalidate();
+
+        useUser("nonsuper", "nonsuper");
+        ResultSet result = executeNet("list superusers");
+        // verify list command returned non-empty results
+        assertTrue(result.iterator().hasNext());
+    }
+
+    @Test
+    public void testListSuperUserStatementToString()
+    {
+        ListSuperUsersStatement listSuperUsersStatement = new 
ListSuperUsersStatement();
+        assertEquals("ListSuperUsersStatement[bindVariables=<null>]", 
listSuperUsersStatement.toString());
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org
For additional commands, e-mail: commits-h...@cassandra.apache.org

Reply via email to