mirror of
https://github.com/actix/actix-extras.git
synced 2025-04-16 07:52:06 +02:00
142 lines
4.3 KiB
Rust
142 lines
4.3 KiB
Rust
use std::{borrow::Cow, fmt, str};
|
|
|
|
use actix_web::{
|
|
http::header::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue},
|
|
web::{BufMut, Bytes, BytesMut},
|
|
};
|
|
|
|
use super::{super::Challenge, BearerBuilder, Error};
|
|
use crate::utils;
|
|
|
|
/// Challenge for [`WWW-Authenticate`] header with HTTP Bearer auth scheme, described in [RFC 6750].
|
|
///
|
|
/// # Examples
|
|
/// ```
|
|
/// # use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer};
|
|
/// use actix_web_httpauth::headers::www_authenticate::bearer::{
|
|
/// Bearer, Error,
|
|
/// };
|
|
/// use actix_web_httpauth::headers::www_authenticate::WwwAuthenticate;
|
|
///
|
|
/// fn index(_req: HttpRequest) -> HttpResponse {
|
|
/// let challenge = Bearer::build()
|
|
/// .realm("example")
|
|
/// .scope("openid profile email")
|
|
/// .error(Error::InvalidToken)
|
|
/// .error_description("The access token expired")
|
|
/// .error_uri("http://example.org")
|
|
/// .finish();
|
|
///
|
|
/// HttpResponse::Unauthorized()
|
|
/// .insert_header(WwwAuthenticate(challenge))
|
|
/// .finish()
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// [`WWW-Authenticate`]: crate::headers::www_authenticate::WwwAuthenticate
|
|
/// [RFC 6750]: https://tools.ietf.org/html/rfc6750#section-3
|
|
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct Bearer {
|
|
pub(crate) scope: Option<Cow<'static, str>>,
|
|
pub(crate) realm: Option<Cow<'static, str>>,
|
|
pub(crate) error: Option<Error>,
|
|
pub(crate) error_description: Option<Cow<'static, str>>,
|
|
pub(crate) error_uri: Option<Cow<'static, str>>,
|
|
}
|
|
|
|
impl Bearer {
|
|
/// Creates the builder for `Bearer` challenge.
|
|
///
|
|
/// # Examples
|
|
/// ```
|
|
/// # use actix_web_httpauth::headers::www_authenticate::bearer::{Bearer};
|
|
/// let challenge = Bearer::build()
|
|
/// .realm("Restricted area")
|
|
/// .scope("openid profile email")
|
|
/// .finish();
|
|
/// ```
|
|
pub fn build() -> BearerBuilder {
|
|
BearerBuilder::default()
|
|
}
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
impl Challenge for Bearer {
|
|
fn to_bytes(&self) -> Bytes {
|
|
let desc_uri_required = self
|
|
.error_description
|
|
.as_ref()
|
|
.map_or(0, |desc| desc.len() + 20)
|
|
+ self.error_uri.as_ref().map_or(0, |url| url.len() + 12);
|
|
|
|
let capacity = 6
|
|
+ self.realm.as_ref().map_or(0, |realm| realm.len() + 9)
|
|
+ self.scope.as_ref().map_or(0, |scope| scope.len() + 9)
|
|
+ desc_uri_required;
|
|
|
|
let mut buffer = BytesMut::with_capacity(capacity);
|
|
buffer.put(&b"Bearer"[..]);
|
|
|
|
if let Some(ref realm) = self.realm {
|
|
buffer.put(&b" realm=\""[..]);
|
|
utils::put_quoted(&mut buffer, realm);
|
|
buffer.put_u8(b'"');
|
|
}
|
|
|
|
if let Some(ref scope) = self.scope {
|
|
buffer.put(&b" scope=\""[..]);
|
|
utils::put_quoted(&mut buffer, scope);
|
|
buffer.put_u8(b'"');
|
|
}
|
|
|
|
if let Some(ref error) = self.error {
|
|
let error_repr = error.as_str();
|
|
let remaining = buffer.remaining_mut();
|
|
let required = desc_uri_required + error_repr.len() + 9; // 9 is for `" error=\"\""`
|
|
|
|
if remaining < required {
|
|
buffer.reserve(required);
|
|
}
|
|
|
|
buffer.put(&b" error=\""[..]);
|
|
utils::put_quoted(&mut buffer, error_repr);
|
|
buffer.put_u8(b'"')
|
|
}
|
|
|
|
if let Some(ref error_description) = self.error_description {
|
|
buffer.put(&b" error_description=\""[..]);
|
|
utils::put_quoted(&mut buffer, error_description);
|
|
buffer.put_u8(b'"');
|
|
}
|
|
|
|
if let Some(ref error_uri) = self.error_uri {
|
|
buffer.put(&b" error_uri=\""[..]);
|
|
utils::put_quoted(&mut buffer, error_uri);
|
|
buffer.put_u8(b'"');
|
|
}
|
|
|
|
buffer.freeze()
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Bearer {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
let bytes = self.to_bytes();
|
|
|
|
let repr = str::from_utf8(&bytes)
|
|
// Should not happen since challenges are crafted manually
|
|
// from `&'static str`'s and Strings
|
|
.map_err(|_| fmt::Error)?;
|
|
|
|
f.write_str(repr)
|
|
}
|
|
}
|
|
|
|
impl TryIntoHeaderValue for Bearer {
|
|
type Error = InvalidHeaderValue;
|
|
|
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
|
HeaderValue::from_maybe_shared(self.to_bytes())
|
|
}
|
|
}
|