omq-rs 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +4 -3
- data/ext/omq_rs_native/Cargo.toml +3 -3
- data/ext/omq_rs_native/src/auth.rs +59 -10
- data/ext/omq_rs_native/src/lib.rs +1 -1
- data/ext/omq_rs_native/src/options.rs +1 -15
- data/ext/omq_rs_native/src/socket.rs +137 -10
- data/lib/omq/rs/socket.rb +54 -7
- data/lib/omq/rs/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d916c6123be10c4d2689f3ced757ae56a96a762ed91a48331c2cc2ae6f5fcef5
|
|
4
|
+
data.tar.gz: 7e19c87b521e037a0d739486c42ab0f2586377754cb0f7dc087b4d581a81820c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4eca9973111eaf059d20637412340440cfa856ff21c99174a12dd4e9f802c077b6cc517ecb5475e0f619252a6f29d5df4ec22838c0700a6d0200dd2060d71abb
|
|
7
|
+
data.tar.gz: fede7168c871a78f2449b2ed5f92eafb943bafa72fa5050240c2d642f42f4beeba5acee0eea200f114563acee8f2fab11eda19599c7babd9be55441e23565415
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.2.0] - 2026-09-04
|
|
6
|
+
|
|
7
|
+
- Add PLAIN username/password allowlists and authentication callbacks through
|
|
8
|
+
`plain_auth:` and `Socket#set_plain_auth`.
|
|
9
|
+
- Expand authenticator peer metadata with transport address and PLAIN
|
|
10
|
+
credentials.
|
|
11
|
+
- Require explicit PLAIN server policy; bare PLAIN server mode fails closed.
|
|
12
|
+
- Reject CURVE public keys that do not match the configured secret key.
|
|
13
|
+
- Require `omq-proto` 0.27.0 and `omq-tokio` 0.22.0.
|
|
14
|
+
|
|
5
15
|
## [0.1.2] - 2026-09-01
|
|
6
16
|
|
|
7
17
|
- Route `Socket#try_recv` directly through the native nonblocking receive path.
|
data/README.md
CHANGED
|
@@ -94,9 +94,10 @@ an `OMQ::Rust::MechanismPeerInfo`, or `nil` to accept every valid CURVE client.
|
|
|
94
94
|
Configure it before the first bind, connect, send, receive, or monitor call.
|
|
95
95
|
`OMQ::Rust.curve_public(secret_key)` derives a public key.
|
|
96
96
|
|
|
97
|
-
PLAIN
|
|
98
|
-
`
|
|
99
|
-
|
|
97
|
+
PLAIN servers require an explicit policy. Pass fixed credentials with
|
|
98
|
+
`plain_server: true, plain_auth: [["alice", "secret"]]`, or pass a callable as
|
|
99
|
+
`plain_auth:`. Clients use `plain_username` plus `plain_password`. PLAIN
|
|
100
|
+
authenticates without encryption; use it only on trusted transports.
|
|
100
101
|
|
|
101
102
|
## Compression
|
|
102
103
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "omq_rs_native"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
rust-version = "1.93"
|
|
6
6
|
license = "ISC"
|
|
@@ -20,8 +20,8 @@ zstd = ["omq-tokio/zstd"]
|
|
|
20
20
|
ws = ["omq-tokio/ws"]
|
|
21
21
|
|
|
22
22
|
[dependencies]
|
|
23
|
-
omq-proto = { version = ">=0.
|
|
24
|
-
omq-tokio = { version = ">=0.
|
|
23
|
+
omq-proto = { version = ">=0.27.0, <0.28.0", default-features = false }
|
|
24
|
+
omq-tokio = { version = ">=0.22.0, <0.23.0", default-features = false }
|
|
25
25
|
yring = { version = "=0.3.14", features = ["async"] }
|
|
26
26
|
|
|
27
27
|
bytes = "1.12.0"
|
|
@@ -6,6 +6,7 @@ use bytes::Bytes;
|
|
|
6
6
|
use rb_sys::VALUE;
|
|
7
7
|
|
|
8
8
|
use crate::notify::PipeNotify;
|
|
9
|
+
#[cfg(feature = "curve")]
|
|
9
10
|
use crate::options::parse_curve_public_key;
|
|
10
11
|
use crate::rb::{self, RbResult};
|
|
11
12
|
|
|
@@ -13,6 +14,9 @@ enum AuthRequest {
|
|
|
13
14
|
Check {
|
|
14
15
|
public_key: [u8; 32],
|
|
15
16
|
identity: Option<Bytes>,
|
|
17
|
+
peer_address: Option<String>,
|
|
18
|
+
username: Option<String>,
|
|
19
|
+
password: Option<String>,
|
|
16
20
|
reply: flume::Sender<bool>,
|
|
17
21
|
},
|
|
18
22
|
Stop,
|
|
@@ -84,32 +88,73 @@ unsafe extern "C" fn auth_worker_main(data: *mut c_void) -> VALUE {
|
|
|
84
88
|
while let Some(AuthRequest::Check {
|
|
85
89
|
public_key,
|
|
86
90
|
identity,
|
|
91
|
+
peer_address,
|
|
92
|
+
username,
|
|
93
|
+
password,
|
|
87
94
|
reply,
|
|
88
95
|
}) = wait_for_request(&data)
|
|
89
96
|
{
|
|
90
|
-
let accepted = invoke_callback(
|
|
97
|
+
let accepted = invoke_callback(
|
|
98
|
+
data.callback,
|
|
99
|
+
public_key,
|
|
100
|
+
identity.as_ref(),
|
|
101
|
+
peer_address.as_deref(),
|
|
102
|
+
username.as_deref(),
|
|
103
|
+
password.as_deref(),
|
|
104
|
+
);
|
|
91
105
|
let _ = reply.send(accepted);
|
|
92
106
|
}
|
|
93
107
|
}));
|
|
94
108
|
rb::qnil()
|
|
95
109
|
}
|
|
96
110
|
|
|
97
|
-
fn invoke_callback(
|
|
111
|
+
fn invoke_callback(
|
|
112
|
+
callback: VALUE,
|
|
113
|
+
public_key: [u8; 32],
|
|
114
|
+
identity: Option<&Bytes>,
|
|
115
|
+
peer_address: Option<&str>,
|
|
116
|
+
username: Option<&str>,
|
|
117
|
+
password: Option<&str>,
|
|
118
|
+
) -> bool {
|
|
98
119
|
let result = (|| -> RbResult<VALUE> {
|
|
99
120
|
let peer = rb::hash_new()?;
|
|
100
|
-
let key =
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
121
|
+
let key = if username.is_some() {
|
|
122
|
+
rb::qnil()
|
|
123
|
+
} else {
|
|
124
|
+
#[cfg(feature = "curve")]
|
|
125
|
+
{
|
|
126
|
+
let key = omq_proto::CurvePublicKey::from_bytes(public_key)
|
|
127
|
+
.to_z85()
|
|
128
|
+
.into_bytes();
|
|
129
|
+
rb::new_binary_string(&key)?
|
|
130
|
+
}
|
|
131
|
+
#[cfg(not(feature = "curve"))]
|
|
132
|
+
{
|
|
133
|
+
let _ = public_key;
|
|
134
|
+
rb::qnil()
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
rb::hash_aset(peer, rb::symbol("public_key")?, key)?;
|
|
108
138
|
let identity = match identity {
|
|
109
139
|
Some(value) => rb::new_binary_string(value)?,
|
|
110
140
|
None => rb::qnil(),
|
|
111
141
|
};
|
|
112
142
|
rb::hash_aset(peer, rb::symbol("identity")?, identity)?;
|
|
143
|
+
let peer_address = match peer_address {
|
|
144
|
+
Some(value) => rb::new_utf8_string(value)?,
|
|
145
|
+
None => rb::qnil(),
|
|
146
|
+
};
|
|
147
|
+
rb::hash_aset(peer, rb::symbol("peer_address")?, peer_address)?;
|
|
148
|
+
let username = match username {
|
|
149
|
+
Some(value) => rb::new_utf8_string(value)?,
|
|
150
|
+
None => rb::qnil(),
|
|
151
|
+
};
|
|
152
|
+
rb::hash_aset(peer, rb::symbol("username")?, username)?;
|
|
153
|
+
let password = match password {
|
|
154
|
+
Some(value) => rb::new_utf8_string(value)?,
|
|
155
|
+
None => rb::qnil(),
|
|
156
|
+
};
|
|
157
|
+
rb::hash_aset(peer, rb::symbol("password")?, password)?;
|
|
113
158
|
rb::call_method_1(callback, c"call", peer)
|
|
114
159
|
})();
|
|
115
160
|
|
|
@@ -121,6 +166,7 @@ fn invoke_callback(callback: VALUE, public_key: [u8; 32], identity: Option<&Byte
|
|
|
121
166
|
}
|
|
122
167
|
}
|
|
123
168
|
|
|
169
|
+
#[cfg(feature = "curve")]
|
|
124
170
|
pub fn allowed_keys(value: VALUE) -> RbResult<omq_proto::Authenticator> {
|
|
125
171
|
let count = rb::array_len(value)?;
|
|
126
172
|
let mut keys = std::collections::HashSet::with_capacity(count);
|
|
@@ -162,6 +208,9 @@ pub fn callback(callback: VALUE) -> RbResult<(omq_proto::Authenticator, AuthWork
|
|
|
162
208
|
let request = AuthRequest::Check {
|
|
163
209
|
public_key: peer.public_key,
|
|
164
210
|
identity: peer.identity.clone(),
|
|
211
|
+
peer_address: peer.peer_address.clone(),
|
|
212
|
+
username: peer.username.clone(),
|
|
213
|
+
password: peer.password.clone(),
|
|
165
214
|
reply,
|
|
166
215
|
};
|
|
167
216
|
if auth_sender.send(request).is_err() {
|
|
@@ -212,7 +212,6 @@ fn apply_mechanism(hash: VALUE, mech_type: &str, opts: &mut omq_tokio::Options)
|
|
|
212
212
|
.ok_or_else(|| RubyErr::arg("CURVE server requires curve_secretkey"))?,
|
|
213
213
|
"curve_secretkey",
|
|
214
214
|
)?;
|
|
215
|
-
validate_curve_keypair(&public, &secret)?;
|
|
216
215
|
opts.mechanism = omq_proto::MechanismSetup::CurveServer {
|
|
217
216
|
our_keypair: omq_proto::CurveKeypair { public, secret },
|
|
218
217
|
options: omq_proto::CurveServerOptions::default(),
|
|
@@ -236,7 +235,6 @@ fn apply_mechanism(hash: VALUE, mech_type: &str, opts: &mut omq_tokio::Options)
|
|
|
236
235
|
.ok_or_else(|| RubyErr::arg("CURVE client requires curve_secretkey"))?,
|
|
237
236
|
"curve_secretkey",
|
|
238
237
|
)?;
|
|
239
|
-
validate_curve_keypair(&public, &secret)?;
|
|
240
238
|
let server_public = parse_curve_public_key(
|
|
241
239
|
&server_key
|
|
242
240
|
.ok_or_else(|| RubyErr::arg("CURVE client requires curve_serverkey"))?,
|
|
@@ -253,7 +251,7 @@ fn apply_mechanism(hash: VALUE, mech_type: &str, opts: &mut omq_tokio::Options)
|
|
|
253
251
|
"plain" => {
|
|
254
252
|
if get_opt_bool_alias(hash, &["plain_server", "mechanism_server"])?.unwrap_or(false) {
|
|
255
253
|
opts.mechanism = omq_proto::MechanismSetup::PlainServer {
|
|
256
|
-
authenticator: omq_proto::Authenticator::new(|_|
|
|
254
|
+
authenticator: omq_proto::Authenticator::new(|_| false),
|
|
257
255
|
};
|
|
258
256
|
} else {
|
|
259
257
|
let username =
|
|
@@ -296,18 +294,6 @@ fn curve_secret_key(bytes: &[u8], label: &str) -> RbResult<omq_proto::CurveSecre
|
|
|
296
294
|
.map_err(|error| RubyErr::arg(format!("invalid {label}: {error}")))
|
|
297
295
|
}
|
|
298
296
|
|
|
299
|
-
#[cfg(feature = "curve")]
|
|
300
|
-
fn validate_curve_keypair(
|
|
301
|
-
public: &omq_proto::CurvePublicKey,
|
|
302
|
-
secret: &omq_proto::CurveSecretKey,
|
|
303
|
-
) -> RbResult<()> {
|
|
304
|
-
if secret.derive_public().as_bytes() == public.as_bytes() {
|
|
305
|
-
Ok(())
|
|
306
|
-
} else {
|
|
307
|
-
Err(RubyErr::arg("CURVE public and secret keys do not match"))
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
297
|
fn option_present(hash: VALUE, keys: &[&str]) -> RbResult<bool> {
|
|
312
298
|
for key in keys {
|
|
313
299
|
if rb::hash_get(hash, key)?.is_some() {
|
|
@@ -28,8 +28,10 @@ pub struct RustSocket {
|
|
|
28
28
|
materialized: RwLock<Option<Materialized>>,
|
|
29
29
|
closed: AtomicBool,
|
|
30
30
|
linger: Mutex<Option<std::time::Duration>>,
|
|
31
|
-
#[cfg(feature = "curve")]
|
|
31
|
+
#[cfg(any(feature = "curve", feature = "plain"))]
|
|
32
32
|
auth_worker: Mutex<Option<crate::auth::AuthWorker>>,
|
|
33
|
+
#[cfg(feature = "plain")]
|
|
34
|
+
plain_auth_configured: AtomicBool,
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
unsafe impl Send for RustSocket {}
|
|
@@ -71,7 +73,7 @@ unsafe extern "C" fn rust_socket_mark(ptr: *mut c_void) {
|
|
|
71
73
|
}
|
|
72
74
|
let _ = catch_unwind(AssertUnwindSafe(|| {
|
|
73
75
|
let socket = unsafe { &*ptr.cast::<RustSocket>() };
|
|
74
|
-
#[cfg(feature = "curve")]
|
|
76
|
+
#[cfg(any(feature = "curve", feature = "plain"))]
|
|
75
77
|
if let Some(worker) = socket.auth_worker.lock().unwrap().as_ref() {
|
|
76
78
|
unsafe {
|
|
77
79
|
rb_sys::rb_gc_mark(worker.callback());
|
|
@@ -139,8 +141,10 @@ fn rust_socket_new_impl(class: VALUE, type_str: VALUE) -> RbResult<VALUE> {
|
|
|
139
141
|
materialized: RwLock::new(None),
|
|
140
142
|
closed: AtomicBool::new(false),
|
|
141
143
|
linger: Mutex::new(None),
|
|
142
|
-
#[cfg(feature = "curve")]
|
|
144
|
+
#[cfg(any(feature = "curve", feature = "plain"))]
|
|
143
145
|
auth_worker: Mutex::new(None),
|
|
146
|
+
#[cfg(feature = "plain")]
|
|
147
|
+
plain_auth_configured: AtomicBool::new(false),
|
|
144
148
|
}),
|
|
145
149
|
rust_socket_data_type(),
|
|
146
150
|
)
|
|
@@ -206,6 +210,95 @@ fn set_curve_authenticator(
|
|
|
206
210
|
Ok(())
|
|
207
211
|
}
|
|
208
212
|
|
|
213
|
+
#[cfg(feature = "plain")]
|
|
214
|
+
fn set_plain_authenticator(
|
|
215
|
+
rb_self: &RustSocket,
|
|
216
|
+
authenticator: omq_proto::Authenticator,
|
|
217
|
+
mut worker: Option<crate::auth::AuthWorker>,
|
|
218
|
+
) -> RbResult<()> {
|
|
219
|
+
if rb_self.materialized.read().unwrap().is_some() {
|
|
220
|
+
if let Some(worker) = worker.take() {
|
|
221
|
+
worker.stop();
|
|
222
|
+
}
|
|
223
|
+
return Err(RubyErr::runtime(
|
|
224
|
+
"PLAIN authentication must be configured before bind or connect",
|
|
225
|
+
));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let mut options_guard = rb_self.options.lock().unwrap();
|
|
229
|
+
let options = options_guard
|
|
230
|
+
.as_mut()
|
|
231
|
+
.ok_or_else(|| RubyErr::runtime("socket options not configured"))?;
|
|
232
|
+
if let omq_proto::MechanismSetup::PlainServer {
|
|
233
|
+
authenticator: configured,
|
|
234
|
+
} = &mut options.mechanism
|
|
235
|
+
{
|
|
236
|
+
*configured = authenticator;
|
|
237
|
+
} else {
|
|
238
|
+
drop(options_guard);
|
|
239
|
+
if let Some(worker) = worker.take() {
|
|
240
|
+
worker.stop();
|
|
241
|
+
}
|
|
242
|
+
return Err(RubyErr::runtime(
|
|
243
|
+
"PLAIN authentication requires a PLAIN server socket",
|
|
244
|
+
));
|
|
245
|
+
}
|
|
246
|
+
drop(options_guard);
|
|
247
|
+
|
|
248
|
+
let previous = rb_self.auth_worker.lock().unwrap().take();
|
|
249
|
+
if let Some(previous) = previous {
|
|
250
|
+
previous.stop();
|
|
251
|
+
}
|
|
252
|
+
*rb_self.auth_worker.lock().unwrap() = worker;
|
|
253
|
+
rb_self.plain_auth_configured.store(true, Ordering::Release);
|
|
254
|
+
Ok(())
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
#[cfg(feature = "plain")]
|
|
258
|
+
unsafe extern "C" fn rust_socket_set_plain_auth_credentials(
|
|
259
|
+
rb_self: VALUE,
|
|
260
|
+
credentials: VALUE,
|
|
261
|
+
) -> VALUE {
|
|
262
|
+
rb::wrap(|| {
|
|
263
|
+
let rb_self = unsafe { rust_socket_ref(rb_self)? };
|
|
264
|
+
let count = rb::array_len(credentials)?;
|
|
265
|
+
let mut allowlist = Vec::with_capacity(count);
|
|
266
|
+
for index in 0..count {
|
|
267
|
+
let pair = rb::array_entry(credentials, index)?;
|
|
268
|
+
if rb::array_len(pair)? != 2 {
|
|
269
|
+
return Err(RubyErr::arg(
|
|
270
|
+
"each PLAIN credential must be a [username, password] pair",
|
|
271
|
+
));
|
|
272
|
+
}
|
|
273
|
+
let username = rb::value_to_string(rb::array_entry(pair, 0)?)?;
|
|
274
|
+
let password = rb::value_to_string(rb::array_entry(pair, 1)?)?;
|
|
275
|
+
if username.len() > 255
|
|
276
|
+
|| password.len() > 255
|
|
277
|
+
|| !username.bytes().all(|byte| byte.is_ascii_graphic())
|
|
278
|
+
|| !password.bytes().all(|byte| byte.is_ascii_graphic())
|
|
279
|
+
{
|
|
280
|
+
return Err(RubyErr::arg(
|
|
281
|
+
"PLAIN credentials must contain at most 255 ASCII VCHAR bytes",
|
|
282
|
+
));
|
|
283
|
+
}
|
|
284
|
+
allowlist.push((username, password));
|
|
285
|
+
}
|
|
286
|
+
let authenticator = omq_proto::Authenticator::plain_credentials(allowlist);
|
|
287
|
+
set_plain_authenticator(rb_self, authenticator, None)?;
|
|
288
|
+
Ok(rb::qnil())
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
#[cfg(feature = "plain")]
|
|
293
|
+
unsafe extern "C" fn rust_socket_set_plain_auth_callback(rb_self: VALUE, callback: VALUE) -> VALUE {
|
|
294
|
+
rb::wrap(|| {
|
|
295
|
+
let rb_self = unsafe { rust_socket_ref(rb_self)? };
|
|
296
|
+
let (authenticator, worker) = crate::auth::callback(callback)?;
|
|
297
|
+
set_plain_authenticator(rb_self, authenticator, Some(worker))?;
|
|
298
|
+
Ok(rb::qnil())
|
|
299
|
+
})
|
|
300
|
+
}
|
|
301
|
+
|
|
209
302
|
#[cfg(feature = "curve")]
|
|
210
303
|
unsafe extern "C" fn rust_socket_set_curve_auth_keys(rb_self: VALUE, keys: VALUE) -> VALUE {
|
|
211
304
|
rb::wrap(|| {
|
|
@@ -250,6 +343,25 @@ fn rust_socket_materialize_impl(rb_self: &RustSocket) -> RbResult<()> {
|
|
|
250
343
|
return Ok(());
|
|
251
344
|
}
|
|
252
345
|
|
|
346
|
+
#[cfg(feature = "plain")]
|
|
347
|
+
if rb_self
|
|
348
|
+
.options
|
|
349
|
+
.lock()
|
|
350
|
+
.unwrap()
|
|
351
|
+
.as_ref()
|
|
352
|
+
.is_some_and(|options| {
|
|
353
|
+
matches!(
|
|
354
|
+
options.mechanism,
|
|
355
|
+
omq_proto::MechanismSetup::PlainServer { .. }
|
|
356
|
+
)
|
|
357
|
+
})
|
|
358
|
+
&& !rb_self.plain_auth_configured.load(Ordering::Acquire)
|
|
359
|
+
{
|
|
360
|
+
return Err(RubyErr::runtime(
|
|
361
|
+
"PLAIN server requires explicit authentication via set_plain_auth",
|
|
362
|
+
));
|
|
363
|
+
}
|
|
364
|
+
|
|
253
365
|
let opts = rb_self.options.lock().unwrap().take().unwrap_or_default();
|
|
254
366
|
let send_cap = opts.send_hwm.max(1) as usize;
|
|
255
367
|
let recv_cap = opts.recv_hwm.max(1) as usize;
|
|
@@ -774,13 +886,15 @@ unsafe extern "C" fn rust_socket_leave(rb_self: VALUE, group: VALUE) -> VALUE {
|
|
|
774
886
|
|
|
775
887
|
fn rust_socket_close_impl(rb_self: &RustSocket, wait_for_auth_worker: bool) {
|
|
776
888
|
rb_self.closed.store(true, Ordering::Relaxed);
|
|
777
|
-
#[cfg(feature = "curve")]
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
if
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
889
|
+
#[cfg(any(feature = "curve", feature = "plain"))]
|
|
890
|
+
{
|
|
891
|
+
let worker = rb_self.auth_worker.lock().unwrap().take();
|
|
892
|
+
if let Some(worker) = worker {
|
|
893
|
+
if wait_for_auth_worker {
|
|
894
|
+
worker.stop();
|
|
895
|
+
} else {
|
|
896
|
+
worker.request_stop();
|
|
897
|
+
}
|
|
784
898
|
}
|
|
785
899
|
}
|
|
786
900
|
let mat = rb_self.materialized.write().unwrap().take();
|
|
@@ -934,6 +1048,19 @@ pub fn register(native: VALUE) -> RbResult<()> {
|
|
|
934
1048
|
)?;
|
|
935
1049
|
rb::define_method_0(class, c"clear_curve_auth", rust_socket_clear_curve_auth)?;
|
|
936
1050
|
}
|
|
1051
|
+
#[cfg(feature = "plain")]
|
|
1052
|
+
{
|
|
1053
|
+
rb::define_method_1(
|
|
1054
|
+
class,
|
|
1055
|
+
c"set_plain_auth_credentials",
|
|
1056
|
+
rust_socket_set_plain_auth_credentials,
|
|
1057
|
+
)?;
|
|
1058
|
+
rb::define_method_1(
|
|
1059
|
+
class,
|
|
1060
|
+
c"set_plain_auth_callback",
|
|
1061
|
+
rust_socket_set_plain_auth_callback,
|
|
1062
|
+
)?;
|
|
1063
|
+
}
|
|
937
1064
|
rb::define_method_0(class, c"materialize", rust_socket_materialize)?;
|
|
938
1065
|
rb::define_method_1(class, c"bind", rust_socket_bind)?;
|
|
939
1066
|
rb::define_method_1(class, c"connect", rust_socket_connect)?;
|
data/lib/omq/rs/socket.rb
CHANGED
|
@@ -81,13 +81,25 @@ module OMQ
|
|
|
81
81
|
].freeze
|
|
82
82
|
private_constant :ROUTED_TYPES, :SINGLE_FRAME_TYPES, :SOCKET_OPTIONS
|
|
83
83
|
|
|
84
|
-
#
|
|
84
|
+
# Peer metadata passed to callable CURVE and PLAIN authenticators.
|
|
85
85
|
#
|
|
86
86
|
# @!attribute [r] public_key
|
|
87
|
-
# @return [String] peer's 40-byte Z85 public key
|
|
87
|
+
# @return [String, nil] peer's 40-byte Z85 public key for CURVE
|
|
88
88
|
# @!attribute [r] identity
|
|
89
89
|
# @return [String, nil] peer's ZMTP identity
|
|
90
|
-
|
|
90
|
+
# @!attribute [r] peer_address
|
|
91
|
+
# @return [String, nil] peer's transport address
|
|
92
|
+
# @!attribute [r] username
|
|
93
|
+
# @return [String, nil] peer's PLAIN username
|
|
94
|
+
# @!attribute [r] password
|
|
95
|
+
# @return [String, nil] peer's PLAIN password
|
|
96
|
+
MechanismPeerInfo = Data.define(
|
|
97
|
+
:public_key,
|
|
98
|
+
:identity,
|
|
99
|
+
:peer_address,
|
|
100
|
+
:username,
|
|
101
|
+
:password,
|
|
102
|
+
)
|
|
91
103
|
|
|
92
104
|
class << self
|
|
93
105
|
# Returns number of OMQ.rs IO threads.
|
|
@@ -150,17 +162,20 @@ module OMQ
|
|
|
150
162
|
Native.curve_public(secret_key)
|
|
151
163
|
end
|
|
152
164
|
|
|
153
|
-
# Adapts a public
|
|
165
|
+
# Adapts a public authenticator to native peer metadata.
|
|
154
166
|
#
|
|
155
167
|
# @param authenticator [#call] callable receiving {MechanismPeerInfo}
|
|
156
168
|
# @return [Proc]
|
|
157
169
|
# @api private
|
|
158
|
-
def
|
|
170
|
+
def wrap_authenticator(authenticator)
|
|
159
171
|
proc do |peer|
|
|
160
172
|
authenticator.call(
|
|
161
173
|
MechanismPeerInfo.new(
|
|
162
174
|
public_key: peer.fetch(:public_key),
|
|
163
175
|
identity: peer[:identity],
|
|
176
|
+
peer_address: peer[:peer_address],
|
|
177
|
+
username: peer[:username],
|
|
178
|
+
password: peer[:password],
|
|
164
179
|
),
|
|
165
180
|
)
|
|
166
181
|
end
|
|
@@ -222,6 +237,8 @@ module OMQ
|
|
|
222
237
|
# @param recv_timeout [Numeric, nil] receive timeout in seconds
|
|
223
238
|
# @param send_timeout [Numeric, nil] send timeout in seconds
|
|
224
239
|
# @param curve_auth [Array<String>, #call, nil] CURVE allowlist or authenticator
|
|
240
|
+
# @param plain_auth [Array<Array(String, String)>, #call, nil] PLAIN
|
|
241
|
+
# credential allowlist or authenticator
|
|
225
242
|
# @param options [Hash] native OMQ.rs socket options
|
|
226
243
|
# @option options [Symbol] :workload_profile +:throughput+ or +:latency+
|
|
227
244
|
# @option options [Integer] :send_hwm outbound message capacity
|
|
@@ -270,7 +287,7 @@ module OMQ
|
|
|
270
287
|
# @option options [String] :curve_serverkey CURVE server raw or Z85 public key
|
|
271
288
|
# @return [Socket]
|
|
272
289
|
# @raise [ArgumentError] if an option is unknown or invalid
|
|
273
|
-
def initialize(recv_timeout: nil, send_timeout: nil, curve_auth: nil, **options)
|
|
290
|
+
def initialize(recv_timeout: nil, send_timeout: nil, curve_auth: nil, plain_auth: nil, **options)
|
|
274
291
|
socket_type = self.class.const_get(:SOCKET_TYPE, false)
|
|
275
292
|
@socket_type = socket_type.to_s.downcase.to_sym
|
|
276
293
|
unless SOCKET_TYPES.include?(@socket_type)
|
|
@@ -290,6 +307,7 @@ module OMQ
|
|
|
290
307
|
@peer_connected = false
|
|
291
308
|
@subscriber_joined = false
|
|
292
309
|
set_curve_auth(curve_auth) unless curve_auth.nil?
|
|
310
|
+
set_plain_auth(plain_auth) unless plain_auth.nil?
|
|
293
311
|
end
|
|
294
312
|
|
|
295
313
|
# Binds socket to an endpoint.
|
|
@@ -363,7 +381,36 @@ module OMQ
|
|
|
363
381
|
raise TypeError, "CURVE authenticator must be an Array, callable, or nil"
|
|
364
382
|
end
|
|
365
383
|
|
|
366
|
-
@native.set_curve_auth_callback(Rust.
|
|
384
|
+
@native.set_curve_auth_callback(Rust.wrap_authenticator(authenticator))
|
|
385
|
+
end
|
|
386
|
+
self
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# Configures PLAIN client admission on a server before materialization.
|
|
390
|
+
#
|
|
391
|
+
# Pass an array of username/password pairs, or a callable receiving
|
|
392
|
+
# {MechanismPeerInfo}. Pairs match exactly and case-sensitively. Each
|
|
393
|
+
# field must contain at most 255 ASCII VCHAR bytes. An empty array denies
|
|
394
|
+
# every client. PLAIN never has an allow-all mode and does not encrypt
|
|
395
|
+
# traffic.
|
|
396
|
+
#
|
|
397
|
+
# @param credentials [Array<Array(String, String)>, #call] credential
|
|
398
|
+
# allowlist or authenticator
|
|
399
|
+
# @yieldparam peer [MechanismPeerInfo]
|
|
400
|
+
# @return [Socket] self
|
|
401
|
+
# @raise [RuntimeError] if socket is already materialized
|
|
402
|
+
# @raise [TypeError, ArgumentError] if credentials are invalid
|
|
403
|
+
def set_plain_auth(credentials = nil, &block)
|
|
404
|
+
raise RuntimeError, "PLAIN authentication must be configured before bind or connect" if @materialized
|
|
405
|
+
|
|
406
|
+
authenticator = block || (credentials if credentials.respond_to?(:call))
|
|
407
|
+
if authenticator
|
|
408
|
+
@native.set_plain_auth_callback(Rust.wrap_authenticator(authenticator))
|
|
409
|
+
else
|
|
410
|
+
unless credentials.is_a?(Array)
|
|
411
|
+
raise TypeError, "PLAIN authentication requires a credential Array or a callable"
|
|
412
|
+
end
|
|
413
|
+
@native.set_plain_auth_credentials(credentials)
|
|
367
414
|
end
|
|
368
415
|
self
|
|
369
416
|
end
|
data/lib/omq/rs/version.rb
CHANGED