Skip to content

Commit

Permalink
chore: snowflake junit test cases added for key pair auth (#34810)
Browse files Browse the repository at this point in the history
## Description
This PR adds JUnit test cases for snowflake Key auth pair feature added.
Following cases have been added:
- For basic authentication mechanism, when we do datasource create ->
hikari config should have non empty username and password and datasource
object inside hikari config should be null
- For key pair authentication mechanism with valid private key -> hikari
config should have datasource object which is of type
SnowflakeBasicDatasource and username, password should be null.
- For key pair authentication mechanism with invalid private key ->
error should be thrown with appropriate error message
- For key pair authentication mechanism with valid private key ->
SnowflakeKeyUtils.readEncryptedPrivateKey should return valid instance
of PrivateKey class.

Note: Valid private key generated for above test cases is unencrypted
private key (i.e. without passphrase). Adding test cases with private
key having passphrase was a little complex than anticipated, hence will
create a separate task and tackle it separately.


Fixes #34692
_or_  
Fixes `Issue URL`
> [!WARNING]  
> _If no issue exists, please create an issue first, and check with the
maintainers if the issue is valid._

## Automation

/ok-to-test tags="@tag.Perf"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/9856542221>
> Commit: 87074a7
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=9856542221&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Perf`
> Spec:
> <hr>Tue, 09 Jul 2024 12:20:08 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [x] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Tests**
- Implemented new tests for key pair authentication with both valid and
invalid private keys.
  - Added tests for basic authentication.
  - Introduced tests for reading an encrypted private key.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: “sneha122” <“[email protected]”>
  • Loading branch information
sneha122 and “sneha122” authored Jul 9, 2024
1 parent d64ddee commit 65bded9
Showing 1 changed file with 141 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@
import com.appsmith.external.models.DatasourceTestResult;
import com.appsmith.external.models.KeyPairAuth;
import com.appsmith.external.models.Property;
import com.appsmith.external.models.UploadedFile;
import com.external.plugins.exceptions.SnowflakeErrorMessages;
import com.external.plugins.exceptions.SnowflakePluginError;
import com.external.utils.ExecutionUtils;
import com.external.utils.SnowflakeKeyUtils;
import com.external.utils.ValidationUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariPoolMXBean;
import lombok.extern.slf4j.Slf4j;
import net.snowflake.client.jdbc.SnowflakeBasicDataSource;
import net.snowflake.client.jdbc.SnowflakeReauthenticationRequest;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
Expand All @@ -27,32 +30,25 @@
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.PrivateKey;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static com.appsmith.external.constants.Authentication.DB_AUTH;
import static com.appsmith.external.constants.Authentication.SNOWFLAKE_KEY_PAIR_AUTH;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;

@Slf4j
public class SnowflakePluginTest {
Expand All @@ -61,6 +57,82 @@ public class SnowflakePluginTest {

private final ObjectMapper objectMapper = new ObjectMapper();

private static String getPrivateKeyWithoutPEMFormatting() throws Exception {
// Generate a private key
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();

// Get encoded bytes of the private key
byte[] privateKeyEncoded = privateKey.getEncoded();

// Encode bytes to Base64
String privateKeyBase64 = Base64.getEncoder().encodeToString(privateKeyEncoded);
return privateKeyBase64;
}

private static String getValidUnEncryptedPrivateKey() throws Exception {
String privateKeyBase64 = getPrivateKeyWithoutPEMFormatting();

// Format as PEM format
StringBuilder pemFormat = new StringBuilder();
pemFormat.append("-----BEGIN PRIVATE KEY-----\n");
pemFormat.append(privateKeyBase64);
pemFormat.append("\n-----END PRIVATE KEY-----\n");

String finalEncodedString =
Base64.getEncoder().encodeToString(pemFormat.toString().getBytes(StandardCharsets.UTF_8));

return finalEncodedString;
}

private static DatasourceConfiguration createBasicAuthConfig() {
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();

// Creates authentication object
DBAuth auth = new DBAuth();
auth.setUsername("test");
auth.setPassword("test");
auth.setAuthenticationType(DB_AUTH);
datasourceConfiguration.setAuthentication(auth);

// Sets default properties
List<Property> properties = new ArrayList<>();
properties.add(new Property("warehouse", "warehouse"));
properties.add(new Property("db", "dbName"));
properties.add(new Property("schema", "schemaName"));
properties.add(new Property("role", "userRole"));
datasourceConfiguration.setUrl("invalid.host.name");
datasourceConfiguration.setProperties(properties);

return datasourceConfiguration;
}

private static DatasourceConfiguration createKeyPairAuthConfig(String privateKeyBase64) {
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
UploadedFile privateKeyFile = new UploadedFile("privateKeyFile", privateKeyBase64);

// Creates authentication object
KeyPairAuth auth = new KeyPairAuth();
auth.setUsername("test");
auth.setPrivateKey(privateKeyFile);
auth.setPassphrase("test");
auth.setAuthenticationType(SNOWFLAKE_KEY_PAIR_AUTH);
datasourceConfiguration.setAuthentication(auth);

// Sets default properties
List<Property> properties = new ArrayList<>();
properties.add(new Property("warehouse", "warehouse"));
properties.add(new Property("db", "dbName"));
properties.add(new Property("schema", "schemaName"));
properties.add(new Property("role", "userRole"));
datasourceConfiguration.setUrl("invalid.host.name");
datasourceConfiguration.setProperties(properties);

return datasourceConfiguration;
}

@Test
public void testValidateDatasource_withInvalidCredentials_returnsInvalids() {
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
Expand Down Expand Up @@ -272,4 +344,56 @@ public void verifyTemplatesHasQuotesAroundMustacheSubstitutions() throws IOExcep
assertEquals(mustacheMatchCount, enclosedMustacheMatchCount);
}
}

@Test
public void testKeyAuthPairAuthValidPrivateKey_shouldCreateHikariConfigWithoutErrors() throws Exception {
DatasourceConfiguration datasourceConfiguration = createKeyPairAuthConfig(getValidUnEncryptedPrivateKey());

Mono<HikariDataSource> datasourceCreateMono = pluginExecutor.datasourceCreate(datasourceConfiguration);

StepVerifier.create(datasourceCreateMono)
.assertNext(dataSource -> {
// These are null as these get set inside datasource object and not on HikariConfig itself
// For key pair authentication, username and password should be null
assertEquals(null, dataSource.getUsername());
assertEquals(null, dataSource.getPassword());
assertInstanceOf(SnowflakeBasicDataSource.class, dataSource.getDataSource());
})
.verifyComplete();
}

@Test
public void testKeyAuthPairAuthInvalidPrivateKey_shouldThrowError() throws Exception {
DatasourceConfiguration datasourceConfiguration = createKeyPairAuthConfig(getPrivateKeyWithoutPEMFormatting());

Mono<HikariDataSource> datasourceCreateMono = pluginExecutor.datasourceCreate(datasourceConfiguration);

StepVerifier.create(datasourceCreateMono)
.verifyErrorMessage(SnowflakeErrorMessages.UNABLE_TO_CREATE_CONNECTION_ERROR_MSG);
}

@Test
public void testBasicAuth_shouldCreateHikariConfigWithoutErrors() throws Exception {
DatasourceConfiguration datasourceConfiguration = createBasicAuthConfig();

Mono<HikariDataSource> datasourceCreateMono = pluginExecutor.datasourceCreate(datasourceConfiguration);

StepVerifier.create(datasourceCreateMono)
.assertNext(dataSource -> {
// Datasource object would be null in this case as it gets set only in case of key pair config
assertEquals("test", dataSource.getUsername());
assertEquals("test", dataSource.getPassword());
assertEquals(null, dataSource.getDataSource());
})
.verifyComplete();
}

public void testReadEncryptedPrivateKeyReturnsValidPrivateKey() throws Exception {
DatasourceConfiguration datasourceConfiguration = createKeyPairAuthConfig(getValidUnEncryptedPrivateKey());

KeyPairAuth auth = (KeyPairAuth) datasourceConfiguration.getAuthentication();
PrivateKey privateKey = SnowflakeKeyUtils.readEncryptedPrivateKey(
auth.getPrivateKey().getDecodedContent(), auth.getPassphrase());
assertInstanceOf(PrivateKey.class, privateKey);
}
}

0 comments on commit 65bded9

Please sign in to comment.