mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 16:51:55 +00:00
authz: Move sub-repo perms to enterprise (#45659)
This commit is contained in:
parent
ae0d88ce8a
commit
e4a6c6aa7e
@ -15,7 +15,6 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/cmd/frontend/envvar"
|
||||
"github.com/sourcegraph/sourcegraph/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/internal/api"
|
||||
"github.com/sourcegraph/sourcegraph/internal/authz"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database/dbtest"
|
||||
@ -489,11 +488,6 @@ func TestRemoveStalePerforceAccount(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, accounts, 1)
|
||||
|
||||
// We also want to add some fake sub-repo permissions and check that they are
|
||||
// deleted
|
||||
err = db.SubRepoPerms().Upsert(ctx, createdUser.ID, createdRepo.ID, authz.SubRepoPermissions{Paths: []string{"**"}})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
assertRemovals := func(t *testing.T) {
|
||||
|
||||
@ -232,9 +232,6 @@ func TestSetUserEmailVerified(t *testing.T) {
|
||||
userExternalAccounts := database.NewMockUserExternalAccountsStore()
|
||||
userExternalAccounts.DeleteFunc.SetDefaultReturn(nil)
|
||||
|
||||
subrepoPerms := database.NewMockSubRepoPermsStore()
|
||||
subrepoPerms.DeleteByUserFunc.SetDefaultReturn(nil)
|
||||
|
||||
db := database.NewMockDB()
|
||||
db.TransactFunc.SetDefaultReturn(db, nil)
|
||||
db.DoneFunc.SetDefaultHook(func(err error) error {
|
||||
@ -245,7 +242,6 @@ func TestSetUserEmailVerified(t *testing.T) {
|
||||
db.UserEmailsFunc.SetDefaultReturn(userEmails)
|
||||
db.AuthzFunc.SetDefaultReturn(authz)
|
||||
db.UserExternalAccountsFunc.SetDefaultReturn(userExternalAccounts)
|
||||
db.SubRepoPermsFunc.SetDefaultReturn(subrepoPerms)
|
||||
|
||||
RunTests(t, test.gqlTests(db))
|
||||
|
||||
|
||||
@ -29,7 +29,6 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/cmd/frontend/internal/siteid"
|
||||
oce "github.com/sourcegraph/sourcegraph/cmd/frontend/oneclickexport"
|
||||
"github.com/sourcegraph/sourcegraph/internal/adminanalytics"
|
||||
"github.com/sourcegraph/sourcegraph/internal/authz"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf/conftypes"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf/deploy"
|
||||
@ -169,7 +168,6 @@ func Main(enterpriseSetupHook func(database.DB, conftypes.UnifiedWatchable) ente
|
||||
// Run enterprise setup hook
|
||||
enterprise := enterpriseSetupHook(db, conf.DefaultClient())
|
||||
|
||||
authz.DefaultSubRepoPermsChecker, err = authz.NewSubRepoPermsClient(db.SubRepoPerms())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to create sub-repo client")
|
||||
}
|
||||
|
||||
@ -10,5 +10,5 @@ func main() {
|
||||
env.Lock()
|
||||
env.HandleHelpFlag()
|
||||
|
||||
shared.Main()
|
||||
shared.Main(nil)
|
||||
}
|
||||
|
||||
@ -75,7 +75,9 @@ var (
|
||||
rateLimitSyncerLimitPerSecond = env.MustGetInt("SRC_REPOS_SYNC_RATE_LIMIT_RATE_PER_SECOND", 80, "Rate limit applied to rate limit syncing")
|
||||
)
|
||||
|
||||
func Main() {
|
||||
type EnterpriseInit func(db database.DB)
|
||||
|
||||
func Main(enterpriseInit EnterpriseInit) {
|
||||
ctx := context.Background()
|
||||
|
||||
logging.Init() //nolint:staticcheck // Deprecated, but logs unmigrated to sourcegraph/log look really bad without this.
|
||||
@ -127,7 +129,10 @@ func Main() {
|
||||
logger.Fatal("failed to initialise keyring", log.Error(err))
|
||||
}
|
||||
|
||||
authz.DefaultSubRepoPermsChecker, err = authz.NewSubRepoPermsClient(db.SubRepoPerms())
|
||||
if enterpriseInit != nil {
|
||||
enterpriseInit(db)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to create sub-repo client", log.Error(err))
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ func main() {
|
||||
observationCtx := observation.NewContext(logger)
|
||||
|
||||
authz.SetProviders(true, []authz.Provider{})
|
||||
if err := shared.Start(observationCtx, nil, nil); err != nil {
|
||||
if err := shared.Start(observationCtx, nil, nil, nil); err != nil {
|
||||
logger.Error(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@ -3,9 +3,6 @@ package workerdb
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/sourcegraph/log"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/authz"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf/conftypes"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
@ -33,10 +30,5 @@ var initDatabaseMemo = memo.NewMemoizedConstructorWithArg(func(observationCtx *o
|
||||
return nil, errors.Errorf("failed to connect to frontend database: %s", err)
|
||||
}
|
||||
|
||||
// ideally we could memoize the LRU cache only for this, and then create new clients on-demand with a passed-in observationCtx
|
||||
authz.DefaultSubRepoPermsChecker, err = authz.NewSubRepoPermsClient(database.NewDB(log.Scoped("initDatabaseMemo", ""), db).SubRepoPerms())
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("Failed to create sub-repo client: %v", err)
|
||||
}
|
||||
return db, nil
|
||||
})
|
||||
|
||||
@ -20,7 +20,9 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/cmd/worker/internal/webhooks"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/worker/internal/zoektrepos"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/worker/job"
|
||||
workerdb "github.com/sourcegraph/sourcegraph/cmd/worker/shared/init/db"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/debugserver"
|
||||
"github.com/sourcegraph/sourcegraph/internal/encryption/keyring"
|
||||
"github.com/sourcegraph/sourcegraph/internal/env"
|
||||
@ -37,8 +39,10 @@ import (
|
||||
|
||||
const addr = ":3189"
|
||||
|
||||
type EnterpriseInit = func(ossDB database.DB)
|
||||
|
||||
// Start runs the worker.
|
||||
func Start(observationCtx *observation.Context, additionalJobs map[string]job.Job, registerEnterpriseMigrators oobmigration.RegisterMigratorsFunc) error {
|
||||
func Start(observationCtx *observation.Context, additionalJobs map[string]job.Job, registerEnterpriseMigrators oobmigration.RegisterMigratorsFunc, enterpriseInit EnterpriseInit) error {
|
||||
registerMigrators := oobmigration.ComposeRegisterMigratorsFuncs(migrations.RegisterOSSMigrators, registerEnterpriseMigrators)
|
||||
|
||||
builtins := map[string]job.Job{
|
||||
@ -73,6 +77,15 @@ func Start(observationCtx *observation.Context, additionalJobs map[string]job.Jo
|
||||
return errors.Wrap(err, "Failed to intialise keyring")
|
||||
}
|
||||
|
||||
if enterpriseInit != nil {
|
||||
db, err := workerdb.InitDB(observationCtx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to create database connection")
|
||||
}
|
||||
|
||||
enterpriseInit(db)
|
||||
}
|
||||
|
||||
// Start debug server
|
||||
ready := make(chan struct{})
|
||||
go debugserver.NewServerRoutine(ready).Start()
|
||||
|
||||
@ -17,6 +17,7 @@ allowed_prefix=(
|
||||
github.com/sourcegraph/sourcegraph/cmd/repo-updater
|
||||
github.com/sourcegraph/sourcegraph/cmd/migrator
|
||||
github.com/sourcegraph/sourcegraph/enterprise/cmd/frontend
|
||||
github.com/sourcegraph/sourcegraph/enterprise/cmd/gitserver
|
||||
github.com/sourcegraph/sourcegraph/enterprise/cmd/worker
|
||||
github.com/sourcegraph/sourcegraph/enterprise/cmd/repo-updater
|
||||
github.com/sourcegraph/sourcegraph/enterprise/cmd/migrator
|
||||
|
||||
@ -108,6 +108,9 @@ Available commands in `sg.config.yaml`:
|
||||
* loki
|
||||
* monitoring-generator
|
||||
* oss-frontend
|
||||
* oss-gitserver-0
|
||||
* oss-gitserver-1
|
||||
* oss-gitserver-template
|
||||
* oss-repo-updater
|
||||
* oss-symbols
|
||||
* oss-web: Open source version of the web app
|
||||
|
||||
@ -16,6 +16,7 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/enterprise/cmd/frontend/internal/authz/webhooks"
|
||||
"github.com/sourcegraph/sourcegraph/enterprise/cmd/frontend/internal/licensing/enforcement"
|
||||
eiauthz "github.com/sourcegraph/sourcegraph/enterprise/internal/authz"
|
||||
srp "github.com/sourcegraph/sourcegraph/enterprise/internal/authz/subrepoperms"
|
||||
"github.com/sourcegraph/sourcegraph/enterprise/internal/codeintel"
|
||||
edb "github.com/sourcegraph/sourcegraph/enterprise/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/enterprise/internal/licensing"
|
||||
@ -29,6 +30,7 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
"github.com/sourcegraph/sourcegraph/internal/timeutil"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
var clock = timeutil.Now
|
||||
@ -70,6 +72,12 @@ func Init(
|
||||
|
||||
enterpriseServices.PermissionsGitHubWebhook = webhooks.NewGitHubWebhook(log.Scoped("PermissionsGitHubWebhook", "permissions sync webhook handler for GitHub webhooks"))
|
||||
|
||||
var err error
|
||||
authz.DefaultSubRepoPermsChecker, err = srp.NewSubRepoPermsClient(edb.NewEnterpriseDB(db).SubRepoPerms())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to createe sub-repo client")
|
||||
}
|
||||
|
||||
// Warn about usage of authz providers that are not enabled by the license.
|
||||
graphqlbackend.AlertFuncs = append(graphqlbackend.AlertFuncs, func(args graphqlbackend.AlertFuncArgs) []*graphqlbackend.Alert {
|
||||
// Only site admins can act on this alert, so only show it to site admins.
|
||||
|
||||
@ -238,11 +238,12 @@ func (r *Resolver) SetSubRepositoryPermissionsForUsers(ctx context.Context, args
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db, err := r.db.Transact(ctx)
|
||||
ossDB, err := r.db.Transact(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "start transaction")
|
||||
}
|
||||
defer func() { err = db.Done(err) }()
|
||||
defer func() { err = ossDB.Done(err) }()
|
||||
db := edb.NewEnterpriseDB(ossDB)
|
||||
|
||||
// Make sure the repo ID is valid.
|
||||
if _, err = db.Repos().Get(ctx, repoID); err != nil {
|
||||
|
||||
@ -1335,7 +1335,7 @@ func TestResolver_SetSubRepositoryPermissionsForUsers(t *testing.T) {
|
||||
users := database.NewStrictMockUserStore()
|
||||
users.GetByCurrentAuthUserFunc.SetDefaultReturn(&types.User{}, nil)
|
||||
|
||||
subrepos := database.NewStrictMockSubRepoPermsStore()
|
||||
subrepos := edb.NewStrictMockSubRepoPermsStore()
|
||||
subrepos.UpsertFunc.SetDefaultHook(func(ctx context.Context, i int32, id api.RepoID, permissions authz.SubRepoPermissions) error {
|
||||
return nil
|
||||
})
|
||||
@ -1373,7 +1373,7 @@ func TestResolver_SetSubRepositoryPermissionsForUsers(t *testing.T) {
|
||||
}, nil
|
||||
})
|
||||
|
||||
subReposStore := database.NewStrictMockSubRepoPermsStore()
|
||||
subReposStore := edb.NewStrictMockSubRepoPermsStore()
|
||||
subReposStore.UpsertFunc.SetDefaultHook(func(ctx context.Context, i int32, id api.RepoID, permissions authz.SubRepoPermissions) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
14
enterprise/cmd/gitserver/main.go
Normal file
14
enterprise/cmd/gitserver/main.go
Normal file
@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/sourcegraph/sourcegraph/cmd/gitserver/shared"
|
||||
enterprise_shared "github.com/sourcegraph/sourcegraph/enterprise/cmd/gitserver/shared"
|
||||
"github.com/sourcegraph/sourcegraph/internal/env"
|
||||
)
|
||||
|
||||
func main() {
|
||||
env.Lock()
|
||||
env.HandleHelpFlag()
|
||||
|
||||
shared.Main(enterprise_shared.EnterpriseInit)
|
||||
}
|
||||
23
enterprise/cmd/gitserver/shared/shared.go
Normal file
23
enterprise/cmd/gitserver/shared/shared.go
Normal file
@ -0,0 +1,23 @@
|
||||
// Package shared is the enterprise gitserrver program's shared main entrypoint.
|
||||
//
|
||||
// It lets the invoker of the OSS gitserver shared entrypoint inject a few
|
||||
// proprietary things into it via e.g. blank/underscore imports in this file
|
||||
// which register side effects with the gitserver package.
|
||||
package shared
|
||||
|
||||
import (
|
||||
"github.com/sourcegraph/log"
|
||||
srp "github.com/sourcegraph/sourcegraph/enterprise/internal/authz/subrepoperms"
|
||||
edb "github.com/sourcegraph/sourcegraph/enterprise/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/authz"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
)
|
||||
|
||||
func EnterpriseInit(db database.DB) {
|
||||
logger := log.Scoped("enterprise", "gitserver enterprise edition")
|
||||
var err error
|
||||
authz.DefaultSubRepoPermsChecker, err = srp.NewSubRepoPermsClient(edb.NewEnterpriseDB(db).SubRepoPerms())
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to create sub-repo client", log.Error(err))
|
||||
}
|
||||
}
|
||||
@ -10,10 +10,12 @@ import (
|
||||
"github.com/sourcegraph/log"
|
||||
|
||||
eiauthz "github.com/sourcegraph/sourcegraph/enterprise/internal/authz"
|
||||
srp "github.com/sourcegraph/sourcegraph/enterprise/internal/authz/subrepoperms"
|
||||
"github.com/sourcegraph/sourcegraph/enterprise/internal/codeintel"
|
||||
codeintelshared "github.com/sourcegraph/sourcegraph/enterprise/internal/codeintel/shared"
|
||||
"github.com/sourcegraph/sourcegraph/enterprise/internal/codeintel/shared/lsifuploadstore"
|
||||
"github.com/sourcegraph/sourcegraph/enterprise/internal/codeintel/uploads"
|
||||
edb "github.com/sourcegraph/sourcegraph/enterprise/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/authz"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf/conftypes"
|
||||
@ -85,7 +87,7 @@ func Main() {
|
||||
|
||||
// Initialize sub-repo permissions client
|
||||
var err error
|
||||
authz.DefaultSubRepoPermsChecker, err = authz.NewSubRepoPermsClient(db.SubRepoPerms())
|
||||
authz.DefaultSubRepoPermsChecker, err = srp.NewSubRepoPermsClient(edb.NewEnterpriseDB(db).SubRepoPerms())
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to create sub-repo client", log.Error(err))
|
||||
}
|
||||
|
||||
@ -428,7 +428,7 @@ func (s *PermsSyncer) fetchUserPermsViaExternalAccounts(ctx context.Context, use
|
||||
extPerms = new(authz.ExternalUserPermissions)
|
||||
|
||||
// Load last synced sub-repo perms for this user and provider
|
||||
currentSubRepoPerms, err := s.db.SubRepoPerms().GetByUserAndService(ctx, user.ID, provider.ServiceType(), provider.ServiceID())
|
||||
currentSubRepoPerms, err := edb.NewEnterpriseDB(s.db).SubRepoPerms().GetByUserAndService(ctx, user.ID, provider.ServiceType(), provider.ServiceID())
|
||||
if err != nil {
|
||||
return results, errors.Wrap(err, "fetching existing sub-repo permissions")
|
||||
}
|
||||
@ -598,7 +598,7 @@ func (s *PermsSyncer) syncUserPerms(ctx context.Context, userID int32, noPerms b
|
||||
}
|
||||
|
||||
// Set sub-repository permissions
|
||||
srp := s.db.SubRepoPerms()
|
||||
srp := edb.NewEnterpriseDB(s.db).SubRepoPerms()
|
||||
for spec, perm := range results.subRepoPerms {
|
||||
if err := srp.UpsertWithSpec(ctx, user.ID, spec, *perm); err != nil {
|
||||
return providerStates, errors.Wrapf(err, "upserting sub repo perms %v for user %q (id: %d)", spec, user.Username, user.ID)
|
||||
|
||||
@ -304,10 +304,10 @@ func TestPermsSyncer_syncUserPermsTemporaryProviderError(t *testing.T) {
|
||||
return []*extsvc.Account{&extAccount}, nil
|
||||
})
|
||||
|
||||
subRepoPerms := database.NewMockSubRepoPermsStore()
|
||||
subRepoPerms := edb.NewMockSubRepoPermsStore()
|
||||
subRepoPerms.GetByUserAndServiceFunc.SetDefaultReturn(nil, nil)
|
||||
|
||||
db := database.NewMockDB()
|
||||
db := edb.NewMockEnterpriseDB()
|
||||
db.UsersFunc.SetDefaultReturn(users)
|
||||
db.ReposFunc.SetDefaultReturn(mockRepos)
|
||||
db.UserEmailsFunc.SetDefaultReturn(userEmails)
|
||||
@ -619,9 +619,9 @@ func TestPermsSyncer_syncUserPerms_subRepoPermissions(t *testing.T) {
|
||||
externalAccounts := database.NewMockUserExternalAccountsStore()
|
||||
externalAccounts.ListFunc.SetDefaultReturn([]*extsvc.Account{&extAccount}, nil)
|
||||
|
||||
subRepoPerms := database.NewMockSubRepoPermsStore()
|
||||
subRepoPerms := edb.NewMockSubRepoPermsStore()
|
||||
|
||||
db := database.NewMockDB()
|
||||
db := edb.NewMockEnterpriseDB()
|
||||
db.UsersFunc.SetDefaultReturn(users)
|
||||
db.ReposFunc.SetDefaultReturn(mockRepos)
|
||||
db.ExternalServicesFunc.SetDefaultReturn(externalServices)
|
||||
|
||||
@ -70,13 +70,12 @@ func startBackgroundPermsSync(ctx context.Context, syncer *authz.PermsSyncer, db
|
||||
go func() {
|
||||
t := time.NewTicker(frontendAuthz.RefreshInterval())
|
||||
for range t.C {
|
||||
allowAccessByDefault, authzProviders, _, _, _ :=
|
||||
frontendAuthz.ProvidersFromConfig(
|
||||
ctx,
|
||||
conf.Get(),
|
||||
db.ExternalServices(),
|
||||
db,
|
||||
)
|
||||
allowAccessByDefault, authzProviders, _, _, _ := frontendAuthz.ProvidersFromConfig(
|
||||
ctx,
|
||||
conf.Get(),
|
||||
db.ExternalServices(),
|
||||
db,
|
||||
)
|
||||
ossAuthz.SetProviders(allowAccessByDefault, authzProviders)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -5,13 +5,30 @@ import (
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/worker/shared"
|
||||
enterprise_shared "github.com/sourcegraph/sourcegraph/enterprise/cmd/worker/shared"
|
||||
srp "github.com/sourcegraph/sourcegraph/enterprise/internal/authz/subrepoperms"
|
||||
edb "github.com/sourcegraph/sourcegraph/enterprise/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/enterprise/internal/oobmigration/migrations"
|
||||
"github.com/sourcegraph/sourcegraph/internal/authz"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/env"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
"github.com/sourcegraph/sourcegraph/internal/oobmigration"
|
||||
"github.com/sourcegraph/sourcegraph/internal/version"
|
||||
)
|
||||
|
||||
func getEnterpriseInit(logger log.Logger) func(database.DB) {
|
||||
return func(ossDB database.DB) {
|
||||
enterpriseDB := edb.NewEnterpriseDB(ossDB)
|
||||
|
||||
var err error
|
||||
authz.DefaultSubRepoPermsChecker, err = srp.NewSubRepoPermsClient(enterpriseDB.SubRepoPerms())
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to create sub-repo client", log.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
liblog := log.Init(log.Resource{
|
||||
Name: env.MyName,
|
||||
@ -24,7 +41,7 @@ func main() {
|
||||
|
||||
go enterprise_shared.SetAuthzProviders(observationCtx)
|
||||
|
||||
if err := shared.Start(observationCtx, enterprise_shared.AdditionalJobs, migrations.RegisterEnterpriseMigrators); err != nil {
|
||||
if err := shared.Start(observationCtx, enterprise_shared.AdditionalJobs, migrations.RegisterEnterpriseMigrators, getEnterpriseInit(logger)); err != nil {
|
||||
logger.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/sourcegraph/log/logtest"
|
||||
|
||||
srp "github.com/sourcegraph/sourcegraph/enterprise/internal/authz/subrepoperms"
|
||||
"github.com/sourcegraph/sourcegraph/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/internal/api"
|
||||
"github.com/sourcegraph/sourcegraph/internal/authz"
|
||||
@ -624,7 +625,7 @@ read group Dev1 * //depot/main/.../*.go
|
||||
} else if ok && tc.noRules {
|
||||
t.Fatal("expected no rules")
|
||||
}
|
||||
checker, err := authz.NewSimpleChecker(api.RepoName(tc.depot), rules.Paths)
|
||||
checker, err := srp.NewSimpleChecker(api.RepoName(tc.depot), rules.Paths)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
429
enterprise/internal/authz/subrepoperms/mocks_temp.go
Normal file
429
enterprise/internal/authz/subrepoperms/mocks_temp.go
Normal file
@ -0,0 +1,429 @@
|
||||
// Code generated by go-mockgen 1.3.7; DO NOT EDIT.
|
||||
//
|
||||
// This file was generated by running `sg generate` (or `go-mockgen`) at the root of
|
||||
// this repository. To add additional mocks to this or another package, add a new entry
|
||||
// to the mockgen.yaml file in the root of this repository.
|
||||
|
||||
package subrepoperms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
api "github.com/sourcegraph/sourcegraph/internal/api"
|
||||
authz "github.com/sourcegraph/sourcegraph/internal/authz"
|
||||
)
|
||||
|
||||
// MockSubRepoPermissionsGetter is a mock implementation of the
|
||||
// SubRepoPermissionsGetter interface (from the package
|
||||
// github.com/sourcegraph/sourcegraph/enterprise/internal/authz/subrepoperms)
|
||||
// used for unit testing.
|
||||
type MockSubRepoPermissionsGetter struct {
|
||||
// GetByUserFunc is an instance of a mock function object controlling
|
||||
// the behavior of the method GetByUser.
|
||||
GetByUserFunc *SubRepoPermissionsGetterGetByUserFunc
|
||||
// RepoIDSupportedFunc is an instance of a mock function object
|
||||
// controlling the behavior of the method RepoIDSupported.
|
||||
RepoIDSupportedFunc *SubRepoPermissionsGetterRepoIDSupportedFunc
|
||||
// RepoSupportedFunc is an instance of a mock function object
|
||||
// controlling the behavior of the method RepoSupported.
|
||||
RepoSupportedFunc *SubRepoPermissionsGetterRepoSupportedFunc
|
||||
}
|
||||
|
||||
// NewMockSubRepoPermissionsGetter creates a new mock of the
|
||||
// SubRepoPermissionsGetter interface. All methods return zero values for
|
||||
// all results, unless overwritten.
|
||||
func NewMockSubRepoPermissionsGetter() *MockSubRepoPermissionsGetter {
|
||||
return &MockSubRepoPermissionsGetter{
|
||||
GetByUserFunc: &SubRepoPermissionsGetterGetByUserFunc{
|
||||
defaultHook: func(context.Context, int32) (r0 map[api.RepoName]authz.SubRepoPermissions, r1 error) {
|
||||
return
|
||||
},
|
||||
},
|
||||
RepoIDSupportedFunc: &SubRepoPermissionsGetterRepoIDSupportedFunc{
|
||||
defaultHook: func(context.Context, api.RepoID) (r0 bool, r1 error) {
|
||||
return
|
||||
},
|
||||
},
|
||||
RepoSupportedFunc: &SubRepoPermissionsGetterRepoSupportedFunc{
|
||||
defaultHook: func(context.Context, api.RepoName) (r0 bool, r1 error) {
|
||||
return
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewStrictMockSubRepoPermissionsGetter creates a new mock of the
|
||||
// SubRepoPermissionsGetter interface. All methods panic on invocation,
|
||||
// unless overwritten.
|
||||
func NewStrictMockSubRepoPermissionsGetter() *MockSubRepoPermissionsGetter {
|
||||
return &MockSubRepoPermissionsGetter{
|
||||
GetByUserFunc: &SubRepoPermissionsGetterGetByUserFunc{
|
||||
defaultHook: func(context.Context, int32) (map[api.RepoName]authz.SubRepoPermissions, error) {
|
||||
panic("unexpected invocation of MockSubRepoPermissionsGetter.GetByUser")
|
||||
},
|
||||
},
|
||||
RepoIDSupportedFunc: &SubRepoPermissionsGetterRepoIDSupportedFunc{
|
||||
defaultHook: func(context.Context, api.RepoID) (bool, error) {
|
||||
panic("unexpected invocation of MockSubRepoPermissionsGetter.RepoIDSupported")
|
||||
},
|
||||
},
|
||||
RepoSupportedFunc: &SubRepoPermissionsGetterRepoSupportedFunc{
|
||||
defaultHook: func(context.Context, api.RepoName) (bool, error) {
|
||||
panic("unexpected invocation of MockSubRepoPermissionsGetter.RepoSupported")
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewMockSubRepoPermissionsGetterFrom creates a new mock of the
|
||||
// MockSubRepoPermissionsGetter interface. All methods delegate to the given
|
||||
// implementation, unless overwritten.
|
||||
func NewMockSubRepoPermissionsGetterFrom(i SubRepoPermissionsGetter) *MockSubRepoPermissionsGetter {
|
||||
return &MockSubRepoPermissionsGetter{
|
||||
GetByUserFunc: &SubRepoPermissionsGetterGetByUserFunc{
|
||||
defaultHook: i.GetByUser,
|
||||
},
|
||||
RepoIDSupportedFunc: &SubRepoPermissionsGetterRepoIDSupportedFunc{
|
||||
defaultHook: i.RepoIDSupported,
|
||||
},
|
||||
RepoSupportedFunc: &SubRepoPermissionsGetterRepoSupportedFunc{
|
||||
defaultHook: i.RepoSupported,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SubRepoPermissionsGetterGetByUserFunc describes the behavior when the
|
||||
// GetByUser method of the parent MockSubRepoPermissionsGetter instance is
|
||||
// invoked.
|
||||
type SubRepoPermissionsGetterGetByUserFunc struct {
|
||||
defaultHook func(context.Context, int32) (map[api.RepoName]authz.SubRepoPermissions, error)
|
||||
hooks []func(context.Context, int32) (map[api.RepoName]authz.SubRepoPermissions, error)
|
||||
history []SubRepoPermissionsGetterGetByUserFuncCall
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// GetByUser delegates to the next hook function in the queue and stores the
|
||||
// parameter and result values of this invocation.
|
||||
func (m *MockSubRepoPermissionsGetter) GetByUser(v0 context.Context, v1 int32) (map[api.RepoName]authz.SubRepoPermissions, error) {
|
||||
r0, r1 := m.GetByUserFunc.nextHook()(v0, v1)
|
||||
m.GetByUserFunc.appendCall(SubRepoPermissionsGetterGetByUserFuncCall{v0, v1, r0, r1})
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SetDefaultHook sets function that is called when the GetByUser method of
|
||||
// the parent MockSubRepoPermissionsGetter instance is invoked and the hook
|
||||
// queue is empty.
|
||||
func (f *SubRepoPermissionsGetterGetByUserFunc) SetDefaultHook(hook func(context.Context, int32) (map[api.RepoName]authz.SubRepoPermissions, error)) {
|
||||
f.defaultHook = hook
|
||||
}
|
||||
|
||||
// PushHook adds a function to the end of hook queue. Each invocation of the
|
||||
// GetByUser method of the parent MockSubRepoPermissionsGetter instance
|
||||
// invokes the hook at the front of the queue and discards it. After the
|
||||
// queue is empty, the default hook function is invoked for any future
|
||||
// action.
|
||||
func (f *SubRepoPermissionsGetterGetByUserFunc) PushHook(hook func(context.Context, int32) (map[api.RepoName]authz.SubRepoPermissions, error)) {
|
||||
f.mutex.Lock()
|
||||
f.hooks = append(f.hooks, hook)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||
// given values.
|
||||
func (f *SubRepoPermissionsGetterGetByUserFunc) SetDefaultReturn(r0 map[api.RepoName]authz.SubRepoPermissions, r1 error) {
|
||||
f.SetDefaultHook(func(context.Context, int32) (map[api.RepoName]authz.SubRepoPermissions, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
// PushReturn calls PushHook with a function that returns the given values.
|
||||
func (f *SubRepoPermissionsGetterGetByUserFunc) PushReturn(r0 map[api.RepoName]authz.SubRepoPermissions, r1 error) {
|
||||
f.PushHook(func(context.Context, int32) (map[api.RepoName]authz.SubRepoPermissions, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
func (f *SubRepoPermissionsGetterGetByUserFunc) nextHook() func(context.Context, int32) (map[api.RepoName]authz.SubRepoPermissions, error) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
if len(f.hooks) == 0 {
|
||||
return f.defaultHook
|
||||
}
|
||||
|
||||
hook := f.hooks[0]
|
||||
f.hooks = f.hooks[1:]
|
||||
return hook
|
||||
}
|
||||
|
||||
func (f *SubRepoPermissionsGetterGetByUserFunc) appendCall(r0 SubRepoPermissionsGetterGetByUserFuncCall) {
|
||||
f.mutex.Lock()
|
||||
f.history = append(f.history, r0)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// History returns a sequence of SubRepoPermissionsGetterGetByUserFuncCall
|
||||
// objects describing the invocations of this function.
|
||||
func (f *SubRepoPermissionsGetterGetByUserFunc) History() []SubRepoPermissionsGetterGetByUserFuncCall {
|
||||
f.mutex.Lock()
|
||||
history := make([]SubRepoPermissionsGetterGetByUserFuncCall, len(f.history))
|
||||
copy(history, f.history)
|
||||
f.mutex.Unlock()
|
||||
|
||||
return history
|
||||
}
|
||||
|
||||
// SubRepoPermissionsGetterGetByUserFuncCall is an object that describes an
|
||||
// invocation of method GetByUser on an instance of
|
||||
// MockSubRepoPermissionsGetter.
|
||||
type SubRepoPermissionsGetterGetByUserFuncCall struct {
|
||||
// Arg0 is the value of the 1st argument passed to this method
|
||||
// invocation.
|
||||
Arg0 context.Context
|
||||
// Arg1 is the value of the 2nd argument passed to this method
|
||||
// invocation.
|
||||
Arg1 int32
|
||||
// Result0 is the value of the 1st result returned from this method
|
||||
// invocation.
|
||||
Result0 map[api.RepoName]authz.SubRepoPermissions
|
||||
// Result1 is the value of the 2nd result returned from this method
|
||||
// invocation.
|
||||
Result1 error
|
||||
}
|
||||
|
||||
// Args returns an interface slice containing the arguments of this
|
||||
// invocation.
|
||||
func (c SubRepoPermissionsGetterGetByUserFuncCall) Args() []interface{} {
|
||||
return []interface{}{c.Arg0, c.Arg1}
|
||||
}
|
||||
|
||||
// Results returns an interface slice containing the results of this
|
||||
// invocation.
|
||||
func (c SubRepoPermissionsGetterGetByUserFuncCall) Results() []interface{} {
|
||||
return []interface{}{c.Result0, c.Result1}
|
||||
}
|
||||
|
||||
// SubRepoPermissionsGetterRepoIDSupportedFunc describes the behavior when
|
||||
// the RepoIDSupported method of the parent MockSubRepoPermissionsGetter
|
||||
// instance is invoked.
|
||||
type SubRepoPermissionsGetterRepoIDSupportedFunc struct {
|
||||
defaultHook func(context.Context, api.RepoID) (bool, error)
|
||||
hooks []func(context.Context, api.RepoID) (bool, error)
|
||||
history []SubRepoPermissionsGetterRepoIDSupportedFuncCall
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// RepoIDSupported delegates to the next hook function in the queue and
|
||||
// stores the parameter and result values of this invocation.
|
||||
func (m *MockSubRepoPermissionsGetter) RepoIDSupported(v0 context.Context, v1 api.RepoID) (bool, error) {
|
||||
r0, r1 := m.RepoIDSupportedFunc.nextHook()(v0, v1)
|
||||
m.RepoIDSupportedFunc.appendCall(SubRepoPermissionsGetterRepoIDSupportedFuncCall{v0, v1, r0, r1})
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SetDefaultHook sets function that is called when the RepoIDSupported
|
||||
// method of the parent MockSubRepoPermissionsGetter instance is invoked and
|
||||
// the hook queue is empty.
|
||||
func (f *SubRepoPermissionsGetterRepoIDSupportedFunc) SetDefaultHook(hook func(context.Context, api.RepoID) (bool, error)) {
|
||||
f.defaultHook = hook
|
||||
}
|
||||
|
||||
// PushHook adds a function to the end of hook queue. Each invocation of the
|
||||
// RepoIDSupported method of the parent MockSubRepoPermissionsGetter
|
||||
// instance invokes the hook at the front of the queue and discards it.
|
||||
// After the queue is empty, the default hook function is invoked for any
|
||||
// future action.
|
||||
func (f *SubRepoPermissionsGetterRepoIDSupportedFunc) PushHook(hook func(context.Context, api.RepoID) (bool, error)) {
|
||||
f.mutex.Lock()
|
||||
f.hooks = append(f.hooks, hook)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||
// given values.
|
||||
func (f *SubRepoPermissionsGetterRepoIDSupportedFunc) SetDefaultReturn(r0 bool, r1 error) {
|
||||
f.SetDefaultHook(func(context.Context, api.RepoID) (bool, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
// PushReturn calls PushHook with a function that returns the given values.
|
||||
func (f *SubRepoPermissionsGetterRepoIDSupportedFunc) PushReturn(r0 bool, r1 error) {
|
||||
f.PushHook(func(context.Context, api.RepoID) (bool, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
func (f *SubRepoPermissionsGetterRepoIDSupportedFunc) nextHook() func(context.Context, api.RepoID) (bool, error) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
if len(f.hooks) == 0 {
|
||||
return f.defaultHook
|
||||
}
|
||||
|
||||
hook := f.hooks[0]
|
||||
f.hooks = f.hooks[1:]
|
||||
return hook
|
||||
}
|
||||
|
||||
func (f *SubRepoPermissionsGetterRepoIDSupportedFunc) appendCall(r0 SubRepoPermissionsGetterRepoIDSupportedFuncCall) {
|
||||
f.mutex.Lock()
|
||||
f.history = append(f.history, r0)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// History returns a sequence of
|
||||
// SubRepoPermissionsGetterRepoIDSupportedFuncCall objects describing the
|
||||
// invocations of this function.
|
||||
func (f *SubRepoPermissionsGetterRepoIDSupportedFunc) History() []SubRepoPermissionsGetterRepoIDSupportedFuncCall {
|
||||
f.mutex.Lock()
|
||||
history := make([]SubRepoPermissionsGetterRepoIDSupportedFuncCall, len(f.history))
|
||||
copy(history, f.history)
|
||||
f.mutex.Unlock()
|
||||
|
||||
return history
|
||||
}
|
||||
|
||||
// SubRepoPermissionsGetterRepoIDSupportedFuncCall is an object that
|
||||
// describes an invocation of method RepoIDSupported on an instance of
|
||||
// MockSubRepoPermissionsGetter.
|
||||
type SubRepoPermissionsGetterRepoIDSupportedFuncCall struct {
|
||||
// Arg0 is the value of the 1st argument passed to this method
|
||||
// invocation.
|
||||
Arg0 context.Context
|
||||
// Arg1 is the value of the 2nd argument passed to this method
|
||||
// invocation.
|
||||
Arg1 api.RepoID
|
||||
// Result0 is the value of the 1st result returned from this method
|
||||
// invocation.
|
||||
Result0 bool
|
||||
// Result1 is the value of the 2nd result returned from this method
|
||||
// invocation.
|
||||
Result1 error
|
||||
}
|
||||
|
||||
// Args returns an interface slice containing the arguments of this
|
||||
// invocation.
|
||||
func (c SubRepoPermissionsGetterRepoIDSupportedFuncCall) Args() []interface{} {
|
||||
return []interface{}{c.Arg0, c.Arg1}
|
||||
}
|
||||
|
||||
// Results returns an interface slice containing the results of this
|
||||
// invocation.
|
||||
func (c SubRepoPermissionsGetterRepoIDSupportedFuncCall) Results() []interface{} {
|
||||
return []interface{}{c.Result0, c.Result1}
|
||||
}
|
||||
|
||||
// SubRepoPermissionsGetterRepoSupportedFunc describes the behavior when the
|
||||
// RepoSupported method of the parent MockSubRepoPermissionsGetter instance
|
||||
// is invoked.
|
||||
type SubRepoPermissionsGetterRepoSupportedFunc struct {
|
||||
defaultHook func(context.Context, api.RepoName) (bool, error)
|
||||
hooks []func(context.Context, api.RepoName) (bool, error)
|
||||
history []SubRepoPermissionsGetterRepoSupportedFuncCall
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// RepoSupported delegates to the next hook function in the queue and stores
|
||||
// the parameter and result values of this invocation.
|
||||
func (m *MockSubRepoPermissionsGetter) RepoSupported(v0 context.Context, v1 api.RepoName) (bool, error) {
|
||||
r0, r1 := m.RepoSupportedFunc.nextHook()(v0, v1)
|
||||
m.RepoSupportedFunc.appendCall(SubRepoPermissionsGetterRepoSupportedFuncCall{v0, v1, r0, r1})
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SetDefaultHook sets function that is called when the RepoSupported method
|
||||
// of the parent MockSubRepoPermissionsGetter instance is invoked and the
|
||||
// hook queue is empty.
|
||||
func (f *SubRepoPermissionsGetterRepoSupportedFunc) SetDefaultHook(hook func(context.Context, api.RepoName) (bool, error)) {
|
||||
f.defaultHook = hook
|
||||
}
|
||||
|
||||
// PushHook adds a function to the end of hook queue. Each invocation of the
|
||||
// RepoSupported method of the parent MockSubRepoPermissionsGetter instance
|
||||
// invokes the hook at the front of the queue and discards it. After the
|
||||
// queue is empty, the default hook function is invoked for any future
|
||||
// action.
|
||||
func (f *SubRepoPermissionsGetterRepoSupportedFunc) PushHook(hook func(context.Context, api.RepoName) (bool, error)) {
|
||||
f.mutex.Lock()
|
||||
f.hooks = append(f.hooks, hook)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||
// given values.
|
||||
func (f *SubRepoPermissionsGetterRepoSupportedFunc) SetDefaultReturn(r0 bool, r1 error) {
|
||||
f.SetDefaultHook(func(context.Context, api.RepoName) (bool, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
// PushReturn calls PushHook with a function that returns the given values.
|
||||
func (f *SubRepoPermissionsGetterRepoSupportedFunc) PushReturn(r0 bool, r1 error) {
|
||||
f.PushHook(func(context.Context, api.RepoName) (bool, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
func (f *SubRepoPermissionsGetterRepoSupportedFunc) nextHook() func(context.Context, api.RepoName) (bool, error) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
if len(f.hooks) == 0 {
|
||||
return f.defaultHook
|
||||
}
|
||||
|
||||
hook := f.hooks[0]
|
||||
f.hooks = f.hooks[1:]
|
||||
return hook
|
||||
}
|
||||
|
||||
func (f *SubRepoPermissionsGetterRepoSupportedFunc) appendCall(r0 SubRepoPermissionsGetterRepoSupportedFuncCall) {
|
||||
f.mutex.Lock()
|
||||
f.history = append(f.history, r0)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// History returns a sequence of
|
||||
// SubRepoPermissionsGetterRepoSupportedFuncCall objects describing the
|
||||
// invocations of this function.
|
||||
func (f *SubRepoPermissionsGetterRepoSupportedFunc) History() []SubRepoPermissionsGetterRepoSupportedFuncCall {
|
||||
f.mutex.Lock()
|
||||
history := make([]SubRepoPermissionsGetterRepoSupportedFuncCall, len(f.history))
|
||||
copy(history, f.history)
|
||||
f.mutex.Unlock()
|
||||
|
||||
return history
|
||||
}
|
||||
|
||||
// SubRepoPermissionsGetterRepoSupportedFuncCall is an object that describes
|
||||
// an invocation of method RepoSupported on an instance of
|
||||
// MockSubRepoPermissionsGetter.
|
||||
type SubRepoPermissionsGetterRepoSupportedFuncCall struct {
|
||||
// Arg0 is the value of the 1st argument passed to this method
|
||||
// invocation.
|
||||
Arg0 context.Context
|
||||
// Arg1 is the value of the 2nd argument passed to this method
|
||||
// invocation.
|
||||
Arg1 api.RepoName
|
||||
// Result0 is the value of the 1st result returned from this method
|
||||
// invocation.
|
||||
Result0 bool
|
||||
// Result1 is the value of the 2nd result returned from this method
|
||||
// invocation.
|
||||
Result1 error
|
||||
}
|
||||
|
||||
// Args returns an interface slice containing the arguments of this
|
||||
// invocation.
|
||||
func (c SubRepoPermissionsGetterRepoSupportedFuncCall) Args() []interface{} {
|
||||
return []interface{}{c.Arg0, c.Arg1}
|
||||
}
|
||||
|
||||
// Results returns an interface slice containing the results of this
|
||||
// invocation.
|
||||
func (c SubRepoPermissionsGetterRepoSupportedFuncCall) Results() []interface{} {
|
||||
return []interface{}{c.Result0, c.Result1}
|
||||
}
|
||||
401
enterprise/internal/authz/subrepoperms/sub_repo_perms.go
Normal file
401
enterprise/internal/authz/subrepoperms/sub_repo_perms.go
Normal file
@ -0,0 +1,401 @@
|
||||
package subrepoperms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"go.uber.org/atomic"
|
||||
"golang.org/x/sync/singleflight"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/api"
|
||||
"github.com/sourcegraph/sourcegraph/internal/authz"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
// SubRepoPermissionsGetter allows getting sub repository permissions.
|
||||
type SubRepoPermissionsGetter interface {
|
||||
// GetByUser returns the sub repository permissions rules known for a user.
|
||||
GetByUser(ctx context.Context, userID int32) (map[api.RepoName]authz.SubRepoPermissions, error)
|
||||
|
||||
// RepoIDSupported returns true if repo with the given ID has sub-repo permissions.
|
||||
RepoIDSupported(ctx context.Context, repoID api.RepoID) (bool, error)
|
||||
|
||||
// RepoSupported returns true if repo with the given name has sub-repo permissions.
|
||||
RepoSupported(ctx context.Context, repo api.RepoName) (bool, error)
|
||||
}
|
||||
|
||||
// SubRepoPermsClient is a concrete implementation of SubRepoPermissionChecker.
|
||||
// Always use NewSubRepoPermsClient to instantiate an instance.
|
||||
type SubRepoPermsClient struct {
|
||||
permissionsGetter SubRepoPermissionsGetter
|
||||
clock func() time.Time
|
||||
since func(time.Time) time.Duration
|
||||
|
||||
group *singleflight.Group
|
||||
cache *lru.Cache
|
||||
enabled *atomic.Bool
|
||||
}
|
||||
|
||||
const (
|
||||
defaultCacheSize = 1000
|
||||
defaultCacheTTL = 10 * time.Second
|
||||
)
|
||||
|
||||
// cachedRules caches the perms rules known for a particular user by repo.
|
||||
type cachedRules struct {
|
||||
rules map[api.RepoName]compiledRules
|
||||
timestamp time.Time
|
||||
}
|
||||
|
||||
type path struct {
|
||||
globPath glob.Glob
|
||||
exclusion bool
|
||||
// the original rule before it was compiled into a glob matcher
|
||||
original string
|
||||
}
|
||||
|
||||
type compiledRules struct {
|
||||
paths []path
|
||||
}
|
||||
|
||||
// GetPermissionsForPath tries to match a given path to a list of rules.
|
||||
// Since the last applicable rule is the one that applies, the list is
|
||||
// traversed in reverse, and the function returns as soon as a match is found.
|
||||
// If no match is found, None is returned.
|
||||
func (rules compiledRules) GetPermissionsForPath(path string) authz.Perms {
|
||||
for i := len(rules.paths) - 1; i >= 0; i-- {
|
||||
if rules.paths[i].globPath.Match(path) {
|
||||
if rules.paths[i].exclusion {
|
||||
return authz.None
|
||||
}
|
||||
return authz.Read
|
||||
}
|
||||
}
|
||||
|
||||
// Return None if no rule matches
|
||||
return authz.None
|
||||
}
|
||||
|
||||
// NewSubRepoPermsClient instantiates an instance of authz.SubRepoPermsClient
|
||||
// which implements SubRepoPermissionChecker.
|
||||
//
|
||||
// SubRepoPermissionChecker is responsible for checking whether a user has access
|
||||
// to data within a repo. Sub-repository permissions enforcement is on top of
|
||||
// existing repository permissions, which means the user must already have access
|
||||
// to the repository itself. The intention is for this client to be created once
|
||||
// at startup and passed in to all places that need to check sub repo
|
||||
// permissions.
|
||||
//
|
||||
// Note that sub-repo permissions are currently opt-in via the
|
||||
// experimentalFeatures.enableSubRepoPermissions option.
|
||||
func NewSubRepoPermsClient(permissionsGetter SubRepoPermissionsGetter) (*SubRepoPermsClient, error) {
|
||||
cache, err := lru.New(defaultCacheSize)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating LRU cache")
|
||||
}
|
||||
|
||||
enabled := atomic.NewBool(false)
|
||||
|
||||
conf.Watch(func() {
|
||||
c := conf.Get()
|
||||
if c.ExperimentalFeatures == nil || c.ExperimentalFeatures.SubRepoPermissions == nil {
|
||||
enabled.Store(false)
|
||||
return
|
||||
}
|
||||
|
||||
cacheSize := c.ExperimentalFeatures.SubRepoPermissions.UserCacheSize
|
||||
if cacheSize == 0 {
|
||||
cacheSize = defaultCacheSize
|
||||
}
|
||||
cache.Resize(cacheSize)
|
||||
enabled.Store(c.ExperimentalFeatures.SubRepoPermissions.Enabled)
|
||||
})
|
||||
|
||||
return &SubRepoPermsClient{
|
||||
permissionsGetter: permissionsGetter,
|
||||
clock: time.Now,
|
||||
since: time.Since,
|
||||
group: &singleflight.Group{},
|
||||
cache: cache,
|
||||
enabled: enabled,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
metricSubRepoPermsPermissionsDurationSuccess prometheus.Observer
|
||||
metricSubRepoPermsPermissionsDurationError prometheus.Observer
|
||||
)
|
||||
|
||||
func init() {
|
||||
// We cache the result of WithLabelValues since we call them in
|
||||
// performance sensitive code. See BenchmarkFilterActorPaths.
|
||||
metric := promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "authz_sub_repo_perms_permissions_duration_seconds",
|
||||
Help: "Time spent calculating permissions of a file for an actor.",
|
||||
}, []string{"error"})
|
||||
metricSubRepoPermsPermissionsDurationSuccess = metric.WithLabelValues("false")
|
||||
metricSubRepoPermsPermissionsDurationError = metric.WithLabelValues("true")
|
||||
}
|
||||
|
||||
var (
|
||||
metricSubRepoPermCacheHit prometheus.Counter
|
||||
metricSubRepoPermCacheMiss prometheus.Counter
|
||||
)
|
||||
|
||||
func init() {
|
||||
// We cache the result of WithLabelValues since we call them in
|
||||
// performance sensitive code. See BenchmarkFilterActorPaths.
|
||||
metric := promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "authz_sub_repo_perms_permissions_cache_count",
|
||||
Help: "The number of sub-repo perms cache hits or misses",
|
||||
}, []string{"hit"})
|
||||
metricSubRepoPermCacheHit = metric.WithLabelValues("true")
|
||||
metricSubRepoPermCacheMiss = metric.WithLabelValues("false")
|
||||
}
|
||||
|
||||
// Permissions return the current permissions granted to the given user on the
|
||||
// given content. If sub-repo permissions are disabled, it is a no-op that return
|
||||
// Read.
|
||||
func (s *SubRepoPermsClient) Permissions(ctx context.Context, userID int32, content authz.RepoContent) (perms authz.Perms, err error) {
|
||||
// Are sub-repo permissions enabled at the site level
|
||||
if !s.Enabled() {
|
||||
return authz.Read, nil
|
||||
}
|
||||
|
||||
began := time.Now()
|
||||
defer func() {
|
||||
took := time.Since(began).Seconds()
|
||||
if err == nil {
|
||||
metricSubRepoPermsPermissionsDurationSuccess.Observe(took)
|
||||
} else {
|
||||
metricSubRepoPermsPermissionsDurationError.Observe(took)
|
||||
}
|
||||
}()
|
||||
|
||||
f, err := s.FilePermissionsFunc(ctx, userID, content.Repo)
|
||||
if err != nil {
|
||||
return authz.None, err
|
||||
}
|
||||
return f(content.Path)
|
||||
}
|
||||
|
||||
// filePermissionsFuncAllRead is a FilePermissionFunc which _always_ returns
|
||||
// Read. Only use in cases that sub repo permission checks should not be done.
|
||||
func filePermissionsFuncAllRead(_ string) (authz.Perms, error) {
|
||||
return authz.Read, nil
|
||||
}
|
||||
|
||||
func (s *SubRepoPermsClient) FilePermissionsFunc(ctx context.Context, userID int32, repo api.RepoName) (authz.FilePermissionFunc, error) {
|
||||
// Are sub-repo permissions enabled at the site level
|
||||
if !s.Enabled() {
|
||||
return filePermissionsFuncAllRead, nil
|
||||
}
|
||||
|
||||
if s.permissionsGetter == nil {
|
||||
return nil, errors.New("permissionsGetter is nil")
|
||||
}
|
||||
|
||||
if userID == 0 {
|
||||
return nil, &authz.ErrUnauthenticated{}
|
||||
}
|
||||
|
||||
repoRules, err := s.getCompiledRules(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "compiling match rules")
|
||||
}
|
||||
|
||||
rules, rulesExist := repoRules[repo]
|
||||
if !rulesExist {
|
||||
// If we make it this far it implies that we have access at the repo level.
|
||||
// Having any empty set of rules here implies that we can access the whole repo.
|
||||
// Repos that support sub-repo permissions will only have an entry in our
|
||||
// repo_permissions table after all sub-repo permissions have been processed.
|
||||
return filePermissionsFuncAllRead, nil
|
||||
}
|
||||
|
||||
return func(path string) (authz.Perms, error) {
|
||||
// An empty path is equivalent to repo permissions so we can assume it has
|
||||
// already been checked at that level.
|
||||
if path == "" {
|
||||
return authz.Read, nil
|
||||
}
|
||||
|
||||
// Prefix path with "/", otherwise suffix rules like "**/file.txt" won't match
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
|
||||
// Iterate through all rules for the current path, and the final match takes
|
||||
// preference.
|
||||
return rules.GetPermissionsForPath(path), nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getCompiledRules fetches rules for the given repo with caching.
|
||||
func (s *SubRepoPermsClient) getCompiledRules(ctx context.Context, userID int32) (map[api.RepoName]compiledRules, error) {
|
||||
// Fast path for cached rules
|
||||
item, _ := s.cache.Get(userID)
|
||||
cached, ok := item.(cachedRules)
|
||||
|
||||
ttl := defaultCacheTTL
|
||||
if c := conf.Get(); c.ExperimentalFeatures != nil && c.ExperimentalFeatures.SubRepoPermissions != nil && c.ExperimentalFeatures.SubRepoPermissions.UserCacheTTLSeconds > 0 {
|
||||
ttl = time.Duration(c.ExperimentalFeatures.SubRepoPermissions.UserCacheTTLSeconds) * time.Second
|
||||
}
|
||||
|
||||
if ok && s.since(cached.timestamp) <= ttl {
|
||||
metricSubRepoPermCacheHit.Inc()
|
||||
return cached.rules, nil
|
||||
}
|
||||
metricSubRepoPermCacheMiss.Inc()
|
||||
|
||||
// Slow path on cache miss or expiry. Ensure that only one goroutine is doing the
|
||||
// work
|
||||
groupKey := strconv.FormatInt(int64(userID), 10)
|
||||
result, err, _ := s.group.Do(groupKey, func() (any, error) {
|
||||
repoPerms, err := s.permissionsGetter.GetByUser(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching rules")
|
||||
}
|
||||
toCache := cachedRules{
|
||||
rules: make(map[api.RepoName]compiledRules, len(repoPerms)),
|
||||
}
|
||||
for repo, perms := range repoPerms {
|
||||
paths := make([]path, 0, len(perms.Paths))
|
||||
for _, rule := range perms.Paths {
|
||||
exclusion := strings.HasPrefix(rule, "-")
|
||||
rule = strings.TrimPrefix(rule, "-")
|
||||
|
||||
if !strings.HasPrefix(rule, "/") {
|
||||
rule = "/" + rule
|
||||
}
|
||||
|
||||
g, err := glob.Compile(rule, '/')
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "building include matcher")
|
||||
}
|
||||
|
||||
paths = append(paths, path{globPath: g, exclusion: exclusion, original: rule})
|
||||
|
||||
// Special case. Our glob package does not handle rules starting with a double
|
||||
// wildcard correctly. For example, we would expect `/**/*.java` to match all
|
||||
// java files, but it does not match files at the root, eg `/foo.java`. To get
|
||||
// around this we add an extra rule to cover this case.
|
||||
if strings.HasPrefix(rule, "/**/") {
|
||||
trimmed := rule
|
||||
for {
|
||||
trimmed = strings.TrimPrefix(trimmed, "/**")
|
||||
if strings.HasPrefix(trimmed, "/**/") {
|
||||
// Keep trimming
|
||||
continue
|
||||
}
|
||||
g, err := glob.Compile(trimmed, '/')
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "building include matcher")
|
||||
}
|
||||
paths = append(paths, path{globPath: g, exclusion: exclusion, original: trimmed})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// We should include all directories above an include rule so that we can browse
|
||||
// to the included items.
|
||||
if exclusion {
|
||||
// Not required for an exclude rule
|
||||
continue
|
||||
}
|
||||
|
||||
dirs := expandDirs(rule)
|
||||
for _, dir := range dirs {
|
||||
g, err := glob.Compile(dir, '/')
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "building include matcher for dir")
|
||||
}
|
||||
paths = append(paths, path{globPath: g, exclusion: false, original: dir})
|
||||
}
|
||||
}
|
||||
|
||||
toCache.rules[repo] = compiledRules{
|
||||
paths: paths,
|
||||
}
|
||||
}
|
||||
toCache.timestamp = s.clock()
|
||||
s.cache.Add(userID, toCache)
|
||||
return toCache.rules, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
compiled := result.(map[api.RepoName]compiledRules)
|
||||
return compiled, nil
|
||||
}
|
||||
|
||||
func (s *SubRepoPermsClient) Enabled() bool {
|
||||
return s.enabled.Load()
|
||||
}
|
||||
|
||||
func (s *SubRepoPermsClient) EnabledForRepoID(ctx context.Context, id api.RepoID) (bool, error) {
|
||||
return s.permissionsGetter.RepoIDSupported(ctx, id)
|
||||
}
|
||||
|
||||
func (s *SubRepoPermsClient) EnabledForRepo(ctx context.Context, repo api.RepoName) (bool, error) {
|
||||
return s.permissionsGetter.RepoSupported(ctx, repo)
|
||||
}
|
||||
|
||||
// expandDirs will return a new set of rules that will match all directories
|
||||
// above the supplied rule. As a special case, if the rule starts with a wildcard
|
||||
// we return a rule to match all directories.
|
||||
func expandDirs(rule string) []string {
|
||||
dirs := make([]string, 0)
|
||||
|
||||
// Make sure the rule starts with a slash
|
||||
if !strings.HasPrefix(rule, "/") {
|
||||
rule = "/" + rule
|
||||
}
|
||||
|
||||
// If a rule starts with a wildcard it can match at any level in the tree
|
||||
// structure so there's no way of walking up the tree and expand out to the list
|
||||
// of valid directories. Instead, we just return a rule that matches any
|
||||
// directory
|
||||
if strings.HasPrefix(rule, "/*") {
|
||||
dirs = append(dirs, "**/")
|
||||
return dirs
|
||||
}
|
||||
|
||||
for {
|
||||
lastSlash := strings.LastIndex(rule, "/")
|
||||
if lastSlash <= 0 { // we have to ignore the slash at index 0
|
||||
break
|
||||
}
|
||||
// Drop anything after the last slash
|
||||
rule = rule[:lastSlash]
|
||||
|
||||
dirs = append(dirs, rule+"/")
|
||||
}
|
||||
|
||||
return dirs
|
||||
}
|
||||
|
||||
// NewSimpleChecker is exposed for testing and allows creation of a simple
|
||||
// checker based on the rules provided. The rules are expected to be in glob
|
||||
// format.
|
||||
func NewSimpleChecker(repo api.RepoName, paths []string) (authz.SubRepoPermissionChecker, error) {
|
||||
getter := NewMockSubRepoPermissionsGetter()
|
||||
getter.GetByUserFunc.SetDefaultHook(func(ctx context.Context, i int32) (map[api.RepoName]authz.SubRepoPermissions, error) {
|
||||
return map[api.RepoName]authz.SubRepoPermissions{
|
||||
repo: {
|
||||
Paths: paths,
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
getter.RepoSupportedFunc.SetDefaultReturn(true, nil)
|
||||
getter.RepoIDSupportedFunc.SetDefaultReturn(true, nil)
|
||||
return NewSubRepoPermsClient(getter)
|
||||
}
|
||||
430
enterprise/internal/authz/subrepoperms/sub_repo_perms_test.go
Normal file
430
enterprise/internal/authz/subrepoperms/sub_repo_perms_test.go
Normal file
@ -0,0 +1,430 @@
|
||||
package subrepoperms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/internal/api"
|
||||
"github.com/sourcegraph/sourcegraph/internal/authz"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/schema"
|
||||
)
|
||||
|
||||
func TestSubRepoPermsPermissions(t *testing.T) {
|
||||
conf.Mock(&conf.Unified{
|
||||
SiteConfiguration: schema.SiteConfiguration{
|
||||
ExperimentalFeatures: &schema.ExperimentalFeatures{
|
||||
SubRepoPermissions: &schema.SubRepoPermissions{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
t.Cleanup(func() { conf.Mock(nil) })
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
userID int32
|
||||
content authz.RepoContent
|
||||
clientFn func() (*SubRepoPermsClient, error)
|
||||
want authz.Perms
|
||||
}{
|
||||
{
|
||||
name: "Empty path",
|
||||
userID: 1,
|
||||
content: authz.RepoContent{
|
||||
Repo: "sample",
|
||||
Path: "",
|
||||
},
|
||||
clientFn: func() (*SubRepoPermsClient, error) {
|
||||
return NewSubRepoPermsClient(NewMockSubRepoPermissionsGetter())
|
||||
},
|
||||
want: authz.Read,
|
||||
},
|
||||
{
|
||||
name: "No rules",
|
||||
userID: 1,
|
||||
content: authz.RepoContent{
|
||||
Repo: "sample",
|
||||
Path: "/dev/thing",
|
||||
},
|
||||
clientFn: func() (*SubRepoPermsClient, error) {
|
||||
getter := NewMockSubRepoPermissionsGetter()
|
||||
getter.GetByUserFunc.SetDefaultHook(func(ctx context.Context, i int32) (map[api.RepoName]authz.SubRepoPermissions, error) {
|
||||
return map[api.RepoName]authz.SubRepoPermissions{
|
||||
"sample": {
|
||||
Paths: []string{},
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
return NewSubRepoPermsClient(getter)
|
||||
},
|
||||
want: authz.None,
|
||||
},
|
||||
{
|
||||
name: "Exclude",
|
||||
userID: 1,
|
||||
content: authz.RepoContent{
|
||||
Repo: "sample",
|
||||
Path: "/dev/thing",
|
||||
},
|
||||
clientFn: func() (*SubRepoPermsClient, error) {
|
||||
getter := NewMockSubRepoPermissionsGetter()
|
||||
getter.GetByUserFunc.SetDefaultHook(func(ctx context.Context, i int32) (map[api.RepoName]authz.SubRepoPermissions, error) {
|
||||
return map[api.RepoName]authz.SubRepoPermissions{
|
||||
"sample": {
|
||||
Paths: []string{"-/dev/*"},
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
return NewSubRepoPermsClient(getter)
|
||||
},
|
||||
want: authz.None,
|
||||
},
|
||||
{
|
||||
name: "Include",
|
||||
userID: 1,
|
||||
content: authz.RepoContent{
|
||||
Repo: "sample",
|
||||
Path: "/dev/thing",
|
||||
},
|
||||
clientFn: func() (*SubRepoPermsClient, error) {
|
||||
getter := NewMockSubRepoPermissionsGetter()
|
||||
getter.GetByUserFunc.SetDefaultHook(func(ctx context.Context, i int32) (map[api.RepoName]authz.SubRepoPermissions, error) {
|
||||
return map[api.RepoName]authz.SubRepoPermissions{
|
||||
"sample": {
|
||||
Paths: []string{"/*"},
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
return NewSubRepoPermsClient(getter)
|
||||
},
|
||||
want: authz.None,
|
||||
},
|
||||
{
|
||||
name: "Last rule takes precedence (exclude)",
|
||||
userID: 1,
|
||||
content: authz.RepoContent{
|
||||
Repo: "sample",
|
||||
Path: "/dev/thing",
|
||||
},
|
||||
clientFn: func() (*SubRepoPermsClient, error) {
|
||||
getter := NewMockSubRepoPermissionsGetter()
|
||||
getter.GetByUserFunc.SetDefaultHook(func(ctx context.Context, i int32) (map[api.RepoName]authz.SubRepoPermissions, error) {
|
||||
return map[api.RepoName]authz.SubRepoPermissions{
|
||||
"sample": {
|
||||
Paths: []string{"/**", "-/dev/*"},
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
return NewSubRepoPermsClient(getter)
|
||||
},
|
||||
want: authz.None,
|
||||
},
|
||||
{
|
||||
name: "Last rule takes precedence (include)",
|
||||
userID: 1,
|
||||
content: authz.RepoContent{
|
||||
Repo: "sample",
|
||||
Path: "/dev/thing",
|
||||
},
|
||||
clientFn: func() (*SubRepoPermsClient, error) {
|
||||
getter := NewMockSubRepoPermissionsGetter()
|
||||
getter.GetByUserFunc.SetDefaultHook(func(ctx context.Context, i int32) (map[api.RepoName]authz.SubRepoPermissions, error) {
|
||||
return map[api.RepoName]authz.SubRepoPermissions{
|
||||
"sample": {
|
||||
Paths: []string{"-/dev/*", "/**"},
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
return NewSubRepoPermsClient(getter)
|
||||
},
|
||||
want: authz.Read,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
client, err := tc.clientFn()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
have, err := client.Permissions(context.Background(), tc.userID, tc.content)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if have != tc.want {
|
||||
t.Fatalf("have %v, want %v", have, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFilterActorPaths(b *testing.B) {
|
||||
// This benchmark is simulating the code path taken by a monorepo with sub
|
||||
// repo permissions. Our goal is to support repos with millions of files.
|
||||
// For now we target a lower number since large numbers don't give enough
|
||||
// runs of the benchmark to be useful.
|
||||
const pathCount = 5_000
|
||||
pathPatterns := []string{
|
||||
"base/%d/foo.go",
|
||||
"%d/stuff/baz",
|
||||
"frontend/%d/stuff/baz/bam",
|
||||
"subdir/sub/sub/sub/%d",
|
||||
"%d/foo/README.md",
|
||||
"subdir/remove/me/please/%d",
|
||||
"subdir/%d/also-remove/me/please",
|
||||
"a/deep/path/%d/.secrets.env",
|
||||
"%d/does/not/match/anything",
|
||||
"does/%d/not/match/anything",
|
||||
"does/not/%d/match/anything",
|
||||
"does/not/match/%d/anything",
|
||||
"does/not/match/anything/%d",
|
||||
}
|
||||
paths := []string{
|
||||
"config.yaml",
|
||||
"dir.yaml",
|
||||
}
|
||||
for i := 0; len(paths) < pathCount; i++ {
|
||||
for _, pat := range pathPatterns {
|
||||
paths = append(paths, fmt.Sprintf(pat, i))
|
||||
}
|
||||
}
|
||||
paths = paths[:pathCount]
|
||||
sort.Strings(paths)
|
||||
|
||||
conf.Mock(&conf.Unified{
|
||||
SiteConfiguration: schema.SiteConfiguration{
|
||||
ExperimentalFeatures: &schema.ExperimentalFeatures{
|
||||
SubRepoPermissions: &schema.SubRepoPermissions{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
defer conf.Mock(nil)
|
||||
repo := api.RepoName("repo")
|
||||
|
||||
getter := NewMockSubRepoPermissionsGetter()
|
||||
getter.GetByUserFunc.SetDefaultHook(func(ctx context.Context, i int32) (map[api.RepoName]authz.SubRepoPermissions, error) {
|
||||
return map[api.RepoName]authz.SubRepoPermissions{
|
||||
repo: {
|
||||
Paths: []string{
|
||||
"/base/**",
|
||||
"/*/stuff/**",
|
||||
"/frontend/**/stuff/*",
|
||||
"/config.yaml",
|
||||
"/subdir/**",
|
||||
"/**/README.md",
|
||||
"/dir.yaml",
|
||||
"-/subdir/remove/",
|
||||
"-/subdir/*/also-remove/**",
|
||||
"-/**/.secrets.env",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
checker, err := NewSubRepoPermsClient(getter)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
a := &actor.Actor{
|
||||
UID: 1,
|
||||
}
|
||||
ctx := actor.WithActor(context.Background(), a)
|
||||
|
||||
b.ResetTimer()
|
||||
start := time.Now()
|
||||
|
||||
for n := 0; n <= b.N; n++ {
|
||||
filtered, err := authz.FilterActorPaths(ctx, checker, a, repo, paths)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if len(filtered) == 0 {
|
||||
b.Fatal("expected paths to be returned")
|
||||
}
|
||||
if len(filtered) == len(paths) {
|
||||
b.Fatal("expected to filter out some paths")
|
||||
}
|
||||
}
|
||||
|
||||
b.ReportMetric(float64(len(paths))*float64(b.N)/time.Since(start).Seconds(), "paths/s")
|
||||
}
|
||||
|
||||
func TestSubRepoPermissionsCanReadDirectoriesInPath(t *testing.T) {
|
||||
conf.Mock(&conf.Unified{
|
||||
SiteConfiguration: schema.SiteConfiguration{
|
||||
ExperimentalFeatures: &schema.ExperimentalFeatures{
|
||||
SubRepoPermissions: &schema.SubRepoPermissions{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
t.Cleanup(func() { conf.Mock(nil) })
|
||||
repoName := api.RepoName("repo")
|
||||
|
||||
testCases := []struct {
|
||||
paths []string
|
||||
canReadAll []string
|
||||
cannotReadAny []string
|
||||
}{
|
||||
{
|
||||
paths: []string{"foo/bar/thing.txt"},
|
||||
canReadAll: []string{"foo/", "foo/bar/"},
|
||||
cannotReadAny: []string{"foo/thing.txt", "foo/bar/other.txt"},
|
||||
},
|
||||
{
|
||||
paths: []string{"foo/bar/**"},
|
||||
canReadAll: []string{"foo/", "foo/bar/", "foo/bar/baz/", "foo/bar/baz/fox/"},
|
||||
},
|
||||
{
|
||||
paths: []string{"foo/bar/"},
|
||||
canReadAll: []string{"foo/", "foo/bar/"},
|
||||
cannotReadAny: []string{"foo/thing.txt", "foo/bar/thing.txt"},
|
||||
},
|
||||
{
|
||||
paths: []string{"baz/*/foo/bar/thing.txt"},
|
||||
canReadAll: []string{"baz/", "baz/x/", "baz/x/foo/bar/"},
|
||||
cannotReadAny: []string{"baz/thing.txt"},
|
||||
},
|
||||
// If we have a wildcard in a path we allow all directories that are not
|
||||
// explicitly excluded.
|
||||
{
|
||||
paths: []string{"**/foo/bar/thing.txt"},
|
||||
canReadAll: []string{"foo/", "foo/bar/"},
|
||||
},
|
||||
{
|
||||
paths: []string{"*/foo/bar/thing.txt"},
|
||||
canReadAll: []string{"foo/", "foo/bar/"},
|
||||
},
|
||||
{
|
||||
paths: []string{"/**/foo/bar/thing.txt"},
|
||||
canReadAll: []string{"foo/", "foo/bar/"},
|
||||
},
|
||||
{
|
||||
paths: []string{"/*/foo/bar/thing.txt"},
|
||||
canReadAll: []string{"foo/", "foo/bar/"},
|
||||
},
|
||||
{
|
||||
paths: []string{"-/**", "/storage/redis/**"},
|
||||
canReadAll: []string{"storage/", "/storage/", "/storage/redis/"},
|
||||
},
|
||||
{
|
||||
paths: []string{"-/**", "-/storage/**", "/storage/redis/**"},
|
||||
canReadAll: []string{"storage/", "/storage/", "/storage/redis/"},
|
||||
},
|
||||
// Even with a wildcard include rule, we should still exclude directories that
|
||||
// are explicitly excluded later
|
||||
{
|
||||
paths: []string{"/**", "-/storage/**"},
|
||||
canReadAll: []string{"/foo"},
|
||||
cannotReadAny: []string{"storage/", "/storage/", "/storage/redis/"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
getter := NewMockSubRepoPermissionsGetter()
|
||||
getter.GetByUserFunc.SetDefaultHook(func(ctx context.Context, i int32) (map[api.RepoName]authz.SubRepoPermissions, error) {
|
||||
return map[api.RepoName]authz.SubRepoPermissions{
|
||||
repoName: {
|
||||
Paths: tc.paths,
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
client, err := NewSubRepoPermsClient(getter)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
for _, path := range tc.canReadAll {
|
||||
content := authz.RepoContent{
|
||||
Repo: repoName,
|
||||
Path: path,
|
||||
}
|
||||
perm, err := client.Permissions(ctx, 1, content)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !perm.Include(authz.Read) {
|
||||
t.Errorf("Should be able to read %q, cannot", path)
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range tc.cannotReadAny {
|
||||
content := authz.RepoContent{
|
||||
Repo: repoName,
|
||||
Path: path,
|
||||
}
|
||||
perm, err := client.Permissions(ctx, 1, content)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if perm.Include(authz.Read) {
|
||||
t.Errorf("Should not be able to read %q, can", path)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubRepoPermsPermissionsCache(t *testing.T) {
|
||||
conf.Mock(&conf.Unified{
|
||||
SiteConfiguration: schema.SiteConfiguration{
|
||||
ExperimentalFeatures: &schema.ExperimentalFeatures{
|
||||
SubRepoPermissions: &schema.SubRepoPermissions{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
t.Cleanup(func() { conf.Mock(nil) })
|
||||
|
||||
getter := NewMockSubRepoPermissionsGetter()
|
||||
client, err := NewSubRepoPermsClient(getter)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
content := authz.RepoContent{
|
||||
Repo: api.RepoName("thing"),
|
||||
Path: "/stuff",
|
||||
}
|
||||
|
||||
// Should hit DB only once
|
||||
for i := 0; i < 3; i++ {
|
||||
_, err = client.Permissions(ctx, 1, content)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
h := getter.GetByUserFunc.History()
|
||||
if len(h) != 1 {
|
||||
t.Fatal("Should have been called once")
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger expiry
|
||||
client.since = func(time time.Time) time.Duration {
|
||||
return defaultCacheTTL + 1
|
||||
}
|
||||
|
||||
_, err = client.Permissions(ctx, 1, content)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
h := getter.GetByUserFunc.History()
|
||||
if len(h) != 2 {
|
||||
t.Fatal("Should have been called twice")
|
||||
}
|
||||
}
|
||||
@ -16,10 +16,11 @@ import (
|
||||
|
||||
// NewAuthzStore returns an OSS database.AuthzStore set with enterprise implementation.
|
||||
func NewAuthzStore(logger log.Logger, db database.DB, clock func() time.Time) database.AuthzStore {
|
||||
enterpriseDB := NewEnterpriseDB(db)
|
||||
return &authzStore{
|
||||
logger: logger,
|
||||
store: Perms(logger, db, clock),
|
||||
srpStore: database.SubRepoPermsWith(basestore.NewWithHandle(db.Handle())),
|
||||
store: Perms(logger, enterpriseDB, clock),
|
||||
srpStore: enterpriseDB.SubRepoPerms(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,14 +28,14 @@ func NewAuthzStoreWith(logger log.Logger, other basestore.ShareableStore, clock
|
||||
return &authzStore{
|
||||
logger: logger,
|
||||
store: PermsWith(logger, other, clock),
|
||||
srpStore: database.SubRepoPermsWith(other),
|
||||
srpStore: SubRepoPermsWith(other),
|
||||
}
|
||||
}
|
||||
|
||||
type authzStore struct {
|
||||
logger log.Logger
|
||||
store PermsStore
|
||||
srpStore database.SubRepoPermsStore
|
||||
srpStore SubRepoPermsStore
|
||||
}
|
||||
|
||||
// GrantPendingPermissions grants pending permissions for a user, which implements the database.AuthzStore interface.
|
||||
|
||||
@ -17,6 +17,7 @@ type EnterpriseDB interface {
|
||||
database.DB
|
||||
CodeMonitors() CodeMonitorStore
|
||||
Perms() PermsStore
|
||||
SubRepoPerms() SubRepoPermsStore
|
||||
}
|
||||
|
||||
func NewEnterpriseDB(db database.DB) EnterpriseDB {
|
||||
@ -42,6 +43,10 @@ func (edb *enterpriseDB) Perms() PermsStore {
|
||||
return &permsStore{Store: basestore.NewWithHandle(edb.Handle()), clock: time.Now}
|
||||
}
|
||||
|
||||
func (edb *enterpriseDB) SubRepoPerms() SubRepoPermsStore {
|
||||
return SubRepoPermsWith(basestore.NewWithHandle(edb.Handle()))
|
||||
}
|
||||
|
||||
type InsightsDB interface {
|
||||
dbutil.DB
|
||||
basestore.ShareableStore
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -18,8 +18,10 @@ import (
|
||||
// and exclude patterns.
|
||||
const SubRepoPermsVersion = 1
|
||||
|
||||
var SubRepoSupportedCodeHostTypes = []string{extsvc.TypePerforce}
|
||||
var supportedTypesQuery = make([]*sqlf.Query, len(SubRepoSupportedCodeHostTypes))
|
||||
var (
|
||||
SubRepoSupportedCodeHostTypes = []string{extsvc.TypePerforce}
|
||||
supportedTypesQuery = make([]*sqlf.Query, len(SubRepoSupportedCodeHostTypes))
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Build this up at startup, so we don't need to rebuild it every time
|
||||
@ -53,6 +55,8 @@ type subRepoPermsStore struct {
|
||||
*basestore.Store
|
||||
}
|
||||
|
||||
var _ SubRepoPermsStore = (*subRepoPermsStore)(nil)
|
||||
|
||||
func SubRepoPermsWith(other basestore.ShareableStore) SubRepoPermsStore {
|
||||
return &subRepoPermsStore{Store: basestore.NewWithHandle(other.Handle())}
|
||||
}
|
||||
@ -13,6 +13,7 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/internal/api"
|
||||
"github.com/sourcegraph/sourcegraph/internal/authz"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database/dbtest"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database/dbutil"
|
||||
"github.com/sourcegraph/sourcegraph/schema"
|
||||
@ -25,7 +26,7 @@ func TestSubRepoPermsInsert(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
logger := logtest.Scoped(t)
|
||||
db := NewDB(logger, dbtest.NewDB(logger, t))
|
||||
db := NewEnterpriseDB(database.NewDB(logger, dbtest.NewDB(logger, t)))
|
||||
|
||||
ctx := context.Background()
|
||||
prepareSubRepoTestData(ctx, t, db)
|
||||
@ -57,7 +58,7 @@ func TestSubRepoPermsDeleteByUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
logger := logtest.Scoped(t)
|
||||
db := NewDB(logger, dbtest.NewDB(logger, t))
|
||||
db := NewEnterpriseDB(database.NewDB(logger, dbtest.NewDB(logger, t)))
|
||||
|
||||
ctx := context.Background()
|
||||
prepareSubRepoTestData(ctx, t, db)
|
||||
@ -92,7 +93,7 @@ func TestSubRepoPermsUpsert(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
logger := logtest.Scoped(t)
|
||||
db := NewDB(logger, dbtest.NewDB(logger, t))
|
||||
db := NewEnterpriseDB(database.NewDB(logger, dbtest.NewDB(logger, t)))
|
||||
|
||||
ctx := context.Background()
|
||||
prepareSubRepoTestData(ctx, t, db)
|
||||
@ -133,7 +134,7 @@ func TestSubRepoPermsUpsertWithSpec(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
logger := logtest.Scoped(t)
|
||||
db := NewDB(logger, dbtest.NewDB(logger, t))
|
||||
db := NewEnterpriseDB(database.NewDB(logger, dbtest.NewDB(logger, t)))
|
||||
|
||||
ctx := context.Background()
|
||||
prepareSubRepoTestData(ctx, t, db)
|
||||
@ -181,7 +182,7 @@ func TestSubRepoPermsGetByUser(t *testing.T) {
|
||||
t.Cleanup(func() { conf.Mock(nil) })
|
||||
|
||||
logger := logtest.Scoped(t)
|
||||
db := NewDB(logger, dbtest.NewDB(logger, t))
|
||||
db := NewEnterpriseDB(database.NewDB(logger, dbtest.NewDB(logger, t)))
|
||||
|
||||
ctx := context.Background()
|
||||
s := db.SubRepoPerms()
|
||||
@ -264,7 +265,7 @@ func TestSubRepoPermsGetByUserAndService(t *testing.T) {
|
||||
|
||||
logger := logtest.Scoped(t)
|
||||
|
||||
db := NewDB(logger, dbtest.NewDB(logger, t))
|
||||
db := NewEnterpriseDB(database.NewDB(logger, dbtest.NewDB(logger, t)))
|
||||
|
||||
ctx := context.Background()
|
||||
s := db.SubRepoPerms()
|
||||
@ -342,7 +343,7 @@ func TestSubRepoPermsSupportedForRepoId(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
logger := logtest.Scoped(t)
|
||||
db := NewDB(logger, dbtest.NewDB(logger, t))
|
||||
db := NewEnterpriseDB(database.NewDB(logger, dbtest.NewDB(logger, t)))
|
||||
|
||||
ctx := context.Background()
|
||||
s := db.SubRepoPerms()
|
||||
158
enterprise/internal/gitserver/integration_tests/commits_test.go
Normal file
158
enterprise/internal/gitserver/integration_tests/commits_test.go
Normal file
@ -0,0 +1,158 @@
|
||||
package inttests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/internal/api"
|
||||
"github.com/sourcegraph/sourcegraph/internal/authz"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/gitserver"
|
||||
"github.com/sourcegraph/sourcegraph/internal/gitserver/gitdomain"
|
||||
inttests "github.com/sourcegraph/sourcegraph/internal/gitserver/integration_tests"
|
||||
)
|
||||
|
||||
func TestGetCommits(t *testing.T) {
|
||||
t.Parallel()
|
||||
inttests.InitGitserver()
|
||||
ctx := actor.WithActor(context.Background(), &actor.Actor{
|
||||
UID: 1,
|
||||
})
|
||||
db := database.NewMockDB()
|
||||
gr := database.NewMockGitserverRepoStore()
|
||||
db.GitserverReposFunc.SetDefaultReturn(gr)
|
||||
|
||||
repo1 := inttests.MakeGitRepository(t, getGitCommandsWithFiles("file1", "file2")...)
|
||||
repo2 := inttests.MakeGitRepository(t, getGitCommandsWithFiles("file3", "file4")...)
|
||||
repo3 := inttests.MakeGitRepository(t, getGitCommandsWithFiles("file5", "file6")...)
|
||||
|
||||
repoCommits := []api.RepoCommit{
|
||||
{Repo: repo1, CommitID: api.CommitID("HEAD")}, // HEAD (file2)
|
||||
{Repo: repo1, CommitID: api.CommitID("HEAD~1")}, // HEAD~1 (file1)
|
||||
{Repo: repo2, CommitID: api.CommitID("67762ad757dd26cac4145f2b744fd93ad10a48e0")}, // HEAD (file4)
|
||||
{Repo: repo2, CommitID: api.CommitID("2b988222e844b570959a493f5b07ec020b89e122")}, // HEAD~1 (file3)
|
||||
{Repo: repo3, CommitID: api.CommitID("01bed0a")}, // abbrev HEAD (file6)
|
||||
{Repo: repo3, CommitID: api.CommitID("unresolvable")}, // unresolvable
|
||||
{Repo: api.RepoName("unresolvable"), CommitID: api.CommitID("deadbeef")}, // unresolvable
|
||||
}
|
||||
|
||||
t.Run("with sub-repo permissions", func(t *testing.T) {
|
||||
expectedCommits := []*gitdomain.Commit{
|
||||
{
|
||||
ID: "2ba4dd2b9a27ec125fea7d72e12b9824ead18631",
|
||||
Author: gitdomain.Signature{Name: "a", Email: "a@a.com", Date: *mustParseDate("2006-01-02T15:04:05Z", t)},
|
||||
Committer: &gitdomain.Signature{Name: "a", Email: "a@a.com", Date: *mustParseDate("2006-01-02T15:04:05Z", t)},
|
||||
Message: "commit2",
|
||||
Parents: []api.CommitID{"d38233a79e037d2ab8170b0d0bc0aa438473e6da"},
|
||||
},
|
||||
nil, // file 1
|
||||
{
|
||||
ID: "67762ad757dd26cac4145f2b744fd93ad10a48e0",
|
||||
Author: gitdomain.Signature{Name: "a", Email: "a@a.com", Date: *mustParseDate("2006-01-02T15:04:05Z", t)},
|
||||
Committer: &gitdomain.Signature{Name: "a", Email: "a@a.com", Date: *mustParseDate("2006-01-02T15:04:05Z", t)},
|
||||
Message: "commit2",
|
||||
Parents: []api.CommitID{"2b988222e844b570959a493f5b07ec020b89e122"},
|
||||
},
|
||||
nil, // file 3
|
||||
{
|
||||
ID: "01bed0ae660668c57539cecaacb4c33d77609f43",
|
||||
Author: gitdomain.Signature{Name: "a", Email: "a@a.com", Date: *mustParseDate("2006-01-02T15:04:05Z", t)},
|
||||
Committer: &gitdomain.Signature{Name: "a", Email: "a@a.com", Date: *mustParseDate("2006-01-02T15:04:05Z", t)},
|
||||
Message: "commit2",
|
||||
Parents: []api.CommitID{"d6ce2e76d171569d81c0afdc4573f461cec17d45"},
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
}
|
||||
|
||||
commits, err := gitserver.NewTestClient(http.DefaultClient, db, inttests.GitserverAddresses).GetCommits(ctx, getTestSubRepoPermsChecker("file1", "file3"), repoCommits, true)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error calling getCommits: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(expectedCommits, commits); diff != "" {
|
||||
t.Errorf("unexpected commits (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// get a test sub-repo permissions checker which allows access to all files (so should be a no-op)
|
||||
func getTestSubRepoPermsChecker(noAccessPaths ...string) authz.SubRepoPermissionChecker {
|
||||
checker := authz.NewMockSubRepoPermissionChecker()
|
||||
checker.EnabledFunc.SetDefaultHook(func() bool {
|
||||
return true
|
||||
})
|
||||
checker.PermissionsFunc.SetDefaultHook(func(ctx context.Context, i int32, content authz.RepoContent) (authz.Perms, error) {
|
||||
for _, noAccessPath := range noAccessPaths {
|
||||
if content.Path == noAccessPath {
|
||||
return authz.None, nil
|
||||
}
|
||||
}
|
||||
return authz.Read, nil
|
||||
})
|
||||
return checker
|
||||
}
|
||||
|
||||
func getGitCommandsWithFiles(fileName1, fileName2 string) []string {
|
||||
return []string{
|
||||
fmt.Sprintf("touch %s", fileName1),
|
||||
fmt.Sprintf("git add %s", fileName1),
|
||||
"GIT_COMMITTER_NAME=a GIT_COMMITTER_EMAIL=a@a.com GIT_COMMITTER_DATE=2006-01-02T15:04:05Z git commit -m commit1 --author='a <a@a.com>' --date 2006-01-02T15:04:05Z",
|
||||
fmt.Sprintf("touch %s", fileName2),
|
||||
fmt.Sprintf("git add %s", fileName2),
|
||||
"GIT_COMMITTER_NAME=a GIT_COMMITTER_EMAIL=a@a.com GIT_COMMITTER_DATE=2006-01-02T15:04:05Z git commit -m commit2 --author='a <a@a.com>' --date 2006-01-02T15:04:05Z",
|
||||
}
|
||||
}
|
||||
|
||||
func mustParseDate(s string, t *testing.T) *time.Time {
|
||||
t.Helper()
|
||||
date, err := time.Parse(time.RFC3339, s)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error parsing date string: %s", err)
|
||||
}
|
||||
return &date
|
||||
}
|
||||
|
||||
func TestHead(t *testing.T) {
|
||||
inttests.InitGitserver()
|
||||
client := gitserver.NewTestClient(http.DefaultClient, database.NewMockDB(), inttests.GitserverAddresses)
|
||||
|
||||
t.Run("with sub-repo permissions", func(t *testing.T) {
|
||||
gitCommands := []string{
|
||||
"touch file",
|
||||
"git add file",
|
||||
"GIT_COMMITTER_NAME=a GIT_COMMITTER_EMAIL=a@a.com GIT_COMMITTER_DATE=2006-01-02T15:04:05Z git commit -m foo --author='a <a@a.com>' --date 2006-01-02T15:04:05Z",
|
||||
}
|
||||
repo := inttests.MakeGitRepository(t, gitCommands...)
|
||||
ctx := actor.WithActor(context.Background(), &actor.Actor{
|
||||
UID: 1,
|
||||
})
|
||||
checker := getTestSubRepoPermsChecker("file")
|
||||
// call Head() when user doesn't have access to view the commit
|
||||
_, exists, err := client.Head(ctx, checker, repo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if exists {
|
||||
t.Fatalf("exists should be false since the user doesn't have access to view the commit")
|
||||
}
|
||||
readAllChecker := getTestSubRepoPermsChecker()
|
||||
// call Head() when user has access to view the commit; should return expected commit
|
||||
head, exists, err := client.Head(ctx, readAllChecker, repo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantHead := "46619ad353dbe4ed4108ebde9aa59ef676994a0b"
|
||||
if head != wantHead {
|
||||
t.Fatalf("Want %q, got %q", wantHead, head)
|
||||
}
|
||||
if !exists {
|
||||
t.Fatal("Should exist")
|
||||
}
|
||||
})
|
||||
}
|
||||
74
enterprise/internal/gitserver/integration_tests/tree_test.go
Normal file
74
enterprise/internal/gitserver/integration_tests/tree_test.go
Normal file
@ -0,0 +1,74 @@
|
||||
package inttests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
srp "github.com/sourcegraph/sourcegraph/enterprise/internal/authz/subrepoperms"
|
||||
edb "github.com/sourcegraph/sourcegraph/enterprise/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/internal/api"
|
||||
"github.com/sourcegraph/sourcegraph/internal/authz"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/gitserver"
|
||||
inttests "github.com/sourcegraph/sourcegraph/internal/gitserver/integration_tests"
|
||||
"github.com/sourcegraph/sourcegraph/schema"
|
||||
)
|
||||
|
||||
func TestReadDir_SubRepoFiltering(t *testing.T) {
|
||||
inttests.InitGitserver()
|
||||
|
||||
ctx := actor.WithActor(context.Background(), &actor.Actor{
|
||||
UID: 1,
|
||||
})
|
||||
gitCommands := []string{
|
||||
"touch file1",
|
||||
"git add file1",
|
||||
"GIT_COMMITTER_NAME=a GIT_COMMITTER_EMAIL=a@a.com GIT_COMMITTER_DATE=2006-01-02T15:04:05Z git commit -m commit1 --author='a <a@a.com>' --date 2006-01-02T15:04:05Z",
|
||||
"mkdir app",
|
||||
"touch app/file2",
|
||||
"git add app",
|
||||
"GIT_COMMITTER_NAME=a GIT_COMMITTER_EMAIL=a@a.com GIT_COMMITTER_DATE=2006-01-02T15:04:05Z git commit -m commit2 --author='a <a@a.com>' --date 2006-01-02T15:04:05Z",
|
||||
}
|
||||
repo := inttests.MakeGitRepository(t, gitCommands...)
|
||||
commitID := api.CommitID("b1c725720de2bbd0518731b4a61959797ff345f3")
|
||||
conf.Mock(&conf.Unified{
|
||||
SiteConfiguration: schema.SiteConfiguration{
|
||||
ExperimentalFeatures: &schema.ExperimentalFeatures{
|
||||
SubRepoPermissions: &schema.SubRepoPermissions{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
defer conf.Mock(nil)
|
||||
srpGetter := edb.NewMockSubRepoPermsStore()
|
||||
testSubRepoPerms := map[api.RepoName]authz.SubRepoPermissions{
|
||||
repo: {
|
||||
Paths: []string{"/**", "-/app/**"},
|
||||
},
|
||||
}
|
||||
srpGetter.GetByUserFunc.SetDefaultReturn(testSubRepoPerms, nil)
|
||||
checker, err := srp.NewSubRepoPermsClient(srpGetter)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating sub-repo perms client: %s", err)
|
||||
}
|
||||
|
||||
db := database.NewMockDB()
|
||||
gr := database.NewMockGitserverRepoStore()
|
||||
db.GitserverReposFunc.SetDefaultReturn(gr)
|
||||
client := gitserver.NewTestClient(http.DefaultClient, db, inttests.GitserverAddresses)
|
||||
files, err := client.ReadDir(ctx, checker, repo, commitID, "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// Because we have a wildcard matcher we still allow directory visibility
|
||||
assert.Len(t, files, 1)
|
||||
assert.Equal(t, "file1", files[0].Name())
|
||||
assert.False(t, files[0].IsDir())
|
||||
}
|
||||
60
enterprise/internal/search/symbol/symbol_test.go
Normal file
60
enterprise/internal/search/symbol/symbol_test.go
Normal file
@ -0,0 +1,60 @@
|
||||
package symbol
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
srp "github.com/sourcegraph/sourcegraph/enterprise/internal/authz/subrepoperms"
|
||||
"github.com/sourcegraph/sourcegraph/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/internal/api"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/search/result"
|
||||
"github.com/sourcegraph/sourcegraph/internal/search/symbol"
|
||||
"github.com/sourcegraph/sourcegraph/schema"
|
||||
)
|
||||
|
||||
func TestFilterZoektResults(t *testing.T) {
|
||||
conf.Mock(&conf.Unified{
|
||||
SiteConfiguration: schema.SiteConfiguration{
|
||||
ExperimentalFeatures: &schema.ExperimentalFeatures{
|
||||
SubRepoPermissions: &schema.SubRepoPermissions{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
t.Cleanup(func() { conf.Mock(nil) })
|
||||
|
||||
repoName := api.RepoName("foo")
|
||||
ctx := context.Background()
|
||||
ctx = actor.WithActor(ctx, &actor.Actor{
|
||||
UID: 1,
|
||||
})
|
||||
checker, err := srp.NewSimpleChecker(repoName, []string{"/**", "-/*_test.go"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
results := []*result.SymbolMatch{
|
||||
{
|
||||
Symbol: result.Symbol{},
|
||||
File: &result.File{
|
||||
Path: "foo.go",
|
||||
},
|
||||
},
|
||||
{
|
||||
Symbol: result.Symbol{},
|
||||
File: &result.File{
|
||||
Path: "foo_test.go",
|
||||
},
|
||||
},
|
||||
}
|
||||
filtered, err := symbol.FilterZoektResults(ctx, checker, repoName, results)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Len(t, filtered, 1)
|
||||
r := filtered[0]
|
||||
assert.Equal(t, r.File.Path, "foo.go")
|
||||
}
|
||||
@ -677,416 +677,3 @@ func (c SubRepoPermissionCheckerPermissionsFuncCall) Args() []interface{} {
|
||||
func (c SubRepoPermissionCheckerPermissionsFuncCall) Results() []interface{} {
|
||||
return []interface{}{c.Result0, c.Result1}
|
||||
}
|
||||
|
||||
// MockSubRepoPermissionsGetter is a mock implementation of the
|
||||
// SubRepoPermissionsGetter interface (from the package
|
||||
// github.com/sourcegraph/sourcegraph/internal/authz) used for unit testing.
|
||||
type MockSubRepoPermissionsGetter struct {
|
||||
// GetByUserFunc is an instance of a mock function object controlling
|
||||
// the behavior of the method GetByUser.
|
||||
GetByUserFunc *SubRepoPermissionsGetterGetByUserFunc
|
||||
// RepoIDSupportedFunc is an instance of a mock function object
|
||||
// controlling the behavior of the method RepoIDSupported.
|
||||
RepoIDSupportedFunc *SubRepoPermissionsGetterRepoIDSupportedFunc
|
||||
// RepoSupportedFunc is an instance of a mock function object
|
||||
// controlling the behavior of the method RepoSupported.
|
||||
RepoSupportedFunc *SubRepoPermissionsGetterRepoSupportedFunc
|
||||
}
|
||||
|
||||
// NewMockSubRepoPermissionsGetter creates a new mock of the
|
||||
// SubRepoPermissionsGetter interface. All methods return zero values for
|
||||
// all results, unless overwritten.
|
||||
func NewMockSubRepoPermissionsGetter() *MockSubRepoPermissionsGetter {
|
||||
return &MockSubRepoPermissionsGetter{
|
||||
GetByUserFunc: &SubRepoPermissionsGetterGetByUserFunc{
|
||||
defaultHook: func(context.Context, int32) (r0 map[api.RepoName]SubRepoPermissions, r1 error) {
|
||||
return
|
||||
},
|
||||
},
|
||||
RepoIDSupportedFunc: &SubRepoPermissionsGetterRepoIDSupportedFunc{
|
||||
defaultHook: func(context.Context, api.RepoID) (r0 bool, r1 error) {
|
||||
return
|
||||
},
|
||||
},
|
||||
RepoSupportedFunc: &SubRepoPermissionsGetterRepoSupportedFunc{
|
||||
defaultHook: func(context.Context, api.RepoName) (r0 bool, r1 error) {
|
||||
return
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewStrictMockSubRepoPermissionsGetter creates a new mock of the
|
||||
// SubRepoPermissionsGetter interface. All methods panic on invocation,
|
||||
// unless overwritten.
|
||||
func NewStrictMockSubRepoPermissionsGetter() *MockSubRepoPermissionsGetter {
|
||||
return &MockSubRepoPermissionsGetter{
|
||||
GetByUserFunc: &SubRepoPermissionsGetterGetByUserFunc{
|
||||
defaultHook: func(context.Context, int32) (map[api.RepoName]SubRepoPermissions, error) {
|
||||
panic("unexpected invocation of MockSubRepoPermissionsGetter.GetByUser")
|
||||
},
|
||||
},
|
||||
RepoIDSupportedFunc: &SubRepoPermissionsGetterRepoIDSupportedFunc{
|
||||
defaultHook: func(context.Context, api.RepoID) (bool, error) {
|
||||
panic("unexpected invocation of MockSubRepoPermissionsGetter.RepoIDSupported")
|
||||
},
|
||||
},
|
||||
RepoSupportedFunc: &SubRepoPermissionsGetterRepoSupportedFunc{
|
||||
defaultHook: func(context.Context, api.RepoName) (bool, error) {
|
||||
panic("unexpected invocation of MockSubRepoPermissionsGetter.RepoSupported")
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewMockSubRepoPermissionsGetterFrom creates a new mock of the
|
||||
// MockSubRepoPermissionsGetter interface. All methods delegate to the given
|
||||
// implementation, unless overwritten.
|
||||
func NewMockSubRepoPermissionsGetterFrom(i SubRepoPermissionsGetter) *MockSubRepoPermissionsGetter {
|
||||
return &MockSubRepoPermissionsGetter{
|
||||
GetByUserFunc: &SubRepoPermissionsGetterGetByUserFunc{
|
||||
defaultHook: i.GetByUser,
|
||||
},
|
||||
RepoIDSupportedFunc: &SubRepoPermissionsGetterRepoIDSupportedFunc{
|
||||
defaultHook: i.RepoIDSupported,
|
||||
},
|
||||
RepoSupportedFunc: &SubRepoPermissionsGetterRepoSupportedFunc{
|
||||
defaultHook: i.RepoSupported,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SubRepoPermissionsGetterGetByUserFunc describes the behavior when the
|
||||
// GetByUser method of the parent MockSubRepoPermissionsGetter instance is
|
||||
// invoked.
|
||||
type SubRepoPermissionsGetterGetByUserFunc struct {
|
||||
defaultHook func(context.Context, int32) (map[api.RepoName]SubRepoPermissions, error)
|
||||
hooks []func(context.Context, int32) (map[api.RepoName]SubRepoPermissions, error)
|
||||
history []SubRepoPermissionsGetterGetByUserFuncCall
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// GetByUser delegates to the next hook function in the queue and stores the
|
||||
// parameter and result values of this invocation.
|
||||
func (m *MockSubRepoPermissionsGetter) GetByUser(v0 context.Context, v1 int32) (map[api.RepoName]SubRepoPermissions, error) {
|
||||
r0, r1 := m.GetByUserFunc.nextHook()(v0, v1)
|
||||
m.GetByUserFunc.appendCall(SubRepoPermissionsGetterGetByUserFuncCall{v0, v1, r0, r1})
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SetDefaultHook sets function that is called when the GetByUser method of
|
||||
// the parent MockSubRepoPermissionsGetter instance is invoked and the hook
|
||||
// queue is empty.
|
||||
func (f *SubRepoPermissionsGetterGetByUserFunc) SetDefaultHook(hook func(context.Context, int32) (map[api.RepoName]SubRepoPermissions, error)) {
|
||||
f.defaultHook = hook
|
||||
}
|
||||
|
||||
// PushHook adds a function to the end of hook queue. Each invocation of the
|
||||
// GetByUser method of the parent MockSubRepoPermissionsGetter instance
|
||||
// invokes the hook at the front of the queue and discards it. After the
|
||||
// queue is empty, the default hook function is invoked for any future
|
||||
// action.
|
||||
func (f *SubRepoPermissionsGetterGetByUserFunc) PushHook(hook func(context.Context, int32) (map[api.RepoName]SubRepoPermissions, error)) {
|
||||
f.mutex.Lock()
|
||||
f.hooks = append(f.hooks, hook)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||
// given values.
|
||||
func (f *SubRepoPermissionsGetterGetByUserFunc) SetDefaultReturn(r0 map[api.RepoName]SubRepoPermissions, r1 error) {
|
||||
f.SetDefaultHook(func(context.Context, int32) (map[api.RepoName]SubRepoPermissions, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
// PushReturn calls PushHook with a function that returns the given values.
|
||||
func (f *SubRepoPermissionsGetterGetByUserFunc) PushReturn(r0 map[api.RepoName]SubRepoPermissions, r1 error) {
|
||||
f.PushHook(func(context.Context, int32) (map[api.RepoName]SubRepoPermissions, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
func (f *SubRepoPermissionsGetterGetByUserFunc) nextHook() func(context.Context, int32) (map[api.RepoName]SubRepoPermissions, error) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
if len(f.hooks) == 0 {
|
||||
return f.defaultHook
|
||||
}
|
||||
|
||||
hook := f.hooks[0]
|
||||
f.hooks = f.hooks[1:]
|
||||
return hook
|
||||
}
|
||||
|
||||
func (f *SubRepoPermissionsGetterGetByUserFunc) appendCall(r0 SubRepoPermissionsGetterGetByUserFuncCall) {
|
||||
f.mutex.Lock()
|
||||
f.history = append(f.history, r0)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// History returns a sequence of SubRepoPermissionsGetterGetByUserFuncCall
|
||||
// objects describing the invocations of this function.
|
||||
func (f *SubRepoPermissionsGetterGetByUserFunc) History() []SubRepoPermissionsGetterGetByUserFuncCall {
|
||||
f.mutex.Lock()
|
||||
history := make([]SubRepoPermissionsGetterGetByUserFuncCall, len(f.history))
|
||||
copy(history, f.history)
|
||||
f.mutex.Unlock()
|
||||
|
||||
return history
|
||||
}
|
||||
|
||||
// SubRepoPermissionsGetterGetByUserFuncCall is an object that describes an
|
||||
// invocation of method GetByUser on an instance of
|
||||
// MockSubRepoPermissionsGetter.
|
||||
type SubRepoPermissionsGetterGetByUserFuncCall struct {
|
||||
// Arg0 is the value of the 1st argument passed to this method
|
||||
// invocation.
|
||||
Arg0 context.Context
|
||||
// Arg1 is the value of the 2nd argument passed to this method
|
||||
// invocation.
|
||||
Arg1 int32
|
||||
// Result0 is the value of the 1st result returned from this method
|
||||
// invocation.
|
||||
Result0 map[api.RepoName]SubRepoPermissions
|
||||
// Result1 is the value of the 2nd result returned from this method
|
||||
// invocation.
|
||||
Result1 error
|
||||
}
|
||||
|
||||
// Args returns an interface slice containing the arguments of this
|
||||
// invocation.
|
||||
func (c SubRepoPermissionsGetterGetByUserFuncCall) Args() []interface{} {
|
||||
return []interface{}{c.Arg0, c.Arg1}
|
||||
}
|
||||
|
||||
// Results returns an interface slice containing the results of this
|
||||
// invocation.
|
||||
func (c SubRepoPermissionsGetterGetByUserFuncCall) Results() []interface{} {
|
||||
return []interface{}{c.Result0, c.Result1}
|
||||
}
|
||||
|
||||
// SubRepoPermissionsGetterRepoIDSupportedFunc describes the behavior when
|
||||
// the RepoIDSupported method of the parent MockSubRepoPermissionsGetter
|
||||
// instance is invoked.
|
||||
type SubRepoPermissionsGetterRepoIDSupportedFunc struct {
|
||||
defaultHook func(context.Context, api.RepoID) (bool, error)
|
||||
hooks []func(context.Context, api.RepoID) (bool, error)
|
||||
history []SubRepoPermissionsGetterRepoIDSupportedFuncCall
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// RepoIDSupported delegates to the next hook function in the queue and
|
||||
// stores the parameter and result values of this invocation.
|
||||
func (m *MockSubRepoPermissionsGetter) RepoIDSupported(v0 context.Context, v1 api.RepoID) (bool, error) {
|
||||
r0, r1 := m.RepoIDSupportedFunc.nextHook()(v0, v1)
|
||||
m.RepoIDSupportedFunc.appendCall(SubRepoPermissionsGetterRepoIDSupportedFuncCall{v0, v1, r0, r1})
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SetDefaultHook sets function that is called when the RepoIDSupported
|
||||
// method of the parent MockSubRepoPermissionsGetter instance is invoked and
|
||||
// the hook queue is empty.
|
||||
func (f *SubRepoPermissionsGetterRepoIDSupportedFunc) SetDefaultHook(hook func(context.Context, api.RepoID) (bool, error)) {
|
||||
f.defaultHook = hook
|
||||
}
|
||||
|
||||
// PushHook adds a function to the end of hook queue. Each invocation of the
|
||||
// RepoIDSupported method of the parent MockSubRepoPermissionsGetter
|
||||
// instance invokes the hook at the front of the queue and discards it.
|
||||
// After the queue is empty, the default hook function is invoked for any
|
||||
// future action.
|
||||
func (f *SubRepoPermissionsGetterRepoIDSupportedFunc) PushHook(hook func(context.Context, api.RepoID) (bool, error)) {
|
||||
f.mutex.Lock()
|
||||
f.hooks = append(f.hooks, hook)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||
// given values.
|
||||
func (f *SubRepoPermissionsGetterRepoIDSupportedFunc) SetDefaultReturn(r0 bool, r1 error) {
|
||||
f.SetDefaultHook(func(context.Context, api.RepoID) (bool, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
// PushReturn calls PushHook with a function that returns the given values.
|
||||
func (f *SubRepoPermissionsGetterRepoIDSupportedFunc) PushReturn(r0 bool, r1 error) {
|
||||
f.PushHook(func(context.Context, api.RepoID) (bool, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
func (f *SubRepoPermissionsGetterRepoIDSupportedFunc) nextHook() func(context.Context, api.RepoID) (bool, error) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
if len(f.hooks) == 0 {
|
||||
return f.defaultHook
|
||||
}
|
||||
|
||||
hook := f.hooks[0]
|
||||
f.hooks = f.hooks[1:]
|
||||
return hook
|
||||
}
|
||||
|
||||
func (f *SubRepoPermissionsGetterRepoIDSupportedFunc) appendCall(r0 SubRepoPermissionsGetterRepoIDSupportedFuncCall) {
|
||||
f.mutex.Lock()
|
||||
f.history = append(f.history, r0)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// History returns a sequence of
|
||||
// SubRepoPermissionsGetterRepoIDSupportedFuncCall objects describing the
|
||||
// invocations of this function.
|
||||
func (f *SubRepoPermissionsGetterRepoIDSupportedFunc) History() []SubRepoPermissionsGetterRepoIDSupportedFuncCall {
|
||||
f.mutex.Lock()
|
||||
history := make([]SubRepoPermissionsGetterRepoIDSupportedFuncCall, len(f.history))
|
||||
copy(history, f.history)
|
||||
f.mutex.Unlock()
|
||||
|
||||
return history
|
||||
}
|
||||
|
||||
// SubRepoPermissionsGetterRepoIDSupportedFuncCall is an object that
|
||||
// describes an invocation of method RepoIDSupported on an instance of
|
||||
// MockSubRepoPermissionsGetter.
|
||||
type SubRepoPermissionsGetterRepoIDSupportedFuncCall struct {
|
||||
// Arg0 is the value of the 1st argument passed to this method
|
||||
// invocation.
|
||||
Arg0 context.Context
|
||||
// Arg1 is the value of the 2nd argument passed to this method
|
||||
// invocation.
|
||||
Arg1 api.RepoID
|
||||
// Result0 is the value of the 1st result returned from this method
|
||||
// invocation.
|
||||
Result0 bool
|
||||
// Result1 is the value of the 2nd result returned from this method
|
||||
// invocation.
|
||||
Result1 error
|
||||
}
|
||||
|
||||
// Args returns an interface slice containing the arguments of this
|
||||
// invocation.
|
||||
func (c SubRepoPermissionsGetterRepoIDSupportedFuncCall) Args() []interface{} {
|
||||
return []interface{}{c.Arg0, c.Arg1}
|
||||
}
|
||||
|
||||
// Results returns an interface slice containing the results of this
|
||||
// invocation.
|
||||
func (c SubRepoPermissionsGetterRepoIDSupportedFuncCall) Results() []interface{} {
|
||||
return []interface{}{c.Result0, c.Result1}
|
||||
}
|
||||
|
||||
// SubRepoPermissionsGetterRepoSupportedFunc describes the behavior when the
|
||||
// RepoSupported method of the parent MockSubRepoPermissionsGetter instance
|
||||
// is invoked.
|
||||
type SubRepoPermissionsGetterRepoSupportedFunc struct {
|
||||
defaultHook func(context.Context, api.RepoName) (bool, error)
|
||||
hooks []func(context.Context, api.RepoName) (bool, error)
|
||||
history []SubRepoPermissionsGetterRepoSupportedFuncCall
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// RepoSupported delegates to the next hook function in the queue and stores
|
||||
// the parameter and result values of this invocation.
|
||||
func (m *MockSubRepoPermissionsGetter) RepoSupported(v0 context.Context, v1 api.RepoName) (bool, error) {
|
||||
r0, r1 := m.RepoSupportedFunc.nextHook()(v0, v1)
|
||||
m.RepoSupportedFunc.appendCall(SubRepoPermissionsGetterRepoSupportedFuncCall{v0, v1, r0, r1})
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SetDefaultHook sets function that is called when the RepoSupported method
|
||||
// of the parent MockSubRepoPermissionsGetter instance is invoked and the
|
||||
// hook queue is empty.
|
||||
func (f *SubRepoPermissionsGetterRepoSupportedFunc) SetDefaultHook(hook func(context.Context, api.RepoName) (bool, error)) {
|
||||
f.defaultHook = hook
|
||||
}
|
||||
|
||||
// PushHook adds a function to the end of hook queue. Each invocation of the
|
||||
// RepoSupported method of the parent MockSubRepoPermissionsGetter instance
|
||||
// invokes the hook at the front of the queue and discards it. After the
|
||||
// queue is empty, the default hook function is invoked for any future
|
||||
// action.
|
||||
func (f *SubRepoPermissionsGetterRepoSupportedFunc) PushHook(hook func(context.Context, api.RepoName) (bool, error)) {
|
||||
f.mutex.Lock()
|
||||
f.hooks = append(f.hooks, hook)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||
// given values.
|
||||
func (f *SubRepoPermissionsGetterRepoSupportedFunc) SetDefaultReturn(r0 bool, r1 error) {
|
||||
f.SetDefaultHook(func(context.Context, api.RepoName) (bool, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
// PushReturn calls PushHook with a function that returns the given values.
|
||||
func (f *SubRepoPermissionsGetterRepoSupportedFunc) PushReturn(r0 bool, r1 error) {
|
||||
f.PushHook(func(context.Context, api.RepoName) (bool, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
func (f *SubRepoPermissionsGetterRepoSupportedFunc) nextHook() func(context.Context, api.RepoName) (bool, error) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
if len(f.hooks) == 0 {
|
||||
return f.defaultHook
|
||||
}
|
||||
|
||||
hook := f.hooks[0]
|
||||
f.hooks = f.hooks[1:]
|
||||
return hook
|
||||
}
|
||||
|
||||
func (f *SubRepoPermissionsGetterRepoSupportedFunc) appendCall(r0 SubRepoPermissionsGetterRepoSupportedFuncCall) {
|
||||
f.mutex.Lock()
|
||||
f.history = append(f.history, r0)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// History returns a sequence of
|
||||
// SubRepoPermissionsGetterRepoSupportedFuncCall objects describing the
|
||||
// invocations of this function.
|
||||
func (f *SubRepoPermissionsGetterRepoSupportedFunc) History() []SubRepoPermissionsGetterRepoSupportedFuncCall {
|
||||
f.mutex.Lock()
|
||||
history := make([]SubRepoPermissionsGetterRepoSupportedFuncCall, len(f.history))
|
||||
copy(history, f.history)
|
||||
f.mutex.Unlock()
|
||||
|
||||
return history
|
||||
}
|
||||
|
||||
// SubRepoPermissionsGetterRepoSupportedFuncCall is an object that describes
|
||||
// an invocation of method RepoSupported on an instance of
|
||||
// MockSubRepoPermissionsGetter.
|
||||
type SubRepoPermissionsGetterRepoSupportedFuncCall struct {
|
||||
// Arg0 is the value of the 1st argument passed to this method
|
||||
// invocation.
|
||||
Arg0 context.Context
|
||||
// Arg1 is the value of the 2nd argument passed to this method
|
||||
// invocation.
|
||||
Arg1 api.RepoName
|
||||
// Result0 is the value of the 1st result returned from this method
|
||||
// invocation.
|
||||
Result0 bool
|
||||
// Result1 is the value of the 2nd result returned from this method
|
||||
// invocation.
|
||||
Result1 error
|
||||
}
|
||||
|
||||
// Args returns an interface slice containing the arguments of this
|
||||
// invocation.
|
||||
func (c SubRepoPermissionsGetterRepoSupportedFuncCall) Args() []interface{} {
|
||||
return []interface{}{c.Arg0, c.Arg1}
|
||||
}
|
||||
|
||||
// Results returns an interface slice containing the results of this
|
||||
// invocation.
|
||||
func (c SubRepoPermissionsGetterRepoSupportedFuncCall) Results() []interface{} {
|
||||
return []interface{}{c.Result0, c.Result1}
|
||||
}
|
||||
|
||||
@ -7,16 +7,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"go.uber.org/atomic"
|
||||
"golang.org/x/sync/singleflight"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/internal/api"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
@ -90,336 +85,12 @@ func (*noopPermsChecker) EnabledForRepo(ctx context.Context, repo api.RepoName)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var _ SubRepoPermissionChecker = &SubRepoPermsClient{}
|
||||
|
||||
// SubRepoPermissionsGetter allows getting sub repository permissions.
|
||||
type SubRepoPermissionsGetter interface {
|
||||
// GetByUser returns the sub repository permissions rules known for a user.
|
||||
GetByUser(ctx context.Context, userID int32) (map[api.RepoName]SubRepoPermissions, error)
|
||||
|
||||
// RepoIDSupported returns true if repo with the given ID has sub-repo permissions.
|
||||
RepoIDSupported(ctx context.Context, repoID api.RepoID) (bool, error)
|
||||
|
||||
// RepoSupported returns true if repo with the given name has sub-repo permissions.
|
||||
RepoSupported(ctx context.Context, repo api.RepoName) (bool, error)
|
||||
}
|
||||
|
||||
// SubRepoPermsClient is a concrete implementation of SubRepoPermissionChecker.
|
||||
// Always use NewSubRepoPermsClient to instantiate an instance.
|
||||
type SubRepoPermsClient struct {
|
||||
permissionsGetter SubRepoPermissionsGetter
|
||||
clock func() time.Time
|
||||
since func(time.Time) time.Duration
|
||||
|
||||
group *singleflight.Group
|
||||
cache *lru.Cache
|
||||
enabled *atomic.Bool
|
||||
}
|
||||
|
||||
const defaultCacheSize = 1000
|
||||
const defaultCacheTTL = 10 * time.Second
|
||||
|
||||
// cachedRules caches the perms rules known for a particular user by repo.
|
||||
type cachedRules struct {
|
||||
rules map[api.RepoName]compiledRules
|
||||
timestamp time.Time
|
||||
}
|
||||
|
||||
type path struct {
|
||||
globPath glob.Glob
|
||||
exclusion bool
|
||||
// the original rule before it was compiled into a glob matcher
|
||||
original string
|
||||
}
|
||||
|
||||
type compiledRules struct {
|
||||
paths []path
|
||||
}
|
||||
|
||||
// GetPermissionsForPath tries to match a given path to a list of rules.
|
||||
// Since the last applicable rule is the one that applies, the list is
|
||||
// traversed in reverse, and the function returns as soon as a match is found.
|
||||
// If no match is found, None is returned.
|
||||
func (rules compiledRules) GetPermissionsForPath(path string) Perms {
|
||||
for i := len(rules.paths) - 1; i >= 0; i-- {
|
||||
if rules.paths[i].globPath.Match(path) {
|
||||
if rules.paths[i].exclusion {
|
||||
return None
|
||||
}
|
||||
return Read
|
||||
}
|
||||
}
|
||||
|
||||
// Return None if no rule matches
|
||||
return None
|
||||
}
|
||||
|
||||
// NewSubRepoPermsClient instantiates an instance of authz.SubRepoPermsClient
|
||||
// which implements SubRepoPermissionChecker.
|
||||
//
|
||||
// SubRepoPermissionChecker is responsible for checking whether a user has access
|
||||
// to data within a repo. Sub-repository permissions enforcement is on top of
|
||||
// existing repository permissions, which means the user must already have access
|
||||
// to the repository itself. The intention is for this client to be created once
|
||||
// at startup and passed in to all places that need to check sub repo
|
||||
// permissions.
|
||||
//
|
||||
// Note that sub-repo permissions are currently opt-in via the
|
||||
// experimentalFeatures.enableSubRepoPermissions option.
|
||||
func NewSubRepoPermsClient(permissionsGetter SubRepoPermissionsGetter) (*SubRepoPermsClient, error) {
|
||||
cache, err := lru.New(defaultCacheSize)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating LRU cache")
|
||||
}
|
||||
|
||||
enabled := atomic.NewBool(false)
|
||||
|
||||
conf.Watch(func() {
|
||||
c := conf.Get()
|
||||
if c.ExperimentalFeatures == nil || c.ExperimentalFeatures.SubRepoPermissions == nil {
|
||||
enabled.Store(false)
|
||||
return
|
||||
}
|
||||
|
||||
cacheSize := c.ExperimentalFeatures.SubRepoPermissions.UserCacheSize
|
||||
if cacheSize == 0 {
|
||||
cacheSize = defaultCacheSize
|
||||
}
|
||||
cache.Resize(cacheSize)
|
||||
enabled.Store(c.ExperimentalFeatures.SubRepoPermissions.Enabled)
|
||||
})
|
||||
|
||||
return &SubRepoPermsClient{
|
||||
permissionsGetter: permissionsGetter,
|
||||
clock: time.Now,
|
||||
since: time.Since,
|
||||
group: &singleflight.Group{},
|
||||
cache: cache,
|
||||
enabled: enabled,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
metricSubRepoPermsPermissionsDurationSuccess prometheus.Observer
|
||||
metricSubRepoPermsPermissionsDurationError prometheus.Observer
|
||||
)
|
||||
|
||||
func init() {
|
||||
// We cache the result of WithLabelValues since we call them in
|
||||
// performance sensitive code. See BenchmarkFilterActorPaths.
|
||||
metric := promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "authz_sub_repo_perms_permissions_duration_seconds",
|
||||
Help: "Time spent calculating permissions of a file for an actor.",
|
||||
}, []string{"error"})
|
||||
metricSubRepoPermsPermissionsDurationSuccess = metric.WithLabelValues("false")
|
||||
metricSubRepoPermsPermissionsDurationError = metric.WithLabelValues("true")
|
||||
}
|
||||
|
||||
var (
|
||||
metricSubRepoPermCacheHit prometheus.Counter
|
||||
metricSubRepoPermCacheMiss prometheus.Counter
|
||||
)
|
||||
|
||||
func init() {
|
||||
// We cache the result of WithLabelValues since we call them in
|
||||
// performance sensitive code. See BenchmarkFilterActorPaths.
|
||||
metric := promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "authz_sub_repo_perms_permissions_cache_count",
|
||||
Help: "The number of sub-repo perms cache hits or misses",
|
||||
}, []string{"hit"})
|
||||
metricSubRepoPermCacheHit = metric.WithLabelValues("true")
|
||||
metricSubRepoPermCacheMiss = metric.WithLabelValues("false")
|
||||
}
|
||||
|
||||
// Permissions return the current permissions granted to the given user on the
|
||||
// given content. If sub-repo permissions are disabled, it is a no-op that return
|
||||
// Read.
|
||||
func (s *SubRepoPermsClient) Permissions(ctx context.Context, userID int32, content RepoContent) (perms Perms, err error) {
|
||||
// Are sub-repo permissions enabled at the site level
|
||||
if !s.Enabled() {
|
||||
return Read, nil
|
||||
}
|
||||
|
||||
began := time.Now()
|
||||
defer func() {
|
||||
took := time.Since(began).Seconds()
|
||||
if err == nil {
|
||||
metricSubRepoPermsPermissionsDurationSuccess.Observe(took)
|
||||
} else {
|
||||
metricSubRepoPermsPermissionsDurationError.Observe(took)
|
||||
}
|
||||
}()
|
||||
|
||||
f, err := s.FilePermissionsFunc(ctx, userID, content.Repo)
|
||||
if err != nil {
|
||||
return None, err
|
||||
}
|
||||
return f(content.Path)
|
||||
}
|
||||
|
||||
// filePermissionsFuncAllRead is a FilePermissionFunc which _always_ returns
|
||||
// Read. Only use in cases that sub repo permission checks should not be done.
|
||||
func filePermissionsFuncAllRead(_ string) (Perms, error) {
|
||||
return Read, nil
|
||||
}
|
||||
|
||||
func (s *SubRepoPermsClient) FilePermissionsFunc(ctx context.Context, userID int32, repo api.RepoName) (FilePermissionFunc, error) {
|
||||
// Are sub-repo permissions enabled at the site level
|
||||
if !s.Enabled() {
|
||||
return filePermissionsFuncAllRead, nil
|
||||
}
|
||||
|
||||
if s.permissionsGetter == nil {
|
||||
return nil, errors.New("permissionsGetter is nil")
|
||||
}
|
||||
|
||||
if userID == 0 {
|
||||
return nil, &ErrUnauthenticated{}
|
||||
}
|
||||
|
||||
repoRules, err := s.getCompiledRules(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "compiling match rules")
|
||||
}
|
||||
|
||||
rules, rulesExist := repoRules[repo]
|
||||
if !rulesExist {
|
||||
// If we make it this far it implies that we have access at the repo level.
|
||||
// Having any empty set of rules here implies that we can access the whole repo.
|
||||
// Repos that support sub-repo permissions will only have an entry in our
|
||||
// repo_permissions table after all sub-repo permissions have been processed.
|
||||
return filePermissionsFuncAllRead, nil
|
||||
}
|
||||
|
||||
return func(path string) (Perms, error) {
|
||||
// An empty path is equivalent to repo permissions so we can assume it has
|
||||
// already been checked at that level.
|
||||
if path == "" {
|
||||
return Read, nil
|
||||
}
|
||||
|
||||
// Prefix path with "/", otherwise suffix rules like "**/file.txt" won't match
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
|
||||
// Iterate through all rules for the current path, and the final match takes
|
||||
// preference.
|
||||
return rules.GetPermissionsForPath(path), nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getCompiledRules fetches rules for the given repo with caching.
|
||||
func (s *SubRepoPermsClient) getCompiledRules(ctx context.Context, userID int32) (map[api.RepoName]compiledRules, error) {
|
||||
// Fast path for cached rules
|
||||
item, _ := s.cache.Get(userID)
|
||||
cached, ok := item.(cachedRules)
|
||||
|
||||
ttl := defaultCacheTTL
|
||||
if c := conf.Get(); c.ExperimentalFeatures != nil && c.ExperimentalFeatures.SubRepoPermissions != nil && c.ExperimentalFeatures.SubRepoPermissions.UserCacheTTLSeconds > 0 {
|
||||
ttl = time.Duration(c.ExperimentalFeatures.SubRepoPermissions.UserCacheTTLSeconds) * time.Second
|
||||
}
|
||||
|
||||
if ok && s.since(cached.timestamp) <= ttl {
|
||||
metricSubRepoPermCacheHit.Inc()
|
||||
return cached.rules, nil
|
||||
}
|
||||
metricSubRepoPermCacheMiss.Inc()
|
||||
|
||||
// Slow path on cache miss or expiry. Ensure that only one goroutine is doing the
|
||||
// work
|
||||
groupKey := strconv.FormatInt(int64(userID), 10)
|
||||
result, err, _ := s.group.Do(groupKey, func() (any, error) {
|
||||
repoPerms, err := s.permissionsGetter.GetByUser(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching rules")
|
||||
}
|
||||
toCache := cachedRules{
|
||||
rules: make(map[api.RepoName]compiledRules, len(repoPerms)),
|
||||
}
|
||||
for repo, perms := range repoPerms {
|
||||
paths := make([]path, 0, len(perms.Paths))
|
||||
for _, rule := range perms.Paths {
|
||||
exclusion := strings.HasPrefix(rule, "-")
|
||||
rule = strings.TrimPrefix(rule, "-")
|
||||
|
||||
if !strings.HasPrefix(rule, "/") {
|
||||
rule = "/" + rule
|
||||
}
|
||||
|
||||
g, err := glob.Compile(rule, '/')
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "building include matcher")
|
||||
}
|
||||
|
||||
paths = append(paths, path{globPath: g, exclusion: exclusion, original: rule})
|
||||
|
||||
// Special case. Our glob package does not handle rules starting with a double
|
||||
// wildcard correctly. For example, we would expect `/**/*.java` to match all
|
||||
// java files, but it does not match files at the root, eg `/foo.java`. To get
|
||||
// around this we add an extra rule to cover this case.
|
||||
if strings.HasPrefix(rule, "/**/") {
|
||||
trimmed := rule
|
||||
for {
|
||||
trimmed = strings.TrimPrefix(trimmed, "/**")
|
||||
if strings.HasPrefix(trimmed, "/**/") {
|
||||
// Keep trimming
|
||||
continue
|
||||
}
|
||||
g, err := glob.Compile(trimmed, '/')
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "building include matcher")
|
||||
}
|
||||
paths = append(paths, path{globPath: g, exclusion: exclusion, original: trimmed})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// We should include all directories above an include rule so that we can browse
|
||||
// to the included items.
|
||||
if exclusion {
|
||||
// Not required for an exclude rule
|
||||
continue
|
||||
}
|
||||
|
||||
dirs := expandDirs(rule)
|
||||
for _, dir := range dirs {
|
||||
g, err := glob.Compile(dir, '/')
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "building include matcher for dir")
|
||||
}
|
||||
paths = append(paths, path{globPath: g, exclusion: false, original: dir})
|
||||
}
|
||||
}
|
||||
|
||||
toCache.rules[repo] = compiledRules{
|
||||
paths: paths,
|
||||
}
|
||||
}
|
||||
toCache.timestamp = s.clock()
|
||||
s.cache.Add(userID, toCache)
|
||||
return toCache.rules, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
compiled := result.(map[api.RepoName]compiledRules)
|
||||
return compiled, nil
|
||||
}
|
||||
|
||||
func (s *SubRepoPermsClient) Enabled() bool {
|
||||
return s.enabled.Load()
|
||||
}
|
||||
|
||||
func (s *SubRepoPermsClient) EnabledForRepoID(ctx context.Context, id api.RepoID) (bool, error) {
|
||||
return s.permissionsGetter.RepoIDSupported(ctx, id)
|
||||
}
|
||||
|
||||
func (s *SubRepoPermsClient) EnabledForRepo(ctx context.Context, repo api.RepoName) (bool, error) {
|
||||
return s.permissionsGetter.RepoSupported(ctx, repo)
|
||||
}
|
||||
|
||||
// expandDirs will return a new set of rules that will match all directories
|
||||
// above the supplied rule. As a special case, if the rule starts with a wildcard
|
||||
// we return a rule to match all directories.
|
||||
@ -454,23 +125,6 @@ func expandDirs(rule string) []string {
|
||||
return dirs
|
||||
}
|
||||
|
||||
// NewSimpleChecker is exposed for testing and allows creation of a simple
|
||||
// checker based on the rules provided. The rules are expected to be in glob
|
||||
// format.
|
||||
func NewSimpleChecker(repo api.RepoName, paths []string) (SubRepoPermissionChecker, error) {
|
||||
getter := NewMockSubRepoPermissionsGetter()
|
||||
getter.GetByUserFunc.SetDefaultHook(func(ctx context.Context, i int32) (map[api.RepoName]SubRepoPermissions, error) {
|
||||
return map[api.RepoName]SubRepoPermissions{
|
||||
repo: {
|
||||
Paths: paths,
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
getter.RepoSupportedFunc.SetDefaultReturn(true, nil)
|
||||
getter.RepoIDSupportedFunc.SetDefaultReturn(true, nil)
|
||||
return NewSubRepoPermsClient(getter)
|
||||
}
|
||||
|
||||
// ActorPermissions returns the level of access the given actor has for the requested
|
||||
// content.
|
||||
//
|
||||
|
||||
@ -2,11 +2,8 @@ package authz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@ -14,161 +11,9 @@ import (
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/internal/api"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/fileutil"
|
||||
"github.com/sourcegraph/sourcegraph/schema"
|
||||
)
|
||||
|
||||
func TestSubRepoPermsPermissions(t *testing.T) {
|
||||
conf.Mock(&conf.Unified{
|
||||
SiteConfiguration: schema.SiteConfiguration{
|
||||
ExperimentalFeatures: &schema.ExperimentalFeatures{
|
||||
SubRepoPermissions: &schema.SubRepoPermissions{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
t.Cleanup(func() { conf.Mock(nil) })
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
userID int32
|
||||
content RepoContent
|
||||
clientFn func() (*SubRepoPermsClient, error)
|
||||
want Perms
|
||||
}{
|
||||
{
|
||||
name: "Empty path",
|
||||
userID: 1,
|
||||
content: RepoContent{
|
||||
Repo: "sample",
|
||||
Path: "",
|
||||
},
|
||||
clientFn: func() (*SubRepoPermsClient, error) {
|
||||
return NewSubRepoPermsClient(NewMockSubRepoPermissionsGetter())
|
||||
},
|
||||
want: Read,
|
||||
},
|
||||
{
|
||||
name: "No rules",
|
||||
userID: 1,
|
||||
content: RepoContent{
|
||||
Repo: "sample",
|
||||
Path: "/dev/thing",
|
||||
},
|
||||
clientFn: func() (*SubRepoPermsClient, error) {
|
||||
getter := NewMockSubRepoPermissionsGetter()
|
||||
getter.GetByUserFunc.SetDefaultHook(func(ctx context.Context, i int32) (map[api.RepoName]SubRepoPermissions, error) {
|
||||
return map[api.RepoName]SubRepoPermissions{
|
||||
"sample": {
|
||||
Paths: []string{},
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
return NewSubRepoPermsClient(getter)
|
||||
},
|
||||
want: None,
|
||||
},
|
||||
{
|
||||
name: "Exclude",
|
||||
userID: 1,
|
||||
content: RepoContent{
|
||||
Repo: "sample",
|
||||
Path: "/dev/thing",
|
||||
},
|
||||
clientFn: func() (*SubRepoPermsClient, error) {
|
||||
getter := NewMockSubRepoPermissionsGetter()
|
||||
getter.GetByUserFunc.SetDefaultHook(func(ctx context.Context, i int32) (map[api.RepoName]SubRepoPermissions, error) {
|
||||
return map[api.RepoName]SubRepoPermissions{
|
||||
"sample": {
|
||||
Paths: []string{"-/dev/*"},
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
return NewSubRepoPermsClient(getter)
|
||||
},
|
||||
want: None,
|
||||
},
|
||||
{
|
||||
name: "Include",
|
||||
userID: 1,
|
||||
content: RepoContent{
|
||||
Repo: "sample",
|
||||
Path: "/dev/thing",
|
||||
},
|
||||
clientFn: func() (*SubRepoPermsClient, error) {
|
||||
getter := NewMockSubRepoPermissionsGetter()
|
||||
getter.GetByUserFunc.SetDefaultHook(func(ctx context.Context, i int32) (map[api.RepoName]SubRepoPermissions, error) {
|
||||
return map[api.RepoName]SubRepoPermissions{
|
||||
"sample": {
|
||||
Paths: []string{"/*"},
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
return NewSubRepoPermsClient(getter)
|
||||
},
|
||||
want: None,
|
||||
},
|
||||
{
|
||||
name: "Last rule takes precedence (exclude)",
|
||||
userID: 1,
|
||||
content: RepoContent{
|
||||
Repo: "sample",
|
||||
Path: "/dev/thing",
|
||||
},
|
||||
clientFn: func() (*SubRepoPermsClient, error) {
|
||||
getter := NewMockSubRepoPermissionsGetter()
|
||||
getter.GetByUserFunc.SetDefaultHook(func(ctx context.Context, i int32) (map[api.RepoName]SubRepoPermissions, error) {
|
||||
return map[api.RepoName]SubRepoPermissions{
|
||||
"sample": {
|
||||
Paths: []string{"/**", "-/dev/*"},
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
return NewSubRepoPermsClient(getter)
|
||||
},
|
||||
want: None,
|
||||
},
|
||||
{
|
||||
name: "Last rule takes precedence (include)",
|
||||
userID: 1,
|
||||
content: RepoContent{
|
||||
Repo: "sample",
|
||||
Path: "/dev/thing",
|
||||
},
|
||||
clientFn: func() (*SubRepoPermsClient, error) {
|
||||
getter := NewMockSubRepoPermissionsGetter()
|
||||
getter.GetByUserFunc.SetDefaultHook(func(ctx context.Context, i int32) (map[api.RepoName]SubRepoPermissions, error) {
|
||||
return map[api.RepoName]SubRepoPermissions{
|
||||
"sample": {
|
||||
Paths: []string{"-/dev/*", "/**"},
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
return NewSubRepoPermsClient(getter)
|
||||
},
|
||||
want: Read,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
client, err := tc.clientFn()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
have, err := client.Permissions(context.Background(), tc.userID, tc.content)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if have != tc.want {
|
||||
t.Fatalf("have %v, want %v", have, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterActorPaths(t *testing.T) {
|
||||
testPaths := []string{"file1", "file2", "file3"}
|
||||
checker := NewMockSubRepoPermissionChecker()
|
||||
@ -202,98 +47,6 @@ func TestFilterActorPaths(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFilterActorPaths(b *testing.B) {
|
||||
// This benchmark is simulating the code path taken by a monorepo with sub
|
||||
// repo permissions. Our goal is to support repos with millions of files.
|
||||
// For now we target a lower number since large numbers don't give enough
|
||||
// runs of the benchmark to be useful.
|
||||
const pathCount = 5_000
|
||||
pathPatterns := []string{
|
||||
"base/%d/foo.go",
|
||||
"%d/stuff/baz",
|
||||
"frontend/%d/stuff/baz/bam",
|
||||
"subdir/sub/sub/sub/%d",
|
||||
"%d/foo/README.md",
|
||||
"subdir/remove/me/please/%d",
|
||||
"subdir/%d/also-remove/me/please",
|
||||
"a/deep/path/%d/.secrets.env",
|
||||
"%d/does/not/match/anything",
|
||||
"does/%d/not/match/anything",
|
||||
"does/not/%d/match/anything",
|
||||
"does/not/match/%d/anything",
|
||||
"does/not/match/anything/%d",
|
||||
}
|
||||
paths := []string{
|
||||
"config.yaml",
|
||||
"dir.yaml",
|
||||
}
|
||||
for i := 0; len(paths) < pathCount; i++ {
|
||||
for _, pat := range pathPatterns {
|
||||
paths = append(paths, fmt.Sprintf(pat, i))
|
||||
}
|
||||
}
|
||||
paths = paths[:pathCount]
|
||||
sort.Strings(paths)
|
||||
|
||||
conf.Mock(&conf.Unified{
|
||||
SiteConfiguration: schema.SiteConfiguration{
|
||||
ExperimentalFeatures: &schema.ExperimentalFeatures{
|
||||
SubRepoPermissions: &schema.SubRepoPermissions{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
defer conf.Mock(nil)
|
||||
repo := api.RepoName("repo")
|
||||
|
||||
getter := NewMockSubRepoPermissionsGetter()
|
||||
getter.GetByUserFunc.SetDefaultHook(func(ctx context.Context, i int32) (map[api.RepoName]SubRepoPermissions, error) {
|
||||
return map[api.RepoName]SubRepoPermissions{
|
||||
repo: {
|
||||
Paths: []string{
|
||||
"/base/**",
|
||||
"/*/stuff/**",
|
||||
"/frontend/**/stuff/*",
|
||||
"/config.yaml",
|
||||
"/subdir/**",
|
||||
"/**/README.md",
|
||||
"/dir.yaml",
|
||||
"-/subdir/remove/",
|
||||
"-/subdir/*/also-remove/**",
|
||||
"-/**/.secrets.env",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
checker, err := NewSubRepoPermsClient(getter)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
a := &actor.Actor{
|
||||
UID: 1,
|
||||
}
|
||||
ctx := actor.WithActor(context.Background(), a)
|
||||
|
||||
b.ResetTimer()
|
||||
start := time.Now()
|
||||
|
||||
for n := 0; n <= b.N; n++ {
|
||||
filtered, err := FilterActorPaths(ctx, checker, a, repo, paths)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if len(filtered) == 0 {
|
||||
b.Fatal("expected paths to be returned")
|
||||
}
|
||||
if len(filtered) == len(paths) {
|
||||
b.Fatal("expected to filter out some paths")
|
||||
}
|
||||
}
|
||||
|
||||
b.ReportMetric(float64(len(paths))*float64(b.N)/time.Since(start).Seconds(), "paths/s")
|
||||
}
|
||||
|
||||
func TestCanReadAllPaths(t *testing.T) {
|
||||
testPaths := []string{"file1", "file2", "file3"}
|
||||
checker := NewMockSubRepoPermissionChecker()
|
||||
@ -352,179 +105,6 @@ func TestCanReadAllPaths(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubRepoPermissionsCanReadDirectoriesInPath(t *testing.T) {
|
||||
conf.Mock(&conf.Unified{
|
||||
SiteConfiguration: schema.SiteConfiguration{
|
||||
ExperimentalFeatures: &schema.ExperimentalFeatures{
|
||||
SubRepoPermissions: &schema.SubRepoPermissions{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
t.Cleanup(func() { conf.Mock(nil) })
|
||||
repoName := api.RepoName("repo")
|
||||
|
||||
testCases := []struct {
|
||||
paths []string
|
||||
canReadAll []string
|
||||
cannotReadAny []string
|
||||
}{
|
||||
{
|
||||
paths: []string{"foo/bar/thing.txt"},
|
||||
canReadAll: []string{"foo/", "foo/bar/"},
|
||||
cannotReadAny: []string{"foo/thing.txt", "foo/bar/other.txt"},
|
||||
},
|
||||
{
|
||||
paths: []string{"foo/bar/**"},
|
||||
canReadAll: []string{"foo/", "foo/bar/", "foo/bar/baz/", "foo/bar/baz/fox/"},
|
||||
},
|
||||
{
|
||||
paths: []string{"foo/bar/"},
|
||||
canReadAll: []string{"foo/", "foo/bar/"},
|
||||
cannotReadAny: []string{"foo/thing.txt", "foo/bar/thing.txt"},
|
||||
},
|
||||
{
|
||||
paths: []string{"baz/*/foo/bar/thing.txt"},
|
||||
canReadAll: []string{"baz/", "baz/x/", "baz/x/foo/bar/"},
|
||||
cannotReadAny: []string{"baz/thing.txt"},
|
||||
},
|
||||
// If we have a wildcard in a path we allow all directories that are not
|
||||
// explicitly excluded.
|
||||
{
|
||||
paths: []string{"**/foo/bar/thing.txt"},
|
||||
canReadAll: []string{"foo/", "foo/bar/"},
|
||||
},
|
||||
{
|
||||
paths: []string{"*/foo/bar/thing.txt"},
|
||||
canReadAll: []string{"foo/", "foo/bar/"},
|
||||
},
|
||||
{
|
||||
paths: []string{"/**/foo/bar/thing.txt"},
|
||||
canReadAll: []string{"foo/", "foo/bar/"},
|
||||
},
|
||||
{
|
||||
paths: []string{"/*/foo/bar/thing.txt"},
|
||||
canReadAll: []string{"foo/", "foo/bar/"},
|
||||
},
|
||||
{
|
||||
paths: []string{"-/**", "/storage/redis/**"},
|
||||
canReadAll: []string{"storage/", "/storage/", "/storage/redis/"},
|
||||
},
|
||||
{
|
||||
paths: []string{"-/**", "-/storage/**", "/storage/redis/**"},
|
||||
canReadAll: []string{"storage/", "/storage/", "/storage/redis/"},
|
||||
},
|
||||
// Even with a wildcard include rule, we should still exclude directories that
|
||||
// are explicitly excluded later
|
||||
{
|
||||
paths: []string{"/**", "-/storage/**"},
|
||||
canReadAll: []string{"/foo"},
|
||||
cannotReadAny: []string{"storage/", "/storage/", "/storage/redis/"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
getter := NewMockSubRepoPermissionsGetter()
|
||||
getter.GetByUserFunc.SetDefaultHook(func(ctx context.Context, i int32) (map[api.RepoName]SubRepoPermissions, error) {
|
||||
return map[api.RepoName]SubRepoPermissions{
|
||||
repoName: {
|
||||
Paths: tc.paths,
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
client, err := NewSubRepoPermsClient(getter)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
for _, path := range tc.canReadAll {
|
||||
content := RepoContent{
|
||||
Repo: repoName,
|
||||
Path: path,
|
||||
}
|
||||
perm, err := client.Permissions(ctx, 1, content)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !perm.Include(Read) {
|
||||
t.Errorf("Should be able to read %q, cannot", path)
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range tc.cannotReadAny {
|
||||
content := RepoContent{
|
||||
Repo: repoName,
|
||||
Path: path,
|
||||
}
|
||||
perm, err := client.Permissions(ctx, 1, content)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if perm.Include(Read) {
|
||||
t.Errorf("Should not be able to read %q, can", path)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubRepoPermsPermissionsCache(t *testing.T) {
|
||||
conf.Mock(&conf.Unified{
|
||||
SiteConfiguration: schema.SiteConfiguration{
|
||||
ExperimentalFeatures: &schema.ExperimentalFeatures{
|
||||
SubRepoPermissions: &schema.SubRepoPermissions{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
t.Cleanup(func() { conf.Mock(nil) })
|
||||
|
||||
getter := NewMockSubRepoPermissionsGetter()
|
||||
client, err := NewSubRepoPermsClient(getter)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
content := RepoContent{
|
||||
Repo: api.RepoName("thing"),
|
||||
Path: "/stuff",
|
||||
}
|
||||
|
||||
// Should hit DB only once
|
||||
for i := 0; i < 3; i++ {
|
||||
_, err = client.Permissions(ctx, 1, content)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
h := getter.GetByUserFunc.History()
|
||||
if len(h) != 1 {
|
||||
t.Fatal("Should have been called once")
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger expiry
|
||||
client.since = func(time time.Time) time.Duration {
|
||||
return defaultCacheTTL + 1
|
||||
}
|
||||
|
||||
_, err = client.Permissions(ctx, 1, content)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
h := getter.GetByUserFunc.History()
|
||||
if len(h) != 2 {
|
||||
t.Fatal("Should have been called twice")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubRepoEnabled(t *testing.T) {
|
||||
t.Run("checker is nil", func(t *testing.T) {
|
||||
if SubRepoEnabled(nil) {
|
||||
|
||||
@ -45,7 +45,6 @@ type DB interface {
|
||||
SavedSearches() SavedSearchStore
|
||||
SearchContexts() SearchContextsStore
|
||||
Settings() SettingsStore
|
||||
SubRepoPerms() SubRepoPermsStore
|
||||
TemporarySettings() TemporarySettingsStore
|
||||
UserCredentials(encryption.Key) UserCredentialsStore
|
||||
UserEmails() UserEmailsStore
|
||||
@ -205,10 +204,6 @@ func (d *db) Settings() SettingsStore {
|
||||
return SettingsWith(d.Store)
|
||||
}
|
||||
|
||||
func (d *db) SubRepoPerms() SubRepoPermsStore {
|
||||
return SubRepoPermsWith(d.Store)
|
||||
}
|
||||
|
||||
func (d *db) TemporarySettings() TemporarySettingsStore {
|
||||
return TemporarySettingsWith(d.Store)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/internal/api"
|
||||
"github.com/sourcegraph/sourcegraph/internal/authz"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/gitserver"
|
||||
"github.com/sourcegraph/sourcegraph/internal/gitserver/gitdomain"
|
||||
@ -79,7 +78,7 @@ func TestGetCommits(t *testing.T) {
|
||||
nil,
|
||||
}
|
||||
|
||||
commits, err := gitserver.NewTestClient(http.DefaultClient, db, gitserverAddresses).GetCommits(ctx, nil, repoCommits, true)
|
||||
commits, err := gitserver.NewTestClient(http.DefaultClient, db, GitserverAddresses).GetCommits(ctx, nil, repoCommits, true)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error calling getCommits: %s", err)
|
||||
}
|
||||
@ -87,61 +86,6 @@ func TestGetCommits(t *testing.T) {
|
||||
t.Errorf("unexpected commits (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("with sub-repo permissions", func(t *testing.T) {
|
||||
expectedCommits := []*gitdomain.Commit{
|
||||
{
|
||||
ID: "2ba4dd2b9a27ec125fea7d72e12b9824ead18631",
|
||||
Author: gitdomain.Signature{Name: "a", Email: "a@a.com", Date: *mustParseDate("2006-01-02T15:04:05Z", t)},
|
||||
Committer: &gitdomain.Signature{Name: "a", Email: "a@a.com", Date: *mustParseDate("2006-01-02T15:04:05Z", t)},
|
||||
Message: "commit2",
|
||||
Parents: []api.CommitID{"d38233a79e037d2ab8170b0d0bc0aa438473e6da"},
|
||||
},
|
||||
nil, // file 1
|
||||
{
|
||||
ID: "67762ad757dd26cac4145f2b744fd93ad10a48e0",
|
||||
Author: gitdomain.Signature{Name: "a", Email: "a@a.com", Date: *mustParseDate("2006-01-02T15:04:05Z", t)},
|
||||
Committer: &gitdomain.Signature{Name: "a", Email: "a@a.com", Date: *mustParseDate("2006-01-02T15:04:05Z", t)},
|
||||
Message: "commit2",
|
||||
Parents: []api.CommitID{"2b988222e844b570959a493f5b07ec020b89e122"},
|
||||
},
|
||||
nil, // file 3
|
||||
{
|
||||
ID: "01bed0ae660668c57539cecaacb4c33d77609f43",
|
||||
Author: gitdomain.Signature{Name: "a", Email: "a@a.com", Date: *mustParseDate("2006-01-02T15:04:05Z", t)},
|
||||
Committer: &gitdomain.Signature{Name: "a", Email: "a@a.com", Date: *mustParseDate("2006-01-02T15:04:05Z", t)},
|
||||
Message: "commit2",
|
||||
Parents: []api.CommitID{"d6ce2e76d171569d81c0afdc4573f461cec17d45"},
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
}
|
||||
|
||||
commits, err := gitserver.NewTestClient(http.DefaultClient, db, gitserverAddresses).GetCommits(ctx, getTestSubRepoPermsChecker("file1", "file3"), repoCommits, true)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error calling getCommits: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(expectedCommits, commits); diff != "" {
|
||||
t.Errorf("unexpected commits (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// get a test sub-repo permissions checker which allows access to all files (so should be a no-op)
|
||||
func getTestSubRepoPermsChecker(noAccessPaths ...string) authz.SubRepoPermissionChecker {
|
||||
checker := authz.NewMockSubRepoPermissionChecker()
|
||||
checker.EnabledFunc.SetDefaultHook(func() bool {
|
||||
return true
|
||||
})
|
||||
checker.PermissionsFunc.SetDefaultHook(func(ctx context.Context, i int32, content authz.RepoContent) (authz.Perms, error) {
|
||||
for _, noAccessPath := range noAccessPaths {
|
||||
if content.Path == noAccessPath {
|
||||
return authz.None, nil
|
||||
}
|
||||
}
|
||||
return authz.Read, nil
|
||||
})
|
||||
return checker
|
||||
}
|
||||
|
||||
func getGitCommandsWithFiles(fileName1, fileName2 string) []string {
|
||||
@ -165,7 +109,7 @@ func mustParseDate(s string, t *testing.T) *time.Time {
|
||||
}
|
||||
|
||||
func TestHead(t *testing.T) {
|
||||
client := gitserver.NewTestClient(http.DefaultClient, database.NewMockDB(), gitserverAddresses)
|
||||
client := gitserver.NewTestClient(http.DefaultClient, database.NewMockDB(), GitserverAddresses)
|
||||
t.Run("basic", func(t *testing.T) {
|
||||
gitCommands := []string{
|
||||
"GIT_COMMITTER_NAME=a GIT_COMMITTER_EMAIL=a@a.com GIT_COMMITTER_DATE=2006-01-02T15:04:05Z git commit --allow-empty -m foo --author='a <a@a.com>' --date 2006-01-02T15:04:05Z",
|
||||
@ -185,38 +129,4 @@ func TestHead(t *testing.T) {
|
||||
t.Fatal("Should exist")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("with sub-repo permissions", func(t *testing.T) {
|
||||
gitCommands := []string{
|
||||
"touch file",
|
||||
"git add file",
|
||||
"GIT_COMMITTER_NAME=a GIT_COMMITTER_EMAIL=a@a.com GIT_COMMITTER_DATE=2006-01-02T15:04:05Z git commit -m foo --author='a <a@a.com>' --date 2006-01-02T15:04:05Z",
|
||||
}
|
||||
repo := MakeGitRepository(t, gitCommands...)
|
||||
ctx := actor.WithActor(context.Background(), &actor.Actor{
|
||||
UID: 1,
|
||||
})
|
||||
checker := getTestSubRepoPermsChecker("file")
|
||||
// call Head() when user doesn't have access to view the commit
|
||||
_, exists, err := client.Head(ctx, checker, repo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if exists {
|
||||
t.Fatalf("exists should be false since the user doesn't have access to view the commit")
|
||||
}
|
||||
readAllChecker := getTestSubRepoPermsChecker()
|
||||
// call Head() when user has access to view the commit; should return expected commit
|
||||
head, exists, err := client.Head(ctx, readAllChecker, repo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantHead := "46619ad353dbe4ed4108ebde9aa59ef676994a0b"
|
||||
if head != wantHead {
|
||||
t.Fatalf("Want %q, got %q", wantHead, head)
|
||||
}
|
||||
if !exists {
|
||||
t.Fatal("Should exist")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,40 +1,18 @@
|
||||
package inttests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/semaphore"
|
||||
|
||||
sglog "github.com/sourcegraph/log"
|
||||
"github.com/sourcegraph/log/logtest"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/gitserver/server"
|
||||
"github.com/sourcegraph/sourcegraph/internal/api"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/gitserver"
|
||||
"github.com/sourcegraph/sourcegraph/internal/httpcli"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
)
|
||||
|
||||
var root string
|
||||
|
||||
// This is a default gitserver test client currently used for RequestRepoUpdate
|
||||
// gitserver calls during invocation of MakeGitRepository function
|
||||
var testGitserverClient gitserver.Client
|
||||
var gitserverAddresses []string
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
InitGitserver()
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if !testing.Verbose() {
|
||||
@ -48,101 +26,11 @@ func TestMain(m *testing.M) {
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// done in init since the go vet analysis "ctrlflow" is tripped up if this is
|
||||
// done as part of TestMain.
|
||||
func init() {
|
||||
// Ignore users configuration in tests
|
||||
os.Setenv("GIT_CONFIG_NOSYSTEM", "true")
|
||||
os.Setenv("HOME", "/dev/null")
|
||||
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
log.Fatalf("listen failed: %s", err)
|
||||
}
|
||||
|
||||
root, err = os.MkdirTemp("", "test")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
db := database.NewMockDB()
|
||||
gr := database.NewMockGitserverRepoStore()
|
||||
db.GitserverReposFunc.SetDefaultReturn(gr)
|
||||
|
||||
srv := &http.Server{
|
||||
Handler: (&server.Server{
|
||||
Logger: sglog.Scoped("server", "the gitserver service"),
|
||||
ObservationCtx: &observation.TestContext,
|
||||
ReposDir: filepath.Join(root, "repos"),
|
||||
GetRemoteURLFunc: func(ctx context.Context, name api.RepoName) (string, error) {
|
||||
return filepath.Join(root, "remotes", string(name)), nil
|
||||
},
|
||||
GetVCSSyncer: func(ctx context.Context, name api.RepoName) (server.VCSSyncer, error) {
|
||||
return &server.GitRepoSyncer{}, nil
|
||||
},
|
||||
GlobalBatchLogSemaphore: semaphore.NewWeighted(32),
|
||||
DB: db,
|
||||
}).Handler(),
|
||||
}
|
||||
go func() {
|
||||
if err := srv.Serve(l); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
serverAddress := l.Addr().String()
|
||||
testGitserverClient = gitserver.NewTestClient(httpcli.InternalDoer, db, []string{serverAddress})
|
||||
gitserverAddresses = []string{serverAddress}
|
||||
}
|
||||
|
||||
var Times = []string{
|
||||
AppleTime("2006-01-02T15:04:05Z"),
|
||||
AppleTime("2014-05-06T19:20:21Z"),
|
||||
}
|
||||
|
||||
// InitGitRepository initializes a new Git repository and runs cmds in a new
|
||||
// temporary directory (returned as dir).
|
||||
func InitGitRepository(t testing.TB, cmds ...string) string {
|
||||
t.Helper()
|
||||
remotes := filepath.Join(root, "remotes")
|
||||
if err := os.MkdirAll(remotes, 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dir, err := os.MkdirTemp(remotes, strings.ReplaceAll(t.Name(), "/", "__"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmds = append([]string{"git init"}, cmds...)
|
||||
for _, cmd := range cmds {
|
||||
out, err := GitCommand(dir, "bash", "-c", cmd).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("Command %q failed. Output was:\n\n%s", cmd, out)
|
||||
}
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
func GitCommand(dir, name string, args ...string) *exec.Cmd {
|
||||
c := exec.Command(name, args...)
|
||||
c.Dir = dir
|
||||
c.Env = append(os.Environ(), "GIT_CONFIG="+path.Join(dir, ".git", "config"))
|
||||
return c
|
||||
}
|
||||
|
||||
// MakeGitRepository calls initGitRepository to create a new Git repository and returns a handle to
|
||||
// it.
|
||||
func MakeGitRepository(t testing.TB, cmds ...string) api.RepoName {
|
||||
t.Helper()
|
||||
dir := InitGitRepository(t, cmds...)
|
||||
repo := api.RepoName(filepath.Base(dir))
|
||||
if resp, err := testGitserverClient.RequestRepoUpdate(context.Background(), repo, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if resp.Error != "" {
|
||||
t.Fatal(resp.Error)
|
||||
}
|
||||
return repo
|
||||
}
|
||||
|
||||
func AppleTime(t string) string {
|
||||
ti, _ := time.Parse(time.RFC3339, t)
|
||||
return ti.Local().Format("200601021504.05")
|
||||
|
||||
@ -35,7 +35,7 @@ func TestGetObject(t *testing.T) {
|
||||
|
||||
for label, test := range tests {
|
||||
t.Run(label, func(t *testing.T) {
|
||||
obj, err := gitserver.NewTestClient(http.DefaultClient, database.NewMockDB(), gitserverAddresses).GetObject(context.Background(), test.repo, test.objectName)
|
||||
obj, err := gitserver.NewTestClient(http.DefaultClient, database.NewMockDB(), GitserverAddresses).GetObject(context.Background(), test.repo, test.objectName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
121
internal/gitserver/integration_tests/test_utils.go
Normal file
121
internal/gitserver/integration_tests/test_utils.go
Normal file
@ -0,0 +1,121 @@
|
||||
package inttests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
sglog "github.com/sourcegraph/log"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/gitserver/server"
|
||||
"github.com/sourcegraph/sourcegraph/internal/api"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/gitserver"
|
||||
"github.com/sourcegraph/sourcegraph/internal/httpcli"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
var root string
|
||||
|
||||
// This is a default gitserver test client currently used for RequestRepoUpdate
|
||||
// gitserver calls during invocation of MakeGitRepository function
|
||||
var (
|
||||
testGitserverClient gitserver.Client
|
||||
GitserverAddresses []string
|
||||
)
|
||||
|
||||
func InitGitserver() {
|
||||
// Ignore users configuration in tests
|
||||
os.Setenv("GIT_CONFIG_NOSYSTEM", "true")
|
||||
os.Setenv("HOME", "/dev/null")
|
||||
logger := sglog.Scoped("gitserver_integration_tests", "")
|
||||
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
logger.Fatal("listen failed", sglog.Error(err))
|
||||
}
|
||||
|
||||
root, err = os.MkdirTemp("", "test")
|
||||
if err != nil {
|
||||
logger.Fatal(err.Error())
|
||||
}
|
||||
|
||||
db := database.NewMockDB()
|
||||
gr := database.NewMockGitserverRepoStore()
|
||||
db.GitserverReposFunc.SetDefaultReturn(gr)
|
||||
|
||||
srv := &http.Server{
|
||||
Handler: (&server.Server{
|
||||
Logger: sglog.Scoped("server", "the gitserver service"),
|
||||
ObservationCtx: &observation.TestContext,
|
||||
ReposDir: filepath.Join(root, "repos"),
|
||||
GetRemoteURLFunc: func(ctx context.Context, name api.RepoName) (string, error) {
|
||||
return filepath.Join(root, "remotes", string(name)), nil
|
||||
},
|
||||
GetVCSSyncer: func(ctx context.Context, name api.RepoName) (server.VCSSyncer, error) {
|
||||
return &server.GitRepoSyncer{}, nil
|
||||
},
|
||||
GlobalBatchLogSemaphore: semaphore.NewWeighted(32),
|
||||
DB: db,
|
||||
}).Handler(),
|
||||
}
|
||||
go func() {
|
||||
if err := srv.Serve(l); err != nil {
|
||||
logger.Fatal(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
serverAddress := l.Addr().String()
|
||||
testGitserverClient = gitserver.NewTestClient(httpcli.InternalDoer, db, []string{serverAddress})
|
||||
GitserverAddresses = []string{serverAddress}
|
||||
}
|
||||
|
||||
// MakeGitRepository calls initGitRepository to create a new Git repository and returns a handle to
|
||||
// it.
|
||||
func MakeGitRepository(t testing.TB, cmds ...string) api.RepoName {
|
||||
t.Helper()
|
||||
dir := InitGitRepository(t, cmds...)
|
||||
repo := api.RepoName(filepath.Base(dir))
|
||||
if resp, err := testGitserverClient.RequestRepoUpdate(context.Background(), repo, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if resp.Error != "" {
|
||||
t.Fatal(resp.Error)
|
||||
}
|
||||
return repo
|
||||
}
|
||||
|
||||
// InitGitRepository initializes a new Git repository and runs cmds in a new
|
||||
// temporary directory (returned as dir).
|
||||
func InitGitRepository(t testing.TB, cmds ...string) string {
|
||||
t.Helper()
|
||||
remotes := filepath.Join(root, "remotes")
|
||||
if err := os.MkdirAll(remotes, 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dir, err := os.MkdirTemp(remotes, strings.ReplaceAll(t.Name(), "/", "__"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmds = append([]string{"git init"}, cmds...)
|
||||
for _, cmd := range cmds {
|
||||
out, err := GitCommand(dir, "bash", "-c", cmd).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("Command %q failed. Output was:\n\n%s", cmd, out)
|
||||
}
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
func GitCommand(dir, name string, args ...string) *exec.Cmd {
|
||||
c := exec.Command(name, args...)
|
||||
c.Dir = dir
|
||||
c.Env = append(os.Environ(), "GIT_CONFIG="+path.Join(dir, ".git", "config"))
|
||||
return c
|
||||
}
|
||||
@ -11,70 +11,13 @@ import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/internal/api"
|
||||
"github.com/sourcegraph/sourcegraph/internal/authz"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/gitserver"
|
||||
"github.com/sourcegraph/sourcegraph/internal/gitserver/gitdomain"
|
||||
"github.com/sourcegraph/sourcegraph/schema"
|
||||
)
|
||||
|
||||
func TestReadDir_SubRepoFiltering(t *testing.T) {
|
||||
ctx := actor.WithActor(context.Background(), &actor.Actor{
|
||||
UID: 1,
|
||||
})
|
||||
gitCommands := []string{
|
||||
"touch file1",
|
||||
"git add file1",
|
||||
"GIT_COMMITTER_NAME=a GIT_COMMITTER_EMAIL=a@a.com GIT_COMMITTER_DATE=2006-01-02T15:04:05Z git commit -m commit1 --author='a <a@a.com>' --date 2006-01-02T15:04:05Z",
|
||||
"mkdir app",
|
||||
"touch app/file2",
|
||||
"git add app",
|
||||
"GIT_COMMITTER_NAME=a GIT_COMMITTER_EMAIL=a@a.com GIT_COMMITTER_DATE=2006-01-02T15:04:05Z git commit -m commit2 --author='a <a@a.com>' --date 2006-01-02T15:04:05Z",
|
||||
}
|
||||
repo := MakeGitRepository(t, gitCommands...)
|
||||
commitID := api.CommitID("b1c725720de2bbd0518731b4a61959797ff345f3")
|
||||
conf.Mock(&conf.Unified{
|
||||
SiteConfiguration: schema.SiteConfiguration{
|
||||
ExperimentalFeatures: &schema.ExperimentalFeatures{
|
||||
SubRepoPermissions: &schema.SubRepoPermissions{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
defer conf.Mock(nil)
|
||||
srpGetter := database.NewMockSubRepoPermsStore()
|
||||
testSubRepoPerms := map[api.RepoName]authz.SubRepoPermissions{
|
||||
repo: {
|
||||
Paths: []string{"/**", "-/app/**"},
|
||||
},
|
||||
}
|
||||
srpGetter.GetByUserFunc.SetDefaultReturn(testSubRepoPerms, nil)
|
||||
checker, err := authz.NewSubRepoPermsClient(srpGetter)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating sub-repo perms client: %s", err)
|
||||
}
|
||||
|
||||
db := database.NewMockDB()
|
||||
gr := database.NewMockGitserverRepoStore()
|
||||
db.GitserverReposFunc.SetDefaultReturn(gr)
|
||||
client := gitserver.NewTestClient(http.DefaultClient, db, gitserverAddresses)
|
||||
files, err := client.ReadDir(ctx, checker, repo, commitID, "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// Because we have a wildcard matcher we still allow directory visibility
|
||||
assert.Len(t, files, 1)
|
||||
assert.Equal(t, "file1", files[0].Name())
|
||||
assert.False(t, files[0].IsDir())
|
||||
}
|
||||
|
||||
func TestRepository_FileSystem(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
@ -116,7 +59,7 @@ func TestRepository_FileSystem(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
client := gitserver.NewTestClient(http.DefaultClient, db, gitserverAddresses)
|
||||
client := gitserver.NewTestClient(http.DefaultClient, db, GitserverAddresses)
|
||||
for label, test := range tests {
|
||||
// notafile should not exist.
|
||||
if _, err := client.Stat(ctx, authz.DefaultSubRepoPermsChecker, test.repo, test.first, "notafile"); !os.IsNotExist(err) {
|
||||
@ -142,7 +85,7 @@ func TestRepository_FileSystem(t *testing.T) {
|
||||
if got, want := "ab771ba54f5571c99ffdae54f44acc7993d9f115", dir1Info.Sys().(gitdomain.ObjectInfo).OID().String(); got != want {
|
||||
t.Errorf("%s: got dir1 OID %q, want %q", label, got, want)
|
||||
}
|
||||
client := gitserver.NewTestClient(http.DefaultClient, db, gitserverAddresses)
|
||||
client := gitserver.NewTestClient(http.DefaultClient, db, GitserverAddresses)
|
||||
|
||||
// dir1 should contain one entry: file1.
|
||||
dir1Entries, err := client.ReadDir(ctx, authz.DefaultSubRepoPermsChecker, test.repo, test.first, "dir1", false)
|
||||
@ -311,7 +254,7 @@ func TestRepository_FileSystem_quoteChars(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
client := gitserver.NewTestClient(http.DefaultClient, db, gitserverAddresses)
|
||||
client := gitserver.NewTestClient(http.DefaultClient, db, GitserverAddresses)
|
||||
for label, test := range tests {
|
||||
commitID, err := client.ResolveRevision(ctx, test.repo, "master", gitserver.ResolveRevisionOptions{})
|
||||
if err != nil {
|
||||
@ -374,7 +317,7 @@ func TestRepository_FileSystem_gitSubmodules(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
client := gitserver.NewTestClient(http.DefaultClient, db, gitserverAddresses)
|
||||
client := gitserver.NewTestClient(http.DefaultClient, db, GitserverAddresses)
|
||||
for label, test := range tests {
|
||||
commitID, err := client.ResolveRevision(ctx, test.repo, "master", gitserver.ResolveRevisionOptions{})
|
||||
if err != nil {
|
||||
|
||||
@ -51,7 +51,7 @@ func indexedSymbolsBranch(ctx context.Context, repo *types.MinimalRepo, commit s
|
||||
return ""
|
||||
}
|
||||
|
||||
func filterZoektResults(ctx context.Context, checker authz.SubRepoPermissionChecker, repo api.RepoName, results []*result.SymbolMatch) ([]*result.SymbolMatch, error) {
|
||||
func FilterZoektResults(ctx context.Context, checker authz.SubRepoPermissionChecker, repo api.RepoName, results []*result.SymbolMatch) ([]*result.SymbolMatch, error) {
|
||||
if !authz.SubRepoEnabled(checker) {
|
||||
return results, nil
|
||||
}
|
||||
@ -197,7 +197,7 @@ func Compute(ctx context.Context, checker authz.SubRepoPermissionChecker, repoNa
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "zoekt symbol search")
|
||||
}
|
||||
results, err = filterZoektResults(ctx, checker, repoName.Name, results)
|
||||
results, err = FilterZoektResults(ctx, checker, repoName.Name, results)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "checking permissions")
|
||||
}
|
||||
|
||||
@ -8,59 +8,8 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/internal/types"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/internal/api"
|
||||
"github.com/sourcegraph/sourcegraph/internal/authz"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/search/result"
|
||||
"github.com/sourcegraph/sourcegraph/schema"
|
||||
)
|
||||
|
||||
func TestFilterZoektResults(t *testing.T) {
|
||||
conf.Mock(&conf.Unified{
|
||||
SiteConfiguration: schema.SiteConfiguration{
|
||||
ExperimentalFeatures: &schema.ExperimentalFeatures{
|
||||
SubRepoPermissions: &schema.SubRepoPermissions{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
t.Cleanup(func() { conf.Mock(nil) })
|
||||
|
||||
repoName := api.RepoName("foo")
|
||||
ctx := context.Background()
|
||||
ctx = actor.WithActor(ctx, &actor.Actor{
|
||||
UID: 1,
|
||||
})
|
||||
checker, err := authz.NewSimpleChecker(repoName, []string{"/**", "-/*_test.go"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
results := []*result.SymbolMatch{
|
||||
{
|
||||
Symbol: result.Symbol{},
|
||||
File: &result.File{
|
||||
Path: "foo.go",
|
||||
},
|
||||
},
|
||||
{
|
||||
Symbol: result.Symbol{},
|
||||
File: &result.File{
|
||||
Path: "foo_test.go",
|
||||
},
|
||||
},
|
||||
}
|
||||
filtered, err := filterZoektResults(ctx, checker, repoName, results)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Len(t, filtered, 1)
|
||||
r := filtered[0]
|
||||
assert.Equal(t, r.File.Path, "foo.go")
|
||||
}
|
||||
|
||||
func TestSearchZoektDoesntPanicWithNilQuery(t *testing.T) {
|
||||
// As soon as we reach Streamer.Search function, we can consider test successful,
|
||||
// that's why we can just mock it.
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
- CodeMonitorStore
|
||||
- EnterpriseDB
|
||||
- PermsStore
|
||||
- SubRepoPermsStore
|
||||
- filename: enterprise/internal/insights/discovery/mocks_temp.go
|
||||
path: github.com/sourcegraph/sourcegraph/enterprise/internal/insights/discovery
|
||||
interfaces:
|
||||
@ -32,6 +33,9 @@
|
||||
path: github.com/sourcegraph/sourcegraph/internal/authz
|
||||
interfaces:
|
||||
- SubRepoPermissionChecker
|
||||
- filename: enterprise/internal/authz/subrepoperms/mocks_temp.go
|
||||
path: github.com/sourcegraph/sourcegraph/enterprise/internal/authz/subrepoperms
|
||||
interfaces:
|
||||
- SubRepoPermissionsGetter
|
||||
- filename: internal/database/mocks_temp.go
|
||||
path: github.com/sourcegraph/sourcegraph/internal/database
|
||||
@ -57,7 +61,6 @@
|
||||
- SearchContextsStore
|
||||
- SecurityEventLogsStore
|
||||
- SettingsStore
|
||||
- SubRepoPermsStore
|
||||
- TemporarySettingsStore
|
||||
- UserCredentialsStore
|
||||
- UserEmailsStore
|
||||
|
||||
@ -51,6 +51,8 @@ env:
|
||||
{ "Name": "frontend", "Host": "127.0.0.1:6063" },
|
||||
{ "Name": "gitserver-0", "Host": "127.0.0.1:3551" },
|
||||
{ "Name": "gitserver-1", "Host": "127.0.0.1:3552" },
|
||||
{ "Name": "oss-gitserver-0", "Host": "127.0.0.1:3551" },
|
||||
{ "Name": "oss-gitserver-1", "Host": "127.0.0.1:3552" },
|
||||
{ "Name": "searcher", "Host": "127.0.0.1:6069" },
|
||||
{ "Name": "oss-symbols", "Host": "127.0.0.1:6071" },
|
||||
{ "Name": "symbols", "Host": "127.0.0.1:6071" },
|
||||
@ -189,7 +191,7 @@ commands:
|
||||
if [ -n "$DELVE" ]; then
|
||||
export GCFLAGS='all=-N -l'
|
||||
fi
|
||||
go build -gcflags="$GCFLAGS" -o .bin/gitserver github.com/sourcegraph/sourcegraph/cmd/gitserver
|
||||
go build -gcflags="$GCFLAGS" -o .bin/gitserver github.com/sourcegraph/sourcegraph/enterprise/cmd/gitserver
|
||||
checkBinary: .bin/gitserver
|
||||
env: &gitserverenv
|
||||
HOSTNAME: 127.0.0.1:3178
|
||||
@ -197,6 +199,41 @@ commands:
|
||||
- lib
|
||||
- internal
|
||||
- cmd/gitserver
|
||||
- enterprise/internal
|
||||
- enterprise/cmd/gitserver
|
||||
|
||||
oss-gitserver-template: &oss_gitserver_template
|
||||
cmd: .bin/oss-gitserver
|
||||
install: |
|
||||
if [ -n "$DELVE" ]; then
|
||||
export GCFLAGS='all=-N -l'
|
||||
fi
|
||||
go build -gcflags="$GCFLAGS" -o .bin/oss-gitserver github.com/sourcegraph/sourcegraph/cmd/gitserver
|
||||
checkBinary: .bin/oss-gitserver
|
||||
env: &oss_gitserverenv
|
||||
HOSTNAME: 127.0.0.1:3178
|
||||
watch:
|
||||
- lib
|
||||
- internal
|
||||
- cmd/gitserver
|
||||
|
||||
oss-gitserver-0:
|
||||
<<: *oss_gitserver_template
|
||||
env:
|
||||
<<: *gitserverenv
|
||||
HOSTNAME: 127.0.0.1:3501
|
||||
GITSERVER_ADDR: 127.0.0.1:3501
|
||||
SRC_REPOS_DIR: $HOME/.sourcegraph/repos_1
|
||||
SRC_PROF_HTTP: 127.0.0.1:3551
|
||||
|
||||
oss-gitserver-1:
|
||||
<<: *oss_gitserver_template
|
||||
env:
|
||||
<<: *oss_gitserverenv
|
||||
HOSTNAME: 127.0.0.1:3502
|
||||
GITSERVER_ADDR: 127.0.0.1:3502
|
||||
SRC_REPOS_DIR: $HOME/.sourcegraph/repos_2
|
||||
SRC_PROF_HTTP: 127.0.0.1:3552
|
||||
|
||||
# This is only here to stay backwards-compatible with people's custom
|
||||
# `sg.config.overwrite.yaml` files
|
||||
@ -794,8 +831,8 @@ commandsets:
|
||||
- oss-worker
|
||||
- oss-repo-updater
|
||||
- oss-symbols
|
||||
- gitserver-0
|
||||
- gitserver-1
|
||||
- oss-gitserver-0
|
||||
- oss-gitserver-1
|
||||
- searcher
|
||||
- oss-web
|
||||
- caddy
|
||||
|
||||
Loading…
Reference in New Issue
Block a user