Skip to content

Commit

Permalink
Use JSON file for token auth
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex-Monahan committed Nov 16, 2024
1 parent e8d239c commit 960d1e1
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 47 deletions.
8 changes: 3 additions & 5 deletions src/gsheets_auth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,11 @@ namespace duckdb
auto result = make_uniq<KeyValueSecret>(scope, input.type, input.provider, input.name);

// Manage specific secret option
CopySecret("secret", input, *result);
CopySecret("email", input, *result);
CopySecret("filename", input, *result);

// Redact sensible keys
RedactCommonKeys(*result);
result->redact_keys.insert("secret");
result->redact_keys.insert("filename");

return std::move(result);
}
Expand Down Expand Up @@ -123,8 +122,7 @@ namespace duckdb

// Register the private key secret provider
CreateSecretFunction private_key_function = {type, "private_key", CreateGsheetSecretFromPrivateKey};
private_key_function.named_parameters["email"] = LogicalType::VARCHAR;
private_key_function.named_parameters["secret"] = LogicalType::VARCHAR;
private_key_function.named_parameters["filename"] = LogicalType::VARCHAR;
RegisterCommonSecretParameters(private_key_function);
ExtensionUtil::RegisterFunction(instance, private_key_function);
}
Expand Down
21 changes: 10 additions & 11 deletions src/gsheets_copy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,23 +57,22 @@ namespace duckdb
if (secret.GetProvider() == "private_key") {
// If using a private key, retrieve the private key from the secret, but convert it
// into a token before use. This is an extra request per Google Sheet read or copy.
Value email_value;
if (!kv_secret->TryGetValue("email", email_value)) {
throw InvalidInputException("'email' not found in 'gsheet' secret");
}
std::string email_string = email_value.ToString();
// The secret is the JSON file that is extracted from Google as per the README


Value secret_value;
if (!kv_secret->TryGetValue("secret", secret_value)) {
throw InvalidInputException("'secret' not found in 'gsheet' secret");
Value filename_value;
if (!kv_secret->TryGetValue("filename", filename_value)) {
throw InvalidInputException("'filename' not found in 'gsheet' secret");
}
std::string secret_string = std::regex_replace(secret_value.ToString(), std::regex(R"(\\n)"), "\n");
std::string filename_string = filename_value.ToString();

std::string token_plus_metadata = get_token(filename_string);

json token_json = parseJson(get_token(email_string, secret_string));
json token_json = parseJson(token_plus_metadata);

json failure_string = "failure";
if (token_json["status"] == failure_string) {
throw InvalidInputException("Conversion from private key to token failed. Check key format (-----BEGIN PRIVATE KEY-----\\n ... -----END PRIVATE KEY-----\\n) and expiration date.");
throw InvalidInputException("Conversion from private key to token failed. Check email, key format in JSON file (-----BEGIN PRIVATE KEY-----\\n ... -----END PRIVATE KEY-----\\n), and expiration date.");
}

token = token_json["access_token"].get<std::string>();
Expand Down
35 changes: 16 additions & 19 deletions src/gsheets_get_token.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,16 @@ namespace duckdb
output[output_index] = '\0';
}

std::string get_token(const std::string& email, const std::string& private_key) {
std::string get_token(const std::string& filename) {
const char *header = "{\"alg\":\"RS256\",\"typ\":\"JWT\"}";

const int iat = time(NULL);
const int exp = iat + 60 * 60 - 1;

char claim_set[1024];

/* Create jwt claim set */
json jwt_claim_set;
std::time_t t = std::time(NULL);
std::ifstream ifs(filename);
json credentials_file = json::parse(ifs);
std::string email = credentials_file["client_email"].get<std::string>();

jwt_claim_set["iss"] = email; /* service account email address */
jwt_claim_set["scope"] = "https://www.googleapis.com/auth/spreadsheets" /* scope of requested access token */;
jwt_claim_set["aud"] = "https://accounts.google.com/o/oauth2/token"; /* intended target of the assertion for an access token */
Expand All @@ -80,13 +79,11 @@ namespace duckdb
// set the limit to 10 minutes. (Max that Google allows is 1 hour)
jwt_claim_set["exp"] = std::to_string(t+600); /* expire time*/

std::copy(jwt_claim_set.dump().cbegin(), jwt_claim_set.dump().cend(), claim_set);

char header_64[1024];
base64encode(header_64, header, strlen(header));

char claim_set_64[1024];
base64encode(claim_set_64, claim_set, strlen(claim_set));
base64encode(claim_set_64, jwt_claim_set.dump().c_str(), strlen(jwt_claim_set.dump().c_str()));

char input[1024];
int input_length = sprintf(input, "%s.%s", header_64, claim_set_64);
Expand All @@ -99,18 +96,17 @@ namespace duckdb

digest_str[SHA256_DIGEST_LENGTH * 2] = '\0';

// This works, but requires a file. The in-memory version is below.
// FILE *key_file = fopen("credentials.key.pem", "r");
// RSA *rsa = PEM_read_RSAPrivateKey(key_file, NULL, NULL, NULL);

BIO *bufio;
RSA *rsa;

bufio = BIO_new_mem_buf(private_key.c_str(), -1);
PEM_read_bio_RSAPrivateKey(bufio, &rsa, 0, NULL);
std::string private_key_string = credentials_file["private_key"].get<std::string>();

BIO* bio = BIO_new(BIO_s_mem());
const void * private_key_pointer = private_key_string.c_str();
int private_key_length = std::strlen(private_key_string.c_str());
BIO_write(bio, private_key_pointer, private_key_length);
EVP_PKEY* evp_key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
RSA* rsa = EVP_PKEY_get1_RSA(evp_key);

if (rsa != NULL) {
unsigned char sigret[4096];
unsigned char sigret[4096] = {};
unsigned int siglen;
if (RSA_sign(NID_sha256, digest, SHA256_DIGEST_LENGTH, sigret, &siglen, rsa)) {
if (RSA_verify(NID_sha256, digest, SHA256_DIGEST_LENGTH, sigret, siglen, rsa)) {
Expand All @@ -137,6 +133,7 @@ namespace duckdb
RSA_free(rsa);
}
std::string failure_message = "{\"status\":\"failed\"}";

return failure_message;
}
}
21 changes: 10 additions & 11 deletions src/gsheets_read.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,23 +137,22 @@ unique_ptr<FunctionData> ReadSheetBind(ClientContext &context, TableFunctionBind
if (secret.GetProvider() == "private_key") {
// If using a private key, retrieve the private key from the secret, but convert it
// into a token before use. This is an extra request per Google Sheet read or copy.
Value email_value;
if (!kv_secret->TryGetValue("email", email_value)) {
throw InvalidInputException("'email' not found in 'gsheet' secret");
}
std::string email_string = email_value.ToString();
// The secret is the JSON file that is extracted from Google as per the README


Value secret_value;
if (!kv_secret->TryGetValue("secret", secret_value)) {
throw InvalidInputException("'secret' not found in 'gsheet' secret");
Value filename_value;
if (!kv_secret->TryGetValue("filename", filename_value)) {
throw InvalidInputException("'filename' not found in 'gsheet' secret");
}
std::string secret_string = std::regex_replace(secret_value.ToString(), std::regex(R"(\\n)"), "\n");
std::string filename_string = filename_value.ToString();

std::string token_plus_metadata = get_token(filename_string);

json token_json = parseJson(get_token(email_string, secret_string));
json token_json = parseJson(token_plus_metadata);

json failure_string = "failure";
if (token_json["status"] == failure_string) {
throw InvalidInputException("Conversion from private key to token failed. Check key format (-----BEGIN PRIVATE KEY-----\\n ... -----END PRIVATE KEY-----\\n) and expiration date.");
throw InvalidInputException("Conversion from private key to token failed. Check email, key format in JSON file (-----BEGIN PRIVATE KEY-----\\n ... -----END PRIVATE KEY-----\\n), and expiration date.");
}

token = token_json["access_token"].get<std::string>();
Expand Down
2 changes: 1 addition & 1 deletion src/include/gsheets_get_token.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ namespace duckdb {

void base64encode(char *output, const char *input, size_t input_length) ;

std::string get_token(const std::string& email, const std::string& private_key) ;
std::string get_token(const std::string& filename) ;

}

0 comments on commit 960d1e1

Please sign in to comment.