• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geo-engine / geoengine / 13809415963

12 Mar 2025 10:42AM UTC coverage: 90.026% (-0.05%) from 90.076%
13809415963

Pull #1013

github

web-flow
Merge b51e2554c into c96026921
Pull Request #1013: Update-utoipa

787 of 935 new or added lines in 41 files covered. (84.17%)

28 existing lines in 10 files now uncovered.

125995 of 139954 relevant lines covered (90.03%)

57510.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.57
/services/src/api/handlers/permissions.rs
1
use crate::api::model::datatypes::LayerId;
2
use crate::contexts::{ApplicationContext, GeoEngineDb, SessionContext};
3
use crate::datasets::storage::DatasetDb;
4
use crate::datasets::DatasetName;
5
use crate::error::{self, Error, Result};
6
use crate::layers::listing::LayerCollectionId;
7
use crate::machine_learning::MlModelDb;
8
use crate::permissions::{
9
    Permission, PermissionDb, PermissionListing as DbPermissionListing, ResourceId, Role, RoleId,
10
};
11
use crate::projects::ProjectId;
12
use actix_web::{web, FromRequest, HttpResponse};
13
use geoengine_datatypes::error::BoxedResultExt;
14
use geoengine_datatypes::machine_learning::MlModelName;
15
use geoengine_macros::type_tag;
16
use serde::{Deserialize, Serialize};
17
use snafu::ResultExt;
18
use std::str::FromStr;
19
use utoipa::{IntoParams, ToSchema};
20
use uuid::Uuid;
21

22
pub(crate) fn init_permissions_routes<C>(cfg: &mut web::ServiceConfig)
342✔
23
where
342✔
24
    C: ApplicationContext,
342✔
25

342✔
26
    C::Session: FromRequest,
342✔
27
{
342✔
28
    cfg.service(
342✔
29
        web::scope("/permissions")
342✔
30
            .service(
342✔
31
                web::resource("")
342✔
32
                    .route(web::put().to(add_permission_handler::<C>))
342✔
33
                    .route(web::delete().to(remove_permission_handler::<C>)),
342✔
34
            )
342✔
35
            .service(
342✔
36
                web::resource("/resources/{resource_type}/{resource_id}")
342✔
37
                    .route(web::get().to(get_resource_permissions_handler::<C>)),
342✔
38
            ),
342✔
39
    );
342✔
40
}
342✔
41

42
/// Request for adding a new permission to the given role on the given resource
43
#[derive(Debug, PartialEq, Eq, Deserialize, Clone, ToSchema)]
44✔
44
#[serde(rename_all = "camelCase")]
45
pub struct PermissionRequest {
46
    resource: Resource,
47
    role_id: RoleId,
48
    permission: Permission,
49
}
50

51
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, ToSchema)]
28✔
52
#[serde(rename_all = "camelCase")]
53
pub struct PermissionListing {
54
    resource: Resource,
55
    role: Role,
56
    permission: Permission,
57
}
58

59
impl PermissionListing {
60
    fn wrap_permission_listing_and_resource(
8✔
61
        resource: Resource,
8✔
62
        db_permission_listing: DbPermissionListing,
8✔
63
    ) -> PermissionListing {
8✔
64
        Self {
8✔
65
            resource,
8✔
66
            role: db_permission_listing.role,
8✔
67
            permission: db_permission_listing.permission,
8✔
68
        }
8✔
69
    }
8✔
70
}
71

72
/// A resource that is affected by a permission.
73
#[derive(Debug, PartialEq, Eq, Deserialize, Clone, ToSchema, Serialize)]
92✔
74
#[serde(rename_all = "camelCase", untagged)]
75
#[schema(discriminator = "type")]
76
pub enum Resource {
77
    Layer(LayerResource),
78
    LayerCollection(LayerCollectionResource),
79
    Project(ProjectResource),
80
    Dataset(DatasetResource),
81
    MlModel(MlModelResource),
82
}
83

84
#[type_tag(tag = "layer")]
48✔
85
#[derive(Debug, PartialEq, Eq, Deserialize, Clone, ToSchema, Serialize)]
96✔
86
#[serde(rename_all = "camelCase")]
87
pub struct LayerResource {
88
    pub id: LayerId,
89
}
90

91
#[type_tag(tag = "layerCollection")]
48✔
92
#[derive(Debug, PartialEq, Eq, Deserialize, Clone, ToSchema, Serialize)]
96✔
93
#[serde(rename_all = "camelCase")]
94
pub struct LayerCollectionResource {
95
    pub id: LayerCollectionId,
96
}
97

98
#[type_tag(tag = "project")]
48✔
99
#[derive(Debug, PartialEq, Eq, Deserialize, Clone, ToSchema, Serialize)]
96✔
100
#[serde(rename_all = "camelCase")]
101
pub struct ProjectResource {
102
    pub id: ProjectId,
103
}
104

105
#[type_tag(tag = "dataset")]
48✔
106
#[derive(Debug, PartialEq, Eq, Deserialize, Clone, ToSchema, Serialize)]
96✔
107
#[serde(rename_all = "camelCase")]
108
pub struct DatasetResource {
109
    pub id: DatasetName,
110
}
111

112
#[type_tag(tag = "mlModel")]
48✔
113
#[derive(Debug, PartialEq, Eq, Deserialize, Clone, ToSchema, Serialize)]
96✔
114
#[serde(rename_all = "camelCase")]
115
pub struct MlModelResource {
116
    #[schema(value_type = String)]
117
    pub id: MlModelName,
118
    // TODO: add a DatasetName to model
119
    // TODO: check model
120
}
121

122
impl Resource {
123
    pub async fn resolve_resource_id<D: DatasetDb + MlModelDb>(
6✔
124
        &self,
6✔
125
        db: &D,
6✔
126
    ) -> Result<ResourceId> {
6✔
127
        match self {
6✔
128
            Resource::Layer(layer) => Ok(ResourceId::Layer(layer.id.clone().into())),
3✔
129
            Resource::LayerCollection(layer_collection) => {
1✔
130
                Ok(ResourceId::LayerCollection(layer_collection.id.clone()))
1✔
131
            }
NEW
132
            Resource::Project(project_id) => Ok(ResourceId::Project(project_id.id)),
×
133
            Resource::Dataset(dataset_name) => {
1✔
134
                let dataset_id_option = db.resolve_dataset_name_to_id(&dataset_name.id).await?;
1✔
135
                dataset_id_option
1✔
136
                    .ok_or(error::Error::UnknownResource {
1✔
137
                        kind: "Dataset".to_owned(),
1✔
138
                        name: dataset_name.id.to_string(),
1✔
139
                    })
1✔
140
                    .map(ResourceId::DatasetId)
1✔
141
            }
142
            Resource::MlModel(model_name) => {
1✔
143
                let actual_name = model_name.id.clone().into();
1✔
144
                let model_id_option =
1✔
145
                    db.resolve_model_name_to_id(&actual_name)
1✔
146
                        .await
1✔
147
                        .map_err(|e| error::Error::MachineLearning {
1✔
148
                            source: Box::new(e),
×
149
                        })?; // should prob. also map to UnknownResource oder something like that
1✔
150
                model_id_option
1✔
151
                    .ok_or(error::Error::UnknownResource {
1✔
152
                        kind: "MlModel".to_owned(),
1✔
153
                        name: actual_name.to_string(),
1✔
154
                    })
1✔
155
                    .map(ResourceId::MlModel)
1✔
156
            }
157
        }
158
    }
6✔
159
}
160

161
impl TryFrom<(String, String)> for Resource {
162
    type Error = Error;
163

164
    /// Transform a tuple of `String` into a `Resource`. The first element is used as type and the second element as the id / name.
165
    fn try_from(value: (String, String)) -> Result<Self> {
9✔
166
        Ok(match value.0.as_str() {
9✔
167
            "layer" => Resource::Layer(LayerResource {
9✔
168
                r#type: Default::default(),
2✔
169
                id: LayerId(value.1),
2✔
170
            }),
2✔
171
            "layerCollection" => Resource::LayerCollection(LayerCollectionResource {
7✔
172
                r#type: Default::default(),
2✔
173
                id: LayerCollectionId(value.1),
2✔
174
            }),
2✔
175
            "project" => Resource::Project(ProjectResource {
5✔
176
                r#type: Default::default(),
1✔
177
                id: ProjectId(Uuid::from_str(&value.1).context(error::Uuid)?),
1✔
178
            }),
179
            "dataset" => Resource::Dataset(DatasetResource {
4✔
180
                r#type: Default::default(),
2✔
181
                id: DatasetName::from_str(&value.1)?,
2✔
182
            }),
183
            "mlModel" => Resource::MlModel(MlModelResource {
2✔
184
                r#type: Default::default(),
2✔
185
                id: MlModelName::from_str(&value.1)?,
2✔
186
            }),
187
            _ => {
188
                return Err(Error::InvalidResourceId {
×
189
                    resource_type: value.0,
×
190
                    resource_id: value.1,
×
191
                })
×
192
            }
193
        })
194
    }
9✔
195
}
196

197
#[derive(Debug, PartialEq, Eq, Deserialize, Clone, IntoParams, ToSchema)]
12✔
198
pub struct PermissionListOptions {
199
    pub limit: u32,
200
    pub offset: u32,
201
}
202

203
/// Lists permission for a given resource.
204
#[utoipa::path(
24✔
205
    tag = "Permissions",
24✔
206
    get,
24✔
207
    path = "/permissions/resources/{resource_type}/{resource_id}",
24✔
208
    responses(
24✔
209
        (status = 200, description = "List of permission", body = Vec<PermissionListing>),        
24✔
210
    ),
24✔
211
    params(
24✔
212
        ("resource_type" = String, description = "Resource Type"),
24✔
213
        ("resource_id" = String, description = "Resource Id"),
24✔
214
        PermissionListOptions,
24✔
215
    ),
24✔
216
    security(
24✔
217
        ("session_token" = [])
24✔
218
    )
24✔
219
)]
24✔
220
async fn get_resource_permissions_handler<C: ApplicationContext>(
4✔
221
    session: C::Session,
4✔
222
    app_ctx: web::Data<C>,
4✔
223
    resource_id: web::Path<(String, String)>,
4✔
224
    options: web::Query<PermissionListOptions>,
4✔
225
) -> Result<web::Json<Vec<PermissionListing>>>
4✔
226
where
4✔
227
    <<C as ApplicationContext>::SessionContext as SessionContext>::GeoEngineDB: GeoEngineDb,
4✔
228
{
4✔
229
    let resource = Resource::try_from(resource_id.into_inner())?;
4✔
230
    let db = app_ctx.session_context(session).db();
4✔
231
    let resource_id = resource.resolve_resource_id(&db).await?;
4✔
232
    let options = options.into_inner();
4✔
233

234
    let permissions = db
4✔
235
        .list_permissions(resource_id, options.offset, options.limit)
4✔
236
        .await
4✔
237
        .boxed_context(crate::error::PermissionDb)?;
4✔
238

239
    let permissions = permissions
4✔
240
        .into_iter()
4✔
241
        .map(|p| PermissionListing::wrap_permission_listing_and_resource(resource.clone(), p))
8✔
242
        .collect();
4✔
243

4✔
244
    Ok(web::Json(permissions))
4✔
245
}
4✔
246

247
/// Adds a new permission.
248
#[utoipa::path(
16✔
249
    tag = "Permissions",
16✔
250
    put,
16✔
251
    path = "/permissions",
16✔
252
    request_body(content = PermissionRequest, example =
16✔
253
        json!({
16✔
254
            "resource": {
16✔
255
                "type": "layer",
16✔
256
                "id": "00000000-0000-0000-0000-000000000000",
16✔
257
            },
16✔
258
            "roleId": "00000000-0000-0000-0000-000000000000",
16✔
259
            "permission": "Read"
16✔
260
        })
16✔
261
    ),
16✔
262
    responses(
16✔
263
        (status = 200, description = "OK"),        
16✔
264
    ),
16✔
265
    security(
16✔
266
        ("session_token" = [])
16✔
267
    )
16✔
268
)]
16✔
269
async fn add_permission_handler<C: ApplicationContext>(
1✔
270
    session: C::Session,
1✔
271
    app_ctx: web::Data<C>,
1✔
272
    permission: web::Json<PermissionRequest>,
1✔
273
) -> Result<HttpResponse> {
1✔
274
    let permission = permission.into_inner();
1✔
275

1✔
276
    let db = app_ctx.session_context(session).db();
1✔
277
    let permission_id = permission.resource.resolve_resource_id(&db).await?;
1✔
278

279
    db.add_permission::<ResourceId>(permission.role_id, permission_id, permission.permission)
1✔
280
        .await
1✔
281
        .boxed_context(crate::error::PermissionDb)?;
1✔
282

283
    Ok(HttpResponse::Ok().finish())
×
284
}
1✔
285

286
/// Removes an existing permission.
287
#[utoipa::path(
16✔
288
    tag = "Permissions",
16✔
289
    delete,
16✔
290
    path = "/permissions",
16✔
291
    request_body(content = PermissionRequest, example =
16✔
292
        json!({
16✔
293
            "resource": {
16✔
294
                "type": "layer",
16✔
295
                "id": "00000000-0000-0000-0000-000000000000",
16✔
296
            },
16✔
297
            "roleId": "00000000-0000-0000-0000-000000000000",
16✔
298
            "permission": "Read"
16✔
299
        })
16✔
300
    ),
16✔
301
    responses(
16✔
302
        (status = 200, description = "OK"),        
16✔
303
    ),
16✔
304
    security(
16✔
305
        ("session_token" = [])
16✔
306
    )
16✔
307
)]
16✔
308
async fn remove_permission_handler<C: ApplicationContext>(
1✔
309
    session: C::Session,
1✔
310
    app_ctx: web::Data<C>,
1✔
311
    permission: web::Json<PermissionRequest>,
1✔
312
) -> Result<HttpResponse> {
1✔
313
    let permission = permission.into_inner();
1✔
314

1✔
315
    let db = app_ctx.session_context(session).db();
1✔
316
    let permission_id = permission.resource.resolve_resource_id(&db).await?;
1✔
317

318
    db.remove_permission::<ResourceId>(permission.role_id, permission_id, permission.permission)
1✔
319
        .await
1✔
320
        .boxed_context(crate::error::PermissionDb)?;
1✔
321

322
    Ok(HttpResponse::Ok().finish())
×
323
}
1✔
324

325
#[cfg(test)]
326
mod tests {
327

328
    use super::*;
329
    use crate::{
330
        api::model::datatypes::RasterDataType as ApiRasterDataType,
331
        contexts::PostgresContext,
332
        datasets::upload::{Upload, UploadDb, UploadId},
333
        ge_context,
334
        layers::{layer::AddLayer, listing::LayerCollectionProvider, storage::LayerDb},
335
        machine_learning::{MlModel, MlModelIdAndName, MlModelMetadata},
336
        users::{UserAuth, UserCredentials, UserRegistration},
337
        util::tests::{
338
            add_ndvi_to_datasets2, add_ports_to_datasets, admin_login, read_body_string,
339
            send_test_request,
340
        },
341
        workflows::workflow::Workflow,
342
    };
343
    use actix_http::header;
344
    use actix_web_httpauth::headers::authorization::Bearer;
345
    use geoengine_datatypes::{primitives::Coordinate2D, util::Identifier};
346
    use geoengine_operators::{
347
        engine::{RasterOperator, TypedOperator, VectorOperator, WorkflowOperatorPath},
348
        mock::{MockPointSource, MockPointSourceParams},
349
        source::{GdalSource, GdalSourceParameters, OgrSource, OgrSourceParameters},
350
    };
351
    use serde_json::{json, Value};
352
    use tokio_postgres::NoTls;
353

354
    #[ge_context::test]
1✔
355
    #[allow(clippy::too_many_lines)]
356
    async fn it_checks_permission_during_intialization(app_ctx: PostgresContext<NoTls>) {
1✔
357
        // create user and sessions
358

359
        let user_id = app_ctx
1✔
360
            .register_user(UserRegistration {
1✔
361
                email: "test@localhost".to_string(),
1✔
362
                real_name: "Foo Bar".to_string(),
1✔
363
                password: "test".to_string(),
1✔
364
            })
1✔
365
            .await
1✔
366
            .unwrap();
1✔
367

368
        let user_session = app_ctx
1✔
369
            .login(UserCredentials {
1✔
370
                email: "test@localhost".to_string(),
1✔
371
                password: "test".to_string(),
1✔
372
            })
1✔
373
            .await
1✔
374
            .unwrap();
1✔
375

376
        let admin_session = admin_login(&app_ctx).await;
1✔
377
        let admin_ctx = app_ctx.session_context(admin_session.clone());
1✔
378

379
        // setup data and operators
380

381
        let (gdal_dataset_id, gdal_dataset_name) =
1✔
382
            add_ndvi_to_datasets2(&app_ctx, false, false).await;
1✔
383
        let gdal = GdalSource {
1✔
384
            params: GdalSourceParameters {
1✔
385
                data: gdal_dataset_name,
1✔
386
            },
1✔
387
        }
1✔
388
        .boxed();
1✔
389

390
        let (ogr_dataset_id, ogr_dataset_name) =
1✔
391
            add_ports_to_datasets(&app_ctx, false, false).await;
1✔
392
        let ogr = OgrSource {
1✔
393
            params: OgrSourceParameters {
1✔
394
                data: ogr_dataset_name,
1✔
395
                attribute_filters: None,
1✔
396
                attribute_projection: None,
1✔
397
            },
1✔
398
        }
1✔
399
        .boxed();
1✔
400

1✔
401
        let user_ctx = app_ctx.session_context(user_session.clone());
1✔
402

1✔
403
        let exe_ctx = user_ctx.execution_context().unwrap();
1✔
404

1✔
405
        // check that workflow can only be intitialized after adding permissions
1✔
406

1✔
407
        assert!(gdal
1✔
408
            .clone()
1✔
409
            .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
410
            .await
1✔
411
            .is_err());
1✔
412

413
        admin_ctx
1✔
414
            .db()
1✔
415
            .add_permission(
1✔
416
                user_id.into(),
1✔
417
                ResourceId::from(gdal_dataset_id),
1✔
418
                Permission::Read,
1✔
419
            )
1✔
420
            .await
1✔
421
            .unwrap();
1✔
422

1✔
423
        assert!(gdal
1✔
424
            .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
425
            .await
1✔
426
            .is_ok());
1✔
427

428
        assert!(ogr
1✔
429
            .clone()
1✔
430
            .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
431
            .await
1✔
432
            .is_err());
1✔
433

434
        admin_ctx
1✔
435
            .db()
1✔
436
            .add_permission(
1✔
437
                user_id.into(),
1✔
438
                ResourceId::from(ogr_dataset_id),
1✔
439
                Permission::Read,
1✔
440
            )
1✔
441
            .await
1✔
442
            .unwrap();
1✔
443

1✔
444
        assert!(ogr
1✔
445
            .initialize(WorkflowOperatorPath::initialize_root(), &exe_ctx)
1✔
446
            .await
1✔
447
            .is_ok());
1✔
448
    }
1✔
449

450
    #[ge_context::test]
1✔
451
    #[allow(clippy::too_many_lines)]
452
    async fn it_lists_dataset_permissions(app_ctx: PostgresContext<NoTls>) {
1✔
453
        let admin_session = admin_login(&app_ctx).await;
1✔
454

455
        let (_dataset_id, dataset_name) = add_ndvi_to_datasets2(&app_ctx, true, true).await;
1✔
456

457
        let req = actix_web::test::TestRequest::get()
1✔
458
            .uri(&format!(
1✔
459
                "/permissions/resources/dataset/{dataset_name}?offset=0&limit=10",
1✔
460
            ))
1✔
461
            .append_header((header::CONTENT_LENGTH, 0))
1✔
462
            .append_header((
1✔
463
                header::AUTHORIZATION,
1✔
464
                Bearer::new(admin_session.id.to_string()),
1✔
465
            ));
1✔
466
        let res = send_test_request(req, app_ctx).await;
1✔
467

468
        let res_status = res.status();
1✔
469
        let res_body = serde_json::from_str::<Value>(&read_body_string(res).await).unwrap();
1✔
470
        assert_eq!(res_status, 200, "{res_body}");
1✔
471

472
        let expected_result = json!([{
1✔
473
               "permission":"Owner",
1✔
474
               "resource":  {
1✔
475
                   "id": dataset_name.to_string(),
1✔
476
                   "type": "dataset"
1✔
477
               },
1✔
478
               "role": {
1✔
479
                   "id": "d5328854-6190-4af9-ad69-4e74b0961ac9",
1✔
480
                   "name":
1✔
481
                   "admin"
1✔
482
               }
1✔
483
           }, {
1✔
484
               "permission": "Read",
1✔
485
               "resource": {
1✔
486
                   "id": dataset_name.to_string(),
1✔
487
                   "type": "dataset"
1✔
488
               },
1✔
489
               "role": {
1✔
490
                   "id": "fd8e87bf-515c-4f36-8da6-1a53702ff102",
1✔
491
                   "name": "anonymous"
1✔
492
               }
1✔
493
           }, {
1✔
494
               "permission": "Read",
1✔
495
               "resource": {
1✔
496
                   "id": dataset_name.to_string(),
1✔
497
                   "type": "dataset",
1✔
498
               },
1✔
499
               "role": {
1✔
500
                   "id": "4e8081b6-8aa6-4275-af0c-2fa2da557d28",
1✔
501
                   "name": "user"
1✔
502
               }
1✔
503
           }]
1✔
504
        );
1✔
505

1✔
506
        assert_eq!(res_body, expected_result, "{res_body} != {expected_result}");
1✔
507
    }
1✔
508

509
    #[ge_context::test]
1✔
510
    #[allow(clippy::too_many_lines)]
511
    async fn it_lists_ml_model_permissions(app_ctx: PostgresContext<NoTls>) {
1✔
512
        let admin_session = admin_login(&app_ctx).await;
1✔
513

514
        let db = app_ctx.session_context(admin_session.clone()).db();
1✔
515

1✔
516
        let upload_id = UploadId::new();
1✔
517
        let upload = Upload {
1✔
518
            id: upload_id,
1✔
519
            files: vec![],
1✔
520
        };
1✔
521
        db.create_upload(upload).await.unwrap();
1✔
522

1✔
523
        let model = MlModel {
1✔
524
            description: "No real model here".to_owned(),
1✔
525
            display_name: "my unreal model".to_owned(),
1✔
526
            metadata: MlModelMetadata {
1✔
527
                file_name: "myUnrealmodel.onnx".to_owned(),
1✔
528
                input_type: ApiRasterDataType::F32,
1✔
529
                num_input_bands: 17,
1✔
530
                output_type: ApiRasterDataType::F64,
1✔
531
            },
1✔
532
            name: MlModelName::new(None, "myUnrealModel").into(),
1✔
533
            upload: upload_id,
1✔
534
        };
1✔
535

536
        let MlModelIdAndName {
537
            id: _model_id,
1✔
538
            name: model_name,
1✔
539
        } = db.add_model(model).await.unwrap();
1✔
540

1✔
541
        let req = actix_web::test::TestRequest::get()
1✔
542
            .uri(&format!(
1✔
543
                "/permissions/resources/mlModel/{model_name}?offset=0&limit=10",
1✔
544
            ))
1✔
545
            .append_header((header::CONTENT_LENGTH, 0))
1✔
546
            .append_header((
1✔
547
                header::AUTHORIZATION,
1✔
548
                Bearer::new(admin_session.id.to_string()),
1✔
549
            ));
1✔
550
        let res = send_test_request(req, app_ctx).await;
1✔
551

552
        let res_status = res.status();
1✔
553
        let res_body = serde_json::from_str::<Value>(&read_body_string(res).await).unwrap();
1✔
554
        assert_eq!(res_status, 200, "{res_body}");
1✔
555

556
        assert_eq!(
1✔
557
            res_body,
1✔
558
            json!([{
1✔
559
                   "permission":"Owner",
1✔
560
                   "resource":  {
1✔
561
                       "id": model_name.to_string(),
1✔
562
                       "type": "mlModel"
1✔
563
                   },
1✔
564
                   "role": {
1✔
565
                       "id": "d5328854-6190-4af9-ad69-4e74b0961ac9",
1✔
566
                       "name": "admin"
1✔
567
                   }
1✔
568
               }]
1✔
569
            )
1✔
570
        );
1✔
571
    }
1✔
572

573
    #[ge_context::test]
1✔
574
    #[allow(clippy::too_many_lines)]
575
    async fn it_lists_layer_collection_permissions(app_ctx: PostgresContext<NoTls>) {
1✔
576
        let admin_session = admin_login(&app_ctx).await;
1✔
577

578
        let db = app_ctx.session_context(admin_session.clone()).db();
1✔
579

580
        let root_collection = &db.get_root_layer_collection_id().await.unwrap();
1✔
581

1✔
582
        let req = actix_web::test::TestRequest::get()
1✔
583
            .uri(&format!(
1✔
584
                "/permissions/resources/layerCollection/{root_collection}?offset=0&limit=10",
1✔
585
            ))
1✔
586
            .append_header((header::CONTENT_LENGTH, 0))
1✔
587
            .append_header((
1✔
588
                header::AUTHORIZATION,
1✔
589
                Bearer::new(admin_session.id.to_string()),
1✔
590
            ));
1✔
591
        let res = send_test_request(req, app_ctx).await;
1✔
592

593
        let res_status = res.status();
1✔
594
        let res_body = serde_json::from_str::<Value>(&read_body_string(res).await).unwrap();
1✔
595
        assert_eq!(res_status, 200, "{res_body}");
1✔
596

597
        assert_eq!(
1✔
598
            res_body,
1✔
599
            json!([{
1✔
600
                   "permission":"Owner",
1✔
601
                   "resource":  {
1✔
602
                       "id": root_collection.to_string(),
1✔
603
                       "type": "layerCollection"
1✔
604
                   },
1✔
605
                   "role": {
1✔
606
                       "id": "d5328854-6190-4af9-ad69-4e74b0961ac9",
1✔
607
                       "name":
1✔
608
                       "admin"
1✔
609
                   }
1✔
610
               }, {
1✔
611
                   "permission": "Read",
1✔
612
                   "resource": {
1✔
613
                       "id": root_collection.to_string(),
1✔
614
                       "type": "layerCollection"
1✔
615
                   },
1✔
616
                   "role": {
1✔
617
                       "id": "fd8e87bf-515c-4f36-8da6-1a53702ff102",
1✔
618
                       "name": "anonymous"
1✔
619
                   }
1✔
620
               }, {
1✔
621
                   "permission": "Read",
1✔
622
                   "resource": {
1✔
623
                       "id": root_collection.to_string(),
1✔
624
                       "type": "layerCollection",
1✔
625
                   },
1✔
626
                   "role": {
1✔
627
                       "id": "4e8081b6-8aa6-4275-af0c-2fa2da557d28",
1✔
628
                       "name": "user"
1✔
629
                   }
1✔
630
               }]
1✔
631
            )
1✔
632
        );
1✔
633
    }
1✔
634

635
    #[ge_context::test]
1✔
636
    #[allow(clippy::too_many_lines)]
637
    async fn it_lists_layer_permissions(app_ctx: PostgresContext<NoTls>) {
1✔
638
        let admin_session = admin_login(&app_ctx).await;
1✔
639

640
        let db = app_ctx.session_context(admin_session.clone()).db();
1✔
641

642
        let root_collection = &db.get_root_layer_collection_id().await.unwrap();
1✔
643

1✔
644
        let layer = AddLayer {
1✔
645
            name: "layer".to_string(),
1✔
646
            description: "description".to_string(),
1✔
647
            workflow: Workflow {
1✔
648
                operator: TypedOperator::Vector(
1✔
649
                    MockPointSource {
1✔
650
                        params: MockPointSourceParams {
1✔
651
                            points: vec![Coordinate2D::new(1., 2.); 3],
1✔
652
                        },
1✔
653
                    }
1✔
654
                    .boxed(),
1✔
655
                ),
1✔
656
            },
1✔
657
            symbology: None,
1✔
658
            metadata: Default::default(),
1✔
659
            properties: Default::default(),
1✔
660
        };
1✔
661

662
        let l_id = db.add_layer(layer, root_collection).await.unwrap();
1✔
663

1✔
664
        let req = actix_web::test::TestRequest::get()
1✔
665
            .uri(&format!(
1✔
666
                "/permissions/resources/layer/{l_id}?offset=0&limit=10",
1✔
667
            ))
1✔
668
            .append_header((header::CONTENT_LENGTH, 0))
1✔
669
            .append_header((
1✔
670
                header::AUTHORIZATION,
1✔
671
                Bearer::new(admin_session.id.to_string()),
1✔
672
            ));
1✔
673
        let res = send_test_request(req, app_ctx).await;
1✔
674

675
        let res_status = res.status();
1✔
676
        let res_body = serde_json::from_str::<Value>(&read_body_string(res).await).unwrap();
1✔
677
        assert_eq!(res_status, 200, "{res_body}");
1✔
678

679
        assert_eq!(
1✔
680
            res_body,
1✔
681
            json!([{
1✔
682
                   "permission":"Owner",
1✔
683
                   "resource":  {
1✔
684
                       "id": l_id.to_string(),
1✔
685
                       "type": "layer"
1✔
686
                   },
1✔
687
                   "role": {
1✔
688
                       "id": "d5328854-6190-4af9-ad69-4e74b0961ac9",
1✔
689
                       "name":
1✔
690
                       "admin"
1✔
691
                   }
1✔
692
               } ]
1✔
693
            )
1✔
694
        );
1✔
695
    }
1✔
696

697
    #[test]
698
    fn resource_from_str_tuple() {
1✔
699
        let test_uuid = Uuid::new_v4();
1✔
700

1✔
701
        let layer_res = Resource::try_from(("layer".to_owned(), "cats".to_owned())).unwrap();
1✔
702
        assert_eq!(
1✔
703
            layer_res,
1✔
704
            Resource::Layer(LayerResource {
1✔
705
                r#type: Default::default(),
1✔
706
                id: LayerId("cats".to_owned())
1✔
707
            })
1✔
708
        );
1✔
709

710
        let layer_col_res =
1✔
711
            Resource::try_from(("layerCollection".to_owned(), "cats".to_owned())).unwrap();
1✔
712
        assert_eq!(
1✔
713
            layer_col_res,
1✔
714
            Resource::LayerCollection(LayerCollectionResource {
1✔
715
                r#type: Default::default(),
1✔
716
                id: LayerCollectionId("cats".to_owned())
1✔
717
            })
1✔
718
        );
1✔
719

720
        let project_res = Resource::try_from(("project".to_owned(), test_uuid.into())).unwrap();
1✔
721
        assert_eq!(
1✔
722
            project_res,
1✔
723
            Resource::Project(ProjectResource {
1✔
724
                r#type: Default::default(),
1✔
725
                id: ProjectId(test_uuid)
1✔
726
            })
1✔
727
        );
1✔
728

729
        let dataset_res = Resource::try_from(("dataset".to_owned(), "cats".to_owned())).unwrap();
1✔
730
        assert_eq!(
1✔
731
            dataset_res,
1✔
732
            Resource::Dataset(DatasetResource {
1✔
733
                r#type: Default::default(),
1✔
734
                id: DatasetName::new(None, "cats".to_owned())
1✔
735
            })
1✔
736
        );
1✔
737

738
        let ml_model_res = Resource::try_from(("mlModel".to_owned(), "cats".to_owned())).unwrap();
1✔
739
        assert_eq!(
1✔
740
            ml_model_res,
1✔
741
            Resource::MlModel(MlModelResource {
1✔
742
                r#type: Default::default(),
1✔
743
                id: MlModelName::new(None, "cats".to_owned())
1✔
744
            })
1✔
745
        );
1✔
746
    }
1✔
747
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc