Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 35 additions & 12 deletions ext/rcb_backend.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -179,18 +179,21 @@ cb_Backend_allocate(VALUE klass)
}

auto
construct_authenticator(VALUE credentials)
-> std::variant<couchbase::password_authenticator, couchbase::certificate_authenticator>
construct_authenticator(VALUE credentials) -> std::variant<couchbase::password_authenticator,
couchbase::certificate_authenticator,
couchbase::jwt_authenticator>
{
cb_check_type(credentials, T_HASH);

static const auto sym_certificate_path{ rb_id2sym(rb_intern("certificate_path")) };
static const auto sym_key_path{ rb_id2sym(rb_intern("key_path")) };
static const auto sym_jwt{ rb_id2sym(rb_intern("jwt")) };

const VALUE certificate_path = rb_hash_aref(credentials, sym_certificate_path);
const VALUE key_path = rb_hash_aref(credentials, sym_key_path);
const VALUE jwt = rb_hash_aref(credentials, sym_jwt);

if (NIL_P(certificate_path) || NIL_P(key_path)) {
if (NIL_P(certificate_path) && NIL_P(key_path) && NIL_P(jwt)) {
static const auto sym_username = rb_id2sym(rb_intern("username"));
static const auto sym_password = rb_id2sym(rb_intern("password"));

Expand All @@ -206,27 +209,45 @@ construct_authenticator(VALUE credentials)
};
}

cb_check_type(certificate_path, T_STRING);
cb_check_type(key_path, T_STRING);
if (NIL_P(jwt)) {
cb_check_type(certificate_path, T_STRING);
cb_check_type(key_path, T_STRING);

return couchbase::certificate_authenticator{
cb_string_new(certificate_path),
cb_string_new(key_path),
return couchbase::certificate_authenticator{
cb_string_new(certificate_path),
cb_string_new(key_path),
};
}

cb_check_type(jwt, T_STRING);
return couchbase::jwt_authenticator{
cb_string_new(jwt),
};
}

auto
construct_cluster_options(VALUE credentials, bool tls_enabled) -> couchbase::cluster_options
{
std::variant<couchbase::password_authenticator, couchbase::certificate_authenticator>
authenticator = construct_authenticator(credentials);
auto authenticator = construct_authenticator(credentials);

if (std::holds_alternative<couchbase::password_authenticator>(authenticator)) {
return couchbase::cluster_options{
std::get<couchbase::password_authenticator>(std::move(authenticator)),
};
}

if (std::holds_alternative<couchbase::jwt_authenticator>(authenticator)) {
if (!tls_enabled) {
throw ruby_exception(
exc_invalid_argument(),
"JWT authenticator requires TLS connection, check the connection string");
}

return couchbase::cluster_options{
std::get<couchbase::jwt_authenticator>(std::move(authenticator)),
};
}

if (!tls_enabled) {
throw ruby_exception(
exc_invalid_argument(),
Expand Down Expand Up @@ -572,13 +593,15 @@ cb_Backend_update_credentials(VALUE self, VALUE credentials)
auto cluster = cb_backend_to_public_api_cluster(self);

try {
std::variant<couchbase::password_authenticator, couchbase::certificate_authenticator>
authenticator = construct_authenticator(credentials);
auto authenticator = construct_authenticator(credentials);

couchbase::error err{};
if (std::holds_alternative<couchbase::password_authenticator>(authenticator)) {
err = cluster.set_authenticator(
std::get<couchbase::password_authenticator>(std::move(authenticator)));
} else if (std::holds_alternative<couchbase::jwt_authenticator>(authenticator)) {
err =
cluster.set_authenticator(std::get<couchbase::jwt_authenticator>(std::move(authenticator)));
} else {
err = cluster.set_authenticator(
std::get<couchbase::certificate_authenticator>(std::move(authenticator)));
Expand Down
14 changes: 14 additions & 0 deletions lib/couchbase/authenticator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,18 @@ def initialize(certificate_path, key_path)
@key_path = key_path
end
end

# Authenticator using a JSON Web Token (JWT)
#
# @!macro uncommitted
class JWTAuthenticator
attr_accessor :token

# Creates a new authenticator with a JSON Web Token (JWT)
#
# @param [String] token the JWT
def initialize(token)
@token = token
end
end
end
11 changes: 10 additions & 1 deletion lib/couchbase/cluster.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ def bucket(name)
Bucket.new(@backend, name, @observability)
end

# Updates the authenticator used for this cluster connection
#
# @param [PasswordAuthenticator, CertificateAuthenticator, JWTAuthenticator] authenticator the new authenticator
def update_authenticator(authenticator)
credentials = {}

Expand All @@ -114,6 +117,10 @@ def update_authenticator(authenticator)
credentials[:key_path] = authenticator.key_path
raise ArgumentError, "missing key path" unless credentials[:key_path]

when JWTAuthenticator
credentials[:jwt] = authenticator.token
raise ArgumentError, "missing token" unless credentials[:jwt]

else
raise ArgumentError, "argument must be an authenticator"
end
Expand Down Expand Up @@ -396,7 +403,9 @@ def initialize(connection_string, *args)

credentials[:key_path] = authenticator.key_path
raise ArgumentError, "missing key path" unless credentials[:key_path]

when JWTAuthenticator
credentials[:jwt] = authenticator.token
raise ArgumentError, "missing token" unless credentials[:jwt]
else
raise ArgumentError, "options must have authenticator configured"
end
Expand Down
2 changes: 1 addition & 1 deletion lib/couchbase/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1672,7 +1672,7 @@ def initialize(get_options: Get.new,
# @see .Cluster
#
class Cluster
attr_accessor :authenticator # @return [PasswordAuthenticator, CertificateAuthenticator]
attr_accessor :authenticator # @return [PasswordAuthenticator, CertificateAuthenticator, JWTAuthenticator]

attr_accessor :preferred_server_group # @return [String]

Expand Down
3 changes: 3 additions & 0 deletions lib/couchbase/protostellar/cluster.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ def self.connect(connection_string_or_config, *args)
when Couchbase::CertificateAuthenticator
raise Couchbase::Error::FeatureNotAvailable,
"The #{Couchbase::Protostellar::NAME} protocol does not support the CertificateAuthenticator"
when Couchbase::JWTAuthenticator
raise Couchbase::Error::FeatureNotAvailable,
"The #{Couchbase::Protostellar::NAME} protocol does not support the JWTAuthenticator"
else
raise ArgumentError, "options must have authenticator configured"
end
Expand Down
Loading