1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
use super::provider::{AuthProvider, AuthToken};
use super::sas_token_generator::SasTokenGenerator;
use super::types::{AuthType, ConnectionStringConfig};
use crate::service_bus_manager::ServiceBusError;
use async_trait::async_trait;
#[derive(Clone)]
pub struct ConnectionStringProvider {
config: ConnectionStringConfig,
key_name: String,
key: String,
sas_generator: SasTokenGenerator,
}
impl ConnectionStringProvider {
/// Creates a new ConnectionStringProvider from a connection string configuration.
///
/// Parses the connection string to extract the namespace, shared access key name,
/// and shared access key. Validates that all required components are present.
///
/// # Arguments
///
/// * `config` - Configuration containing the Service Bus connection string
///
/// # Returns
///
/// A configured ConnectionStringProvider ready for authentication
///
/// # Errors
///
/// Returns [`ServiceBusError::ConfigurationError`] if:
/// - Connection string is empty
/// - Connection string is missing required components (Endpoint, SharedAccessKeyName, SharedAccessKey)
/// - Connection string format is invalid
///
/// # Examples
///
/// ```no_run
/// use quetty_server::auth::{ConnectionStringProvider, ConnectionStringConfig};
///
/// let config = ConnectionStringConfig {
/// value: "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=abc123".to_string(),
/// };
///
/// let provider = ConnectionStringProvider::new(config)?;
/// ```
pub fn new(config: ConnectionStringConfig) -> Result<Self, ServiceBusError> {
if config.value.is_empty() {
return Err(ServiceBusError::ConfigurationError(
"Connection string cannot be empty".to_string(),
));
}
// Parse connection string to extract components
let mut namespace = None;
let mut key_name = None;
let mut key = None;
for part in config.value.split(';') {
let part = part.trim();
if part.is_empty() {
continue;
}
if let Some(endpoint) = part.strip_prefix("Endpoint=") {
// Extract namespace from endpoint like "sb://namespace.servicebus.windows.net/"
if let Some(ns_start) = endpoint.find("://") {
let ns_part = &endpoint[ns_start + 3..];
if let Some(dot_pos) = ns_part.find('.') {
namespace = Some(ns_part[..dot_pos].to_string());
}
}
} else if let Some(kn) = part.strip_prefix("SharedAccessKeyName=") {
key_name = Some(kn.to_string());
} else if let Some(k) = part.strip_prefix("SharedAccessKey=") {
key = Some(k.to_string());
}
}
let namespace = namespace.ok_or_else(|| {
ServiceBusError::ConfigurationError(
"Missing namespace in connection string".to_string(),
)
})?;
let key_name = key_name.ok_or_else(|| {
ServiceBusError::ConfigurationError(
"Missing SharedAccessKeyName in connection string".to_string(),
)
})?;
let key = key.ok_or_else(|| {
ServiceBusError::ConfigurationError(
"Missing SharedAccessKey in connection string".to_string(),
)
})?;
let sas_generator = SasTokenGenerator::new(namespace.clone());
Ok(Self {
config,
key_name,
key,
sas_generator,
})
}
/// Gets the original connection string value.
///
/// # Returns
///
/// The complete connection string as provided in the configuration
pub fn connection_string(&self) -> &str {
&self.config.value
}
}
#[async_trait]
impl AuthProvider for ConnectionStringProvider {
/// Authenticates using the connection string by generating a SAS token.
///
/// Creates a time-limited SAS token (24 hours) using the shared access key
/// from the connection string. The token can be used to authenticate
/// Service Bus operations.
///
/// # Returns
///
/// An [`AuthToken`] containing the SAS-based connection string and expiration
///
/// # Errors
///
/// Returns [`ServiceBusError`] if SAS token generation fails
async fn authenticate(&self) -> Result<AuthToken, ServiceBusError> {
// Generate a SAS token valid for 24 hours
let sas_token = self.sas_generator.generate_sas_token(
&self.key_name,
&self.key,
24, // 24 hours validity
)?;
// Create a connection string with the SAS token
let connection_string = self
.sas_generator
.create_connection_string_from_sas(&sas_token);
Ok(AuthToken {
token: connection_string,
token_type: "ConnectionString".to_string(),
expires_in_secs: Some(24 * 3600), // 24 hours in seconds
})
}
/// Returns the authentication type for this provider.
///
/// # Returns
///
/// [`AuthType::ConnectionString`] indicating connection string authentication
fn auth_type(&self) -> AuthType {
AuthType::ConnectionString
}
/// Indicates whether this provider's tokens require periodic refresh.
///
/// Connection string authentication uses SAS tokens that expire,
/// so refresh is required.
///
/// # Returns
///
/// `true` because SAS tokens have limited validity
fn requires_refresh(&self) -> bool {
true // SAS tokens expire, so we need refresh
}
}