Signed by hanna SSH key fingerprint: SHA256:4g9cWhkAAw8gwqhJUcVbSnGEvdGwklW+1Aa/fMUu59k
crates/api/src/lib.rs +1 −0 crates/model/src/lib.rs +1 −1 crates/model/src/name.rs +6 −0 crates/store/src/groups.rs +5 −0 crates/store/src/lib.rs +6 −0 crates/store/src/tests.rs +28 −0 data/repos/01/01kydxc9fqzy2yje8r0w39amae.git/HEAD +1 −0 data/repos/01/01kydxc9fqzy2yje8r0w39amae.git/config +9 −0 data/repos/01/01kydxc9fqzy2yje8r0w39amae.git/description +1 −0 data/repos/01/01kydxc9fqzy2yje8r0w39amae.git/hooks/README.sample +5 −0 data/repos/01/01kydxc9fqzy2yje8r0w39amae.git/hooks/post-receive +2 −0 data/repos/01/01kydxc9fqzy2yje8r0w39amae.git/hooks/pre-receive +2 −0 data/repos/01/01kydxc9fqzy2yje8r0w39amae.git/info/exclude +2 −0 data/repos/01/01kydxcsngxmfpk2bzfdt0sfmx.git/HEAD +1 −0 data/repos/01/01kydxcsngxmfpk2bzfdt0sfmx.git/config +9 −0 data/repos/01/01kydxcsngxmfpk2bzfdt0sfmx.git/description +1 −0 data/repos/01/01kydxcsngxmfpk2bzfdt0sfmx.git/hooks/README.sample +5 −0 data/repos/01/01kydxcsngxmfpk2bzfdt0sfmx.git/hooks/post-receive +2 −0 data/repos/01/01kydxcsngxmfpk2bzfdt0sfmx.git/hooks/pre-receive +2 −0 data/repos/01/01kydxcsngxmfpk2bzfdt0sfmx.git/info/exclude +2 −0 crates/api/src/lib.rs +1 −0 Expand 92 hidden lines 93 93 Self :: Conflict ( format ! ( "{entity} already exists with that {field}" ) ) 94 94 } 95 95 store:: StoreError :: Name ( e) => Self :: BadRequest ( e. to_string ( ) ) , 96 + e @ store:: StoreError :: GroupTooDeep { .. } => Self :: BadRequest ( e. to_string ( ) ) , 96 97 other => Self :: internal ( other) , 97 98 } 98 99 }
crates/model/src/lib.rs +1 −1 Expand 14 hidden lines 15 15 ApiToken , Comment , Group , Invite , Issue , IssueState , Key , KeyKind , Label , PullRequest , Repo , 16 16 Timestamp , User , UserEmail , Visibility , 17 17 } ; 18 - pub use name:: { MAX_NAME_LEN , NameError , RESERVED_NAMES , validate_name} ; 18 + pub use name:: { MAX_GROUP_DEPTH , MAX_NAME_LEN , NameError , RESERVED_NAMES , validate_name} ;
crates/model/src/name.rs +6 −0 Expand 20 hidden lines 21 21 22 22 pub const MAX_NAME_LEN : usize = 39 ; 23 23 24 + 25 + 26 + 27 + 28 + pub const MAX_GROUP_DEPTH : usize = 5 ; 29 + 24 30 25 31 26 32
crates/store/src/groups.rs +5 −0 Expand 29 hidden lines 30 30 if segments. is_empty ( ) { 31 31 return Err ( StoreError :: Name ( model:: NameError :: Empty ) ) ; 32 32 } 33 + if segments. len ( ) > model:: MAX_GROUP_DEPTH { 34 + return Err ( StoreError :: GroupTooDeep { 35 + max: model:: MAX_GROUP_DEPTH , 36 + } ) ; 37 + } 33 38 for segment in & segments { 34 39 model:: validate_name ( segment) ?; 35 40 }
crates/store/src/lib.rs +6 −0 Expand 123 hidden lines 124 124 125 125 # [ error ( "invalid name: {0}" ) ] 126 126 Name ( # [ from ] model:: NameError ) , 127 + 128 + # [ error ( "groups may nest at most {max} levels deep" ) ] 129 + GroupTooDeep { 130 + 131 + max: usize , 132 + } , 127 133 128 134 # [ error ( "{entity} already exists with that {field}" ) ] 129 135 Conflict {
crates/store/src/tests.rs +28 −0 Expand 796 hidden lines 797 797 } 798 798 799 799 # [ tokio :: test ] 800 + async fn ensure_group_path_rejects_excessive_nesting ( ) { 801 + for fx in sqlite_fixtures ( ) . await { 802 + let owner = fx 803 + . store 804 + . create_user ( sample_user ( "deep" , "deep@example.com" ) ) 805 + . await 806 + . unwrap ( ) ; 807 + 808 + let at_limit = ( 1 ..=model:: MAX_GROUP_DEPTH ) 809 + . map ( |n| format ! ( "g{n}" ) ) 810 + . collect :: < Vec < _ > > ( ) 811 + . join ( "/" ) ; 812 + assert ! ( 813 + fx. store 814 + . ensure_group_path ( & owner. id, & at_limit) 815 + . await 816 + . is_ok ( ) 817 + ) ; 818 + 819 + let too_deep = format ! ( "{at_limit}/extra" ) ; 820 + assert ! ( matches ! ( 821 + fx. store. ensure_group_path ( & owner. id, & too_deep) . await , 822 + Err ( StoreError :: GroupTooDeep { max } ) if max == model:: MAX_GROUP_DEPTH 823 + ) ) ; 824 + } 825 + } 826 + 827 + # [ tokio :: test ] 800 828 async fn keys_get_stable_per_user_ordinals ( ) { 801 829 for fx in sqlite_fixtures ( ) . await { 802 830 let user = fx
data/repos/01/01kydxc9fqzy2yje8r0w39amae.git/HEAD +1 −0 data/repos/01/01kydxc9fqzy2yje8r0w39amae.git/config +9 −0 1 + [core] 2 + bare = true 3 + repositoryformatversion = 0 4 + filemode = true 5 + logallrefupdates = true 6 + [receive] 7 + denyNonFastForwards = false 8 + [gc] 9 + auto = 0
data/repos/01/01kydxc9fqzy2yje8r0w39amae.git/description +1 −0 1 + Unnamed repository; edit this file 'description' to name the repository.
data/repos/01/01kydxc9fqzy2yje8r0w39amae.git/hooks/README.sample +5 −0 data/repos/01/01kydxc9fqzy2yje8r0w39amae.git/hooks/post-receive +2 −0 1 + 2 + exec "/nix/store/f93gacynk1wfdwg1s90pybfy828cdic0-fabrica-0.1.0/bin/.fabrica-wrapped" hook post-receive
data/repos/01/01kydxc9fqzy2yje8r0w39amae.git/hooks/pre-receive +2 −0 data/repos/01/01kydxc9fqzy2yje8r0w39amae.git/info/exclude +2 −0 1 + # File patterns to ignore; see `git help ignore` for more information. 2 + # Lines that start with '#' are comments.
data/repos/01/01kydxcsngxmfpk2bzfdt0sfmx.git/HEAD +1 −0 data/repos/01/01kydxcsngxmfpk2bzfdt0sfmx.git/config +9 −0 1 + [core] 2 + bare = true 3 + repositoryformatversion = 0 4 + filemode = true 5 + logallrefupdates = true 6 + [receive] 7 + denyNonFastForwards = false 8 + [gc] 9 + auto = 0
data/repos/01/01kydxcsngxmfpk2bzfdt0sfmx.git/description +1 −0 1 + Unnamed repository; edit this file 'description' to name the repository.
data/repos/01/01kydxcsngxmfpk2bzfdt0sfmx.git/hooks/README.sample +5 −0 data/repos/01/01kydxcsngxmfpk2bzfdt0sfmx.git/hooks/post-receive +2 −0 1 + 2 + exec "/nix/store/f93gacynk1wfdwg1s90pybfy828cdic0-fabrica-0.1.0/bin/.fabrica-wrapped" hook post-receive
data/repos/01/01kydxcsngxmfpk2bzfdt0sfmx.git/hooks/pre-receive +2 −0 data/repos/01/01kydxcsngxmfpk2bzfdt0sfmx.git/info/exclude +2 −0 1 + # File patterns to ignore; see `git help ignore` for more information. 2 + # Lines that start with '#' are comments.