refactor: replace native_db with memdb and add tests

This commit is contained in:
rustmailer
2026-05-14 02:29:23 +08:00
parent 0abaa66a40
commit 5406c4322c
102 changed files with 7225 additions and 2735 deletions
+7 -13
View File
@@ -38,11 +38,9 @@ impl AccessTokenApi {
&self,
context: WrappedContext,
) -> ApiResult<Json<Vec<AccessTokenResp>>> {
context
.require_permission(None, Permission::TOKEN_MANAGE)
.await?;
context.require_permission(None, Permission::TOKEN_MANAGE)?;
Ok(Json(AccessTokenModel::list_all_api_tokens().await?))
Ok(Json(AccessTokenModel::list_all_api_tokens()?))
}
/// Deletes a specific access token.
@@ -58,14 +56,12 @@ impl AccessTokenApi {
context: WrappedContext,
) -> ApiResult<()> {
let token = token.0.trim();
let token = AccessTokenModel::get_token(token).await?;
let token = AccessTokenModel::get_token(token)?;
if context.user.id != token.user_id {
context
.require_permission(None, Permission::TOKEN_MANAGE)
.await?;
context.require_permission(None, Permission::TOKEN_MANAGE)?;
}
Ok(AccessTokenModel::delete(&token.token).await?)
Ok(AccessTokenModel::delete(&token.token)?)
}
/// Creates a new api token.
@@ -83,12 +79,10 @@ impl AccessTokenApi {
let current_user_id = context.user.id;
let target_user_id = payload.0.user_id.unwrap_or(current_user_id);
if target_user_id != current_user_id {
context
.require_permission(None, Permission::USER_MANAGE)
.await?;
context.require_permission(None, Permission::USER_MANAGE)?;
}
let token_string = AccessTokenModel::create_api_token(target_user_id, payload.0).await?;
let token_string = AccessTokenModel::create_api_token(target_user_id, payload.0)?;
Ok(PlainText(token_string))
}
}
+26 -43
View File
@@ -56,10 +56,8 @@ impl AccountApi {
context: WrappedContext,
) -> ApiResult<Json<AccountModel>> {
let account_id = account_id.0;
context
.require_permission(Some(account_id), Permission::ACCOUNT_READ_DETAILS)
.await?;
Ok(Json(AccountModel::async_get(account_id).await?))
context.require_permission(Some(account_id), Permission::ACCOUNT_READ_DETAILS)?;
Ok(Json(AccountModel::get(account_id)?))
}
/// Delete an account by ID - WARNING: This permanently removes the account and all associated resources
@@ -75,10 +73,9 @@ impl AccountApi {
context: WrappedContext,
) -> ApiResult<()> {
let account_id = account_id.0;
context
.require_permission(Some(account_id), Permission::ACCOUNT_MANAGE)
.await?;
Ok(AccountModel::delete(account_id).await?)
context.require_permission(Some(account_id), Permission::ACCOUNT_MANAGE)?;
AccountModel::delete(account_id).await?;
Ok(())
}
/// Create a new account
@@ -89,9 +86,7 @@ impl AccountApi {
payload: Json<AccountCreateRequest>,
context: WrappedContext,
) -> ApiResult<Json<AccountModel>> {
context
.require_permission(None, Permission::ACCOUNT_CREATE)
.await?;
context.require_permission(None, Permission::ACCOUNT_CREATE)?;
let account = AccountModel::create_account(context.user.id, payload.0).await?;
Ok(Json(account))
}
@@ -111,10 +106,8 @@ impl AccountApi {
context: WrappedContext,
) -> ApiResult<()> {
let account_id = account_id.0;
context
.require_permission(Some(account_id), Permission::ACCOUNT_MANAGE)
.await?;
Ok(AccountModel::update(account_id, payload.0, true).await?)
context.require_permission(Some(account_id), Permission::ACCOUNT_MANAGE)?;
Ok(AccountModel::update(account_id, payload.0, true)?)
}
/// List accounts with optional pagination parameters
@@ -129,16 +122,15 @@ impl AccountApi {
desc: Query<Option<bool>>,
context: WrappedContext,
) -> ApiResult<Json<DataPage<AccountResp>>> {
let is_admin = context.user.is_admin().await;
let is_admin = context.user.is_admin();
let sort_desc = desc.0.unwrap_or(true);
let user_map: HashMap<u64, UserModel> = UserModel::list_all()
.await?
let user_map: HashMap<u64, UserModel> = UserModel::list_all()?
.into_iter()
.map(|u| (u.id, u))
.collect();
let page_data: DataPage<AccountModel> = if is_admin {
AccountModel::paginate_list(page.0, page_size.0, desc.0).await?
AccountModel::paginate_list(page.0, page_size.0, desc.0)?
} else {
let authorized_ids: HashSet<u64> =
context.user.account_access_map.keys().cloned().collect();
@@ -153,8 +145,7 @@ impl AccountApi {
}));
}
let mut accounts: Vec<AccountModel> = AccountModel::list_all()
.await?
let mut accounts: Vec<AccountModel> = AccountModel::list_all()?
.into_iter()
.filter(|acct| authorized_ids.contains(&acct.id))
.collect();
@@ -198,11 +189,9 @@ impl AccountApi {
context: WrappedContext,
) -> ApiResult<Json<DownloadState>> {
let account_id = account_id.0;
AccountModel::check_account_exists(account_id).await?;
context
.require_permission(Some(account_id), Permission::ACCOUNT_READ_DETAILS)
.await?;
let state = DownloadState::get(account_id).await?;
AccountModel::check_account_exists(account_id)?;
context.require_permission(Some(account_id), Permission::ACCOUNT_READ_DETAILS)?;
let state = DownloadState::get(account_id)?;
let state = state.unwrap_or(DownloadState::empty(account_id));
Ok(Json(state))
}
@@ -220,16 +209,14 @@ impl AccountApi {
context: WrappedContext,
) -> ApiResult<()> {
let account_id = account_id.0;
let account = AccountModel::check_account_exists(account_id).await?;
let account = AccountModel::check_account_exists(account_id)?;
if !matches!(account.account_type, AccountType::IMAP) {
return Err(raise_error!(
format!("Manual download is not supported for '{:#?}' accounts. Only IMAP accounts are supported.", account.account_type),
ErrorCode::InvalidParameter
))?;
}
context
.require_permission(Some(account_id), Permission::ACCOUNT_MANAGE)
.await?;
context.require_permission(Some(account_id), Permission::ACCOUNT_MANAGE)?;
SYNC_TASKS.start_manual_task(account_id).await?;
Ok(())
}
@@ -247,7 +234,7 @@ impl AccountApi {
context: WrappedContext,
) -> ApiResult<()> {
let account_id = account_id.0;
let account = AccountModel::check_account_exists(account_id).await?;
let account = AccountModel::check_account_exists(account_id)?;
if !matches!(account.account_type, AccountType::IMAP) {
return Err(raise_error!(
@@ -255,9 +242,7 @@ impl AccountApi {
ErrorCode::InvalidParameter
))?;
}
context
.require_permission(Some(account_id), Permission::ACCOUNT_MANAGE)
.await?;
context.require_permission(Some(account_id), Permission::ACCOUNT_MANAGE)?;
if !SYNC_TASKS.is_manual_running(account_id).await {
return Err(raise_error!(
@@ -282,11 +267,9 @@ impl AccountApi {
context: WrappedContext,
) -> ApiResult<Json<AccountStats>> {
let account_id = account_id.0;
AccountModel::check_account_exists(account_id).await?;
context
.require_permission(Some(account_id), Permission::ACCOUNT_READ_DETAILS)
.await?;
let state = ENVELOPE_MANAGER.get_account_stats(account_id).await?;
AccountModel::check_account_exists(account_id)?;
context.require_permission(Some(account_id), Permission::ACCOUNT_READ_DETAILS)?;
let state = ENVELOPE_MANAGER.get_account_stats(account_id)?;
Ok(Json(state))
}
@@ -304,10 +287,10 @@ impl AccountApi {
only_nosync: Query<Option<bool>>,
context: WrappedContext,
) -> ApiResult<Json<Vec<MinimalAccount>>> {
let is_admin = context.user.is_admin().await;
let is_admin = context.user.is_admin();
let only_nosync = only_nosync.0.unwrap_or_default();
let minimal_list = AccountModel::minimal_list(only_nosync).await?;
let minimal_list = AccountModel::minimal_list(only_nosync)?;
if is_admin {
return Ok(Json(minimal_list));
}
@@ -323,8 +306,8 @@ impl AccountApi {
req: Json<BatchAccountRoleRequest>,
context: WrappedContext,
) -> ApiResult<()> {
req.validate_existence().await?;
req.0.do_assign(&context).await?;
req.validate_existence()?;
req.0.do_assign(&context)?;
Ok(())
}
}
+30 -47
View File
@@ -52,17 +52,13 @@ impl AttachmentApi {
payload: Json<AttachmentSearchRequest>,
context: WrappedContext,
) -> ApiResult<Json<DataPage<AttachmentModel>>> {
let authorized_ids: Option<HashSet<u64>> = if context
.has_permission(None, Permission::DATA_READ_ALL)
.await
{
None
} else {
Some(context.user.account_access_map.keys().cloned().collect())
};
Ok(Json(
search_attachment_impl(authorized_ids, payload.0).await?,
))
let authorized_ids: Option<HashSet<u64>> =
if context.has_permission(None, Permission::DATA_READ_ALL) {
None
} else {
Some(context.user.account_access_map.keys().cloned().collect())
};
Ok(Json(search_attachment_impl(authorized_ids, payload.0)?))
}
/// Retrieves the attachment (metadata) of a specific message.
@@ -80,13 +76,10 @@ impl AttachmentApi {
context: WrappedContext,
) -> ApiResult<Json<AttachmentModel>> {
let account_id = account_id.0;
context
.require_permission(Some(account_id), Permission::DATA_READ)
.await?;
context.require_permission(Some(account_id), Permission::DATA_READ)?;
let attachment_id = attachment_id.0;
let a = ATTACHMENT_MANAGER
.get_attachment_by_id(account_id, &attachment_id)
.await?
.get_attachment_by_id(account_id, &attachment_id)?
.ok_or_else(|| {
raise_error!(
format!(
@@ -109,15 +102,13 @@ impl AttachmentApi {
&self,
context: WrappedContext,
) -> ApiResult<Json<Vec<TagCount>>> {
let authorized_ids: Option<HashSet<u64>> = if context
.has_permission(None, Permission::DATA_READ_ALL)
.await
{
None
} else {
Some(context.user.account_access_map.keys().cloned().collect())
};
Ok(Json(ATTACHMENT_MANAGER.get_all_tags(authorized_ids).await?))
let authorized_ids: Option<HashSet<u64>> =
if context.has_permission(None, Permission::DATA_READ_ALL) {
None
} else {
Some(context.user.account_access_map.keys().cloned().collect())
};
Ok(Json(ATTACHMENT_MANAGER.get_all_tags(authorized_ids)?))
}
/// Adds or removes facet tags for multiple emails across accounts.
@@ -137,9 +128,7 @@ impl AttachmentApi {
}
for account_id in req.updates.keys() {
context
.require_permission(Some(*account_id), Permission::DATA_MANAGE)
.await?;
context.require_permission(Some(*account_id), Permission::DATA_MANAGE)?;
}
ATTACHMENT_MANAGER.update_attachment_tags(req).await?;
@@ -156,17 +145,13 @@ impl AttachmentApi {
&self,
context: WrappedContext,
) -> ApiResult<Json<HashSet<String>>> {
let authorized_ids: Option<HashSet<u64>> = if context
.has_permission(None, Permission::DATA_READ_ALL)
.await
{
None
} else {
Some(context.user.account_access_map.keys().cloned().collect())
};
Ok(Json(
ATTACHMENT_MANAGER.get_all_senders(authorized_ids).await?,
))
let authorized_ids: Option<HashSet<u64>> =
if context.has_permission(None, Permission::DATA_READ_ALL) {
None
} else {
Some(context.user.account_access_map.keys().cloned().collect())
};
Ok(Json(ATTACHMENT_MANAGER.get_all_senders(authorized_ids)?))
}
/// Retrieves unique metadata for all attachments across authorized accounts.
@@ -179,14 +164,12 @@ impl AttachmentApi {
&self,
context: WrappedContext,
) -> ApiResult<Json<AttachmentMetadata>> {
let authorized_ids: Option<HashSet<u64>> = if context
.has_permission(None, Permission::DATA_READ_ALL)
.await
{
None
} else {
Some(context.user.account_access_map.keys().cloned().collect())
};
let authorized_ids: Option<HashSet<u64>> =
if context.has_permission(None, Permission::DATA_READ_ALL) {
None
} else {
Some(context.user.account_access_map.keys().cloned().collect())
};
Ok(Json(
ATTACHMENT_MANAGER.collect_attachment_metadata(authorized_ids)?,
))
+1 -4
View File
@@ -44,10 +44,7 @@ impl AutoConfigApi {
email_address: Path<String>,
context: WrappedContext,
) -> ApiResult<Json<MailServerConfig>> {
context
.require_permission(None, Permission::ACCOUNT_CREATE)
.await?;
context.require_permission(None, Permission::ACCOUNT_CREATE)?;
let result = resolve_autoconfig(email_address.0.trim())
.await?
.ok_or_else(|| {
+1 -3
View File
@@ -44,9 +44,7 @@ impl ImportApi {
payload: Json<BatchEmlRequest>,
context: WrappedContext,
) -> ApiResult<Json<BatchEmlResult>> {
context
.require_permission(Some(payload.0.account_id), Permission::DATA_IMPORT_BATCH)
.await?;
context.require_permission(Some(payload.0.account_id), Permission::DATA_IMPORT_BATCH)?;
Ok(Json(ImportEmls::do_import(payload.0).await?))
}
}
+2 -6
View File
@@ -53,9 +53,7 @@ impl MailBoxApi {
context: WrappedContext,
) -> ApiResult<Json<Vec<MailBox>>> {
let account_id = account_id.0;
context
.require_permission(Some(account_id), Permission::ACCOUNT_READ_DETAILS)
.await?;
context.require_permission(Some(account_id), Permission::ACCOUNT_READ_DETAILS)?;
let remote = remote.0.unwrap_or(false);
Ok(Json(get_account_mailboxes(account_id, remote).await?))
}
@@ -82,9 +80,7 @@ impl MailBoxApi {
) -> ApiResult<()> {
let account_id = account_id.0;
let mailbox_id = mailbox_id.0;
context
.require_permission(Some(account_id), Permission::DATA_DELETE)
.await?;
context.require_permission(Some(account_id), Permission::DATA_DELETE)?;
Ok(delete_mailbox_impl(account_id, mailbox_id).await?)
}
}
+51 -78
View File
@@ -35,8 +35,8 @@ use bichon_core::message::search::{search_messages_impl, EmailSearchRequest};
use bichon_core::message::tags::TagCount;
use bichon_core::message::tags::TagsRequest;
use bichon_core::raise_error;
use bichon_core::store::envelope::Envelope;
use bichon_core::store::blob::get_reader;
use bichon_core::store::envelope::Envelope;
use bichon_core::store::tantivy::envelope::ENVELOPE_MANAGER;
use bichon_core::store::tantivy::validate_facet;
use bichon_core::users::permissions::Permission;
@@ -65,9 +65,7 @@ impl MessageApi {
) -> ApiResult<()> {
let request = payload.0;
for account_id in request.keys() {
context
.require_permission(Some(*account_id), Permission::DATA_DELETE)
.await?;
context.require_permission(Some(*account_id), Permission::DATA_DELETE)?;
}
Ok(delete_messages_impl(request).await?)
}
@@ -84,15 +82,13 @@ impl MessageApi {
payload: Json<EmailSearchRequest>,
context: WrappedContext,
) -> ApiResult<Json<DataPage<Envelope>>> {
let authorized_ids: Option<HashSet<u64>> = if context
.has_permission(None, Permission::DATA_READ_ALL)
.await
{
None
} else {
Some(context.user.account_access_map.keys().cloned().collect())
};
Ok(Json(search_messages_impl(authorized_ids, payload.0).await?))
let authorized_ids: Option<HashSet<u64>> =
if context.has_permission(None, Permission::DATA_READ_ALL) {
None
} else {
Some(context.user.account_access_map.keys().cloned().collect())
};
Ok(Json(search_messages_impl(authorized_ids, payload.0)?))
}
/// Retrieves all messages belonging to a specific thread. Requires `thread_id`, `page`, and `page_size` query parameters.
@@ -115,12 +111,13 @@ impl MessageApi {
) -> ApiResult<Json<DataPage<Envelope>>> {
let account_id = account_id.0;
let thread_id = thread_id.0.trim();
context
.require_permission(Some(account_id), Permission::DATA_READ)
.await?;
Ok(Json(
get_thread_messages(account_id, thread_id, page.0, page_size.0).await?,
))
context.require_permission(Some(account_id), Permission::DATA_READ)?;
Ok(Json(get_thread_messages(
account_id,
thread_id,
page.0,
page_size.0,
)?))
}
/// Fetches the content of a specific email.
@@ -138,12 +135,8 @@ impl MessageApi {
context: WrappedContext,
) -> ApiResult<Json<FullMessageContent>> {
let account_id = account_id.0;
context
.require_permission(Some(account_id), Permission::DATA_READ)
.await?;
Ok(Json(
retrieve_email_content(account_id, envelope_id.0).await?,
))
context.require_permission(Some(account_id), Permission::DATA_READ)?;
Ok(Json(retrieve_email_content(account_id, envelope_id.0)?))
}
/// Retrieves the content of an email embedded as an attachment.
@@ -162,13 +155,13 @@ impl MessageApi {
context: WrappedContext,
) -> ApiResult<Json<FullNestedMessageContent>> {
let account_id = account_id.0;
context
.require_permission(Some(account_id), Permission::DATA_READ)
.await?;
context.require_permission(Some(account_id), Permission::DATA_READ)?;
let content_hash = content_hash.0.trim();
Ok(Json(
retrieve_nested_eml_content(account_id, envelope_id.0, content_hash).await?,
))
Ok(Json(retrieve_nested_eml_content(
account_id,
envelope_id.0,
content_hash,
)?))
}
/// Retrieves the envelope (metadata) of a specific message.
@@ -186,13 +179,10 @@ impl MessageApi {
context: WrappedContext,
) -> ApiResult<Json<Envelope>> {
let account_id = account_id.0;
context
.require_permission(Some(account_id), Permission::DATA_READ)
.await?;
context.require_permission(Some(account_id), Permission::DATA_READ)?;
let envelope_id = envelope_id.0;
let e = ENVELOPE_MANAGER
.get_envelope_by_id(account_id, &envelope_id)
.await?
.get_envelope_by_id(account_id, &envelope_id)?
.ok_or_else(|| {
raise_error!(
format!(
@@ -220,12 +210,10 @@ impl MessageApi {
context: WrappedContext,
) -> ApiResult<Attachment<Body>> {
let account_id = account_id.0;
AccountModel::check_account_exists(account_id).await?;
context
.require_permission(Some(account_id), Permission::DATA_RAW_DOWNLOAD)
.await?;
AccountModel::check_account_exists(account_id)?;
context.require_permission(Some(account_id), Permission::DATA_RAW_DOWNLOAD)?;
let envelope_id = envelope_id.0;
let reader = get_reader(account_id, envelope_id.clone()).await?;
let reader = get_reader(account_id, envelope_id.clone())?;
let body = Body::from_async_read(reader);
let attachment = Attachment::new(body)
.attachment_type(AttachmentType::Attachment)
@@ -247,9 +235,7 @@ impl MessageApi {
context: WrappedContext,
) -> ApiResult<()> {
let account_id = account_id.0;
context
.require_permission(Some(account_id), Permission::DATA_EXPORT_BATCH)
.await?;
context.require_permission(Some(account_id), Permission::DATA_EXPORT_BATCH)?;
Ok(restore_emails(account_id, payload.0.envelope_ids).await?)
}
@@ -271,12 +257,10 @@ impl MessageApi {
) -> ApiResult<Attachment<Body>> {
let account_id = account_id.0;
let envelope_id = envelope_id.0.trim().to_string();
AccountModel::check_account_exists(account_id).await?;
context
.require_permission(Some(account_id), Permission::DATA_READ)
.await?;
AccountModel::check_account_exists(account_id)?;
context.require_permission(Some(account_id), Permission::DATA_READ)?;
let content_hash = content_hash.0.trim();
let reader = retrieve_attachment_content(account_id, envelope_id, content_hash).await?;
let reader = retrieve_attachment_content(account_id, envelope_id, content_hash)?;
let body = Body::from_async_read(reader);
let attachment = Attachment::new(body)
.attachment_type(AttachmentType::Attachment)
@@ -303,10 +287,8 @@ impl MessageApi {
) -> ApiResult<Attachment<Body>> {
let account_id = account_id.0;
let envelope_id = envelope_id.0.trim().to_string();
AccountModel::check_account_exists(account_id).await?;
context
.require_permission(Some(account_id), Permission::DATA_READ)
.await?;
AccountModel::check_account_exists(account_id)?;
context.require_permission(Some(account_id), Permission::DATA_READ)?;
let content_hash = content_hash.0.trim();
let nested_content_hash = nested_content_hash.0.trim();
let reader = retrieve_nested_attachment_content(
@@ -314,8 +296,7 @@ impl MessageApi {
envelope_id,
content_hash,
nested_content_hash,
)
.await?;
)?;
let body = Body::from_async_read(reader);
let attachment = Attachment::new(body)
.attachment_type(AttachmentType::Attachment)
@@ -326,15 +307,13 @@ impl MessageApi {
/// Returns all facets in the index along with their document counts.
#[oai(path = "/all-tags", method = "get", operation_id = "get_all_tags")]
async fn get_all_tags(&self, context: WrappedContext) -> ApiResult<Json<Vec<TagCount>>> {
let authorized_ids: Option<HashSet<u64>> = if context
.has_permission(None, Permission::DATA_READ_ALL)
.await
{
None
} else {
Some(context.user.account_access_map.keys().cloned().collect())
};
Ok(Json(ENVELOPE_MANAGER.get_all_tags(authorized_ids).await?))
let authorized_ids: Option<HashSet<u64>> =
if context.has_permission(None, Permission::DATA_READ_ALL) {
None
} else {
Some(context.user.account_access_map.keys().cloned().collect())
};
Ok(Json(ENVELOPE_MANAGER.get_all_tags(authorized_ids)?))
}
/// Adds or removes facet tags for multiple emails across accounts.
@@ -354,9 +333,7 @@ impl MessageApi {
}
for account_id in req.updates.keys() {
context
.require_permission(Some(*account_id), Permission::DATA_MANAGE)
.await?;
context.require_permission(Some(*account_id), Permission::DATA_MANAGE)?;
}
ENVELOPE_MANAGER.update_envelope_tags(req).await?;
@@ -370,16 +347,12 @@ impl MessageApi {
operation_id = "get_all_contacts"
)]
async fn get_all_contacts(&self, context: WrappedContext) -> ApiResult<Json<HashSet<String>>> {
let authorized_ids: Option<HashSet<u64>> = if context
.has_permission(None, Permission::DATA_READ_ALL)
.await
{
None
} else {
Some(context.user.account_access_map.keys().cloned().collect())
};
Ok(Json(
ENVELOPE_MANAGER.get_all_contacts(authorized_ids).await?,
))
let authorized_ids: Option<HashSet<u64>> =
if context.has_permission(None, Permission::DATA_READ_ALL) {
None
} else {
Some(context.user.account_access_map.keys().cloned().collect())
};
Ok(Json(ENVELOPE_MANAGER.get_all_contacts(authorized_ids)?))
}
}
+25 -33
View File
@@ -52,13 +52,13 @@ impl OAuth2Api {
context: WrappedContext,
) -> ApiResult<Json<OAuth2>> {
let id = id.0;
let mut oauth2 = OAuth2::get(id).await?.ok_or_else(|| {
let mut oauth2 = OAuth2::get(id)?.ok_or_else(|| {
raise_error!(
format!("OAuth2 configuration id='{id}' not found"),
ErrorCode::ResourceNotFound
)
})?;
if context.has_permission(None, Permission::ROOT).await {
if context.has_permission(None, Permission::ROOT) {
return Ok(Json(oauth2));
}
oauth2.scrub_sensitive_fields();
@@ -80,8 +80,8 @@ impl OAuth2Api {
id: Path<u64>,
context: WrappedContext,
) -> ApiResult<()> {
context.require_permission(None, Permission::ROOT).await?;
Ok(OAuth2::delete(id.0).await?)
context.require_permission(None, Permission::ROOT)?;
Ok(OAuth2::delete(id.0)?)
}
/// Creates a new OAuth2 configuration.
@@ -99,9 +99,9 @@ impl OAuth2Api {
request: Json<OAuth2CreateRequest>,
context: WrappedContext,
) -> ApiResult<()> {
context.require_permission(None, Permission::ROOT).await?;
context.require_permission(None, Permission::ROOT)?;
let entity = OAuth2::new(request.0)?;
Ok(entity.save().await?)
Ok(entity.save()?)
}
/// Updates an existing OAuth2 configuration.
@@ -121,8 +121,8 @@ impl OAuth2Api {
payload: Json<OAuth2UpdateRequest>,
context: WrappedContext,
) -> ApiResult<()> {
context.require_permission(None, Permission::ROOT).await?;
Ok(OAuth2::update(id.0, payload.0).await?)
context.require_permission(None, Permission::ROOT)?;
Ok(OAuth2::update(id.0, payload.0)?)
}
/// Lists OAuth2 configurations with pagination and sorting options.
@@ -144,8 +144,8 @@ impl OAuth2Api {
desc: Query<Option<bool>>,
context: WrappedContext,
) -> ApiResult<Json<DataPage<OAuth2>>> {
let mut list = OAuth2::paginate_list(page.0, page_size.0, desc.0).await?;
if context.has_permission(None, Permission::ROOT).await {
let mut list = OAuth2::paginate_list(page.0, page_size.0, desc.0)?;
if context.has_permission(None, Permission::ROOT) {
return Ok(Json(list));
}
//Non-root users can only view masked data.
@@ -172,15 +172,13 @@ impl OAuth2Api {
context: WrappedContext,
) -> ApiResult<PlainText<String>> {
let request = request.0;
context
.require_any_permission(vec![
(None, Permission::ACCOUNT_CREATE),
(Some(request.account_id), Permission::ACCOUNT_MANAGE),
])
.await?;
context.require_any_permission(vec![
(None, Permission::ACCOUNT_CREATE),
(Some(request.account_id), Permission::ACCOUNT_MANAGE),
])?;
let flow = OAuth2Flow::new(request.oauth2_id);
Ok(PlainText(flow.authorize_url(request.account_id).await?))
Ok(PlainText(flow.authorize_url(request.account_id)?))
}
/// Retrieves OAuth2 access tokens for a specified account.
@@ -198,17 +196,13 @@ impl OAuth2Api {
context: WrappedContext,
) -> ApiResult<Json<OAuth2AccessToken>> {
let account = account_id.0;
context
.require_permission(Some(account), Permission::ACCOUNT_MANAGE)
.await?;
Ok(Json(OAuth2AccessToken::get(account).await?.ok_or_else(
|| {
raise_error!(
"OAuth2 access tokens not found".into(),
ErrorCode::ResourceNotFound
)
},
)?))
context.require_permission(Some(account), Permission::ACCOUNT_MANAGE)?;
Ok(Json(OAuth2AccessToken::get(account)?.ok_or_else(|| {
raise_error!(
"OAuth2 access tokens not found".into(),
ErrorCode::ResourceNotFound
)
})?))
}
/// Configures an external OAuth2 token for a specified account.
@@ -239,12 +233,10 @@ impl OAuth2Api {
context: WrappedContext,
) -> ApiResult<()> {
let account_id = account_id.0;
AccountModel::check_account_exists(account_id).await?;
AccountModel::check_account_exists(account_id)?;
// Check account access permissions
context
.require_permission(Some(account_id), Permission::ACCOUNT_MANAGE)
.await?;
OAuth2AccessToken::upsert_external_oauth_token(account_id, request.0).await?;
context.require_permission(Some(account_id), Permission::ACCOUNT_MANAGE)?;
OAuth2AccessToken::upsert_external_oauth_token(account_id, request.0)?;
Ok(())
}
}
+9 -10
View File
@@ -75,7 +75,6 @@ impl SystemApi {
async fn list_proxy(&self, _context: WrappedContext) -> ApiResult<Json<Vec<Proxy>>> {
//The proxy list is visible to all users.
let proxies = Proxy::list_all()
.await
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
Ok(Json(proxies))
}
@@ -88,8 +87,8 @@ impl SystemApi {
id: Path<u64>,
context: WrappedContext,
) -> ApiResult<()> {
context.require_permission(None, Permission::ROOT).await?;
Ok(Proxy::delete(id.0).await?)
context.require_permission(None, Permission::ROOT)?;
Ok(Proxy::delete(id.0)?)
}
/// Retrieve a specific proxy configuration by ID. Requires root permission.
@@ -100,16 +99,16 @@ impl SystemApi {
id: Path<u64>,
context: WrappedContext,
) -> ApiResult<Json<Proxy>> {
context.require_permission(None, Permission::ROOT).await?;
Ok(Json(Proxy::get(id.0).await?))
context.require_permission(None, Permission::ROOT)?;
Ok(Json(Proxy::get(id.0)?))
}
/// Create a new proxy configuration. Requires root permission.
#[oai(path = "/proxy", method = "post", operation_id = "create_proxy")]
async fn create_proxy(&self, url: PlainText<String>, context: WrappedContext) -> ApiResult<()> {
context.require_permission(None, Permission::ROOT).await?;
context.require_permission(None, Permission::ROOT)?;
let entity = Proxy::new(url.0);
Ok(entity.save().await?)
Ok(entity.save()?)
}
/// Update the URL of a specific proxy by ID. Requires root permission.
@@ -120,8 +119,8 @@ impl SystemApi {
url: PlainText<String>,
context: WrappedContext,
) -> ApiResult<()> {
context.require_permission(None, Permission::ROOT).await?;
Ok(Proxy::update(id.0, url.0).await?)
context.require_permission(None, Permission::ROOT)?;
Ok(Proxy::update(id.0, url.0)?)
}
/// Get system configurations.
///
@@ -136,7 +135,7 @@ impl SystemApi {
&self,
context: WrappedContext,
) -> ApiResult<Json<SystemConfigurations>> {
context.require_permission(None, Permission::ROOT).await?;
context.require_permission(None, Permission::ROOT)?;
let config: SystemConfigurations = SystemConfigurations::from(&*SETTINGS);
Ok(Json(config))
}
+27 -53
View File
@@ -40,11 +40,8 @@ pub struct UsersApi;
impl UsersApi {
#[oai(path = "/list-roles", method = "get", operation_id = "list_roles")]
async fn list_roles(&self, context: WrappedContext) -> ApiResult<Json<Vec<UserRole>>> {
context
.require_permission(None, Permission::USER_MANAGE)
.await?;
Ok(Json(UserRole::list_all().await?))
context.require_permission(None, Permission::USER_MANAGE)?;
Ok(Json(UserRole::list_all()?))
}
#[oai(path = "/roles/:id", method = "delete", operation_id = "remove_role")]
@@ -55,10 +52,8 @@ impl UsersApi {
context: WrappedContext,
) -> ApiResult<()> {
let id = id.0;
context
.require_permission(None, Permission::USER_MANAGE)
.await?;
Ok(UserRole::delete(id).await?)
context.require_permission(None, Permission::USER_MANAGE)?;
Ok(UserRole::delete(id)?)
}
/// Create a new account
@@ -69,10 +64,8 @@ impl UsersApi {
payload: Json<RoleCreateRequest>,
context: WrappedContext,
) -> ApiResult<Json<UserRole>> {
context
.require_permission(None, Permission::USER_MANAGE)
.await?;
let role = UserRole::create(payload.0).await?;
context.require_permission(None, Permission::USER_MANAGE)?;
let role = UserRole::create(payload.0)?;
Ok(Json(role))
}
@@ -87,20 +80,16 @@ impl UsersApi {
context: WrappedContext,
) -> ApiResult<()> {
let id = id.0;
context
.require_permission(None, Permission::USER_MANAGE)
.await?;
Ok(UserRole::update(id, payload.0).await?)
context.require_permission(None, Permission::USER_MANAGE)?;
Ok(UserRole::update(id, payload.0)?)
}
#[oai(path = "/list-users", method = "get", operation_id = "list_users")]
async fn list_users(&self, context: WrappedContext) -> ApiResult<Json<Vec<UserView>>> {
context
.require_permission(None, Permission::USER_MANAGE)
.await?;
let roles = UserRole::list_all().await?;
context.require_permission(None, Permission::USER_MANAGE)?;
let roles = UserRole::list_all()?;
let role_lookup: BTreeMap<u64, UserRole> = roles.into_iter().map(|r| (r.id, r)).collect();
let users = UserModel::list_all().await?;
let users = UserModel::list_all()?;
let users = users.into_iter().map(|u| u.to_view(&role_lookup)).collect();
Ok(Json(users))
}
@@ -116,13 +105,11 @@ impl UsersApi {
context: WrappedContext,
) -> ApiResult<Json<Vec<AccessTokenModel>>> {
let target_user_id = id.0;
let tokens = AccessTokenModel::get_user_api_tokens(target_user_id).await?;
let tokens = AccessTokenModel::get_user_api_tokens(target_user_id)?;
if context.user.id == target_user_id {
return Ok(Json(tokens));
}
context
.require_permission(None, Permission::USER_MANAGE)
.await?;
context.require_permission(None, Permission::USER_MANAGE)?;
Ok(Json(tokens))
}
@@ -134,10 +121,8 @@ impl UsersApi {
context: WrappedContext,
) -> ApiResult<()> {
let id = id.0;
context
.require_permission(None, Permission::USER_MANAGE)
.await?;
Ok(UserModel::remove(id).await?)
context.require_permission(None, Permission::USER_MANAGE)?;
Ok(UserModel::remove(id)?)
}
#[oai(path = "/users", method = "post", operation_id = "create_user")]
@@ -146,11 +131,9 @@ impl UsersApi {
payload: Json<UserCreateRequest>,
context: WrappedContext,
) -> ApiResult<Json<UserView>> {
context
.require_permission(None, Permission::USER_MANAGE)
.await?;
let user = UserModel::create(payload.0).await?;
let roles = UserRole::list_all().await?;
context.require_permission(None, Permission::USER_MANAGE)?;
let user = UserModel::create(payload.0)?;
let roles = UserRole::list_all()?;
let role_lookup: BTreeMap<u64, UserRole> = roles.into_iter().map(|r| (r.id, r)).collect();
Ok(Json(user.to_view(&role_lookup)))
}
@@ -165,19 +148,15 @@ impl UsersApi {
let target_id = id.0;
let current_user_id = context.user.id;
if current_user_id != target_id {
context
.require_permission(None, Permission::USER_MANAGE)
.await?;
context.require_permission(None, Permission::USER_MANAGE)?;
}
let mut update_data = payload.0;
if current_user_id == target_id
&& !context.has_permission(None, Permission::USER_MANAGE).await
{
if current_user_id == target_id && !context.has_permission(None, Permission::USER_MANAGE) {
update_data.global_roles = None;
update_data.account_access_map = None;
update_data.acl = None;
}
Ok(UserModel::update(target_id, update_data).await?)
Ok(UserModel::update(target_id, update_data)?)
}
#[oai(
@@ -186,7 +165,7 @@ impl UsersApi {
operation_id = "get_current_user"
)]
async fn get_current_user(&self, context: WrappedContext) -> ApiResult<Json<UserView>> {
let roles = UserRole::list_all().await?;
let roles = UserRole::list_all()?;
let role_lookup: BTreeMap<u64, UserRole> = roles.into_iter().map(|r| (r.id, r)).collect();
Ok(Json(context.0.user.to_view(&role_lookup)))
}
@@ -200,15 +179,12 @@ impl UsersApi {
&self,
context: WrappedContext,
) -> ApiResult<Json<Vec<MinimalUser>>> {
let is_admin = context.user.is_admin().await;
let minimal_list = MinimalUser::list_all().await?;
let is_admin = context.user.is_admin();
let minimal_list = MinimalUser::list_all()?;
if is_admin {
return Ok(Json(minimal_list));
}
context
.require_permission(None, Permission::USER_VIEW)
.await?;
context.require_permission(None, Permission::USER_VIEW)?;
Ok(Json(minimal_list))
}
@@ -218,10 +194,8 @@ impl UsersApi {
operation_id = "list_account_roles"
)]
async fn list_account_roles(&self, context: WrappedContext) -> ApiResult<Json<Vec<UserRole>>> {
context
.require_permission(None, Permission::USER_VIEW)
.await?;
let all = UserRole::list_all().await?;
context.require_permission(None, Permission::USER_VIEW)?;
let all = UserRole::list_all()?;
Ok(Json(
all.into_iter()
.filter(|r| matches!(r.role_type, RoleType::Account))
+2 -2
View File
@@ -32,9 +32,9 @@ pub struct LoginPayload {
/// Accepts a plain text password and returns the `root_token`
/// on successful authentication.
#[handler]
pub async fn login(payload: Json<LoginPayload>) -> Response {
pub fn login(payload: Json<LoginPayload>) -> Response {
let payload = payload.0;
match UserModel::authenticate_user(payload.username, payload.password).await {
match UserModel::authenticate_user(payload.username, payload.password) {
Ok(result) => match serde_json::to_string(&result) {
Ok(json_string) => Response::builder()
.status(http::StatusCode::OK)
+2 -3
View File
@@ -16,7 +16,6 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use bichon_core::oauth2::{flow::OAuth2Flow, pending::OAuth2PendingEntity};
use poem::{
handler,
@@ -56,7 +55,7 @@ pub async fn oauth2_callback(
}
};
let pending = match OAuth2PendingEntity::get(state).await {
let pending = match OAuth2PendingEntity::get(state) {
Ok(Some(pending)) => pending,
_ => {
let message =
@@ -86,7 +85,7 @@ pub async fn oauth2_callback(
.into_response());
}
if let Err(e) = OAuth2PendingEntity::delete(state).await {
if let Err(e) = OAuth2PendingEntity::delete(state) {
error!("Failed to delete pending OAuth2 entity: {}", e);
}