From 831729b4fffef3cf85c4d963b64ec150c95bae29 Mon Sep 17 00:00:00 2001 From: ElysaSrc <101974839+ElysaSrc@users.noreply.github.com> Date: Mon, 1 Jul 2024 23:41:15 +0200 Subject: [PATCH 1/9] backend: extend access token --- .../20240701212445_extend_permissions.sql | 2 + backend/src/api/map.rs | 55 ++++++++++++++++--- backend/src/models/access_token.rs | 5 ++ frontend/openapi.json | 38 ++++++++++--- 4 files changed, 84 insertions(+), 16 deletions(-) create mode 100644 backend/migrations/20240701212445_extend_permissions.sql diff --git a/backend/migrations/20240701212445_extend_permissions.sql b/backend/migrations/20240701212445_extend_permissions.sql new file mode 100644 index 0000000..c0d580a --- /dev/null +++ b/backend/migrations/20240701212445_extend_permissions.sql @@ -0,0 +1,2 @@ +UPDATE access_tokens +SET permissions = permissions || '{"can_list_entities": true, "can_access_entity": true, "can_add_entity": true, "can_add_comment": true}'::jsonb; diff --git a/backend/src/api/map.rs b/backend/src/api/map.rs index 2085a80..089415b 100644 --- a/backend/src/api/map.rs +++ b/backend/src/api/map.rs @@ -120,8 +120,12 @@ pub async fn viewer_view_request( ) -> Result, AppError> { tracing::trace!("Received view request {}", request); + if !token.perms.can_list_entities { + return Err(AppError::Forbidden); + } + if !is_token_allowed_for_family(&token, &request.family_id) { - return Err(AppError::Unauthorized); + return Err(AppError::Forbidden); } let dyn_config = app_state.dyn_config.read().await; @@ -206,8 +210,12 @@ async fn viewer_search_request( ) -> Result, AppError> { tracing::trace!("Received search request {}", request); + if !token.perms.can_list_entities { + return Err(AppError::Forbidden); + } + if !is_token_allowed_for_family(&token, &request.family_id) { - return Err(AppError::Unauthorized); + return Err(AppError::Forbidden); } // Check if some of the constraints are forbidden @@ -245,14 +253,14 @@ async fn viewer_search_request( #[derive(Serialize, Deserialize, ToSchema, Debug)] pub struct PublicNewEntityRequest { entity: PublicNewEntity, - comment: PublicNewComment, + comment: Option, hcaptcha_token: Option, } #[derive(Serialize, Deserialize, ToSchema, Debug)] pub struct PublicNewEntityResponse { entity: PublicEntity, - comment: PublicComment, + comment: Option, } async fn check_captcha(state: AppState, response: Option) -> Result<(), AppError> { @@ -299,16 +307,26 @@ async fn check_captcha(state: AppState, response: Option) -> Result<(), async fn viewer_new_entity( DbConn(mut conn): DbConn, State(state): State, + token: MapUserTokenClaims, Json(request): Json, ) -> Result, AppError> { + if !token.perms.can_add_entity { + return Err(AppError::Forbidden); + } + + if !token.perms.can_add_comment && request.comment.is_some() { + return Err(AppError::Forbidden); + } + check_captcha(state, request.hcaptcha_token).await?; let db_entity = PublicEntity::new(request.entity, &mut conn).await?; + let mut db_comment = None; - let mut new_comment = request.comment; - new_comment.entity_id = db_entity.id; - - let db_comment = PublicComment::new(new_comment, &mut conn).await?; + if let Some(mut comment) = request.comment { + comment.entity_id = db_entity.id; + db_comment = Some(PublicComment::new(comment, &mut conn).await?); + } Ok(AppJson(PublicNewEntityResponse { entity: db_entity, @@ -334,8 +352,19 @@ pub struct NewCommentRequest { async fn viewer_new_comment( DbConn(mut conn): DbConn, State(state): State, + token: MapUserTokenClaims, Json(request): Json, ) -> Result, AppError> { + if !token.perms.can_add_comment { + return Err(AppError::Forbidden); + } + + let target_entity = PublicEntity::get(request.comment.entity_id, &mut conn).await?; + + if !is_token_allowed_for_family(&token, &target_entity.family_id) { + return Err(AppError::Forbidden); + } + check_captcha(state, request.hcaptcha_token).await?; let db_comment = PublicComment::new(request.comment, &mut conn).await?; Ok(AppJson(db_comment)) @@ -371,8 +400,16 @@ async fn viewer_fetch_entity( Path(id): Path, Json(request): Json, ) -> Result, AppError> { + if !token.perms.can_access_entity { + return Err(AppError::Forbidden); + } + let entity = PublicEntity::get(id, &mut conn).await?; + if !is_token_allowed_for_family(&token, &entity.family_id) { + return Err(AppError::Forbidden); + } + let can_read_entity = (token.perms.families_policy.allow_all || token .perms @@ -463,7 +500,7 @@ async fn viewer_fetch_entity( .collect(); if !can_read_entity && filtered_children.is_empty() { - return Err(AppError::Unauthorized); + return Err(AppError::Forbidden); } let comments = match token.perms.can_access_comments { diff --git a/backend/src/models/access_token.rs b/backend/src/models/access_token.rs index 3c85877..93572d9 100644 --- a/backend/src/models/access_token.rs +++ b/backend/src/models/access_token.rs @@ -11,7 +11,12 @@ pub struct Permissions { pub categories_policy: PermissionPolicy, pub tags_policy: PermissionPolicy, pub geographic_restrictions: Option, + + pub can_list_entities: bool, + pub can_access_entity: bool, pub can_access_comments: bool, + pub can_add_entity: bool, + pub can_add_comment: bool, } #[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] diff --git a/frontend/openapi.json b/frontend/openapi.json index 56eaa1d..8aeb6e5 100644 --- a/frontend/openapi.json +++ b/frontend/openapi.json @@ -4002,12 +4002,28 @@ "families_policy", "categories_policy", "tags_policy", - "can_access_comments" + "can_list_entities", + "can_access_entity", + "can_access_comments", + "can_add_entity", + "can_add_comment" ], "properties": { "can_access_comments": { "type": "boolean" }, + "can_access_entity": { + "type": "boolean" + }, + "can_add_comment": { + "type": "boolean" + }, + "can_add_entity": { + "type": "boolean" + }, + "can_list_entities": { + "type": "boolean" + }, "categories_policy": { "$ref": "#/components/schemas/PermissionPolicy" }, @@ -4201,12 +4217,16 @@ "PublicNewEntityRequest": { "type": "object", "required": [ - "entity", - "comment" + "entity" ], "properties": { "comment": { - "$ref": "#/components/schemas/PublicNewComment" + "allOf": [ + { + "$ref": "#/components/schemas/PublicNewComment" + } + ], + "nullable": true }, "entity": { "$ref": "#/components/schemas/PublicNewEntity" @@ -4220,12 +4240,16 @@ "PublicNewEntityResponse": { "type": "object", "required": [ - "entity", - "comment" + "entity" ], "properties": { "comment": { - "$ref": "#/components/schemas/PublicComment" + "allOf": [ + { + "$ref": "#/components/schemas/PublicComment" + } + ], + "nullable": true }, "entity": { "$ref": "#/components/schemas/PublicEntity" From a0e76afcbc4e34c3e9bba237bcba8af1b9c34039 Mon Sep 17 00:00:00 2001 From: "Alice K." Date: Tue, 2 Jul 2024 01:11:29 +0200 Subject: [PATCH 2/9] frontend: add new permissions editing to access tokens --- frontend/lib.d.ts | 2 +- frontend/pages/admin/access-tokens/[id].vue | 57 ++++++++++++++++++++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/frontend/lib.d.ts b/frontend/lib.d.ts index 07dd9ff..79f8fd1 100644 --- a/frontend/lib.d.ts +++ b/frontend/lib.d.ts @@ -18,7 +18,7 @@ export type Category = api.components['schemas']['Category'] export type Tag = api.components['schemas']['Tag'] export type PublicEntity = api.components['schemas']['PublicEntity'] -export type PublicListedEntity = api.components['schemas']['ListedEntity'] +export type PublicListedEntity = api.components['schemas']['PublicListedEntity'] export type PublicNewEntity = api.components['schemas']['PublicNewEntity'] export type PublicNewEntityRequest = api.components['schemas']['PublicNewEntityRequest'] export type PublicNewEntityResponse = api.components['schemas']['PublicNewEntityResponse'] diff --git a/frontend/pages/admin/access-tokens/[id].vue b/frontend/pages/admin/access-tokens/[id].vue index 1e27565..3d5c30f 100644 --- a/frontend/pages/admin/access-tokens/[id].vue +++ b/frontend/pages/admin/access-tokens/[id].vue @@ -24,10 +24,40 @@ label="Actif" /> + + + + + + + + + + @@ -117,7 +147,11 @@ const editedAccessToken: Ref = ref( ? { active: true, permissions: { + can_list_entities: false, + can_access_entity: false, + can_add_entity: false, can_access_comments: false, + can_add_comment: false, categories_policy: { allow_all: true, allow_list: [], @@ -157,6 +191,25 @@ watch(geographicRestrictionsOn, (newValue) => { const processingRequest = ref(false) const toast = useToast() +watch( + () => editedAccessToken.value.permissions.can_list_entities, + (newVal) => { + if (!newVal) { + editedAccessToken.value.permissions.can_access_entity = false + editedAccessToken.value.permissions.can_add_comment = false + } + }, +) + +watch( + () => editedAccessToken.value.permissions.can_access_entity, + (newVal) => { + if (!newVal) { + editedAccessToken.value.permissions.can_access_comments = false + } + }, +) + const initAdminLayout = inject('initAdminLayout')! initAdminLayout( isNew ? `Édition d'un nouveau jeton` : `Édition du jeton ${fetchedAccessToken!.title}`, From 9ffc5fc38dd3ce89e5b24c693620520f3fa3dc17 Mon Sep 17 00:00:00 2001 From: "Alice K." Date: Tue, 2 Jul 2024 01:50:38 +0200 Subject: [PATCH 3/9] backend, frontend: expose new permissions to the viewer and restrict ui and redirect accordingly --- backend/src/api/root.rs | 8 ++++++++ frontend/components/viewer/Navbar.vue | 1 + frontend/lib.d.ts | 7 +++++++ frontend/lib/viewer-state.ts | 15 +++++++++------ frontend/openapi.json | 16 ++++++++++++++++ frontend/pages/map/[token].vue | 3 +++ frontend/pages/search/[token].vue | 8 ++++++++ 7 files changed, 52 insertions(+), 6 deletions(-) diff --git a/backend/src/api/root.rs b/backend/src/api/root.rs index 8a0c14e..6246f35 100644 --- a/backend/src/api/root.rs +++ b/backend/src/api/root.rs @@ -70,7 +70,11 @@ pub struct BootstrapResponse { categories: Vec, allowed_categories: Vec, allowed_tags: Vec, + can_list_entities: bool, + can_access_entity: bool, + can_add_entity: bool, can_access_comments: bool, + can_add_comment: bool, tags: Vec, cartography_init_config: CartographyInitConfig, } @@ -177,7 +181,11 @@ async fn bootstrap( categories, allowed_categories, allowed_tags, + can_list_entities: perms.can_list_entities, + can_access_entity: perms.can_access_entity, + can_add_entity: perms.can_add_entity, can_access_comments: perms.can_access_comments, + can_add_comment: perms.can_add_comment, tags, cartography_init_config: dyn_config.cartography_init.clone(), }; diff --git a/frontend/components/viewer/Navbar.vue b/frontend/components/viewer/Navbar.vue index 5ee4a9c..b9a24c3 100644 --- a/frontend/components/viewer/Navbar.vue +++ b/frontend/components/viewer/Navbar.vue @@ -353,6 +353,7 @@ @@ -77,6 +78,8 @@ const route = useRoute() const token = route.params.token as string try { await state.bootstrapWithToken(token) + if (!state.permissions?.can_access_entity) + throw 'Unauthorized' } catch { toast.add({ diff --git a/frontend/pages/search/[token].vue b/frontend/pages/search/[token].vue index 95d1f74..ddd24a6 100644 --- a/frontend/pages/search/[token].vue +++ b/frontend/pages/search/[token].vue @@ -170,6 +170,12 @@ + + Date: Tue, 2 Jul 2024 03:42:12 +0200 Subject: [PATCH 4/9] frontend: search page now support pure addition modes --- frontend/components/viewer/CommentAddForm.vue | 4 ++-- frontend/components/viewer/FullResult.vue | 14 +++++++++----- frontend/components/viewer/Navbar.vue | 2 ++ frontend/pages/admin/access-tokens/[id].vue | 2 +- frontend/pages/search/[token].vue | 16 +++++++--------- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/frontend/components/viewer/CommentAddForm.vue b/frontend/components/viewer/CommentAddForm.vue index 26456b3..9231293 100644 --- a/frontend/components/viewer/CommentAddForm.vue +++ b/frontend/components/viewer/CommentAddForm.vue @@ -113,7 +113,7 @@ diff --git a/frontend/components/viewer/Navbar.vue b/frontend/components/viewer/Navbar.vue index b9a24c3..4dc6f10 100644 --- a/frontend/components/viewer/Navbar.vue +++ b/frontend/components/viewer/Navbar.vue @@ -98,6 +98,7 @@