From 31641b1ea1d28973c276baebad7ec2f959c570dc Mon Sep 17 00:00:00 2001 From: Baptiste Gelez Date: Tue, 23 Oct 2018 10:37:24 +0100 Subject: [PATCH] New request guard: Authorization Filter requests that don't have an API token authorized to read or write a specific scope; --- plume-models/src/api_tokens.rs | 2 +- src/api/authorization.rs | 57 ++++++++++++++++++++++++++++++++++ src/api/mod.rs | 1 + src/api/posts.rs | 29 ++++++----------- 4 files changed, 68 insertions(+), 21 deletions(-) create mode 100644 src/api/authorization.rs diff --git a/plume-models/src/api_tokens.rs b/plume-models/src/api_tokens.rs index b5a244da..50953904 100644 --- a/plume-models/src/api_tokens.rs +++ b/plume-models/src/api_tokens.rs @@ -44,7 +44,7 @@ impl ApiToken { insert!(api_tokens, NewApiToken); find_by!(api_tokens, find_by_value, value as String); - fn can(&self, what: &'static str, scope: &'static str) -> bool { + pub fn can(&self, what: &'static str, scope: &'static str) -> bool { let full_scope = what.to_owned() + ":" + scope; for s in self.scopes.split('+') { if s == what || s == full_scope { diff --git a/src/api/authorization.rs b/src/api/authorization.rs new file mode 100644 index 00000000..faebac57 --- /dev/null +++ b/src/api/authorization.rs @@ -0,0 +1,57 @@ +use rocket::{ + Outcome, + http::Status, + request::{self, FromRequest, Request} +}; +use plume_models::{self, api_tokens::ApiToken}; + +// Actions +pub trait Action { + fn to_str() -> &'static str; +} +pub struct Read; +impl Action for Read { + fn to_str() -> &'static str { + "read" + } +} +pub struct Write; +impl Action for Write { + fn to_str() -> &'static str { + "write" + } +} + +// Scopes +pub trait Scope { + fn to_str() -> &'static str; +} +impl Scope for plume_models::posts::Post { + fn to_str() -> &'static str { + "posts" + } +} + +// We have to use A and S in the struct definition +// otherwise rustc complains they are useless +// +// A nicer solution is probably possible. +pub struct Authorization (Option, Option); + +impl<'a, 'r, A, S> FromRequest<'a, 'r> for Authorization +where A: Action, + S: Scope +{ + type Error = (); + + fn from_request(request: &'a Request<'r>) -> request::Outcome, ()> { + request.guard::() + .map_failure(|_| (Status::Unauthorized, ())) + .and_then(|token| if token.can(A::to_str(), S::to_str()) { + Outcome::Success(Authorization(None, None)) + } else { + Outcome::Failure((Status::Unauthorized, ())) + }) + } +} + diff --git a/src/api/mod.rs b/src/api/mod.rs index 11353f4d..7383c4ff 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -51,4 +51,5 @@ fn oauth(query: OAuthRequest, conn: DbConn) -> Json { } pub mod apps; +pub mod authorization; pub mod posts; diff --git a/src/api/posts.rs b/src/api/posts.rs index 25ce45bf..31b84321 100644 --- a/src/api/posts.rs +++ b/src/api/posts.rs @@ -7,31 +7,20 @@ use serde_qs; use plume_api::posts::PostEndpoint; use plume_models::{ Connection, - api_tokens::ApiToken, db_conn::DbConn, posts::Post, }; +use api::authorization::*; #[get("/posts/")] -fn get(id: i32, conn: DbConn, token: ApiToken) -> Json { - if token.can_read("posts") { - let post = >::get(&*conn, id).ok(); - Json(json!(post)) - } else { - Json(json!({ - "error": "Unauthorized" - })) - } +fn get(id: i32, conn: DbConn, _auth: Authorization) -> Json { + let post = >::get(&*conn, id).ok(); + Json(json!(post)) } #[get("/posts")] -fn list(conn: DbConn, uri: &Origin, token: ApiToken) -> Json { - if token.can_read("posts") { - let query: PostEndpoint = serde_qs::from_str(uri.query().unwrap_or("")).expect("api::list: invalid query error"); - let post = >::list(&*conn, query); - Json(json!(post)) - } else { - Json(json!({ - "error": "Unauthorized" - })) - }} +fn list(conn: DbConn, uri: &Origin, _auth: Authorization) -> Json { + let query: PostEndpoint = serde_qs::from_str(uri.query().unwrap_or("")).expect("api::list: invalid query error"); + let post = >::list(&*conn, query); + Json(json!(post)) +}