v0.3.3, fix #25: better HTTP auth support

This commit is contained in:
Scott Lamb 2021-10-20 13:38:49 -07:00
parent 3a3f01118e
commit 969afbf96c
5 changed files with 60 additions and 74 deletions

View File

@ -1,3 +1,11 @@
## `v0.3.3` (2021-10-20)
* [#25](https://github.com/scottlamb/retina/issues/25): better HTTP
authentication support via the new [`http-auth`
crate](https://crates.io/crates/http-auth). Before, `retina` would only
authenticate properly if the first requested challenge was `Digest`. Now, it
will pick out a `Digest` or `Basic` challenge from a list.
## `v0.3.2` (2021-09-29)
* better `TEARDOWN` handling, which often avoids the need to wait for session

40
Cargo.lock generated
View File

@ -277,19 +277,6 @@ dependencies = [
"generic-array",
]
[[package]]
name = "digest_auth"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa30657988b2ced88f68fe490889e739bf98d342916c33ed3100af1d6f1cbc9c"
dependencies = [
"digest",
"hex",
"md-5",
"rand",
"sha2",
]
[[package]]
name = "either"
version = "1.6.1"
@ -481,6 +468,21 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "http-auth"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "805afa6c41edf02ff4643e6672810974a83c2d866be06e0c377e1789084f6a7e"
dependencies = [
"base64",
"digest",
"hex",
"md-5",
"memchr",
"rand",
"sha2",
]
[[package]]
name = "idna"
version = "0.2.3"
@ -587,9 +589,9 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.3.4"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "memoffset"
@ -650,9 +652,9 @@ dependencies = [
[[package]]
name = "nom"
version = "6.2.1"
version = "6.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c5c51b9083a3c620fa67a2a635d1ce7d95b897e957d6b28ff9a5da960a103a6"
checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
dependencies = [
"bitvec",
"funty",
@ -977,17 +979,17 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "retina"
version = "0.3.2"
version = "0.3.3"
dependencies = [
"anyhow",
"base64",
"bitreader",
"bytes",
"criterion",
"digest_auth",
"futures",
"h264-reader",
"hex",
"http-auth",
"itertools",
"log",
"mylog",

View File

@ -1,6 +1,6 @@
[package]
name = "retina"
version = "0.3.2"
version = "0.3.3"
authors = ["Scott Lamb <slamb@slamb.org>"]
license = "MIT/Apache-2.0"
edition = "2018"
@ -14,10 +14,10 @@ include = ["src/**/*", "benches", "Cargo.toml"]
base64 = "0.13.0"
bitreader = "0.3.3"
bytes = "1.0.1"
digest_auth = "0.3.0"
futures = "0.3.14"
hex = "0.4.3"
h264-reader = "0.5.0"
hex = "0.4.3"
http-auth = "0.1.2"
log = "0.4.8"
once_cell = "1.7.2"
pin-project = "1.0.7"

View File

@ -1,13 +1,14 @@
// Copyright (C) 2021 Scott Lamb <slamb@slamb.org>
// SPDX-License-Identifier: MIT OR Apache-2.0
use std::convert::TryFrom;
use std::mem::MaybeUninit;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::num::NonZeroU32;
use std::sync::{Arc, Mutex};
use std::task::Poll;
use std::time::Instant;
use std::{borrow::Cow, fmt::Debug, num::NonZeroU16, pin::Pin};
use std::{fmt::Debug, num::NonZeroU16, pin::Pin};
use self::channel_mapping::*;
pub use self::timeline::Timeline;
@ -656,7 +657,7 @@ struct SessionInner {
runtime_handle: Option<tokio::runtime::Handle>,
options: SessionOptions,
requested_auth: Option<digest_auth::WwwAuthenticateHeader>,
requested_auth: Option<http_auth::PasswordClient>,
presentation: Presentation,
/// This will be set iff one or more `SETUP` calls have been issued.
@ -727,7 +728,7 @@ impl RtspConnection {
&mut self,
mode: ResponseMode,
options: &SessionOptions,
requested_auth: &mut Option<digest_auth::WwwAuthenticateHeader>,
requested_auth: &mut Option<http_auth::PasswordClient>,
req: &mut rtsp_types::Request<Bytes>,
) -> Result<(RtspMessageContext, u32, rtsp_types::Response<Bytes>), Error> {
loop {
@ -828,22 +829,6 @@ impl RtspConnection {
}),
Some(h) => h,
};
let www_authenticate = www_authenticate.as_str();
if !www_authenticate.starts_with("Digest ") {
// TODO: the header(s) might also indicate both Basic and Digest; we shouldn't
// error or not based on ordering.
bail!(ErrorInt::RtspResponseError {
conn_ctx: *self.inner.ctx(),
msg_ctx,
method: req.method().clone(),
cseq,
status: resp.status(),
description: format!(
"Non-digest authentication requested: {}",
www_authenticate
),
})
}
if options.creds.is_none() {
bail!(ErrorInt::RtspResponseError {
conn_ctx: *self.inner.ctx(),
@ -855,21 +840,18 @@ impl RtspConnection {
.to_owned(),
})
}
let www_authenticate = digest_auth::WwwAuthenticateHeader::parse(www_authenticate)
.map_err(|e| {
wrap!(ErrorInt::RtspResponseError {
conn_ctx: *self.inner.ctx(),
msg_ctx,
method: req.method().clone(),
cseq,
status: resp.status(),
description: format!(
"Bad WWW-Authenticate header {:?}: {}",
www_authenticate, e
),
})
})?;
*requested_auth = Some(www_authenticate);
let www_authenticate = www_authenticate.as_str();
*requested_auth = match http_auth::PasswordClient::try_from(www_authenticate) {
Ok(c) => Some(c),
Err(e) => bail!(ErrorInt::RtspResponseError {
conn_ctx: *self.inner.ctx(),
msg_ctx,
method: req.method().clone(),
cseq,
status: resp.status(),
description: format!("Can't understand WWW-Authenticate header: {}", e),
}),
};
continue;
} else if !resp.status().is_success() {
bail!(ErrorInt::RtspResponseError {
@ -889,7 +871,7 @@ impl RtspConnection {
fn fill_req(
&mut self,
options: &SessionOptions,
requested_auth: &mut Option<digest_auth::WwwAuthenticateHeader>,
requested_auth: &mut Option<http_auth::PasswordClient>,
req: &mut rtsp_types::Request<Bytes>,
) -> Result<u32, Error> {
let cseq = self.next_cseq;
@ -899,21 +881,15 @@ impl RtspConnection {
.creds
.as_ref()
.expect("creds were checked when filling request_auth");
let uri = req.request_uri().map(|u| u.as_str()).unwrap_or("*");
let method = digest_auth::HttpMethod(Cow::Borrowed(req.method().into()));
let ctx = digest_auth::AuthContext::new_with_method(
&creds.username,
&creds.password,
uri,
Option::<&'static [u8]>::None,
method,
);
// digest_auth's comments seem to say 'respond' failing means a parser bug.
let authorization = auth
.respond(&ctx)
.map_err(|e| wrap!(ErrorInt::Internal(e.into())))?
.to_string();
.respond(&http_auth::PasswordParams {
username: &creds.username,
password: &creds.password,
uri: req.request_uri().map(|u| u.as_str()).unwrap_or("*"),
method: req.method().into(),
body: Some(&[]),
})
.map_err(|e| wrap!(ErrorInt::Internal(e.into())))?;
req.insert_header(rtsp_types::headers::AUTHORIZATION, authorization);
}
req.insert_header(rtsp_types::headers::CSEQ, cseq.to_string());

View File

@ -19,7 +19,7 @@ pub(super) async fn background_teardown(
base_url: Url,
session_id: Box<str>,
options: SessionOptions,
requested_auth: Option<digest_auth::WwwAuthenticateHeader>,
requested_auth: Option<http_auth::PasswordClient>,
conn: Option<RtspConnection>,
mut tx: tokio::sync::watch::Sender<Option<Result<(), Error>>>,
expires: tokio::time::Instant,
@ -63,7 +63,7 @@ pub(super) async fn teardown_loop_forever(
url: Url,
session_id: &str,
options: &SessionOptions,
mut requested_auth: Option<digest_auth::WwwAuthenticateHeader>,
mut requested_auth: Option<http_auth::PasswordClient>,
mut conn: Option<RtspConnection>,
tx: &mut tokio::sync::watch::Sender<Option<Result<(), Error>>>,
) {
@ -155,7 +155,7 @@ pub(super) async fn teardown_loop_forever(
async fn attempt(
req: &mut Request<Bytes>,
options: &SessionOptions,
requested_auth: &mut Option<digest_auth::WwwAuthenticateHeader>,
requested_auth: &mut Option<http_auth::PasswordClient>,
mut conn: RtspConnection,
) -> Result<rtsp_types::StatusCode, Error> {
let e = match conn