1use std::sync::Arc;
2
3use axum::http::StatusCode;
4use kellnr_common::account_name::UserName;
5use kellnr_common::canonical_name::CanonicalName;
6use kellnr_db::provider::DbResult;
7use kellnr_db::{CrateVisibility, DbProvider};
8
9use crate::error::ApiError;
10use crate::identity::Identity;
11
12pub fn require_admin(user: &Identity) -> Result<(), ApiError> {
14 if user.is_admin {
15 Ok(())
16 } else {
17 Err(ApiError::new(
18 "Admin access required",
19 StatusCode::FORBIDDEN,
20 ))
21 }
22}
23
24pub async fn require_owner(
26 crate_name: &CanonicalName,
27 user: &Identity,
28 db: &Arc<dyn DbProvider>,
29) -> Result<(), ApiError> {
30 if user.is_admin || db.is_owner(crate_name, &user.name).await? {
31 Ok(())
32 } else {
33 Err(ApiError::new(
34 "Not the owner of the crate",
35 StatusCode::FORBIDDEN,
36 ))
37 }
38}
39
40pub fn require_writable(user: &Identity) -> Result<(), ApiError> {
43 if !user.is_admin && user.is_read_only {
44 Err(ApiError::new(
45 "Read-only users cannot modify the registry",
46 StatusCode::FORBIDDEN,
47 ))
48 } else {
49 Ok(())
50 }
51}
52
53pub async fn has_restricted_crate_access(
56 crate_name: &CanonicalName,
57 user: &UserName,
58 is_admin: bool,
59 db: &Arc<dyn DbProvider>,
60) -> DbResult<bool> {
61 Ok(is_admin
62 || db.is_crate_user(crate_name, user).await?
63 || db.is_crate_group_user(crate_name, user).await?
64 || db.is_owner(crate_name, user).await?)
65}
66
67pub async fn can_read_crate(
70 crate_name: &CanonicalName,
71 user: Option<&Identity>,
72 db: &Arc<dyn DbProvider>,
73) -> DbResult<bool> {
74 if !db.is_download_restricted(crate_name).await? {
75 return Ok(true);
76 }
77 match user {
78 Some(user) => has_restricted_crate_access(crate_name, &user.name, user.is_admin, db).await,
79 None => Ok(false),
80 }
81}
82
83pub fn crate_visibility(user: Option<&Identity>) -> CrateVisibility {
85 match user {
86 None => CrateVisibility::Unrestricted,
87 Some(user) if user.is_admin => CrateVisibility::All,
88 Some(user) => CrateVisibility::User(user.name.clone()),
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use kellnr_common::account_name::GroupName;
95 use kellnr_db::UserFlags;
96 use kellnr_db::test_utils::TestCrateBuilder;
97 use kellnr_test_db::TestDb;
98
99 use super::*;
100 use crate::identity::Source;
101
102 fn name() -> CanonicalName {
103 CanonicalName::try_from("restricted").unwrap()
104 }
105
106 fn user_name() -> UserName {
107 UserName::new("user").unwrap()
108 }
109
110 fn user(is_admin: bool) -> Identity {
111 Identity {
112 name: user_name(),
113 is_admin,
114 is_read_only: false,
115 source: Source::Session,
116 }
117 }
118
119 async fn crate_db(restricted: bool) -> TestDb {
122 let db = TestDb::sqlite().await;
123 TestCrateBuilder::new(&db)
124 .name("restricted")
125 .build()
126 .await
127 .unwrap();
128 db.change_download_restricted(&name(), restricted)
129 .await
130 .unwrap();
131 db.add_user(&user_name(), "pwd", UserFlags::default())
132 .await
133 .unwrap();
134 db
135 }
136
137 async fn can_read(db: &TestDb, user: Option<&Identity>) -> bool {
138 can_read_crate(&name(), user, &db.provider()).await.unwrap()
139 }
140
141 #[test]
142 fn crate_visibility_depends_on_caller() {
143 assert_eq!(CrateVisibility::Unrestricted, crate_visibility(None));
144 assert_eq!(CrateVisibility::All, crate_visibility(Some(&user(true))));
145 assert_eq!(
146 CrateVisibility::User(UserName::new("user").unwrap()),
147 crate_visibility(Some(&user(false)))
148 );
149 }
150
151 #[tokio::test]
152 async fn unrestricted_crate_is_readable_by_anyone() {
153 let db = crate_db(false).await;
154 assert!(can_read(&db, None).await);
155 }
156
157 #[tokio::test]
158 async fn restricted_crate_is_hidden_from_anonymous() {
159 let db = crate_db(true).await;
160 assert!(!can_read(&db, None).await);
161 }
162
163 #[tokio::test]
164 async fn restricted_crate_is_readable_by_admin() {
165 let db = crate_db(true).await;
166 assert!(can_read(&db, Some(&user(true))).await);
167 }
168
169 #[tokio::test]
170 async fn restricted_crate_is_hidden_from_user_without_access() {
171 let db = crate_db(true).await;
172 assert!(!can_read(&db, Some(&user(false))).await);
173 }
174
175 #[tokio::test]
176 async fn restricted_crate_is_readable_by_crate_user() {
177 let db = crate_db(true).await;
178 db.add_crate_user(&name(), &user_name()).await.unwrap();
179 assert!(can_read(&db, Some(&user(false))).await);
180 }
181
182 #[tokio::test]
183 async fn restricted_crate_is_readable_by_crate_group_member() {
184 let db = crate_db(true).await;
185 let group = GroupName::new("group").unwrap();
186 db.add_group(&group).await.unwrap();
187 db.add_group_user(&group, &user_name()).await.unwrap();
188 db.add_crate_group(&name(), &group).await.unwrap();
189 assert!(can_read(&db, Some(&user(false))).await);
190 }
191
192 #[tokio::test]
193 async fn restricted_crate_is_readable_by_owner() {
194 let db = crate_db(true).await;
195 db.add_owner(&name(), &user_name()).await.unwrap();
196 assert!(can_read(&db, Some(&user(false))).await);
197 }
198}
199
200#[cfg(test)]
201mod require_tests {
202 use super::*;
203 use crate::identity::Source;
204
205 fn user(is_admin: bool, is_read_only: bool) -> Identity {
206 Identity {
207 name: UserName::new("user").unwrap(),
208 is_admin,
209 is_read_only,
210 source: Source::Token(None),
211 }
212 }
213
214 fn status(result: Result<(), ApiError>) -> Option<StatusCode> {
215 result.err().map(|e| e.status())
216 }
217
218 #[test]
219 fn only_admins_pass_require_admin() {
220 assert_eq!(status(require_admin(&user(true, false))), None);
221 assert_eq!(
222 status(require_admin(&user(false, false))),
223 Some(StatusCode::FORBIDDEN)
224 );
225 }
226
227 #[test]
228 fn read_only_users_are_forbidden_to_write() {
229 assert_eq!(
230 status(require_writable(&user(false, true))),
231 Some(StatusCode::FORBIDDEN)
232 );
233 assert_eq!(status(require_writable(&user(false, false))), None);
234 assert_eq!(status(require_writable(&user(true, true))), None);
235 }
236}