headless_lms_server/domain/
authorization.rs1use crate::prelude::*;
4use actix_web::{HttpRequest, Responder};
5
6pub use headless_lms_authorization::error::{AuthorizationError, AuthorizationErrorType};
7pub use headless_lms_authorization::{
8 Action, ActionOnResource, AuthorizationToken, Resource, authorize, authorize_access_to_chatbot,
9 authorize_access_to_course_material, authorize_with_fetched_list_of_roles,
10 can_user_view_chapter, is_permitted, is_user_global_admin, skip_authorize,
11};
12
13#[derive(Copy, Clone)]
16pub struct AuthorizedResponse<T> {
17 pub data: T,
18}
19
20impl<T: Responder> Responder for AuthorizedResponse<T> {
21 type Body = T::Body;
22
23 fn respond_to(self, req: &HttpRequest) -> actix_web::HttpResponse<Self::Body> {
24 T::respond_to(self.data, req)
25 }
26}
27
28pub trait AuthorizedOk {
32 fn authorized_ok<T>(self, t: T) -> ControllerResult<T>;
33}
34
35impl AuthorizedOk for AuthorizationToken {
36 fn authorized_ok<T>(self, t: T) -> ControllerResult<T> {
37 Ok(AuthorizedResponse { data: t })
38 }
39}
40
41pub async fn ensure_user_can_add_exercise_tasks(
44 conn: &mut PgConnection,
45 user_id: Uuid,
46 page_id: Option<Uuid>,
47 exercise_tasks: &[models::pages::CmsPageExerciseTask],
48) -> Result<(), ControllerError> {
49 let added_service_names =
50 models::pages::get_global_admin_only_exercise_service_names_added_by_tasks(
51 conn,
52 models::pages::ExistingTasks::OnPage(page_id),
53 exercise_tasks,
54 )
55 .await?;
56 let Some(service_name) = added_service_names.first() else {
57 return Ok(());
58 };
59 if is_user_global_admin(conn, user_id).await? {
60 return Ok(());
61 }
62 Err(controller_err!(
63 BadRequest,
64 format!("Only global admins can add exercises of type “{service_name}”.")
65 ))
66}
67
68#[cfg(test)]
69mod test {
70 use super::*;
71 use crate::insert_data;
74 use crate::test_helper::*;
75 use headless_lms_models::*;
76 use models::roles::{RoleDomain, UserRole};
77
78 #[actix_web::test]
79 async fn test_authorization() {
80 let mut conn = Conn::init().await;
81 let mut tx = conn.begin().await;
82
83 let user = users::insert(
84 tx.as_mut(),
85 PKeyPolicy::Generate,
86 "auth@example.com",
87 None,
88 None,
89 )
90 .await
91 .unwrap();
92 let org = organizations::insert(
93 tx.as_mut(),
94 PKeyPolicy::Generate,
95 "auth",
96 "auth",
97 Some("auth"),
98 false,
99 )
100 .await
101 .unwrap();
102
103 authorize(
104 tx.as_mut(),
105 Action::Edit,
106 Some(user),
107 Resource::Organization(org),
108 )
109 .await
110 .unwrap_err();
111
112 roles::insert(
113 tx.as_mut(),
114 user,
115 UserRole::Teacher,
116 RoleDomain::Organization(org),
117 )
118 .await
119 .unwrap();
120
121 authorize(
122 tx.as_mut(),
123 Action::Edit,
124 Some(user),
125 Resource::Organization(org),
126 )
127 .await
128 .unwrap();
129 }
130
131 #[actix_web::test]
132 async fn course_role_chapter_resource() {
133 insert_data!(:tx, :user, :org, :course, instance: _instance, :course_module, :chapter);
134
135 authorize(
136 tx.as_mut(),
137 Action::Edit,
138 Some(user),
139 Resource::Chapter(chapter),
140 )
141 .await
142 .unwrap_err();
143
144 roles::insert(
145 tx.as_mut(),
146 user,
147 UserRole::Teacher,
148 RoleDomain::Course(course),
149 )
150 .await
151 .unwrap();
152
153 authorize(
154 tx.as_mut(),
155 Action::Edit,
156 Some(user),
157 Resource::Chapter(chapter),
158 )
159 .await
160 .unwrap();
161 }
162
163 #[actix_web::test]
164 async fn anonymous_user_can_view_open_course() {
165 insert_data!(:tx, :user, :org, :course);
166
167 authorize(tx.as_mut(), Action::View, None, Resource::Course(course))
168 .await
169 .unwrap();
170 }
171}