Skip to content

Commit 13751c3

Browse files
committed
add cassandra-5.0 module
1 parent 7535349 commit 13751c3

20 files changed

+1401
-1
lines changed

README.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ There are five implementation modules:
2626
* cassandra-3.0 - builds against version 3.0.28
2727
* cassandra-3.11 - builds against version 3.11.14
2828
* cassandra-4.0 - builds against version 4.0.7
29-
* cassandra-4.1 - builds aganst version 4.1.0
29+
* cassandra-4.1 - builds aganist version 4.1.0
30+
* cassandra-5.0 - builds aganist version 5.0.0
3031
3132
Project is built as:
3233

cassandra-5.0/pom.xml

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
3+
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<groupId>com.instaclustr</groupId>
8+
<artifactId>cassandra-ldap-parent</artifactId>
9+
<version>1.1.2</version>
10+
<relativePath>../pom.xml</relativePath>
11+
</parent>
12+
13+
<artifactId>cassandra-ldap-5.0.0</artifactId>
14+
<version>1.0.0</version>
15+
16+
<name>Cassandra LDAP Authenticator for Cassandra 5.0</name>
17+
<description>Pluggable LDAP authentication implementation for Apache Cassandra 5.0</description>
18+
19+
<properties>
20+
<version.cassandra50>5.0.0</version.cassandra50>
21+
22+
<version.shrinkwrap.bom>1.2.6</version.shrinkwrap.bom>
23+
<version.shrinkwrap.resolvers>3.1.3</version.shrinkwrap.resolvers>
24+
25+
<version.embedded.cassandra>4.0.1</version.embedded.cassandra>
26+
<version.cassandra.driver>3.11.0</version.cassandra.driver>
27+
<version.testng>6.14.3</version.testng>
28+
<version.awaitility>4.0.3</version.awaitility>
29+
<version.testcontainers>1.15.3</version.testcontainers>
30+
</properties>
31+
32+
<dependencyManagement>
33+
<dependencies>
34+
<dependency>
35+
<groupId>org.jboss.shrinkwrap</groupId>
36+
<artifactId>shrinkwrap-bom</artifactId>
37+
<version>${version.shrinkwrap.bom}</version>
38+
<type>pom</type>
39+
<scope>import</scope>
40+
</dependency>
41+
</dependencies>
42+
</dependencyManagement>
43+
44+
<dependencies>
45+
<dependency>
46+
<groupId>org.apache.cassandra</groupId>
47+
<artifactId>cassandra-all</artifactId>
48+
<version>${version.cassandra50}</version>
49+
<scope>provided</scope>
50+
</dependency>
51+
52+
<dependency>
53+
<groupId>com.instaclustr</groupId>
54+
<artifactId>cassandra-ldap-base</artifactId>
55+
<version>1.1.2</version>
56+
<exclusions>
57+
<exclusion>
58+
<groupId>org.apache.cassandra</groupId>
59+
<artifactId>cassandra-all</artifactId>
60+
</exclusion>
61+
</exclusions>
62+
</dependency>
63+
64+
<!-- test -->
65+
66+
<dependency>
67+
<groupId>org.jboss.shrinkwrap</groupId>
68+
<artifactId>shrinkwrap-depchain</artifactId>
69+
<type>pom</type>
70+
<scope>test</scope>
71+
</dependency>
72+
73+
<dependency>
74+
<groupId>org.jboss.shrinkwrap.resolver</groupId>
75+
<artifactId>shrinkwrap-resolver-depchain</artifactId>
76+
<version>${version.shrinkwrap.resolvers}</version>
77+
<scope>test</scope>
78+
<type>pom</type>
79+
</dependency>
80+
81+
<dependency>
82+
<groupId>com.github.nosan</groupId>
83+
<artifactId>embedded-cassandra</artifactId>
84+
<version>${version.embedded.cassandra}</version>
85+
<scope>test</scope>
86+
<exclusions>
87+
<exclusion>
88+
<groupId>com.datastax.oss</groupId>
89+
<artifactId>java-driver-core</artifactId>
90+
</exclusion>
91+
</exclusions>
92+
</dependency>
93+
94+
<dependency>
95+
<groupId>com.datastax.cassandra</groupId>
96+
<artifactId>cassandra-driver-core</artifactId>
97+
<version>${version.cassandra.driver}</version>
98+
<exclusions>
99+
<exclusion>
100+
<groupId>com.google.guava</groupId>
101+
<artifactId>guava</artifactId>
102+
</exclusion>
103+
<exclusion>
104+
<groupId>io.netty</groupId>
105+
<artifactId>netty-handler</artifactId>
106+
</exclusion>
107+
<exclusion>
108+
<groupId>io.netty</groupId>
109+
<artifactId>netty-buffer</artifactId>
110+
</exclusion>
111+
<exclusion>
112+
<groupId>io.netty</groupId>
113+
<artifactId>netty-codec</artifactId>
114+
</exclusion>
115+
</exclusions>
116+
</dependency>
117+
118+
<dependency>
119+
<groupId>org.testng</groupId>
120+
<artifactId>testng</artifactId>
121+
<version>${version.testng}</version>
122+
<scope>test</scope>
123+
</dependency>
124+
125+
<dependency>
126+
<groupId>org.awaitility</groupId>
127+
<artifactId>awaitility</artifactId>
128+
<version>${version.awaitility}</version>
129+
<scope>test</scope>
130+
</dependency>
131+
132+
<dependency>
133+
<groupId>org.testcontainers</groupId>
134+
<artifactId>testcontainers</artifactId>
135+
<version>${version.testcontainers}</version>
136+
<scope>test</scope>
137+
</dependency>
138+
</dependencies>
139+
140+
<build>
141+
<plugins>
142+
<plugin>
143+
<groupId>org.apache.maven.plugins</groupId>
144+
<artifactId>maven-shade-plugin</artifactId>
145+
<version>${maven.shade.plugin.version}</version>
146+
<configuration>
147+
<finalName>cassandra-ldap-${version.cassandra41}-${project.version}</finalName>
148+
</configuration>
149+
</plugin>
150+
<plugin>
151+
<groupId>org.vafer</groupId>
152+
<artifactId>jdeb</artifactId>
153+
<version>${version.jdeb}</version>
154+
</plugin>
155+
<plugin>
156+
<groupId>de.dentrassi.maven</groupId>
157+
<artifactId>rpm</artifactId>
158+
<version>${version.rpm}</version>
159+
<executions>
160+
<execution>
161+
<phase>package</phase>
162+
<goals>
163+
<goal>rpm</goal>
164+
</goals>
165+
<configuration>
166+
<requires>
167+
<require>
168+
<name>cassandra</name>
169+
<version>4.0</version>
170+
<greaterOrEqual/>
171+
</require>
172+
</requires>
173+
</configuration>
174+
</execution>
175+
</executions>
176+
</plugin>
177+
</plugins>
178+
</build>
179+
180+
</project>
181+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Package: [[name]]
2+
Version: [[version]]
3+
Section: misc
4+
Priority: optional
5+
Architecture: all
6+
Depends: cassandra (>= 5.0)
7+
Maintainer: [[maintainer]]
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package com.instaclustr.cassandra.ldap.auth;
19+
20+
import static java.lang.String.format;
21+
import static java.util.Collections.singletonList;
22+
import static org.apache.cassandra.db.ConsistencyLevel.LOCAL_ONE;
23+
24+
import java.util.Collections;
25+
26+
import com.google.common.base.Function;
27+
import org.apache.cassandra.auth.AuthKeyspace;
28+
import org.apache.cassandra.auth.LDAPCassandraRoleManager.Role;
29+
import org.apache.cassandra.cql3.CQLStatement;
30+
import org.apache.cassandra.cql3.QueryOptions;
31+
import org.apache.cassandra.cql3.QueryProcessor;
32+
import org.apache.cassandra.cql3.UntypedResultSet;
33+
import org.apache.cassandra.cql3.UntypedResultSet.Row;
34+
import org.apache.cassandra.cql3.statements.CreateRoleStatement;
35+
import org.apache.cassandra.cql3.statements.SelectStatement;
36+
import org.apache.cassandra.cql3.statements.GrantRoleStatement;
37+
import org.apache.cassandra.db.ConsistencyLevel;
38+
import org.apache.cassandra.db.marshal.UTF8Type;
39+
import org.apache.cassandra.exceptions.RequestExecutionException;
40+
import org.apache.cassandra.exceptions.RequestValidationException;
41+
import org.apache.cassandra.service.ClientState;
42+
import org.apache.cassandra.service.QueryState;
43+
import org.apache.cassandra.transport.Dispatcher;
44+
import org.apache.cassandra.transport.messages.ResultMessage;
45+
import org.apache.cassandra.utils.ByteBufferUtil;
46+
import org.slf4j.Logger;
47+
import org.slf4j.LoggerFactory;
48+
49+
public class Cassandra50SystemAuthRoles implements SystemAuthRoles
50+
{
51+
52+
private static final Logger logger = LoggerFactory.getLogger(SystemAuthRoles.class);
53+
54+
public static final String SELECT_ROLE_STATEMENT = "SELECT role FROM %s.%s where role = ?";
55+
56+
public static final String CREATE_ROLE_STATEMENT_WITH_LOGIN = "CREATE ROLE IF NOT EXISTS \"%s\" WITH LOGIN = true AND SUPERUSER = %s";
57+
58+
public static final String GRANT_ROLE_STATEMENT = "GRANT '%s' TO '%s'";
59+
60+
private ClientState clientState;
61+
62+
public void setClientState(ClientState clientState)
63+
{
64+
this.clientState = clientState;
65+
}
66+
67+
public ClientState getClientState()
68+
{
69+
return clientState;
70+
}
71+
72+
public boolean hasAdminRole(String role) throws RequestExecutionException
73+
{
74+
// Try looking up the 'cassandra' default role first, to avoid the range query if possible.
75+
String defaultSUQuery = "SELECT * FROM system_auth.roles WHERE role = '" + role + "'";
76+
String allUsersQuery = "SELECT * FROM system_auth.roles LIMIT 1";
77+
return !QueryProcessor.process(defaultSUQuery, ConsistencyLevel.ONE).isEmpty()
78+
|| !QueryProcessor.process(defaultSUQuery, ConsistencyLevel.QUORUM).isEmpty()
79+
|| !QueryProcessor.process(allUsersQuery, ConsistencyLevel.QUORUM).isEmpty();
80+
}
81+
82+
83+
public boolean hasAdminRole() throws RequestExecutionException
84+
{
85+
return hasAdminRole("cassandra");
86+
}
87+
88+
public CQLStatement prepare(String template, String keyspace, String table) {
89+
try {
90+
return QueryProcessor.parseStatement(String.format(template, keyspace, table)).prepare(ClientState.forInternalCalls());
91+
} catch (RequestValidationException e) {
92+
throw new AssertionError(e); // not supposed to happen
93+
}
94+
}
95+
96+
protected ConsistencyLevel getConsistencyForRole(String defaultSuperUserName, String role, ConsistencyLevel roleConsistencyLevel) {
97+
ConsistencyLevel cl = role.equals(defaultSuperUserName) ? ConsistencyLevel.QUORUM : roleConsistencyLevel;
98+
99+
logger.debug(String.format("Resolved consistency level for role %s: %s", role, cl));
100+
101+
return cl;
102+
}
103+
104+
// NullObject returned when a supplied role name not found in AuthKeyspace.ROLES
105+
protected static final Role NULL_ROLE = new Role(null, false, false, Collections.<String>emptySet());
106+
107+
protected static final Function<Row, Role> ROW_TO_ROLE = new Function<UntypedResultSet.Row, Role>() {
108+
public Role apply(UntypedResultSet.Row row) {
109+
try {
110+
return new Role(row.getString("role"),
111+
row.getBoolean("is_superuser"),
112+
row.getBoolean("can_login"),
113+
row.has("member_of") ? row.getSet("member_of", UTF8Type.instance)
114+
: Collections.<String>emptySet());
115+
}
116+
// Failing to deserialize a boolean in is_superuser or can_login will throw an NPE
117+
catch (NullPointerException e) {
118+
logger.warn("An invalid value has been detected in the {} table for role {}. If you are " +
119+
"unable to login, you may need to disable authentication and confirm " +
120+
"that values in that table are accurate", AuthKeyspace.ROLES, row.getString("role"));
121+
throw new RuntimeException(String.format("Invalid metadata has been detected for role %s", row.getString("role")), e);
122+
}
123+
124+
}
125+
};
126+
127+
128+
public boolean roleMissing(String dn) {
129+
assert getClientState() != null;
130+
131+
final SelectStatement selStmt = (SelectStatement) QueryProcessor.getStatement(format(SELECT_ROLE_STATEMENT,
132+
"system_auth",
133+
AuthKeyspace.ROLES),
134+
getClientState());
135+
136+
final ResultMessage.Rows rows = selStmt.execute(new QueryState(getClientState()),
137+
QueryOptions.forInternalCalls(singletonList(ByteBufferUtil.bytes(dn))),
138+
Dispatcher.RequestTime.forImmediateExecution());
139+
140+
return rows.result.isEmpty();
141+
}
142+
143+
public void createRole(String roleName, boolean superUser, String defaultRoleMembership)
144+
{
145+
final CreateRoleStatement createStmt = (CreateRoleStatement) QueryProcessor.getStatement(format(CREATE_ROLE_STATEMENT_WITH_LOGIN,
146+
roleName,
147+
superUser),
148+
getClientState());
149+
150+
createStmt.execute(new QueryState(getClientState()),
151+
QueryOptions.forInternalCalls(LOCAL_ONE, singletonList(ByteBufferUtil.bytes(roleName))),
152+
Dispatcher.RequestTime.forImmediateExecution());
153+
154+
if (defaultRoleMembership != null)
155+
{
156+
if (roleMissing(defaultRoleMembership))
157+
{
158+
logger.warn("Unable to add user to default role {} because it doesn't exist.", defaultRoleMembership);
159+
}
160+
else
161+
{
162+
logger.debug("Adding user {} to default role {}", roleName, defaultRoleMembership);
163+
final GrantRoleStatement grantRoleStmt = (GrantRoleStatement) QueryProcessor.getStatement(format(GRANT_ROLE_STATEMENT,
164+
defaultRoleMembership,
165+
roleName),
166+
getClientState());
167+
168+
grantRoleStmt.execute(new QueryState(getClientState()),
169+
QueryOptions.forInternalCalls(LOCAL_ONE, singletonList(ByteBufferUtil.bytes(roleName))),
170+
Dispatcher.RequestTime.forImmediateExecution());
171+
}
172+
}
173+
}
174+
175+
@Override
176+
public Role getRole(String name, ConsistencyLevel roleConsistencyLevel)
177+
throws RequestExecutionException, RequestValidationException {
178+
179+
SelectStatement loadRoleStatement = (SelectStatement) prepare("SELECT * from %s.%s WHERE role = ?", "system_auth", "roles");
180+
181+
ResultMessage.Rows rows = loadRoleStatement.execute(QueryState.forInternalCalls(),
182+
QueryOptions.forInternalCalls(getConsistencyForRole("cassandra", name, roleConsistencyLevel),
183+
Collections.singletonList(ByteBufferUtil.bytes(name))),
184+
Dispatcher.RequestTime.forImmediateExecution());
185+
186+
if (rows.result.isEmpty()) {
187+
return NULL_ROLE;
188+
}
189+
190+
return ROW_TO_ROLE.apply(UntypedResultSet.create(rows.result).one());
191+
}
192+
}

0 commit comments

Comments
 (0)