blob: 93f8b001f664711ec7ef880b82d13323dbf585cf [file] [log] [blame]
/*
* 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.functions.masking;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.BeforeClass;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.cql3.functions.ScalarFunction;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.ReversedType;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.SchemaKeyspace;
import org.apache.cassandra.schema.SchemaKeyspaceTables;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.UserFunctions;
import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
/**
* Tests table columns with attached dynamic data masking functions.
*/
public class ColumnMaskTester extends CQLTester
{
protected static final String USERNAME = "ddm_user";
protected static final String PASSWORD = "ddm_password";
@BeforeClass
public static void beforeClass()
{
CQLTester.setUpClass();
requireAuthentication();
requireNetwork();
}
@Before
public void before() throws Throwable
{
DatabaseDescriptor.setDynamicDataMaskingEnabled(true);
useSuperUser();
executeNet(format("CREATE USER IF NOT EXISTS %s WITH PASSWORD '%s'", USERNAME, PASSWORD));
executeNet(format("GRANT ALL ON KEYSPACE %s TO %s", KEYSPACE, USERNAME));
executeNet(format("REVOKE UNMASK ON KEYSPACE %s FROM %s", KEYSPACE, USERNAME));
useUser(USERNAME, PASSWORD);
}
protected void assertTableColumnsAreNotMasked(String... columns) throws Throwable
{
for (String column : columns)
{
assertColumnIsNotMasked(currentTable(), column);
}
}
protected void assertViewColumnsAreNotMasked(String... columns) throws Throwable
{
for (String column : columns)
{
assertColumnIsNotMasked(currentView(), column);
}
}
protected void assertColumnIsNotMasked(String table, String column) throws Throwable
{
ColumnMask mask = getColumnMask(table, column);
assertNull(format("Mask for column '%s'", column), mask);
assertRows(execute(format("SELECT * FROM %s.%s WHERE keyspace_name = ? AND table_name = ? AND column_name = ?",
SchemaConstants.SCHEMA_KEYSPACE_NAME, SchemaKeyspaceTables.COLUMN_MASKS),
KEYSPACE, table, column));
}
protected void assertColumnIsMasked(String table,
String column,
String functionName,
List<AbstractType<?>> partialArgumentTypes,
List<ByteBuffer> partialArgumentValues) throws Throwable
{
KeyspaceMetadata keyspaceMetadata = Keyspace.open(KEYSPACE).getMetadata();
TableMetadata tableMetadata = keyspaceMetadata.getTableOrViewNullable(table);
assertNotNull(tableMetadata);
ColumnMetadata columnMetadata = tableMetadata.getColumn(ColumnIdentifier.getInterned(column, false));
assertNotNull(columnMetadata);
AbstractType<?> columnType = columnMetadata.type;
// Verify the column mask in the in-memory schema
ColumnMask mask = getColumnMask(table, column);
assertNotNull(mask);
assertThat(mask.partialArgumentTypes()).isEqualTo(columnType.isReversed() && functionName.equals("mask_replace")
? Collections.singletonList(ReversedType.getInstance(partialArgumentTypes.get(0)))
: partialArgumentTypes);
assertThat(mask.partialArgumentValues()).isEqualTo(partialArgumentValues);
// Verify the function in the column mask
ScalarFunction function = mask.function;
assertNotNull(function);
assertThat(function.name().name).isEqualTo(functionName);
assertThat(function.argTypes().get(0).asCQL3Type()).isEqualTo(columnMetadata.type.asCQL3Type());
assertThat(function.argTypes().size()).isEqualTo(partialArgumentTypes.size() + 1);
// Retrieve the persisted column metadata
UntypedResultSet columnRows = execute("SELECT * FROM system_schema.columns " +
"WHERE keyspace_name = ? AND table_name = ? AND column_name = ?",
KEYSPACE, table, column);
ColumnMetadata persistedColumn = SchemaKeyspace.createColumnFromRow(columnRows.one(), keyspaceMetadata.types, UserFunctions.none());
// Verify the column mask in the persisted schema
ColumnMask savedMask = persistedColumn.getMask();
assertNotNull(savedMask);
assertThat(mask).isEqualTo(savedMask);
assertThat(mask.function.argTypes()).isEqualTo(savedMask.function.argTypes());
}
@Nullable
protected ColumnMask getColumnMask(String table, String column)
{
TableMetadata tableMetadata = Schema.instance.getTableMetadata(KEYSPACE, table);
assertNotNull(tableMetadata);
ColumnMetadata columnMetadata = tableMetadata.getColumn(ColumnIdentifier.getInterned(column, false));
if (columnMetadata == null)
fail(format("Unknown column '%s'", column));
return columnMetadata.getMask();
}
}