; lib/gitea/tests/access.sx — Phase 2: visibility, collaborators, org ; teams, acl-backed can?, tokens, and auth-gated web routes. (define gitea-access-pass 0) (define gitea-access-fail 0) (define gitea-access-fails (list)) (define gitea-access-test (fn (name actual expected) (if (= actual expected) (set! gitea-access-pass (+ gitea-access-pass 1)) (begin (set! gitea-access-fail (+ gitea-access-fail 1)) (set! gitea-access-fails (append gitea-access-fails (list {:name name :expected (inspect expected) :actual (inspect actual)}))))))) (define ga-db (persist/mem-backend)) (define ga-forge (gitea/forge ga-db)) (gitea/user-create! ga-forge "alice") (gitea/user-create! ga-forge "bob") (gitea/user-create! ga-forge "carol") (gitea/user-create! ga-forge "eve") (gitea/org-create! ga-forge "acme") (gitea/repo-create! ga-forge "alice" "pub" {}) (gitea/repo-create! ga-forge "alice" "sec" {:visibility "private"}) (gitea/repo-create! ga-forge "acme" "app" {:visibility "private"}) ; ── can? basics ────────────────────────────────────────────────────── (gitea-access-test "public read anon" (gitea/can? ga-forge nil "read" "alice" "pub") true) (gitea-access-test "public read any user" (gitea/can? ga-forge "eve" "read" "alice" "pub") true) (gitea-access-test "public write anon denied" (gitea/can? ga-forge nil "write" "alice" "pub") false) (gitea-access-test "public write stranger denied" (gitea/can? ga-forge "eve" "write" "alice" "pub") false) (gitea-access-test "private read anon denied" (gitea/can? ga-forge nil "read" "alice" "sec") false) (gitea-access-test "private read stranger denied" (gitea/can? ga-forge "eve" "read" "alice" "sec") false) (gitea-access-test "owner reads private" (gitea/can? ga-forge "alice" "read" "alice" "sec") true) (gitea-access-test "owner writes private" (gitea/can? ga-forge "alice" "write" "alice" "sec") true) (gitea-access-test "owner admins private" (gitea/can? ga-forge "alice" "admin" "alice" "sec") true) (gitea-access-test "owner admins public" (gitea/can? ga-forge "alice" "admin" "alice" "pub") true) (gitea-access-test "missing repo denied" (gitea/can? ga-forge "alice" "read" "alice" "nope") false) ; ── collaborators ──────────────────────────────────────────────────── (gitea-access-test "collab-add! bob write" (get (gitea/collab-add! ga-forge "alice" "sec" "bob" "write") :role) "write") (gitea-access-test "collab-role" (gitea/collab-role ga-forge "alice" "sec" "bob") "write") (gitea-access-test "collabs list" (gitea/collabs ga-forge "alice" "sec") (list "bob")) (gitea-access-test "write collab reads" (gitea/can? ga-forge "bob" "read" "alice" "sec") true) (gitea-access-test "write collab writes" (gitea/can? ga-forge "bob" "write" "alice" "sec") true) (gitea-access-test "write collab cannot admin" (gitea/can? ga-forge "bob" "admin" "alice" "sec") false) (gitea/collab-add! ga-forge "alice" "sec" "carol" "read") (gitea-access-test "read collab reads" (gitea/can? ga-forge "carol" "read" "alice" "sec") true) (gitea-access-test "read collab cannot write" (gitea/can? ga-forge "carol" "write" "alice" "sec") false) (gitea/collab-add! ga-forge "alice" "sec" "carol" "write") (gitea-access-test "collab upsert to write" (gitea/can? ga-forge "carol" "write" "alice" "sec") true) (gitea-access-test "collab-add! missing repo" (get (gitea/collab-add! ga-forge "alice" "nope" "bob" "read") :error) "no-such-repo") (gitea-access-test "collab-add! missing user" (get (gitea/collab-add! ga-forge "alice" "sec" "zeb" "read") :error) "no-such-user") (gitea-access-test "collab-add! bad role" (get (gitea/collab-add! ga-forge "alice" "sec" "bob" "boss") :error) "invalid-role") (gitea-access-test "collab-remove! carol" (gitea/collab-remove! ga-forge "alice" "sec" "carol") true) (gitea-access-test "removed collab cannot write" (gitea/can? ga-forge "carol" "write" "alice" "sec") false) (gitea-access-test "removed collab cannot read private" (gitea/can? ga-forge "carol" "read" "alice" "sec") false) (gitea-access-test "collab-remove! again false" (gitea/collab-remove! ga-forge "alice" "sec" "carol") false) ; ── teams ──────────────────────────────────────────────────────────── (gitea-access-test "team-create! owners" (get (gitea/team-create! ga-forge "acme" "owners" "admin") :role) "admin") (gitea-access-test "team-create! duplicate conflicts" (get (gitea/team-create! ga-forge "acme" "owners" "read") :conflict) true) (gitea-access-test "team-create! on user rejected" (get (gitea/team-create! ga-forge "alice" "crew" "read") :error) "no-such-org") (gitea-access-test "team-create! bad role" (get (gitea/team-create! ga-forge "acme" "crew" "boss") :error) "invalid-role") (gitea/team-add-member! ga-forge "acme" "owners" "alice") (gitea-access-test "team-members" (gitea/team-members ga-forge "acme" "owners") (list "alice")) (gitea-access-test "team-add-member! missing team" (get (gitea/team-add-member! ga-forge "acme" "ghosts" "bob") :error) "no-such-team") (gitea-access-test "team-add-member! missing user" (get (gitea/team-add-member! ga-forge "acme" "owners" "zeb") :error) "no-such-user") (gitea-access-test "owners member admins org repo" (gitea/can? ga-forge "alice" "admin" "acme" "app") true) (gitea-access-test "owners member reads org repo" (gitea/can? ga-forge "alice" "read" "acme" "app") true) (gitea-access-test "non-member cannot read org private" (gitea/can? ga-forge "bob" "read" "acme" "app") false) (gitea-access-test "org-admin? alice" (gitea/org-admin? ga-forge "acme" "alice") true) (gitea-access-test "org-admin? bob" (gitea/org-admin? ga-forge "acme" "bob") false) (gitea/team-create! ga-forge "acme" "devs" "write") (gitea/team-set-repos! ga-forge "acme" "devs" (list "app")) (gitea/team-add-member! ga-forge "acme" "devs" "bob") (gitea-access-test "devs member writes covered repo" (gitea/can? ga-forge "bob" "write" "acme" "app") true) (gitea-access-test "devs member cannot admin" (gitea/can? ga-forge "bob" "admin" "acme" "app") false) (gitea-access-test "org-admin? devs member" (gitea/org-admin? ga-forge "acme" "bob") false) (gitea/repo-create! ga-forge "acme" "site" {:visibility "private"}) (gitea-access-test "scoped team does not cover new repo" (gitea/can? ga-forge "bob" "read" "acme" "site") false) (gitea-access-test "all-repos team covers new repo" (gitea/can? ga-forge "alice" "admin" "acme" "site") true) (gitea/team-set-repos! ga-forge "acme" "devs" "all") (gitea-access-test "widened team covers site" (gitea/can? ga-forge "bob" "write" "acme" "site") true) (gitea/team-set-repos! ga-forge "acme" "devs" (list "app")) (gitea-access-test "re-narrowed team loses site" (gitea/can? ga-forge "bob" "write" "acme" "site") false) (gitea/team-remove-member! ga-forge "acme" "devs" "bob") (gitea-access-test "removed member loses access" (gitea/can? ga-forge "bob" "write" "acme" "app") false) (gitea/team-add-member! ga-forge "acme" "devs" "bob") (gitea-access-test "team-delete!" (gitea/team-delete! ga-forge "acme" "devs") true) (gitea-access-test "deleted team gone" (gitea/teams ga-forge "acme") (list "owners")) (gitea-access-test "deleted team members purged" (gitea/team-members ga-forge "acme" "devs") (list)) (gitea-access-test "deleted team access revoked" (gitea/can? ga-forge "bob" "write" "acme" "app") false) (gitea-access-test "team-delete! missing false" (gitea/team-delete! ga-forge "acme" "devs") false) ; ── visibility ─────────────────────────────────────────────────────── (gitea-access-test "visible anon" (gitea/visible-repos ga-forge nil) (list "alice/pub")) (gitea-access-test "visible eve" (gitea/visible-repos ga-forge "eve") (list "alice/pub")) (gitea-access-test "visible bob (collab on sec)" (gitea/visible-repos ga-forge "bob") (list "alice/pub" "alice/sec")) (gitea-access-test "visible alice (owner + org admin)" (gitea/visible-repos ga-forge "alice") (list "acme/app" "acme/site" "alice/pub" "alice/sec")) ; ── create permission ──────────────────────────────────────────────── (gitea-access-test "create under self" (gitea/create-allowed? ga-forge "alice" "alice") true) (gitea-access-test "create under other user" (gitea/create-allowed? ga-forge "bob" "alice") false) (gitea-access-test "create anon" (gitea/create-allowed? ga-forge nil "alice") false) (gitea-access-test "org admin creates in org" (gitea/create-allowed? ga-forge "alice" "acme") true) (gitea-access-test "non-admin cannot create in org" (gitea/create-allowed? ga-forge "eve" "acme") false) (gitea-access-test "create under unknown owner" (gitea/create-allowed? ga-forge "alice" "zeb") false) ; ── tokens ─────────────────────────────────────────────────────────── (gitea/token-create! ga-forge "alice" "tok-a") (gitea/token-create! ga-forge "bob" "tok-b") (gitea/token-create! ga-forge "eve" "tok-e") (gitea-access-test "token resolves user" (gitea/token-user ga-forge "tok-a") "alice") (gitea-access-test "unknown token" (gitea/token-user ga-forge "tok-zzz") nil) (gitea-access-test "token for unknown user" (get (gitea/token-create! ga-forge "zeb" "tok-z") :error) "no-such-user") (gitea/token-create! ga-forge "carol" "tok-c") (gitea/token-revoke! ga-forge "tok-c") (gitea-access-test "revoked token" (gitea/token-user ga-forge "tok-c") nil) ; ── auth-gated web routes ──────────────────────────────────────────── ; content in the private repo, to browse (define ga-gsec (gitea/repo-git ga-forge "alice" "sec")) (git/add! ga-gsec "secret.txt" "s3cret\n") (git/commit! ga-gsec {:message "hide" :time 1 :author "alice"}) (define ga-app (gitea/app ga-forge)) (define ga-hdr (fn (tok) (if (nil? tok) {} {:authorization (str "Bearer " tok)}))) (define ga-get (fn (target tok) (ga-app (dream-request "GET" target (ga-hdr tok) "")))) (define ga-post (fn (target tok body) (ga-app (dream-request "POST" target (ga-hdr tok) body)))) (define ga-put (fn (target tok body) (ga-app (dream-request "PUT" target (ga-hdr tok) body)))) (define ga-del (fn (target tok) (ga-app (dream-request "DELETE" target (ga-hdr tok) "")))) (gitea-access-test "web: public repo anon" (dream-status (ga-get "/alice/pub" nil)) 200) (gitea-access-test "web: private repo anon hidden" (dream-status (ga-get "/alice/sec" nil)) 404) (gitea-access-test "web: private repo stranger hidden" (dream-status (ga-get "/alice/sec" "tok-e")) 404) (gitea-access-test "web: private repo collab" (dream-status (ga-get "/alice/sec" "tok-b")) 200) (gitea-access-test "web: private repo owner" (dream-status (ga-get "/alice/sec" "tok-a")) 200) (gitea-access-test "web: private tree anon hidden" (dream-status (ga-get "/alice/sec/tree/main" nil)) 404) (gitea-access-test "web: private tree collab" (dream-status (ga-get "/alice/sec/tree/main" "tok-b")) 200) (gitea-access-test "web: private raw stranger hidden" (dream-status (ga-get "/alice/sec/raw/main/secret.txt" "tok-e")) 404) (gitea-access-test "web: private raw collab exact" (dream-resp-body (ga-get "/alice/sec/raw/main/secret.txt" "tok-b")) "s3cret\n") (gitea-access-test "web: private commits owner" (dream-status (ga-get "/alice/sec/commits/main" "tok-a")) 200) (gitea-access-test "web: index anon shows public" (contains? (dream-resp-body (ga-get "/" nil)) "alice/pub") true) (gitea-access-test "web: index anon hides private" (contains? (dream-resp-body (ga-get "/" nil)) "alice/sec") false) (gitea-access-test "web: index owner shows private" (contains? (dream-resp-body (ga-get "/" "tok-a")) "alice/sec") true) (gitea-access-test "web: api repos anon" (dream-json-parse (dream-resp-body (ga-get "/api/repos" nil))) (list "alice/pub")) (gitea-access-test "web: api repos owner" (dream-json-parse (dream-resp-body (ga-get "/api/repos" "tok-a"))) (list "acme/app" "acme/site" "alice/pub" "alice/sec")) (gitea-access-test "web: create anon 401" (dream-status (ga-post "/api/repos" nil (dream-json-encode {:name "x" :owner "alice"}))) 401) (gitea-access-test "web: create for other user 403" (dream-status (ga-post "/api/repos" "tok-b" (dream-json-encode {:name "x" :owner "alice"}))) 403) (gitea-access-test "web: create own 201" (dream-status (ga-post "/api/repos" "tok-a" (dream-json-encode {:name "x" :owner "alice"}))) 201) (gitea-access-test "web: org admin create 201" (dream-status (ga-post "/api/repos" "tok-a" (dream-json-encode {:name "tools" :owner "acme"}))) 201) (gitea-access-test "web: org non-admin create 403" (dream-status (ga-post "/api/repos" "tok-e" (dream-json-encode {:name "z" :owner "acme"}))) 403) (gitea-access-test "web: create unknown owner 400" (dream-status (ga-post "/api/repos" "tok-a" (dream-json-encode {:name "z" :owner "zeb"}))) 400) (gitea-access-test "web: delete anon public 401" (dream-status (ga-del "/api/repos/alice/x" nil)) 401) (gitea-access-test "web: delete anon private hidden 404" (dream-status (ga-del "/api/repos/alice/sec" nil)) 404) (gitea-access-test "web: delete stranger private hidden 404" (dream-status (ga-del "/api/repos/alice/sec" "tok-e")) 404) (gitea-access-test "web: delete non-admin 403" (dream-status (ga-del "/api/repos/alice/x" "tok-b")) 403) (gitea-access-test "web: delete admin 200" (dream-status (ga-del "/api/repos/alice/x" "tok-a")) 200) (gitea-access-test "web: deleted repo gone" (dream-status (ga-del "/api/repos/alice/x" "tok-a")) 404) (gitea-access-test "web: collab put anon 401" (dream-status (ga-put "/api/repos/alice/pub/collab/eve" nil "{}")) 401) (gitea-access-test "web: collab put non-admin 403" (dream-status (ga-put "/api/repos/alice/pub/collab/eve" "tok-b" (dream-json-encode {:role "write"}))) 403) (gitea-access-test "web: collab put on hidden repo 404" (dream-status (ga-put "/api/repos/alice/sec/collab/eve" "tok-e" (dream-json-encode {:role "write"}))) 404) (gitea-access-test "web: collab put admin 200" (dream-status (ga-put "/api/repos/alice/pub/collab/eve" "tok-a" (dream-json-encode {:role "write"}))) 200) (gitea-access-test "web: collab granted write" (gitea/can? ga-forge "eve" "write" "alice" "pub") true) (gitea-access-test "web: collab put bad role 400" (dream-status (ga-put "/api/repos/alice/pub/collab/eve" "tok-a" (dream-json-encode {:role "boss"}))) 400) (gitea-access-test "web: collab put unknown user 400" (dream-status (ga-put "/api/repos/alice/pub/collab/zeb" "tok-a" (dream-json-encode {:role "read"}))) 400) (gitea-access-test "web: collab delete admin 200" (dream-status (ga-del "/api/repos/alice/pub/collab/eve" "tok-a")) 200) (gitea-access-test "web: collab revoked" (gitea/can? ga-forge "eve" "write" "alice" "pub") false) (gitea-access-test "web: collab delete missing 404" (dream-status (ga-del "/api/repos/alice/pub/collab/eve" "tok-a")) 404)