Merge branch 'openid-api' into 'next'
feat: support OpenID endpoints Closes #453 See merge request famedly/conduit!681
This commit is contained in:
commit
817f382c5f
10 changed files with 127 additions and 4 deletions
|
@ -11,6 +11,7 @@ mod keys;
|
|||
mod media;
|
||||
mod membership;
|
||||
mod message;
|
||||
mod openid;
|
||||
mod presence;
|
||||
mod profile;
|
||||
mod push;
|
||||
|
@ -47,6 +48,7 @@ pub use keys::*;
|
|||
pub use media::*;
|
||||
pub use membership::*;
|
||||
pub use message::*;
|
||||
pub use openid::*;
|
||||
pub use presence::*;
|
||||
pub use profile::*;
|
||||
pub use push::*;
|
||||
|
|
23
src/api/client_server/openid.rs
Normal file
23
src/api/client_server/openid.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use ruma::{api::client::account, authentication::TokenType};
|
||||
|
||||
use crate::{services, Result, Ruma};
|
||||
|
||||
/// # `POST /_matrix/client/r0/user/{userId}/openid/request_token`
|
||||
///
|
||||
/// Request an OpenID token to verify identity with third-party services.
|
||||
///
|
||||
/// - The token generated is only valid for the OpenID API.
|
||||
pub async fn create_openid_token_route(
|
||||
body: Ruma<account::request_openid_token::v3::Request>,
|
||||
) -> Result<account::request_openid_token::v3::Response> {
|
||||
let (access_token, expires_in) = services().users.create_openid_token(&body.user_id)?;
|
||||
|
||||
Ok(account::request_openid_token::v3::Response {
|
||||
access_token,
|
||||
token_type: TokenType::Bearer,
|
||||
matrix_server_name: services().globals.server_name().to_owned(),
|
||||
expires_in: Duration::from_secs(expires_in),
|
||||
})
|
||||
}
|
|
@ -102,10 +102,15 @@ where
|
|||
let (sender_user, sender_device, sender_servername, appservice_info) =
|
||||
match (metadata.authentication, token) {
|
||||
(_, Token::Invalid) => {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::UnknownToken { soft_logout: false },
|
||||
"Unknown access token.",
|
||||
))
|
||||
// OpenID endpoint uses a query param with the same name, drop this once query params for user auth are removed from the spec
|
||||
if query_params.access_token.is_some() {
|
||||
(None, None, None, None)
|
||||
} else {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::UnknownToken { soft_logout: false },
|
||||
"Unknown access token.",
|
||||
));
|
||||
}
|
||||
}
|
||||
(AuthScheme::AccessToken, Token::Appservice(info)) => {
|
||||
let user_id = query_params
|
||||
|
|
|
@ -24,6 +24,7 @@ use ruma::{
|
|||
event::{get_event, get_missing_events, get_room_state, get_room_state_ids},
|
||||
keys::{claim_keys, get_keys},
|
||||
membership::{create_invite, create_join_event, prepare_join_event},
|
||||
openid::get_openid_userinfo,
|
||||
query::{get_profile_information, get_room_information},
|
||||
transactions::{
|
||||
edu::{DeviceListUpdateContent, DirectDeviceContent, Edu, SigningKeyUpdateContent},
|
||||
|
@ -1914,6 +1915,25 @@ pub async fn claim_keys_route(
|
|||
})
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/federation/v1/openid/userinfo`
|
||||
///
|
||||
/// Get information about the user that generated the OpenID token.
|
||||
pub async fn get_openid_userinfo_route(
|
||||
body: Ruma<get_openid_userinfo::v1::Request>,
|
||||
) -> Result<get_openid_userinfo::v1::Response> {
|
||||
Ok(get_openid_userinfo::v1::Response::new(
|
||||
services()
|
||||
.users
|
||||
.find_from_openid_token(&body.access_token)?
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest(
|
||||
ErrorKind::Unauthorized,
|
||||
"OpenID token has expired or does not exist.",
|
||||
)
|
||||
})?,
|
||||
))
|
||||
}
|
||||
|
||||
/// # `GET /.well-known/matrix/server`
|
||||
///
|
||||
/// Returns the federation server discovery information.
|
||||
|
|
|
@ -47,6 +47,8 @@ pub struct Config {
|
|||
#[serde(default = "false_fn")]
|
||||
pub allow_registration: bool,
|
||||
pub registration_token: Option<String>,
|
||||
#[serde(default = "default_openid_token_ttl")]
|
||||
pub openid_token_ttl: u64,
|
||||
#[serde(default = "true_fn")]
|
||||
pub allow_encryption: bool,
|
||||
#[serde(default = "false_fn")]
|
||||
|
@ -302,6 +304,10 @@ fn default_turn_ttl() -> u64 {
|
|||
60 * 60 * 24
|
||||
}
|
||||
|
||||
fn default_openid_token_ttl() -> u64 {
|
||||
60 * 60
|
||||
}
|
||||
|
||||
// I know, it's a great name
|
||||
pub fn default_default_room_version() -> RoomVersionId {
|
||||
RoomVersionId::V10
|
||||
|
|
|
@ -11,6 +11,7 @@ use ruma::{
|
|||
use tracing::warn;
|
||||
|
||||
use crate::{
|
||||
api::client_server::TOKEN_LENGTH,
|
||||
database::KeyValueDatabase,
|
||||
service::{self, users::clean_signatures},
|
||||
services, utils, Error, Result,
|
||||
|
@ -943,6 +944,52 @@ impl service::users::Data for KeyValueDatabase {
|
|||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
// Creates an OpenID token, which can be used to prove that a user has access to an account (primarily for integrations)
|
||||
fn create_openid_token(&self, user_id: &UserId) -> Result<(String, u64)> {
|
||||
let token = utils::random_string(TOKEN_LENGTH);
|
||||
|
||||
let expires_in = services().globals.config.openid_token_ttl;
|
||||
let expires_at = utils::millis_since_unix_epoch()
|
||||
.checked_add(expires_in * 1000)
|
||||
.expect("time is valid");
|
||||
|
||||
let mut value = expires_at.to_be_bytes().to_vec();
|
||||
value.extend_from_slice(user_id.as_bytes());
|
||||
|
||||
self.openidtoken_expiresatuserid
|
||||
.insert(token.as_bytes(), value.as_slice())?;
|
||||
|
||||
Ok((token, expires_in))
|
||||
}
|
||||
|
||||
/// Find out which user an OpenID access token belongs to.
|
||||
fn find_from_openid_token(&self, token: &str) -> Result<Option<OwnedUserId>> {
|
||||
let Some(value) = self.openidtoken_expiresatuserid.get(token.as_bytes())? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let (expires_at_bytes, user_bytes) = value.split_at(0u64.to_be_bytes().len());
|
||||
|
||||
let expires_at = u64::from_be_bytes(
|
||||
expires_at_bytes
|
||||
.try_into()
|
||||
.map_err(|_| Error::bad_database("expires_at in openid_userid is invalid u64."))?,
|
||||
);
|
||||
|
||||
if expires_at < utils::millis_since_unix_epoch() {
|
||||
self.openidtoken_expiresatuserid.remove(token.as_bytes())?;
|
||||
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Some(
|
||||
UserId::parse(utils::string_from_bytes(user_bytes).map_err(|_| {
|
||||
Error::bad_database("User ID in openid_userid is invalid unicode.")
|
||||
})?)
|
||||
.map_err(|_| Error::bad_database("User ID in openid_userid is invalid.")),
|
||||
)
|
||||
.transpose()
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyValueDatabase {}
|
||||
|
|
|
@ -57,6 +57,7 @@ pub struct KeyValueDatabase {
|
|||
pub(super) userid_masterkeyid: Arc<dyn KvTree>,
|
||||
pub(super) userid_selfsigningkeyid: Arc<dyn KvTree>,
|
||||
pub(super) userid_usersigningkeyid: Arc<dyn KvTree>,
|
||||
pub(super) openidtoken_expiresatuserid: Arc<dyn KvTree>, // expiresatuserid = expiresat + userid
|
||||
|
||||
pub(super) userfilterid_filter: Arc<dyn KvTree>, // UserFilterId = UserId + FilterId
|
||||
|
||||
|
@ -290,6 +291,7 @@ impl KeyValueDatabase {
|
|||
userid_masterkeyid: builder.open_tree("userid_masterkeyid")?,
|
||||
userid_selfsigningkeyid: builder.open_tree("userid_selfsigningkeyid")?,
|
||||
userid_usersigningkeyid: builder.open_tree("userid_usersigningkeyid")?,
|
||||
openidtoken_expiresatuserid: builder.open_tree("openidtoken_expiresatuserid")?,
|
||||
userfilterid_filter: builder.open_tree("userfilterid_filter")?,
|
||||
todeviceid_events: builder.open_tree("todeviceid_events")?,
|
||||
|
||||
|
|
|
@ -277,6 +277,7 @@ fn routes(config: &Config) -> Router {
|
|||
.ruma_route(client_server::get_room_aliases_route)
|
||||
.ruma_route(client_server::get_filter_route)
|
||||
.ruma_route(client_server::create_filter_route)
|
||||
.ruma_route(client_server::create_openid_token_route)
|
||||
.ruma_route(client_server::set_global_account_data_route)
|
||||
.ruma_route(client_server::set_room_account_data_route)
|
||||
.ruma_route(client_server::get_global_account_data_route)
|
||||
|
@ -431,6 +432,7 @@ fn routes(config: &Config) -> Router {
|
|||
.ruma_route(server_server::get_profile_information_route)
|
||||
.ruma_route(server_server::get_keys_route)
|
||||
.ruma_route(server_server::claim_keys_route)
|
||||
.ruma_route(server_server::get_openid_userinfo_route)
|
||||
.ruma_route(server_server::well_known_server)
|
||||
} else {
|
||||
router
|
||||
|
|
|
@ -211,4 +211,10 @@ pub trait Data: Send + Sync {
|
|||
fn create_filter(&self, user_id: &UserId, filter: &FilterDefinition) -> Result<String>;
|
||||
|
||||
fn get_filter(&self, user_id: &UserId, filter_id: &str) -> Result<Option<FilterDefinition>>;
|
||||
|
||||
// Creates an OpenID token, which can be used to prove that a user has access to an account (primarily for integrations)
|
||||
fn create_openid_token(&self, user_id: &UserId) -> Result<(String, u64)>;
|
||||
|
||||
/// Find out which user an OpenID access token belongs to.
|
||||
fn find_from_openid_token(&self, token: &str) -> Result<Option<OwnedUserId>>;
|
||||
}
|
||||
|
|
|
@ -598,6 +598,16 @@ impl Service {
|
|||
) -> Result<Option<FilterDefinition>> {
|
||||
self.db.get_filter(user_id, filter_id)
|
||||
}
|
||||
|
||||
// Creates an OpenID token, which can be used to prove that a user has access to an account (primarily for integrations)
|
||||
pub fn create_openid_token(&self, user_id: &UserId) -> Result<(String, u64)> {
|
||||
self.db.create_openid_token(user_id)
|
||||
}
|
||||
|
||||
/// Find out which user an OpenID access token belongs to.
|
||||
pub fn find_from_openid_token(&self, token: &str) -> Result<Option<OwnedUserId>> {
|
||||
self.db.find_from_openid_token(token)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure that a user only sees signatures from themselves and the target user
|
||||
|
|
Loading…
Reference in a new issue