use http_auth::parser::ChallengeParser;
use crate::error::{Error, Result};
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Scheme {
Basic,
Digest,
Bearer,
Other(String),
}
impl Scheme {
pub fn from_token(token: &str) -> Self {
if token.eq_ignore_ascii_case("Basic") {
Self::Basic
} else if token.eq_ignore_ascii_case("Digest") {
Self::Digest
} else if token.eq_ignore_ascii_case("Bearer") {
Self::Bearer
} else {
Self::Other(token.to_string())
}
}
pub fn name(&self) -> &str {
match self {
Self::Basic => "Basic",
Self::Digest => "Digest",
Self::Bearer => "Bearer",
Self::Other(s) => s,
}
}
}
impl core::fmt::Display for Scheme {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.name())
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BearerError {
InvalidRequest,
InvalidToken,
InsufficientScope,
Other(String),
}
impl BearerError {
pub fn from_code(code: &str) -> Self {
match code {
"invalid_request" => Self::InvalidRequest,
"invalid_token" => Self::InvalidToken,
"insufficient_scope" => Self::InsufficientScope,
_ => Self::Other(code.to_string()),
}
}
pub fn name(&self) -> &str {
match self {
Self::InvalidRequest => "invalid_request",
Self::InvalidToken => "invalid_token",
Self::InsufficientScope => "insufficient_scope",
Self::Other(s) => s,
}
}
}
impl core::fmt::Display for BearerError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.name())
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Challenge {
pub scheme: Scheme,
pub realm: Option<String>,
pub scope: Option<String>,
pub error: Option<BearerError>,
pub error_description: Option<String>,
pub error_uri: Option<String>,
pub resource_metadata: Option<url::Url>,
pub other_params: Vec<(String, String)>,
}
const RESOURCE_METADATA_SCHEME: &str = "https";
pub const MAX_CHALLENGES: usize = 16;
#[non_exhaustive]
#[derive(Debug, Default)]
pub struct ParsedChallenges {
pub challenges: Vec<Challenge>,
pub errors: Vec<Error>,
}
pub fn parse_challenges(lines: &[&str]) -> ParsedChallenges {
let mut out = ParsedChallenges::default();
for line in lines {
if line.trim().is_empty() {
continue;
}
for item in ChallengeParser::new(line) {
if out.challenges.len() >= MAX_CHALLENGES {
out.errors.push(Error::TooManyChallenges {
max: MAX_CHALLENGES,
});
return out;
}
match item
.map_err(|e| Error::ChallengeParse(e.to_string()))
.and_then(convert)
{
Ok(c) => out.challenges.push(c),
Err(e) => out.errors.push(e),
}
}
}
out
}
fn convert(cr: http_auth::ChallengeRef<'_>) -> Result<Challenge> {
let scheme = Scheme::from_token(cr.scheme);
let mut realm: Option<String> = None;
let mut scope: Option<String> = None;
let mut error: Option<BearerError> = None;
let mut error_description: Option<String> = None;
let mut error_uri: Option<String> = None;
let mut resource_metadata: Option<url::Url> = None;
let mut other_params: Vec<(String, String)> = Vec::new();
for (name, value) in &cr.params {
let key = name.to_ascii_lowercase();
let val = value.to_unescaped();
match key.as_str() {
"realm" => {
if realm.is_some() {
return Err(Error::DuplicateParameter { name: key });
}
realm = Some(val);
}
"scope" => {
if scope.is_some() {
return Err(Error::DuplicateParameter { name: key });
}
scope = Some(val);
}
"error" => {
if error.is_some() {
return Err(Error::DuplicateParameter { name: key });
}
error = Some(BearerError::from_code(&val));
}
"error_description" => {
if error_description.is_some() {
return Err(Error::DuplicateParameter { name: key });
}
error_description = Some(val);
}
"error_uri" => {
if error_uri.is_some() {
return Err(Error::DuplicateParameter { name: key });
}
error_uri = Some(val);
}
"resource_metadata" => {
if resource_metadata.is_some() {
return Err(Error::DuplicateParameter { name: key });
}
let url = url::Url::parse(&val)
.ok()
.filter(|u| u.scheme() == RESOURCE_METADATA_SCHEME)
.ok_or(Error::InvalidResourceMetadata)?;
resource_metadata = Some(url);
}
_ => other_params.push((name.to_string(), val)),
}
}
Ok(Challenge {
scheme,
realm,
scope,
error,
error_description,
error_uri,
resource_metadata,
other_params,
})
}