From f84307f025468e4e4fca2860f71b1f4035e1e33e Mon Sep 17 00:00:00 2001 From: Lei Da Date: Mon, 9 Dec 2024 18:35:16 +0800 Subject: [PATCH] allow admin to read private repos --- component/repo.go | 10 +- component/repo_test.go | 213 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 222 insertions(+), 1 deletion(-) diff --git a/component/repo.go b/component/repo.go index d5920e49..b30a2ed8 100644 --- a/component/repo.go +++ b/component/repo.go @@ -1493,7 +1493,15 @@ func (c *repoComponentImpl) GetUserRepoPermission(ctx context.Context, userName func (c *repoComponentImpl) CheckCurrentUserPermission(ctx context.Context, userName string, namespace string, role membership.Role) (bool, error) { ns, err := c.namespaceStore.FindByPath(ctx, namespace) if err != nil { - return false, err + return false, fmt.Errorf("fail to find namespace '%s', err:%w", namespace, err) + } + + u, err := c.userStore.FindByUsername(ctx, userName) + if err != nil { + return false, fmt.Errorf("fail to find user '%s', err:%w", userName, err) + } + if u.CanAdmin() { + return true, nil } if ns.NamespaceType == "user" { diff --git a/component/repo_test.go b/component/repo_test.go index 25fcd093..ce2f111f 100644 --- a/component/repo_test.go +++ b/component/repo_test.go @@ -5,8 +5,10 @@ import ( "testing" "github.com/alibabacloud-go/tea/tea" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "opencsg.com/csghub-server/builder/git/gitserver" + "opencsg.com/csghub-server/builder/git/membership" "opencsg.com/csghub-server/builder/store/database" "opencsg.com/csghub-server/common/types" ) @@ -254,3 +256,214 @@ func TestRepoComponent_DeleteRepo(t *testing.T) { // }) // } + +// func TestRepoComponent_Tree(t *testing.T) { +// { +// t.Run("can read self-owned", func(t *testing.T) { +// ctx := context.TODO() +// repoComp := initializeTestRepoComponent(ctx, t) + +// user := database.User{} +// user.Username = "user_name" +// repoComp.mocks.stores.UserMock().EXPECT().FindByUsername(mock.Anything, user.Username).Return(user, nil) + +// ns := database.Namespace{} +// ns.NamespaceType = "user" +// ns.Path = "user_name" +// repoComp.mocks.stores.NamespaceMock().EXPECT().FindByPath(mock.Anything, ns.Path).Return(ns, nil) + +// repo := &database.Repository{ +// Private: true, +// User: user, +// Path: fmt.Sprintf("%s/%s", ns.Path, "repo_name"), +// Source: types.LocalSource, +// } +// repoComp.mocks.stores.RepoMock().EXPECT().FindByPath(mock.Anything, types.ModelRepo, ns.Path, repo.Name).Return(repo, nil) + +// tree := []*types.File{} +// repoComp.mocks.gitServer.EXPECT().GetRepoFileTree(mock.Anything, mock.Anything).Return(tree, nil) + +// actualTree, err := repoComp.Tree(context.Background(), &types.GetFileReq{ +// Namespace: ns.Path, +// Name: repo.Name, +// Path: "", +// RepoType: types.ModelRepo, +// CurrentUser: user.Username, +// }) +// require.Nil(t, err) +// require.Equal(t, tree, actualTree) + +// }) + +// t.Run("forbidden anoymous user to read private repo", func(t *testing.T) { +// ctx := context.TODO() +// repoComp := initializeTestRepoComponent(ctx, t) + +// repoComp.mocks.stores.RepoMock().EXPECT().FindByPath(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&database.Repository{ +// // private repo don't allow read from other user +// Private: true, +// }, nil) + +// actualTree, err := repoComp.Tree(context.Background(), &types.GetFileReq{}) +// require.Nil(t, actualTree) +// require.Equal(t, err, ErrForbidden) + +// }) +// } + +// } +func TestRepoComponent_checkCurrentUserPermission(t *testing.T) { + + t.Run("can read self-owned", func(t *testing.T) { + repoComp := initializeTestRepoComponent(context.TODO(), t) + ns := database.Namespace{} + ns.NamespaceType = "user" + ns.Path = "user_name" + repoComp.mocks.stores.NamespaceMock().EXPECT().FindByPath(mock.Anything, ns.Path).Return(ns, nil) + + user := database.User{} + user.Username = "user_name" + repoComp.mocks.stores.UserMock().EXPECT().FindByUsername(mock.Anything, user.Username).Return(user, nil) + + yes, err := repoComp.CheckCurrentUserPermission(context.Background(), user.Username, ns.Path, membership.RoleRead) + require.True(t, yes) + require.NoError(t, err) + + yes, err = repoComp.CheckCurrentUserPermission(context.Background(), user.Username, ns.Path, membership.RoleWrite) + require.True(t, yes) + require.NoError(t, err) + + yes, err = repoComp.CheckCurrentUserPermission(context.Background(), user.Username, ns.Path, membership.RoleAdmin) + require.True(t, yes) + require.NoError(t, err) + }) + + t.Run("can not read other's", func(t *testing.T) { + repoComp := initializeTestRepoComponent(context.TODO(), t) + ns := database.Namespace{} + ns.NamespaceType = "user" + ns.Path = "user_name_other" + repoComp.mocks.stores.NamespaceMock().EXPECT().FindByPath(mock.Anything, ns.Path).Return(ns, nil) + + user := database.User{} + user.Username = "user_name" + repoComp.mocks.stores.UserMock().EXPECT().FindByUsername(mock.Anything, user.Username).Return(user, nil) + + yes, err := repoComp.CheckCurrentUserPermission(context.Background(), user.Username, ns.Path, membership.RoleRead) + require.False(t, yes) + require.NoError(t, err) + + yes, err = repoComp.CheckCurrentUserPermission(context.Background(), user.Username, ns.Path, membership.RoleWrite) + require.False(t, yes) + require.NoError(t, err) + + yes, err = repoComp.CheckCurrentUserPermission(context.Background(), user.Username, ns.Path, membership.RoleAdmin) + require.False(t, yes) + require.NoError(t, err) + }) + + t.Run("can not read org's if not org member", func(t *testing.T) { + repoComp := initializeTestRepoComponent(context.TODO(), t) + ns := database.Namespace{} + ns.NamespaceType = "organization" + ns.Path = "org_name" + repoComp.mocks.stores.NamespaceMock().EXPECT().FindByPath(mock.Anything, ns.Path).Return(ns, nil) + + user := database.User{} + user.Username = "user_name" + repoComp.mocks.stores.UserMock().EXPECT().FindByUsername(mock.Anything, user.Username).Return(user, nil) + + //user not belongs to org + repoComp.mocks.userSvcClient.EXPECT().GetMemberRole(mock.Anything, ns.Path, user.Username).Return(membership.RoleUnknown, nil) + + yes, err := repoComp.CheckCurrentUserPermission(context.Background(), user.Username, ns.Path, membership.RoleRead) + require.False(t, yes) + require.NoError(t, err) + + yes, err = repoComp.CheckCurrentUserPermission(context.Background(), user.Username, ns.Path, membership.RoleWrite) + require.False(t, yes) + require.NoError(t, err) + + yes, err = repoComp.CheckCurrentUserPermission(context.Background(), user.Username, ns.Path, membership.RoleAdmin) + require.False(t, yes) + require.NoError(t, err) + }) + + t.Run("can read org's as org member", func(t *testing.T) { + repoComp := initializeTestRepoComponent(context.TODO(), t) + ns := database.Namespace{} + ns.NamespaceType = "organization" + ns.Path = "org_name" + repoComp.mocks.stores.NamespaceMock().EXPECT().FindByPath(mock.Anything, ns.Path).Return(ns, nil) + + user := database.User{} + user.Username = "user_name" + repoComp.mocks.stores.UserMock().EXPECT().FindByUsername(mock.Anything, user.Username).Return(user, nil) + + //user is read-only member of the org + repoComp.mocks.userSvcClient.EXPECT().GetMemberRole(mock.Anything, ns.Path, user.Username).Return(membership.RoleRead, nil) + + //can read + yes, err := repoComp.CheckCurrentUserPermission(context.Background(), user.Username, ns.Path, membership.RoleRead) + require.True(t, yes) + require.NoError(t, err) + //can't write + yes, err = repoComp.CheckCurrentUserPermission(context.Background(), user.Username, ns.Path, membership.RoleWrite) + require.False(t, yes) + require.NoError(t, err) + //can't admin + yes, err = repoComp.CheckCurrentUserPermission(context.Background(), user.Username, ns.Path, membership.RoleAdmin) + require.False(t, yes) + require.NoError(t, err) + }) + + t.Run("admin read org's", func(t *testing.T) { + repoComp := initializeTestRepoComponent(context.TODO(), t) + ns := database.Namespace{} + ns.NamespaceType = "organization" + ns.Path = "org_name" + repoComp.mocks.stores.NamespaceMock().EXPECT().FindByPath(mock.Anything, ns.Path).Return(ns, nil) + + user := database.User{} + user.Username = "user_name_admin" + user.RoleMask = "admin" + repoComp.mocks.stores.UserMock().EXPECT().FindByUsername(mock.Anything, user.Username).Return(user, nil) + + yes, err := repoComp.CheckCurrentUserPermission(context.Background(), user.Username, ns.Path, membership.RoleRead) + require.True(t, yes) + require.NoError(t, err) + + yes, err = repoComp.CheckCurrentUserPermission(context.Background(), user.Username, ns.Path, membership.RoleWrite) + require.True(t, yes) + require.NoError(t, err) + + yes, err = repoComp.CheckCurrentUserPermission(context.Background(), user.Username, ns.Path, membership.RoleAdmin) + require.True(t, yes) + require.NoError(t, err) + }) + + t.Run("admin read other's", func(t *testing.T) { + repoComp := initializeTestRepoComponent(context.TODO(), t) + ns := database.Namespace{} + ns.NamespaceType = "user" + ns.Path = "user_name" + repoComp.mocks.stores.NamespaceMock().EXPECT().FindByPath(mock.Anything, ns.Path).Return(ns, nil) + + user := database.User{} + user.Username = "user_name_admin" + user.RoleMask = "admin" + repoComp.mocks.stores.UserMock().EXPECT().FindByUsername(mock.Anything, user.Username).Return(user, nil) + + yes, err := repoComp.CheckCurrentUserPermission(context.Background(), user.Username, ns.Path, membership.RoleRead) + require.True(t, yes) + require.NoError(t, err) + + yes, err = repoComp.CheckCurrentUserPermission(context.Background(), user.Username, ns.Path, membership.RoleWrite) + require.True(t, yes) + require.NoError(t, err) + + yes, err = repoComp.CheckCurrentUserPermission(context.Background(), user.Username, ns.Path, membership.RoleAdmin) + require.True(t, yes) + require.NoError(t, err) + }) +}