Add scope filter to personal sessions list

This commit is contained in:
Olivier 'reivilibre
2025-10-21 11:02:59 +01:00
parent a7a29cfd32
commit 9c88510540

View File

@@ -3,17 +3,17 @@
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::str::FromStr as _;
use aide::{OperationIo, transform::TransformOperation};
use axum::{
Json,
extract::{Query, rejection::QueryRejection},
response::IntoResponse,
};
use axum::{Json, response::IntoResponse};
use axum_extra::extract::{Query, QueryRejection};
use axum_macros::FromRequestParts;
use chrono::{DateTime, Utc};
use hyper::StatusCode;
use mas_axum_utils::record_error;
use mas_storage::personal::PersonalSessionFilter;
use oauth2_types::scope::{Scope, ScopeToken};
use schemars::JsonSchema;
use serde::Deserialize;
use ulid::Ulid;
@@ -64,6 +64,10 @@ pub struct FilterParams {
#[schemars(with = "Option<crate::admin::schema::Ulid>")]
actor_user: Option<Ulid>,
/// Retrieve the items with the given scope
#[serde(default, rename = "filter[scope]")]
scope: Vec<String>,
/// Filter by session status
#[serde(rename = "filter[status]")]
status: Option<PersonalSessionStatus>,
@@ -97,6 +101,10 @@ impl std::fmt::Display for FilterParams {
write!(f, "{sep}filter[actor_user]={actor_user}")?;
sep = '&';
}
for scope in &self.scope {
write!(f, "{sep}filter[scope]={scope}")?;
sep = '&';
}
if let Some(status) = self.status {
write!(f, "{sep}filter[status]={status}")?;
sep = '&';
@@ -141,6 +149,9 @@ pub enum RouteError {
#[error("Invalid filter parameters")]
InvalidFilter(#[from] QueryRejection),
#[error("Invalid scope {0:?} in filter parameters")]
InvalidScope(String),
}
impl_from_error_for_route!(mas_storage::RepositoryError);
@@ -153,7 +164,7 @@ impl IntoResponse for RouteError {
let status = match self {
Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
Self::UserNotFound(_) | Self::ClientNotFound(_) => StatusCode::NOT_FOUND,
Self::InvalidFilter(_) => StatusCode::BAD_REQUEST,
Self::InvalidScope(_) | Self::InvalidFilter(_) => StatusCode::BAD_REQUEST,
};
(status, sentry_event_id, Json(error)).into_response()
}
@@ -259,7 +270,18 @@ pub async fn handler(
None => filter,
};
// Apply status filter
let scope: Scope = params
.scope
.into_iter()
.map(|s| ScopeToken::from_str(&s).map_err(|_| RouteError::InvalidScope(s)))
.collect::<Result<_, _>>()?;
let filter = if scope.is_empty() {
filter
} else {
filter.with_scope(&scope)
};
let filter = match params.status {
Some(PersonalSessionStatus::Active) => filter.active_only(),
Some(PersonalSessionStatus::Revoked) => filter.finished_only(),
@@ -402,7 +424,7 @@ mod tests {
PersonalSessionOwner::from(&user),
&user,
"Another test session".to_owned(),
Scope::from_iter([OPENID]),
Scope::from_iter([OPENID, "urn:mas:admin".parse().unwrap()]),
)
.await
.unwrap();
@@ -490,7 +512,7 @@ mod tests {
"owner_client_id": null,
"actor_user_id": "01FSHN9AG09FE39KETP6F390F8",
"human_name": "Another test session",
"scope": "openid",
"scope": "openid urn:mas:admin",
"last_active_at": null,
"last_active_ip": null,
"expires_at": "2022-02-01T14:40:00Z"
@@ -533,6 +555,10 @@ mod tests {
&["01FSHN9AG0YQYAR04VCYTHJ8SK", "01FSPT2RG08Y11Y5BM4VZ4CN8K"],
),
("filter[expires]=false", &["01FSM7P1G0VBGAMK9D9QMGQ5MY"]),
(
"filter[scope]=urn:mas:admin",
&["01FSPT2RG08Y11Y5BM4VZ4CN8K"],
),
];
for (filter, expected_ids) in filters_and_expected {