Configure rustfmt & clippy lints

This commit is contained in:
Quentin Gliech
2021-07-09 16:03:39 +02:00
parent c569c56e58
commit b7d0ac20b5
20 changed files with 98 additions and 75 deletions

6
.rustfmt.toml Normal file
View File

@@ -0,0 +1,6 @@
max_width = 100
comment_width = 80
wrap_comments = true
imports_granularity = "Crate"
use_small_heuristics = "Default"
group_imports = "StdExternalCrate"

View File

@@ -12,12 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use time::Duration;
use csrf::AesGcmCsrfProtection;
use serde::Deserialize;
use serde_with::serde_as;
use tide::Middleware;
use time::Duration;
use crate::middlewares::CsrfMiddleware;

View File

@@ -29,10 +29,12 @@ fn default_connect_timeout() -> Duration {
Duration::from_secs(30)
}
#[allow(clippy::unnecessary_wraps)]
fn default_idle_timeout() -> Option<Duration> {
Some(Duration::from_secs(10 * 60))
}
#[allow(clippy::unnecessary_wraps)]
fn default_max_lifetime() -> Option<Duration> {
Some(Duration::from_secs(30 * 60))
}

View File

@@ -24,10 +24,12 @@ mod database;
mod http;
mod oauth2;
pub use self::csrf::Config as CsrfConfig;
pub use self::database::Config as DatabaseConfig;
pub use self::http::Config as HttpConfig;
pub use self::oauth2::{ClientConfig as OAuth2ClientConfig, Config as OAuth2Config};
pub use self::{
csrf::Config as CsrfConfig,
database::Config as DatabaseConfig,
http::Config as HttpConfig,
oauth2::{ClientConfig as OAuth2ClientConfig, Config as OAuth2Config},
};
#[derive(Debug, Deserialize)]
pub struct RootConfig {

View File

@@ -40,7 +40,7 @@ impl Default for Config {
fn default() -> Self {
Self {
issuer: default_oauth2_issuer(),
clients: Default::default(),
clients: Vec::new(),
}
}
}

View File

@@ -58,7 +58,7 @@ async fn redirect_uri_from_params<T>(
params: QueryParams,
storage: &Storage<T>,
) -> Result<Url, RedirectUriLookupError> {
use RedirectUriLookupError::*;
use RedirectUriLookupError::MissingClientId;
let client_id = params.client_id.ok_or(MissingClientId)?;
let client = storage.lookup_client(&client_id).await?;
let redirect_uri: Option<Url> = if let Some(uri) = params.redirect_uri {

View File

@@ -12,9 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use tide::{Body, Request, Response};
use oauth2_types::requests::AuthorizationRequest;
use tide::{Body, Request, Response};
use crate::state::State;

View File

@@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use tide::{Body, Request, Response};
use std::collections::HashSet;
use oauth2_types::oidc::Metadata;
use tide::{Body, Request, Response};
use crate::state::State;
@@ -26,10 +27,10 @@ pub async fn get(req: Request<State>) -> tide::Result {
token_endpoint: state.token_endpoint(),
jwks_uri: state.jwks_uri(),
registration_endpoint: None,
scopes_supported: Default::default(),
response_types_supported: Default::default(),
response_modes_supported: Default::default(),
grant_types_supported: Default::default(),
scopes_supported: HashSet::default(),
response_types_supported: HashSet::default(),
response_modes_supported: HashSet::default(),
grant_types_supported: HashSet::default(),
};
let body = Body::from_json(&m)?;

View File

@@ -14,8 +14,7 @@
use tide::{Request, Response};
use crate::state::State;
use crate::templates::common_context;
use crate::{state::State, templates::common_context};
pub async fn get(req: Request<State>) -> tide::Result {
let state = req.state();

View File

@@ -15,9 +15,7 @@
use serde::Deserialize;
use tide::{Redirect, Request, Response};
use crate::csrf::CsrfForm;
use crate::state::State;
use crate::templates::common_context;
use crate::{csrf::CsrfForm, state::State, templates::common_context};
#[derive(Deserialize)]
struct LoginForm {

View File

@@ -18,7 +18,7 @@ use crate::{csrf::CsrfForm, state::State};
pub async fn post(mut req: Request<State>) -> tide::Result {
let form: CsrfForm<()> = req.body_form().await?;
let _ = form.verify_csrf(&req)?;
form.verify_csrf(&req)?;
let session = req.session_mut();
session.remove("current_user");

View File

@@ -13,6 +13,9 @@
// limitations under the License.
#![forbid(unsafe_code)]
#![deny(clippy::all)]
#![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
use anyhow::Context;
use tracing::{info_span, Instrument};
@@ -26,9 +29,7 @@ mod state;
mod storage;
mod templates;
use self::config::RootConfig;
use self::state::State;
use self::storage::MIGRATOR;
use self::{config::RootConfig, state::State, storage::MIGRATOR};
#[async_std::main]
async fn main() -> tide::Result<()> {

View File

@@ -12,9 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::cmp::Reverse;
use std::future::Future;
use std::pin::Pin;
use std::{cmp::Reverse, future::Future, pin::Pin};
use mime::{Mime, STAR};
use serde::Serialize;
@@ -25,19 +23,20 @@ use tide::{
};
use tracing::debug;
use crate::state::State;
use crate::templates::common_context;
use crate::{state::State, templates::common_context};
/// Get the weight parameter for a mime type from 0 to 1000
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
fn get_weight(mime: &Mime) -> usize {
let q = mime
.get_param("q")
.map(|q| q.as_str().parse().unwrap_or(0.0))
.unwrap_or(1.0_f64)
.map_or(1.0_f64, |q| q.as_str().parse().unwrap_or(0.0))
.min(1.0)
.max(0.0);
// Weight have a 3 digit precision so we can multiply by 1000 and cast to int
// Weight have a 3 digit precision so we can multiply by 1000 and cast to
// int. Sign loss should not happen here because of the min/max up there and
// truncation does not matter here.
(q * 1000.0) as _
}
@@ -47,12 +46,12 @@ fn preferred_mime_type<'a>(
supported_types: &'a [Mime],
) -> Option<&'a Mime> {
let accept = request.header(ACCEPT)?;
// Parse the Accept header as a list of mime types with their associated weight
// Parse the Accept header as a list of mime types with their associated
// weight
let accepted_types: Vec<(Mime, usize)> = {
let v: Option<Vec<_>> = accept
.into_iter()
.map(|value| value.as_str().split(','))
.flatten()
.flat_map(|value| value.as_str().split(','))
.map(|mime| {
mime.trim().parse().ok().map(|mime| {
let q = get_weight(&mime);
@@ -65,7 +64,8 @@ fn preferred_mime_type<'a>(
v
};
// For each supported content type, find out if it is accepted with what weight and specificity
// For each supported content type, find out if it is accepted with what
// weight and specificity
let mut types: Vec<_> = supported_types
.iter()
.enumerate()
@@ -133,7 +133,8 @@ pub fn middleware<'a>(
let mut response = next.run(request).await;
// Find out what message should be displayed from the response status code
// Find out what message should be displayed from the response status
// code
let (code, description) = match response.status() {
StatusCode::NotFound => (Some("Not found".to_string()), None),
StatusCode::MethodNotAllowed => (Some("Method not allowed".to_string()), None),
@@ -148,8 +149,8 @@ pub fn middleware<'a>(
_ => (None, None),
};
// If there is an error associated to the response, format it in a nice way with
// a backtrace if we have one
// If there is an error associated to the response, format it in a nice
// way with a backtrace if we have one
let details = response.take_error().map(|err| {
format!(
"{}{}",
@@ -166,7 +167,8 @@ pub fn middleware<'a>(
details,
};
// This is the case if one of the code, description or details is not None
// This is the case if one of the code, description or details is not
// None
if error_context.should_render() {
match content_type {
Some(c) if c == &mime::APPLICATION_JSON => {

View File

@@ -15,5 +15,4 @@
mod csrf;
mod errors;
pub use self::csrf::Middleware as CsrfMiddleware;
pub use self::errors::middleware as errors;
pub use self::{csrf::Middleware as CsrfMiddleware, errors::middleware as errors};

View File

@@ -20,8 +20,10 @@ use sqlx::migrate::Migrator;
mod client;
mod user;
pub use self::client::{Client, ClientLookupError, InvalidRedirectUriError};
pub use self::user::User;
pub use self::{
client::{Client, ClientLookupError, InvalidRedirectUriError},
user::User,
};
pub static MIGRATOR: Migrator = sqlx::migrate!();
@@ -36,8 +38,8 @@ impl<Pool> Storage<Pool> {
pub fn new(pool: Pool) -> Self {
Self {
pool,
clients: Default::default(),
users: Default::default(),
clients: RwLock::default(),
users: RwLock::default(),
}
}

View File

@@ -29,17 +29,17 @@ impl User {
#[derive(Debug, Error)]
#[error("Invalid credentials")]
pub struct UserLoginError;
pub struct LoginError;
#[derive(Debug, Error)]
#[error("Could not find user")]
pub struct UserLookupError;
pub struct LookupError;
impl<T> super::Storage<T> {
pub async fn login(&self, name: &str, password: &str) -> Result<User, UserLoginError> {
pub async fn login(&self, name: &str, password: &str) -> Result<User, LoginError> {
// Hardcoded bad password to test login failures
if password == "bad" {
Err(UserLoginError)
Err(LoginError)
} else {
// First lookup for an existing user
let users = self.users.upgradable_read().await;
@@ -57,8 +57,8 @@ impl<T> super::Storage<T> {
}
}
pub async fn lookup_user(&self, name: &str) -> Result<User, UserLookupError> {
pub async fn lookup_user(&self, name: &str) -> Result<User, LookupError> {
let users = self.users.read().await;
users.get(name).cloned().ok_or(UserLookupError)
users.get(name).cloned().ok_or(LookupError)
}
}

View File

@@ -22,23 +22,25 @@ pub trait OAuth2Error: std::fmt::Debug {
/// Maps to the required "error" field.
fn error(&self) -> &'static str;
/// Human-readable ASCII text providing additional information, used to assist the client
/// developer in understanding the error that occurred.
/// Human-readable ASCII text providing additional information, used to
/// assist the client developer in understanding the error that
/// occurred.
///
/// Maps to the optional "error_description" field.
/// Maps to the optional `error_description` field.
fn description(&self) -> Option<String> {
None
}
/// A URI identifying a human-readable web page with information about the error, used to
/// provide the client developer with additional information about the error.
/// A URI identifying a human-readable web page with information about the
/// error, used to provide the client developer with additional
/// information about the error.
///
/// Maps to the optional "error_uri" field.
/// Maps to the optional `error_uri` field.
fn uri(&self) -> Option<Url> {
None
}
/// Wraps the error with an ErrorResponse to help serializing.
/// Wraps the error with an `ErrorResponse` to help serializing.
fn into_response(self) -> ErrorResponse<Self>
where
Self: Sized,

View File

@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#![forbid(unsafe_code)]
#![deny(clippy::all)]
#![warn(clippy::pedantic)]
pub mod errors;
pub mod oidc;
pub mod requests;

View File

@@ -22,8 +22,8 @@ use crate::requests::{GrantType, ResponseMode, ResponseType};
// TODO: https://datatracker.ietf.org/doc/html/rfc8414#section-2
#[derive(Serialize)]
pub struct Metadata {
/// The authorization server's issuer identifier, which is a URL that uses the "https" scheme
/// and has no query or fragment components.
/// The authorization server's issuer identifier, which is a URL that uses
/// the "https" scheme and has no query or fragment components.
pub issuer: Url,
/// URL of the authorization server's authorization endpoint.
@@ -38,28 +38,29 @@ pub struct Metadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub jwks_uri: Option<Url>,
/// URL of the authorization server's OAuth 2.0 Dynamic Client Registration endpoint.
/// URL of the authorization server's OAuth 2.0 Dynamic Client Registration
/// endpoint.
#[serde(skip_serializing_if = "Option::is_none")]
pub registration_endpoint: Option<Url>,
/// JSON array containing a list of the OAuth 2.0 "scope" values that this authorization server
/// supports.
/// JSON array containing a list of the OAuth 2.0 "scope" values that this
/// authorization server supports.
#[serde(skip_serializing_if = "HashSet::is_empty")]
pub scopes_supported: HashSet<String>,
/// JSON array containing a list of the OAuth 2.0 "response_type" values that this
/// authorization server supports.
/// JSON array containing a list of the OAuth 2.0 "response_type" values
/// that this authorization server supports.
#[serde(skip_serializing_if = "HashSet::is_empty")]
pub response_types_supported: HashSet<ResponseType>,
/// JSON array containing a list of the OAuth 2.0 "response_mode" values that this
/// authorization server supports, as specified in "OAuth 2.0 Multiple Response Type Encoding
/// Practices".
/// JSON array containing a list of the OAuth 2.0 "response_mode" values
/// that this authorization server supports, as specified in "OAuth 2.0
/// Multiple Response Type Encoding Practices".
#[serde(skip_serializing_if = "HashSet::is_empty")]
pub response_modes_supported: HashSet<ResponseMode>,
/// JSON array containing a list of the OAuth 2.0 grant type values that this authorization
/// server supports.
/// JSON array containing a list of the OAuth 2.0 grant type values that
/// this authorization server supports.
#[serde(skip_serializing_if = "HashSet::is_empty")]
pub grant_types_supported: HashSet<GrantType>,
}

View File

@@ -14,10 +14,16 @@
//! Utilitary types for serde
use serde::{Deserialize, Serialize};
use std::{collections::HashSet, hash::Hash, time::Duration};
use std::{
collections::{hash_map::RandomState, HashSet},
hash::Hash,
time::Duration,
};
/// A HashSet that serializes to a space-separated string in alphanumerical order
use serde::{Deserialize, Serialize};
/// A `HashSet` that serializes to a space-separated string in alphanumerical
/// order
#[derive(Debug, PartialEq)]
pub struct StringHashSet<T: Eq + Hash>(HashSet<T>);
@@ -27,7 +33,7 @@ impl<T: Eq + Hash> From<HashSet<T>> for StringHashSet<T> {
}
}
impl<T: Eq + Hash> From<StringHashSet<T>> for HashSet<T> {
impl<T: Eq + Hash> From<StringHashSet<T>> for HashSet<T, RandomState> {
fn from(set: StringHashSet<T>) -> Self {
set.0
}