1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
use log::{error,info};
use std::future::Future;
use std::pin::Pin;
use url::Url;
use tide::prelude::*;
use tide::{Request, Response, Next, Result};
use crate::database;
use crate::ApplicationState;
#[derive(Deserialize, Serialize, Debug, PartialEq)]
pub struct User {
pub me: Url,
pub client_id: Url,
scope: String
}
impl User {
pub fn check_scope(&self, scope: &str) -> bool {
self.scopes().any(|i| i == scope)
}
pub fn scopes(&self) -> std::str::SplitAsciiWhitespace<'_> {
self.scope.split_ascii_whitespace()
}
#[cfg(test)]
pub fn new(me: &str, client_id: &str, scope: &str) -> Self {
Self {
me: Url::parse(me).unwrap(),
client_id: Url::parse(client_id).unwrap(),
scope: scope.to_string()
}
}
}
async fn get_token_data(token: String, token_endpoint: &http_types::Url, http_client: &surf::Client) -> (http_types::StatusCode, Option<User>) {
match http_client.get(token_endpoint).header("Authorization", token).header("Accept", "application/json").send().await {
Ok(mut resp) => {
if resp.status() == 200 {
match resp.body_json::<User>().await {
Ok(user) => {
info!("Token endpoint request successful. Validated user: {}", user.me);
(resp.status(), Some(user))
},
Err(err) => {
error!("Token endpoint parsing error (HTTP status {}): {}", resp.status(), err);
(http_types::StatusCode::InternalServerError, None)
}
}
} else {
error!("Token endpoint returned non-200: {}", resp.status());
(resp.status(), None)
}
}
Err(err) => {
error!("Token endpoint connection error: {}", err);
(http_types::StatusCode::InternalServerError, None)
}
}
}
// TODO: Figure out how to cache these authorization values - they can potentially take a lot of processing time
pub fn check_auth<'a, Backend>(mut req: Request<ApplicationState<Backend>>, next: Next<'a, ApplicationState<Backend>>) -> Pin<Box<dyn Future<Output = Result> + Send + 'a>>
where
Backend: database::Storage + Send + Sync + Clone
{
Box::pin(async {
let header = req.header("Authorization");
match header {
None => {
Ok(Response::builder(401).body(json!({
"error": "unauthorized",
"error_description": "Please provide an access token."
})).build())
},
Some(value) => {
let endpoint = &req.state().token_endpoint;
let http_client = &req.state().http_client;
match get_token_data(value.last().to_string(), endpoint, http_client).await {
(http_types::StatusCode::Ok, Some(user)) => {
req.set_ext(user);
Ok(next.run(req).await)
},
(http_types::StatusCode::InternalServerError, None) => {
Ok(Response::builder(500).body(json!({
"error": "token_endpoint_fail",
"error_description": "Token endpoint made a boo-boo and refused to answer."
})).build())
},
(_, None) => {
Ok(Response::builder(401).body(json!({
"error": "unauthorized",
"error_description": "The token endpoint refused to accept your token."
})).build())
},
(_, Some(_)) => {
// This shouldn't happen.
panic!("The token validation function has caught rabies and returns malformed responses. Aborting.");
}
}
}
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn user_scopes_are_checkable() {
let user = User::new("https://fireburn.ru/", "https://quill.p3k.io/", "create update media");
assert!(user.check_scope("create"));
assert!(!user.check_scope("delete"));
}
}
|