Skip to content

Commit 35b68b1

Browse files
committed
RSCBC-200: Add support for JWT authentication
1 parent be74b7a commit 35b68b1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+581
-553
lines changed

sdk/couchbase-core/src/agent.rs

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ use std::sync::{Arc, Weak};
7878
use std::time::Duration;
7979

8080
use crate::componentconfigs::{AgentComponentConfigs, HttpClientConfig};
81+
use crate::httpx::request::{Auth, BasicAuth, BearerAuth};
8182
use crate::kvclient_babysitter::{KvTarget, StdKvClientBabysitter};
8283
use crate::kvendpointclientmanager::{
8384
KvEndpointClientManager, KvEndpointClientManagerOptions, StdKvEndpointClientManager,
@@ -314,17 +315,7 @@ impl Agent {
314315
let client_name = format!("couchbase-rs-core {build_version}");
315316
info!("Creating new agent {client_name}");
316317

317-
let auth_mechanisms = if opts.auth_mechanisms.is_empty() {
318-
if opts.tls_config.is_some() {
319-
vec![AuthMechanism::Plain]
320-
} else {
321-
vec![
322-
AuthMechanism::ScramSha512,
323-
AuthMechanism::ScramSha256,
324-
AuthMechanism::ScramSha1,
325-
]
326-
}
327-
} else {
318+
let auth_mechanisms = if !opts.auth_mechanisms.is_empty() {
328319
if opts.tls_config.is_none() && opts.auth_mechanisms.contains(&AuthMechanism::Plain) {
329320
warn!("PLAIN sends credentials in plaintext, this will cause credential leakage on the network");
330321
} else if opts.tls_config.is_some()
@@ -336,6 +327,8 @@ impl Agent {
336327
}
337328

338329
opts.auth_mechanisms
330+
} else {
331+
vec![]
339332
};
340333

341334
let mut state = AgentState {
@@ -672,21 +665,24 @@ impl Agent {
672665
for endpoint_config in http_configs.values() {
673666
let endpoint = endpoint_config.endpoint.clone();
674667
let host = get_host_port_from_uri(&endpoint)?;
675-
let user_pass = match &endpoint_config.authenticator {
668+
let auth = match &endpoint_config.authenticator {
676669
Authenticator::PasswordAuthenticator(authenticator) => {
677-
authenticator.get_credentials(&ServiceType::MGMT, host)?
670+
let user_pass = authenticator.get_credentials(&ServiceType::MGMT, host)?;
671+
Auth::BasicAuth(BasicAuth::new(user_pass.username, user_pass.password))
672+
}
673+
Authenticator::CertificateAuthenticator(_authenticator) => {
674+
Auth::BasicAuth(BasicAuth::new("".to_string(), "".to_string()))
678675
}
679-
Authenticator::CertificateAuthenticator(authenticator) => {
680-
authenticator.get_credentials(&ServiceType::MGMT, host)?
676+
Authenticator::JwtAuthenticator(authenticator) => {
677+
Auth::BearerAuth(BearerAuth::new(authenticator.get_token()))
681678
}
682679
};
683680

684681
match Self::fetch_http_config(
685682
http_client.clone(),
686683
endpoint,
687684
endpoint_config.user_agent.clone(),
688-
user_pass.username,
689-
user_pass.password,
685+
auth,
690686
endpoint_config.bucket_name.clone(),
691687
)
692688
.await
@@ -709,8 +705,7 @@ impl Agent {
709705
http_client: Arc<C>,
710706
endpoint: String,
711707
user_agent: String,
712-
username: String,
713-
password: String,
708+
auth: Auth,
714709
bucket_name: Option<String>,
715710
) -> Result<ParsedConfig> {
716711
debug!("Polling config from {}", &endpoint);
@@ -723,8 +718,7 @@ impl Agent {
723718
http_client,
724719
user_agent,
725720
endpoint,
726-
username,
727-
password,
721+
auth,
728722
}
729723
.get_terse_bucket_config(&GetTerseBucketConfigOptions {
730724
bucket_name: &bucket_name,
@@ -739,8 +733,7 @@ impl Agent {
739733
http_client,
740734
user_agent,
741735
endpoint,
742-
username,
743-
password,
736+
auth,
744737
}
745738
.get_terse_cluster_config(&GetTerseClusterConfigOptions {
746739
on_behalf_of_info: None,

sdk/couchbase-core/src/auth_mechanism.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub enum AuthMechanism {
2525
ScramSha1,
2626
ScramSha256,
2727
ScramSha512,
28+
OAuthBearer,
2829
}
2930

3031
impl From<AuthMechanism> for Vec<u8> {
@@ -34,6 +35,7 @@ impl From<AuthMechanism> for Vec<u8> {
3435
AuthMechanism::ScramSha1 => "SCRAM-SHA1",
3536
AuthMechanism::ScramSha256 => "SCRAM-SHA256",
3637
AuthMechanism::ScramSha512 => "SCRAM-SHA512",
38+
AuthMechanism::OAuthBearer => "OAUTHBEARER",
3739
};
3840

3941
txt.into()
@@ -49,6 +51,7 @@ impl TryFrom<&str> for AuthMechanism {
4951
"SCRAM-SHA1" => AuthMechanism::ScramSha1,
5052
"SCRAM-SHA256" => AuthMechanism::ScramSha256,
5153
"SCRAM-SHA512" => AuthMechanism::ScramSha512,
54+
"OAUTHBEARER" => AuthMechanism::OAuthBearer,
5255
_ => {
5356
return Err(Error::new_invalid_argument_error(
5457
format!("unsupported auth mechanism {value}"),
@@ -68,6 +71,7 @@ impl From<AuthMechanism> for memdx::auth_mechanism::AuthMechanism {
6871
AuthMechanism::ScramSha1 => memdx::auth_mechanism::AuthMechanism::ScramSha1,
6972
AuthMechanism::ScramSha256 => memdx::auth_mechanism::AuthMechanism::ScramSha256,
7073
AuthMechanism::ScramSha512 => memdx::auth_mechanism::AuthMechanism::ScramSha512,
74+
AuthMechanism::OAuthBearer => memdx::auth_mechanism::AuthMechanism::OAuthBearer,
7175
}
7276
}
7377
}

sdk/couchbase-core/src/authenticator.rs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,26 @@
1616
*
1717
*/
1818

19-
use std::fmt::Debug;
20-
19+
use crate::auth_mechanism::AuthMechanism;
2120
use crate::error::Result;
2221
use crate::service_type::ServiceType;
22+
use std::fmt::Debug;
2323

2424
#[derive(Debug, Clone, PartialEq, Hash)]
2525
#[non_exhaustive]
2626
pub enum Authenticator {
2727
PasswordAuthenticator(PasswordAuthenticator),
2828
CertificateAuthenticator(CertificateAuthenticator),
29+
JwtAuthenticator(JwtAuthenticator),
2930
}
3031

31-
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
32+
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
3233
pub struct UserPassPair {
3334
pub username: String,
3435
pub password: String,
3536
}
3637

37-
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
38+
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
3839
pub struct PasswordAuthenticator {
3940
pub username: String,
4041
pub password: String,
@@ -51,6 +52,14 @@ impl PasswordAuthenticator {
5152
password: self.password.clone(),
5253
})
5354
}
55+
56+
pub fn get_auth_mechanisms(&self) -> Vec<AuthMechanism> {
57+
vec![
58+
AuthMechanism::ScramSha512,
59+
AuthMechanism::ScramSha256,
60+
AuthMechanism::ScramSha1,
61+
]
62+
}
5463
}
5564

5665
impl From<PasswordAuthenticator> for Authenticator {
@@ -81,3 +90,24 @@ impl From<CertificateAuthenticator> for Authenticator {
8190
Authenticator::CertificateAuthenticator(value)
8291
}
8392
}
93+
94+
#[derive(Debug, Clone, PartialEq, Hash)]
95+
pub struct JwtAuthenticator {
96+
pub token: String,
97+
}
98+
99+
impl JwtAuthenticator {
100+
pub fn get_token(&self) -> &str {
101+
&self.token
102+
}
103+
104+
pub fn get_auth_mechanisms(&self) -> Vec<AuthMechanism> {
105+
vec![AuthMechanism::OAuthBearer]
106+
}
107+
}
108+
109+
impl From<JwtAuthenticator> for Authenticator {
110+
fn from(value: JwtAuthenticator) -> Self {
111+
Authenticator::JwtAuthenticator(value)
112+
}
113+
}

sdk/couchbase-core/src/httpcomponent.rs

Lines changed: 38 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use crate::authenticator::Authenticator;
2020
use crate::error;
2121
use crate::error::ErrorKind;
2222
use crate::httpx::client::Client;
23+
use crate::httpx::request::{Auth, BasicAuth, BearerAuth};
2324
use crate::retrybesteffort::BackoffCalculator;
2425
use crate::service_type::ServiceType;
2526
use crate::util::get_host_port_from_uri;
@@ -44,12 +45,37 @@ pub(crate) struct HttpComponentState {
4445
}
4546

4647
pub(crate) struct HttpEndpointProperties {
47-
pub username: String,
48-
pub password: String,
48+
pub auth: Auth,
4949
pub endpoint: String,
5050
pub endpoint_id: Option<String>,
5151
}
5252

53+
pub(crate) fn auth_from_authenticator(
54+
authenticator: &Authenticator,
55+
service_type: &ServiceType,
56+
host: &str,
57+
) -> error::Result<Auth> {
58+
match authenticator {
59+
Authenticator::PasswordAuthenticator(authenticator) => {
60+
let creds = authenticator.get_credentials(service_type, host.to_string())?;
61+
Ok(Auth::BasicAuth(BasicAuth::new(
62+
creds.username,
63+
creds.password,
64+
)))
65+
}
66+
Authenticator::CertificateAuthenticator(authenticator) => {
67+
let creds = authenticator.get_credentials(service_type, host.to_string())?;
68+
Ok(Auth::BasicAuth(BasicAuth::new(
69+
creds.username,
70+
creds.password,
71+
)))
72+
}
73+
Authenticator::JwtAuthenticator(authenticator) => {
74+
Ok(Auth::BearerAuth(BearerAuth::new(authenticator.get_token())))
75+
}
76+
}
77+
}
78+
5379
impl<C: Client> HttpComponent<C> {
5480
pub fn new(
5581
service_type: ServiceType,
@@ -94,22 +120,14 @@ impl<C: Client> HttpComponent<C> {
94120
};
95121

96122
let host = get_host_port_from_uri(found_endpoint)?;
97-
let user_pass = match &state.authenticator {
98-
Authenticator::PasswordAuthenticator(authenticator) => {
99-
authenticator.get_credentials(&self.service_type, host)?
100-
}
101-
Authenticator::CertificateAuthenticator(a) => {
102-
a.get_credentials(&self.service_type, host)?
103-
}
104-
};
123+
let auth = auth_from_authenticator(&state.authenticator, &self.service_type, &host)?;
105124

106125
Ok((
107126
self.client.clone(),
108127
HttpEndpointProperties {
109128
endpoint_id: None,
110129
endpoint: found_endpoint.clone(),
111-
username: user_pass.username,
112-
password: user_pass.password,
130+
auth,
113131
},
114132
))
115133
}
@@ -145,22 +163,14 @@ impl<C: Client> HttpComponent<C> {
145163
let endpoint = remaining_endpoints[endpoint_id];
146164

147165
let host = get_host_port_from_uri(endpoint)?;
148-
let user_pass = match &state.authenticator {
149-
Authenticator::PasswordAuthenticator(authenticator) => {
150-
authenticator.get_credentials(&self.service_type, host)?
151-
}
152-
Authenticator::CertificateAuthenticator(a) => {
153-
a.get_credentials(&self.service_type, host)?
154-
}
155-
};
166+
let auth = auth_from_authenticator(&state.authenticator, &self.service_type, &host)?;
156167

157168
Ok(Some((
158169
self.client.clone(),
159170
HttpEndpointProperties {
160171
endpoint_id: Some(endpoint_id.clone()),
161172
endpoint: endpoint.clone(),
162-
username: user_pass.username,
163-
password: user_pass.password,
173+
auth,
164174
},
165175
)))
166176
}
@@ -172,7 +182,7 @@ impl<C: Client> HttpComponent<C> {
172182
pub async fn orchestrate_endpoint<Resp, Fut>(
173183
&self,
174184
endpoint_id: Option<String>,
175-
operation: impl Fn(Arc<C>, String, String, String, String) -> Fut + Send + Sync,
185+
operation: impl Fn(Arc<C>, String, String, Auth) -> Fut + Send + Sync,
176186
) -> error::Result<Resp>
177187
where
178188
C: Client,
@@ -186,8 +196,7 @@ impl<C: Client> HttpComponent<C> {
186196
client,
187197
endpoint_id,
188198
endpoint_properties.endpoint,
189-
endpoint_properties.username,
190-
endpoint_properties.password,
199+
endpoint_properties.auth,
191200
)
192201
.await;
193202
}
@@ -205,8 +214,7 @@ impl<C: Client> HttpComponent<C> {
205214
client,
206215
endpoint_properties.endpoint_id.unwrap_or_default(),
207216
endpoint_properties.endpoint,
208-
endpoint_properties.username,
209-
endpoint_properties.password,
217+
endpoint_properties.auth,
210218
)
211219
.await
212220
}
@@ -231,21 +239,9 @@ impl<C: Client> HttpComponent<C> {
231239
let mut targets = Vec::with_capacity(remaining_endpoints.len());
232240
for (_ep_id, endpoint) in remaining_endpoints {
233241
let host = get_host_port_from_uri(endpoint)?;
242+
let auth = auth_from_authenticator(&state.authenticator, &self.service_type, &host)?;
234243

235-
let user_pass = match &state.authenticator {
236-
Authenticator::PasswordAuthenticator(authenticator) => {
237-
authenticator.get_credentials(&self.service_type, host)?
238-
}
239-
Authenticator::CertificateAuthenticator(a) => {
240-
a.get_credentials(&self.service_type, host)?
241-
}
242-
};
243-
244-
targets.push(T::new(
245-
endpoint.clone(),
246-
user_pass.username,
247-
user_pass.password,
248-
));
244+
targets.push(T::new(endpoint.clone(), auth));
249245
}
250246

251247
Ok((self.client.clone(), targets))
@@ -289,5 +285,5 @@ impl HttpComponentState {
289285
}
290286

291287
pub(crate) trait NodeTarget {
292-
fn new(endpoint: String, username: String, password: String) -> Self;
288+
fn new(endpoint: String, auth: Auth) -> Self;
293289
}

sdk/couchbase-core/src/httpx/client.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ impl Client for ReqwestClient {
172172
Auth::BasicAuth(basic) => {
173173
builder = builder.basic_auth(&basic.username, Some(&basic.password))
174174
}
175+
Auth::BearerAuth(bearer) => builder = builder.bearer_auth(&bearer.token),
175176
Auth::OnBehalfOf(obo) => {
176177
match &obo.password_or_domain {
177178
OboPasswordOrDomain::Password(password) => {

sdk/couchbase-core/src/httpx/request.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,24 @@ impl BasicAuth {
9393
}
9494
}
9595

96-
#[derive(PartialEq, Eq, Debug)]
96+
#[derive(PartialEq, Eq, Debug, Clone)]
97+
pub struct BearerAuth {
98+
pub(crate) token: String,
99+
}
100+
101+
impl BearerAuth {
102+
pub fn new(token: impl Into<String>) -> Self {
103+
Self {
104+
token: token.into(),
105+
}
106+
}
107+
}
108+
109+
#[derive(Clone, PartialEq, Eq, Debug)]
97110
#[non_exhaustive]
98111
pub enum Auth {
99112
BasicAuth(BasicAuth),
113+
BearerAuth(BearerAuth),
100114
OnBehalfOf(OnBehalfOfInfo),
101115
}
102116

0 commit comments

Comments
 (0)