diff --git a/routers/api/v1/org/member.go b/routers/api/v1/org/member.go index 11b46a05c1..ce7f227af6 100644 --- a/routers/api/v1/org/member.go +++ b/routers/api/v1/org/member.go @@ -119,6 +119,12 @@ func ListPublicMembers(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" + // don't disclose membership of organizations the doer cannot see + if !organization.HasOrgOrUserVisible(ctx, ctx.Org.Organization.AsUser(), ctx.Doer) { + ctx.APIErrorNotFound() + return + } + listMembers(ctx, false) } @@ -201,6 +207,11 @@ func IsPublicMember(ctx *context.APIContext) { if ctx.Written() { return } + // don't disclose membership of organizations the doer cannot see + if !organization.HasOrgOrUserVisible(ctx, ctx.Org.Organization.AsUser(), ctx.Doer) { + ctx.APIErrorNotFound() + return + } is, err := organization.IsPublicMembership(ctx, ctx.Org.Organization.ID, userToCheck.ID) if err != nil { ctx.APIErrorInternal(err) diff --git a/tests/integration/api_org_test.go b/tests/integration/api_org_test.go index 3306c6539c..017c7baaf2 100644 --- a/tests/integration/api_org_test.go +++ b/tests/integration/api_org_test.go @@ -263,6 +263,32 @@ func testAPIOrgGeneral(t *testing.T) { }) } +func TestAPIOrgPrivateMembersNotLeaked(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // privated_org (org 23) has private visibility and a single member, user5 + const orgName = "privated_org" + const memberName = "user5" + + // member publicizes their own membership inside the private org + memberSession := loginUser(t, memberName) + memberToken := getTokenForLoggedInUser(t, memberSession, auth_model.AccessTokenScopeWriteOrganization) + req := NewRequest(t, "PUT", "/api/v1/orgs/"+orgName+"/public_members/"+memberName).AddTokenAuth(memberToken) + MakeRequest(t, req, http.StatusNoContent) + + // an outsider must not be able to learn about the membership of a private org + outsiderSession := loginUser(t, "user2") + outsiderToken := getTokenForLoggedInUser(t, outsiderSession, auth_model.AccessTokenScopeReadOrganization) + req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/public_members/"+memberName).AddTokenAuth(outsiderToken) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/public_members").AddTokenAuth(outsiderToken) + MakeRequest(t, req, http.StatusNotFound) + + // the member can still see the public membership of their own org + req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/public_members/"+memberName).AddTokenAuth(memberToken) + MakeRequest(t, req, http.StatusNoContent) +} + func testAPIDeleteOrgRepos(t *testing.T) { org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) orgRepos, err := repo_model.GetOrgRepositories(t.Context(), org3.ID)