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]
236240fn 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) ]
376383mod 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