conduit/src/ruma_wrapper.rs

232 lines
8.5 KiB
Rust
Raw Normal View History

use crate::Error;
use ruma::{
api::{AuthScheme, OutgoingRequest},
identifiers::{DeviceId, UserId},
Outgoing,
};
use std::{
convert::{TryFrom, TryInto},
ops::Deref,
};
#[cfg(feature = "conduit_bin")]
use {
crate::utils,
log::{debug, warn},
rocket::{
data::{
ByteUnit, Data, FromDataFuture, FromTransformedData, Transform, TransformFuture,
Transformed,
},
http::Status,
2020-07-27 15:38:00 +00:00
outcome::Outcome::*,
response::{self, Responder},
tokio::io::AsyncReadExt,
Request, State,
},
std::io::Cursor,
};
2020-02-15 21:42:21 +00:00
2020-03-28 22:08:59 +00:00
/// This struct converts rocket requests into ruma structs by converting them into http requests
/// first.
2020-09-08 15:32:03 +00:00
pub struct Ruma<T: Outgoing> {
pub body: T::Incoming,
pub sender_user: Option<UserId>,
pub sender_device: Option<Box<DeviceId>>,
2020-05-23 09:20:00 +00:00
pub json_body: Option<Box<serde_json::value::RawValue>>, // This is None when body is not a valid string
pub from_appservice: bool,
2020-03-28 17:50:02 +00:00
}
2020-03-28 22:08:59 +00:00
#[cfg(feature = "conduit_bin")]
2020-09-08 15:32:03 +00:00
impl<'a, T: Outgoing + OutgoingRequest> FromTransformedData<'a> for Ruma<T>
where
<T as Outgoing>::Incoming: TryFrom<http::request::Request<std::vec::Vec<u8>>> + std::fmt::Debug,
<<T as Outgoing>::Incoming as std::convert::TryFrom<
http::request::Request<std::vec::Vec<u8>>,
>>::Error: std::fmt::Debug,
{
2020-03-29 19:05:20 +00:00
type Error = (); // TODO: Better error handling
2020-04-04 18:50:01 +00:00
type Owned = Data;
type Borrowed = Self::Owned;
2020-02-15 21:42:21 +00:00
2020-04-06 15:37:13 +00:00
fn transform<'r>(
_req: &'r Request<'_>,
2020-04-06 15:37:13 +00:00
data: Data,
) -> TransformFuture<'r, Self::Owned, Self::Error> {
2020-04-04 18:50:01 +00:00
Box::pin(async move { Transform::Owned(Success(data)) })
}
fn from_data(
request: &'a Request<'_>,
2020-04-04 18:50:01 +00:00
outcome: Transformed<'a, Self>,
) -> FromDataFuture<'a, Self, Self::Error> {
Box::pin(async move {
let data = rocket::try_outcome!(outcome.owned());
let db = request
.guard::<State<'_, crate::Database>>()
.await
.expect("database was loaded");
2020-04-04 18:50:01 +00:00
// Get token from header or query value
let token = request
.headers()
.get_one("Authorization")
.map(|s| s[7..].to_owned()) // Split off "Bearer "
.or_else(|| request.get_query_value("access_token").and_then(|r| r.ok()));
let (sender_user, sender_device, from_appservice) = if let Some((_id, registration)) =
db.appservice
.iter_all()
.filter_map(|r| r.ok())
.find(|(_id, registration)| {
registration
.get("as_token")
.and_then(|as_token| as_token.as_str())
.map_or(false, |as_token| token.as_deref() == Some(as_token))
}) {
match T::METADATA.authentication {
AuthScheme::AccessToken | AuthScheme::QueryOnlyAccessToken => {
let user_id = request.get_query_value::<String>("user_id").map_or_else(
|| {
UserId::parse_with_server_name(
registration
.get("sender_localpart")
.unwrap()
.as_str()
.unwrap(),
db.globals.server_name(),
)
.unwrap()
},
|string| {
UserId::try_from(string.expect("parsing to string always works"))
.unwrap()
},
);
if !db.users.exists(&user_id).unwrap() {
return Failure((Status::Unauthorized, ()));
}
// TODO: Check if appservice is allowed to be that user
(Some(user_id), None, true)
}
AuthScheme::ServerSignatures => (None, None, true),
AuthScheme::None => (None, None, true),
}
} else {
match T::METADATA.authentication {
AuthScheme::AccessToken | AuthScheme::QueryOnlyAccessToken => {
if let Some(token) = token {
match db.users.find_from_token(&token).unwrap() {
// TODO: M_UNKNOWN_TOKEN
None => return Failure((Status::Unauthorized, ())),
Some((user_id, device_id)) => {
(Some(user_id), Some(device_id.into()), false)
}
}
} else {
// TODO: M_MISSING_TOKEN
return Failure((Status::Unauthorized, ()));
}
}
AuthScheme::ServerSignatures => (None, None, false),
AuthScheme::None => (None, None, false),
}
};
2020-03-29 19:05:20 +00:00
2020-04-04 18:50:01 +00:00
let mut http_request = http::Request::builder()
.uri(request.uri().to_string())
.method(&*request.method().to_string());
for header in request.headers().iter() {
http_request = http_request.header(header.name.as_str(), &*header.value);
2020-03-29 19:05:20 +00:00
}
2020-02-15 21:42:21 +00:00
let limit = db.globals.max_request_size();
let mut handle = data.open(ByteUnit::Byte(limit.into()));
2020-04-04 18:50:01 +00:00
let mut body = Vec::new();
handle.read_to_end(&mut body).await.unwrap();
let http_request = http_request.body(body.clone()).unwrap();
debug!("{:?}", http_request);
2020-04-04 18:50:01 +00:00
2020-09-08 15:32:03 +00:00
match <T as Outgoing>::Incoming::try_from(http_request) {
2020-04-04 18:50:01 +00:00
Ok(t) => Success(Ruma {
body: t,
sender_user,
sender_device,
2020-05-18 15:53:34 +00:00
// TODO: Can we avoid parsing it again? (We only need this for append_pdu)
2020-05-23 09:20:00 +00:00
json_body: utils::string_from_bytes(&body)
.ok()
.and_then(|s| serde_json::value::RawValue::from_string(s).ok()),
from_appservice,
2020-04-04 18:50:01 +00:00
}),
Err(e) => {
2020-04-11 18:03:22 +00:00
warn!("{:?}", e);
Failure((Status::BadRequest, ()))
2020-04-04 18:50:01 +00:00
}
2020-02-18 21:07:57 +00:00
}
2020-04-04 18:50:01 +00:00
})
2020-02-15 21:42:21 +00:00
}
}
2020-09-08 15:32:03 +00:00
impl<T: Outgoing> Deref for Ruma<T> {
type Target = T::Incoming;
2020-02-15 21:42:21 +00:00
fn deref(&self) -> &Self::Target {
2020-03-28 17:50:02 +00:00
&self.body
}
}
2020-03-28 22:08:59 +00:00
/// This struct converts ruma responses into rocket http responses.
2020-06-09 13:13:17 +00:00
pub type ConduitResult<T> = std::result::Result<RumaResponse<T>, Error>;
2020-04-04 18:50:01 +00:00
2020-06-09 13:13:17 +00:00
pub struct RumaResponse<T: TryInto<http::Response<Vec<u8>>>>(pub T);
2020-02-18 21:07:57 +00:00
2020-06-09 13:13:17 +00:00
impl<T: TryInto<http::Response<Vec<u8>>>> From<T> for RumaResponse<T> {
fn from(t: T) -> Self {
Self(t)
2020-02-18 21:07:57 +00:00
}
}
#[cfg(feature = "conduit_bin")]
impl<'r, 'o, T> Responder<'r, 'o> for RumaResponse<T>
2020-04-06 15:37:13 +00:00
where
2020-04-08 21:25:19 +00:00
T: Send + TryInto<http::Response<Vec<u8>>>,
2020-04-06 15:37:13 +00:00
T::Error: Send,
'o: 'r,
2020-04-06 15:37:13 +00:00
{
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
2020-06-09 13:13:17 +00:00
let http_response: Result<http::Response<_>, _> = self.0.try_into();
2020-02-18 21:07:57 +00:00
match http_response {
2020-02-15 21:42:21 +00:00
Ok(http_response) => {
let mut response = rocket::response::Response::build();
let status = http_response.status();
response.raw_status(status.into(), "");
2020-02-15 21:42:21 +00:00
for header in http_response.headers() {
response
.raw_header(header.0.to_string(), header.1.to_str().unwrap().to_owned());
}
2020-03-28 17:50:02 +00:00
let http_body = http_response.into_body();
response.sized_body(http_body.len(), Cursor::new(http_body));
2020-03-28 17:50:02 +00:00
response.raw_header("Access-Control-Allow-Origin", "*");
response.raw_header(
"Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS",
);
response.raw_header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization",
);
2020-02-15 21:42:21 +00:00
response.ok()
}
Err(_) => Err(Status::InternalServerError),
}
}
}