Skip to content

Commit cdaf596

Browse files
authored
RCBC-522: Support for JWT authentication (#218)
1 parent 0827267 commit cdaf596

File tree

5 files changed

+63
-14
lines changed

5 files changed

+63
-14
lines changed

ext/rcb_backend.cxx

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -179,18 +179,21 @@ cb_Backend_allocate(VALUE klass)
179179
}
180180

181181
auto
182-
construct_authenticator(VALUE credentials)
183-
-> std::variant<couchbase::password_authenticator, couchbase::certificate_authenticator>
182+
construct_authenticator(VALUE credentials) -> std::variant<couchbase::password_authenticator,
183+
couchbase::certificate_authenticator,
184+
couchbase::jwt_authenticator>
184185
{
185186
cb_check_type(credentials, T_HASH);
186187

187188
static const auto sym_certificate_path{ rb_id2sym(rb_intern("certificate_path")) };
188189
static const auto sym_key_path{ rb_id2sym(rb_intern("key_path")) };
190+
static const auto sym_jwt{ rb_id2sym(rb_intern("jwt")) };
189191

190192
const VALUE certificate_path = rb_hash_aref(credentials, sym_certificate_path);
191193
const VALUE key_path = rb_hash_aref(credentials, sym_key_path);
194+
const VALUE jwt = rb_hash_aref(credentials, sym_jwt);
192195

193-
if (NIL_P(certificate_path) || NIL_P(key_path)) {
196+
if (NIL_P(certificate_path) && NIL_P(key_path) && NIL_P(jwt)) {
194197
static const auto sym_username = rb_id2sym(rb_intern("username"));
195198
static const auto sym_password = rb_id2sym(rb_intern("password"));
196199

@@ -206,27 +209,45 @@ construct_authenticator(VALUE credentials)
206209
};
207210
}
208211

209-
cb_check_type(certificate_path, T_STRING);
210-
cb_check_type(key_path, T_STRING);
212+
if (NIL_P(jwt)) {
213+
cb_check_type(certificate_path, T_STRING);
214+
cb_check_type(key_path, T_STRING);
211215

212-
return couchbase::certificate_authenticator{
213-
cb_string_new(certificate_path),
214-
cb_string_new(key_path),
216+
return couchbase::certificate_authenticator{
217+
cb_string_new(certificate_path),
218+
cb_string_new(key_path),
219+
};
220+
}
221+
222+
cb_check_type(jwt, T_STRING);
223+
return couchbase::jwt_authenticator{
224+
cb_string_new(jwt),
215225
};
216226
}
217227

218228
auto
219229
construct_cluster_options(VALUE credentials, bool tls_enabled) -> couchbase::cluster_options
220230
{
221-
std::variant<couchbase::password_authenticator, couchbase::certificate_authenticator>
222-
authenticator = construct_authenticator(credentials);
231+
auto authenticator = construct_authenticator(credentials);
223232

224233
if (std::holds_alternative<couchbase::password_authenticator>(authenticator)) {
225234
return couchbase::cluster_options{
226235
std::get<couchbase::password_authenticator>(std::move(authenticator)),
227236
};
228237
}
229238

239+
if (std::holds_alternative<couchbase::jwt_authenticator>(authenticator)) {
240+
if (!tls_enabled) {
241+
throw ruby_exception(
242+
exc_invalid_argument(),
243+
"JWT authenticator requires TLS connection, check the connection string");
244+
}
245+
246+
return couchbase::cluster_options{
247+
std::get<couchbase::jwt_authenticator>(std::move(authenticator)),
248+
};
249+
}
250+
230251
if (!tls_enabled) {
231252
throw ruby_exception(
232253
exc_invalid_argument(),
@@ -572,13 +593,15 @@ cb_Backend_update_credentials(VALUE self, VALUE credentials)
572593
auto cluster = cb_backend_to_public_api_cluster(self);
573594

574595
try {
575-
std::variant<couchbase::password_authenticator, couchbase::certificate_authenticator>
576-
authenticator = construct_authenticator(credentials);
596+
auto authenticator = construct_authenticator(credentials);
577597

578598
couchbase::error err{};
579599
if (std::holds_alternative<couchbase::password_authenticator>(authenticator)) {
580600
err = cluster.set_authenticator(
581601
std::get<couchbase::password_authenticator>(std::move(authenticator)));
602+
} else if (std::holds_alternative<couchbase::jwt_authenticator>(authenticator)) {
603+
err =
604+
cluster.set_authenticator(std::get<couchbase::jwt_authenticator>(std::move(authenticator)));
582605
} else {
583606
err = cluster.set_authenticator(
584607
std::get<couchbase::certificate_authenticator>(std::move(authenticator)));

lib/couchbase/authenticator.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,18 @@ def initialize(certificate_path, key_path)
6363
@key_path = key_path
6464
end
6565
end
66+
67+
# Authenticator using a JSON Web Token (JWT)
68+
#
69+
# @!macro uncommitted
70+
class JWTAuthenticator
71+
attr_accessor :token
72+
73+
# Creates a new authenticator with a JSON Web Token (JWT)
74+
#
75+
# @param [String] token the JWT
76+
def initialize(token)
77+
@token = token
78+
end
79+
end
6680
end

lib/couchbase/cluster.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ def bucket(name)
9696
Bucket.new(@backend, name, @observability)
9797
end
9898

99+
# Updates the authenticator used for this cluster connection
100+
#
101+
# @param [PasswordAuthenticator, CertificateAuthenticator, JWTAuthenticator] authenticator the new authenticator
99102
def update_authenticator(authenticator)
100103
credentials = {}
101104

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

120+
when JWTAuthenticator
121+
credentials[:jwt] = authenticator.token
122+
raise ArgumentError, "missing token" unless credentials[:jwt]
123+
117124
else
118125
raise ArgumentError, "argument must be an authenticator"
119126
end
@@ -396,7 +403,9 @@ def initialize(connection_string, *args)
396403

397404
credentials[:key_path] = authenticator.key_path
398405
raise ArgumentError, "missing key path" unless credentials[:key_path]
399-
406+
when JWTAuthenticator
407+
credentials[:jwt] = authenticator.token
408+
raise ArgumentError, "missing token" unless credentials[:jwt]
400409
else
401410
raise ArgumentError, "options must have authenticator configured"
402411
end

lib/couchbase/options.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1672,7 +1672,7 @@ def initialize(get_options: Get.new,
16721672
# @see .Cluster
16731673
#
16741674
class Cluster
1675-
attr_accessor :authenticator # @return [PasswordAuthenticator, CertificateAuthenticator]
1675+
attr_accessor :authenticator # @return [PasswordAuthenticator, CertificateAuthenticator, JWTAuthenticator]
16761676

16771677
attr_accessor :preferred_server_group # @return [String]
16781678

lib/couchbase/protostellar/cluster.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ def self.connect(connection_string_or_config, *args)
7979
when Couchbase::CertificateAuthenticator
8080
raise Couchbase::Error::FeatureNotAvailable,
8181
"The #{Couchbase::Protostellar::NAME} protocol does not support the CertificateAuthenticator"
82+
when Couchbase::JWTAuthenticator
83+
raise Couchbase::Error::FeatureNotAvailable,
84+
"The #{Couchbase::Protostellar::NAME} protocol does not support the JWTAuthenticator"
8285
else
8386
raise ArgumentError, "options must have authenticator configured"
8487
end

0 commit comments

Comments
 (0)