Skip to content

Commit 3caacc9

Browse files
committed
feat: grant unittests
1 parent 308cf15 commit 3caacc9

File tree

2 files changed

+185
-15
lines changed

2 files changed

+185
-15
lines changed

justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ protoc-docs:
104104
-docker network rm {{DOCKER_NETWORK}} > /dev/null 2>&1
105105

106106
unittest:
107-
cargo llvm-cov --bin feedback-fusion --no-report
107+
FEEDBACK_FUSION_CONFIG="./tests/_common/configs/postgres.yaml" cargo llvm-cov --bin feedback-fusion --no-report
108108

109109
integration:
110110
OIDC_PROVIDER="http://localhost:5151" OIDC_CLIENT_ID="client" OIDC_CLIENT_SECRET="secret" RUST_LOG="INFO" GRPC_ENDPOINT="http://localhost:8000" cargo llvm-cov --no-report --no-fail-fast --test integration_test || (cat ./target/feedback-fusion.log; just stop-backend; cargo llvm-cov report; exit 1)

src/services/authorization.rs

Lines changed: 184 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//SPDX-FileCopyrightText: 2023 OneLiteFeatherNet
1+
//SPDX-FileCopyrightText: 2025 OneLiteFeatherNet
22
//SPDX-License-Identifier: MIT
33

44
//MIT License
@@ -123,6 +123,8 @@ impl UserContext {
123123
}
124124
}
125125

126+
/// Only for internal user matrix creation, use `authorize` for UserContext based permission
127+
/// checks
126128
#[instrument(skip_all)]
127129
pub fn has_grant<'a>(
128130
scopes: &BTreeSet<&ScopeTokenRef>,
@@ -142,27 +144,28 @@ impl UserContext {
142144
// verify the groups
143145
let group = groups.iter().find(|group| entry.1.contains(group.as_str()));
144146

145-
if scope.is_none() && group.is_none() {
147+
let mut authorizations = {
146148
// find possibly matching authorizations
147149
let authorizations = authorizations.iter().filter(|authorization| {
148150
let matches_permission = authorization.authorization_grant().eq(&permission);
149151
let matches_endpoint = authorization.resource_kind().eq(&endpoint);
150-
let matches_id = authorization.resource_id().as_ref().is_none_or(|pattern| {
151-
Wildcard::new(pattern.as_bytes())
152-
.unwrap()
153-
.is_match(authorization_string.as_bytes())
154-
});
152+
// we do not have to verify the endpoint here as this function is only called for
153+
// user matrix creation
155154

156-
matches_permission && matches_endpoint && matches_id
155+
matches_permission && matches_endpoint
157156
});
158157

159-
Ok(authorizations
158+
authorizations
160159
.into_iter()
161160
.map(ResourceAuthorization::to_string)
162-
.collect())
163-
} else {
164-
Ok(vec![authorization_string])
161+
.collect::<Vec<_>>()
162+
};
163+
164+
if scope.is_some() || group.is_some() {
165+
authorizations.push(authorization_string);
165166
}
167+
168+
Ok(authorizations)
166169
}
167170

168171
#[instrument(skip(self, connection))]
@@ -232,6 +235,7 @@ impl UserContext {
232235
}
233236
}
234237

238+
/// Verify wether a auth key does match the given endpoint and permission
235239
#[instrument]
236240
fn is_match(key: &String, endpoint: &Endpoint, permission: &Permission) -> bool {
237241
let mut split = key.split("::");
@@ -263,11 +267,14 @@ fn is_match(key: &String, endpoint: &Endpoint, permission: &Permission) -> bool
263267
| Endpoint::Field(Some(id))
264268
| Endpoint::Response(Some(id)) => vec![id],
265269
Endpoint::Export(Some(list)) => list.iter().collect(),
270+
// we do not allow further recursion, therefore we ignore this
266271
_ => Vec::new(),
267272
},
268273
_ => Vec::new(),
269274
};
270275

276+
// if the list is emptry it means we have a None inside the target endpoint, therefore we have
277+
// to check wether the wildcard matches everything
271278
if ids.is_empty() {
272279
second.eq("*")
273280
} else {
@@ -374,9 +381,24 @@ async fn get_target_id_by_field_id(connection: &DatabaseConnection, id: &str) ->
374381

375382
#[cfg(test)]
376383
mod tests {
377-
use std::borrow::Cow;
384+
use std::{
385+
borrow::Cow,
386+
collections::{BTreeSet, HashMap},
387+
};
378388

379-
use crate::{authorization::is_match, Endpoint, Permission};
389+
use aliri_oauth2::scope::ScopeTokenRef;
390+
391+
use crate::{
392+
authorization::is_match,
393+
database::schema::{
394+
authorization::{
395+
ResourceAuthorization, ResourceAuthorizationGrant, ResourceAuthorizationType,
396+
ResourceKind,
397+
},
398+
user::{User, UserContext},
399+
},
400+
Endpoint, Permission,
401+
};
380402

381403
#[test]
382404
fn test_is_match_empty() {
@@ -504,4 +526,152 @@ mod tests {
504526
permission
505527
));
506528
}
529+
530+
#[test]
531+
fn test_has_grant_empty() {
532+
assert!(UserContext::has_grant(
533+
&BTreeSet::new(),
534+
&BTreeSet::new(),
535+
&[],
536+
Endpoint::Target(None),
537+
Permission::Write
538+
)
539+
.is_ok_and(|list| list.is_empty()));
540+
}
541+
542+
fn has_grant_setup<'a>() -> (
543+
BTreeSet<&'a ScopeTokenRef>,
544+
BTreeSet<String>,
545+
BTreeSet<&'a ScopeTokenRef>,
546+
BTreeSet<String>,
547+
Vec<ResourceAuthorization>,
548+
) {
549+
let valid_scopes: BTreeSet<&ScopeTokenRef> =
550+
BTreeSet::from([ScopeTokenRef::from_static("api:feedback-fusion")]);
551+
let valid_groups: BTreeSet<String> = BTreeSet::from(["admin".to_owned()]);
552+
553+
let invalid_scopes: BTreeSet<&ScopeTokenRef> =
554+
BTreeSet::from([ScopeTokenRef::from_static("foo")]);
555+
let invalid_groups: BTreeSet<String> = BTreeSet::from(["bar".to_owned()]);
556+
557+
let resource_authorizations = vec![
558+
ResourceAuthorization::builder()
559+
.authorization_type(ResourceAuthorizationType::Scope)
560+
.authorization_grant(ResourceAuthorizationGrant::Write)
561+
.authorization_value("".to_owned())
562+
.resource_kind(ResourceKind::Target)
563+
.resource_id("".to_owned())
564+
.build(),
565+
ResourceAuthorization::builder()
566+
.authorization_type(ResourceAuthorizationType::Scope)
567+
.authorization_grant(ResourceAuthorizationGrant::Read)
568+
.authorization_value("".to_owned())
569+
.resource_kind(ResourceKind::Prompt)
570+
.resource_id("".to_owned())
571+
.build(),
572+
];
573+
574+
(
575+
valid_scopes,
576+
valid_groups,
577+
invalid_scopes,
578+
invalid_groups,
579+
resource_authorizations,
580+
)
581+
}
582+
583+
#[test]
584+
fn test_has_grant_scopes_and_groups_only() {
585+
let endpoint = Endpoint::Target(None);
586+
let permission = Permission::Write;
587+
let (valid_scopes, valid_groups, invalid_scopes, invalid_groups, _) = has_grant_setup();
588+
let valid_groups = valid_groups.iter().collect::<BTreeSet<&String>>();
589+
let invalid_groups = invalid_groups.iter().collect::<BTreeSet<&String>>();
590+
591+
assert!(UserContext::has_grant(
592+
&valid_scopes,
593+
&valid_groups,
594+
&[],
595+
endpoint.clone(),
596+
permission.clone()
597+
)
598+
.is_ok_and(|list| !list.is_empty()));
599+
600+
assert!(UserContext::has_grant(
601+
&valid_scopes,
602+
&invalid_groups,
603+
&[],
604+
endpoint.clone(),
605+
permission.clone()
606+
)
607+
.is_ok_and(|list| !list.is_empty()));
608+
609+
assert!(UserContext::has_grant(
610+
&invalid_scopes,
611+
&valid_groups,
612+
&[],
613+
endpoint.clone(),
614+
permission.clone()
615+
)
616+
.is_ok_and(|list| !list.is_empty()));
617+
618+
assert!(UserContext::has_grant(
619+
&invalid_scopes,
620+
&invalid_groups,
621+
&[],
622+
endpoint.clone(),
623+
permission.clone()
624+
)
625+
.is_ok_and(|list| list.is_empty()));
626+
}
627+
628+
#[test]
629+
fn test_has_grant_resource_authorizations_only() {
630+
let (_, _, _, _, resource_authorizations) = has_grant_setup();
631+
632+
assert!(UserContext::has_grant(
633+
&BTreeSet::new(),
634+
&BTreeSet::new(),
635+
resource_authorizations.as_slice(),
636+
Endpoint::Target(None),
637+
Permission::Write
638+
)
639+
.is_ok_and(|list| !list.is_empty()));
640+
641+
assert!(UserContext::has_grant(
642+
&BTreeSet::new(),
643+
&BTreeSet::new(),
644+
resource_authorizations.as_slice(),
645+
Endpoint::Target(None),
646+
Permission::Read
647+
)
648+
.is_ok_and(|list| list.is_empty()));
649+
650+
assert!(UserContext::has_grant(
651+
&BTreeSet::new(),
652+
&BTreeSet::new(),
653+
resource_authorizations.as_slice(),
654+
Endpoint::Field(None),
655+
Permission::Read
656+
)
657+
.is_ok_and(|list| list.is_empty()));
658+
659+
assert!(UserContext::has_grant(
660+
&BTreeSet::new(),
661+
&BTreeSet::new(),
662+
resource_authorizations.as_slice(),
663+
Endpoint::Prompt(None),
664+
Permission::Read
665+
)
666+
.is_ok_and(|list| !list.is_empty()));
667+
668+
assert!(UserContext::has_grant(
669+
&BTreeSet::new(),
670+
&BTreeSet::new(),
671+
resource_authorizations.as_slice(),
672+
Endpoint::Prompt(None),
673+
Permission::Write
674+
)
675+
.is_ok_and(|list| list.is_empty()));
676+
}
507677
}

0 commit comments

Comments
 (0)