mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 19:51:50 +00:00
[cleanup] Remove old GitHub App code (#50933)
This commit is contained in:
parent
a0e3cbf7c6
commit
1eee46e679
9
cmd/gitserver/shared/BUILD.bazel
generated
9
cmd/gitserver/shared/BUILD.bazel
generated
@ -24,7 +24,6 @@ go_library(
|
||||
"//internal/env",
|
||||
"//internal/extsvc",
|
||||
"//internal/extsvc/crates",
|
||||
"//internal/extsvc/github",
|
||||
"//internal/extsvc/gomodproxy",
|
||||
"//internal/extsvc/npm",
|
||||
"//internal/extsvc/pypi",
|
||||
@ -43,12 +42,10 @@ go_library(
|
||||
"//internal/requestclient",
|
||||
"//internal/service",
|
||||
"//internal/trace",
|
||||
"//internal/types",
|
||||
"//lib/errors",
|
||||
"//schema",
|
||||
"@com_github_json_iterator_go//:go",
|
||||
"@com_github_sourcegraph_log//:log",
|
||||
"@com_github_tidwall_gjson//:gjson",
|
||||
"@org_golang_x_sync//semaphore",
|
||||
"@org_golang_x_time//rate",
|
||||
],
|
||||
@ -66,16 +63,10 @@ go_test(
|
||||
"//cmd/gitserver/server",
|
||||
"//internal/api",
|
||||
"//internal/codeintel/dependencies",
|
||||
"//internal/conf",
|
||||
"//internal/database",
|
||||
"//internal/extsvc",
|
||||
"//internal/extsvc/github",
|
||||
"//internal/types",
|
||||
"//lib/errors",
|
||||
"//schema",
|
||||
"@com_github_sourcegraph_log//:log",
|
||||
"@com_github_sourcegraph_log//logtest",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
"database/sql"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
@ -16,7 +15,6 @@ import (
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/sourcegraph/log"
|
||||
"github.com/tidwall/gjson"
|
||||
"golang.org/x/sync/semaphore"
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
@ -33,7 +31,6 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/internal/env"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc/crates"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc/github"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc/gomodproxy"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc/npm"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc/pypi"
|
||||
@ -52,7 +49,6 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/internal/requestclient"
|
||||
"github.com/sourcegraph/sourcegraph/internal/service"
|
||||
"github.com/sourcegraph/sourcegraph/internal/trace"
|
||||
"github.com/sourcegraph/sourcegraph/internal/types"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
"github.com/sourcegraph/sourcegraph/schema"
|
||||
)
|
||||
@ -141,7 +137,7 @@ func Main(ctx context.Context, observationCtx *observation.Context, ready servic
|
||||
ReposDir: config.ReposDir,
|
||||
DesiredPercentFree: wantPctFree2,
|
||||
GetRemoteURLFunc: func(ctx context.Context, repo api.RepoName) (string, error) {
|
||||
return getRemoteURLFunc(ctx, externalServiceStore, repoStore, nil, repo)
|
||||
return getRemoteURLFunc(ctx, externalServiceStore, repoStore, repo)
|
||||
},
|
||||
GetVCSSyncer: func(ctx context.Context, repo api.RepoName) (server.VCSSyncer, error) {
|
||||
return getVCSSyncer(ctx, externalServiceStore, repoStore, dependenciesSvc, repo, config.ReposDir, config.CoursierCacheDir)
|
||||
@ -314,7 +310,6 @@ func getRemoteURLFunc(
|
||||
ctx context.Context,
|
||||
externalServiceStore database.ExternalServiceStore,
|
||||
repoStore database.RepoStore,
|
||||
cli httpcli.Doer,
|
||||
repo api.RepoName,
|
||||
) (string, error) {
|
||||
r, err := repoStore.GetByName(ctx, repo)
|
||||
@ -338,89 +333,11 @@ func getRemoteURLFunc(
|
||||
continue
|
||||
}
|
||||
|
||||
if svc.Kind == extsvc.KindGitHub {
|
||||
config, err := conf.GitHubAppConfig()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if config.Configured() {
|
||||
rawConfig, err := svc.Config.Decrypt(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
installationID := gjson.Get(rawConfig, "githubAppInstallationID").Int()
|
||||
if installationID > 0 {
|
||||
rawConfig, err = editGitHubAppExternalServiceConfigToken(ctx, externalServiceStore, svc, rawConfig, config.PrivateKey, config.AppID, installationID, cli)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "edit GitHub App external service config token")
|
||||
}
|
||||
svc.Config.Set(rawConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
return repos.EncryptableCloneURL(ctx, log.Scoped("repos.CloneURL", ""), svc.Kind, svc.Config, r)
|
||||
}
|
||||
return "", errors.Errorf("no sources for %q", repo)
|
||||
}
|
||||
|
||||
// editGitHubAppExternalServiceConfigToken updates the "token" field of the given
|
||||
// external service config through GitHub App and returns a new copy of the
|
||||
// config ensuring the token is always valid.
|
||||
func editGitHubAppExternalServiceConfigToken(
|
||||
ctx context.Context,
|
||||
externalServiceStore database.ExternalServiceStore,
|
||||
svc *types.ExternalService,
|
||||
rawConfig string,
|
||||
privateKey []byte,
|
||||
appID string,
|
||||
installationID int64,
|
||||
cli httpcli.Doer,
|
||||
) (string, error) {
|
||||
logger := log.Scoped("editGitHubAppExternalServiceConfigToken", "updates the 'token' field of the given external service")
|
||||
|
||||
baseURL, err := url.Parse(gjson.Get(rawConfig, "url").String())
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "parse base URL")
|
||||
}
|
||||
|
||||
apiURL, githubDotCom := github.APIRoot(baseURL)
|
||||
if !githubDotCom {
|
||||
return "", errors.Errorf("only GitHub App on GitHub.com is supported, but got %q", baseURL)
|
||||
}
|
||||
|
||||
var c schema.GitHubConnection
|
||||
if err := jsonc.Unmarshal(rawConfig, &c); err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
appAuther, err := github.NewGitHubAppAuthenticator(appID, privateKey)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "new authenticator with GitHub App")
|
||||
}
|
||||
|
||||
scopedLogger := logger.Scoped("app", "github client for github app").With(log.String("appID", appID))
|
||||
appClient := github.NewV3Client(scopedLogger, svc.URN(), apiURL, appAuther, cli)
|
||||
|
||||
token, err := appClient.CreateAppInstallationAccessToken(ctx, installationID)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "get or renew GitHub App installation access token")
|
||||
}
|
||||
|
||||
// NOTE: Use `json.Marshal` breaks the actual external service config that fails
|
||||
// validation with missing "repos" property when no repository has been selected,
|
||||
// due to generated JSON tag of ",omitempty".
|
||||
config, err := jsonc.Edit(rawConfig, token.Token, "token")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "edit token")
|
||||
}
|
||||
err = externalServiceStore.Update(ctx, conf.Get().AuthProviders, svc.ID,
|
||||
&database.ExternalServiceUpdate{
|
||||
Config: &config,
|
||||
TokenExpiresAt: token.ExpiresAt,
|
||||
})
|
||||
return config, err
|
||||
}
|
||||
|
||||
func getVCSSyncer(
|
||||
ctx context.Context,
|
||||
externalServiceStore database.ExternalServiceStore,
|
||||
|
||||
@ -1,31 +1,22 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"flag"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"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/codeintel/dependencies"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc/github"
|
||||
"github.com/sourcegraph/sourcegraph/internal/types"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
"github.com/sourcegraph/sourcegraph/schema"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@ -71,78 +62,6 @@ func (c *mockDoer) Do(r *http.Request) (*http.Response, error) {
|
||||
return c.do(r)
|
||||
}
|
||||
|
||||
func TestGetRemoteURLFunc_GitHubApp(t *testing.T) {
|
||||
externalServiceStore := database.NewMockExternalServiceStore()
|
||||
externalServiceStore.GetByIDFunc.SetDefaultReturn(
|
||||
&types.ExternalService{
|
||||
ID: 1,
|
||||
Kind: extsvc.KindGitHub,
|
||||
Config: extsvc.NewUnencryptedConfig(`
|
||||
{
|
||||
"url": "https://github.com",
|
||||
"githubAppInstallationID": "21994992",
|
||||
"repos": [],
|
||||
"authorization": {},
|
||||
}`),
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
repoStore := database.NewMockRepoStore()
|
||||
repoStore.GetByNameFunc.SetDefaultReturn(
|
||||
&types.Repo{
|
||||
ID: 1,
|
||||
Name: "test-repo-1",
|
||||
Sources: map[string]*types.SourceInfo{
|
||||
"extsvc:github:1": {
|
||||
ID: "extsvc:github:1",
|
||||
CloneURL: "https://github.com/sgtest/test-repo-1",
|
||||
},
|
||||
},
|
||||
Metadata: &github.Repository{
|
||||
URL: "https://github.com/sgtest/test-repo-1",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
doer := &mockDoer{
|
||||
do: func(r *http.Request) (*http.Response, error) {
|
||||
want := "http://github-proxy/app/installations/21994992/access_tokens"
|
||||
if r.URL.String() != want {
|
||||
return nil, errors.Errorf("URL: want %q but got %q", want, r.URL)
|
||||
}
|
||||
|
||||
body := `{"token": "mock-installtion-access-token"}`
|
||||
return &http.Response{
|
||||
Status: http.StatusText(http.StatusOK),
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(bytes.NewReader([]byte(body))),
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
const bogusKey = `LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCUEFJQkFBSkJBUEpIaWprdG1UMUlLYUd0YTVFZXAzQVo5Q2VPZUw4alBESUZUN3dRZ0tabXQzRUZxRGhCCk93bitRVUhKdUs5Zm92UkROSmVWTDJvWTVCT0l6NHJ3L0cwQ0F3RUFBUUpCQU1BK0o5Mks0d2NQVllsbWMrM28KcHU5NmlKTkNwMmp5Nm5hK1pEQlQzK0VvSUo1VFJGdnN3R2kvTHUzZThYUWwxTDNTM21ub0xPSlZNcTF0bUxOMgpIY0VDSVFEK3daeS83RlYxUEFtdmlXeWlYVklETzJnNWJOaUJlbmdKQ3hFa3Nia1VtUUloQVBOMlZaczN6UFFwCk1EVG9vTlJXcnl0RW1URERkamdiOFpzTldYL1JPRGIxQWlCZWNKblNVQ05TQllLMXJ5VTFmNURTbitoQU9ZaDkKWDFBMlVnTDE3bWhsS1FJaEFPK2JMNmRDWktpTGZORWxmVnRkTUtxQnFjNlBIK01heFU2VzlkVlFvR1dkQWlFQQptdGZ5cE9zYTFiS2hFTDg0blovaXZFYkJyaVJHalAya3lERHYzUlg0V0JrPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=`
|
||||
conf.Mock(&conf.Unified{
|
||||
SiteConfiguration: schema.SiteConfiguration{
|
||||
GitHubApp: &schema.GitHubApp{
|
||||
AppID: "404",
|
||||
PrivateKey: bogusKey,
|
||||
Slug: "test-app",
|
||||
ClientID: "Iv1.deb0cd1048cf1040",
|
||||
ClientSecret: "c6d0ed049217a89825c457898c701c30324f873b",
|
||||
},
|
||||
},
|
||||
})
|
||||
defer conf.Mock(nil)
|
||||
|
||||
got, err := getRemoteURLFunc(context.Background(), externalServiceStore, repoStore, doer, "test-repo-1")
|
||||
require.NoError(t, err)
|
||||
|
||||
want := "https://x-access-token:mock-installtion-access-token@github.com/sgtest/test-repo-1"
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func TestGetVCSSyncer(t *testing.T) {
|
||||
tempReposDir, err := os.MkdirTemp("", "TestGetVCSSyncer")
|
||||
if err != nil {
|
||||
|
||||
48
enterprise/cmd/frontend/internal/app/BUILD.bazel
generated
48
enterprise/cmd/frontend/internal/app/BUILD.bazel
generated
@ -1,48 +0,0 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "app",
|
||||
srcs = ["app.go"],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/enterprise/cmd/frontend/internal/app",
|
||||
visibility = ["//enterprise/cmd/frontend:__subpackages__"],
|
||||
deps = [
|
||||
"//cmd/frontend/enterprise",
|
||||
"//cmd/frontend/envvar",
|
||||
"//enterprise/internal/codeintel",
|
||||
"//internal/actor",
|
||||
"//internal/conf",
|
||||
"//internal/conf/conftypes",
|
||||
"//internal/database",
|
||||
"//internal/extsvc",
|
||||
"//internal/extsvc/github",
|
||||
"//internal/jsonc",
|
||||
"//internal/observation",
|
||||
"//internal/types",
|
||||
"//lib/errors",
|
||||
"@com_github_google_go_github_v41//github",
|
||||
"@com_github_inconshreveable_log15//:log15",
|
||||
"@com_github_sourcegraph_log//:log",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "app_test",
|
||||
timeout = "short",
|
||||
srcs = [
|
||||
"app_test.go",
|
||||
"mocks_test.go",
|
||||
],
|
||||
embed = [":app"],
|
||||
deps = [
|
||||
"//internal/actor",
|
||||
"//internal/conf",
|
||||
"//internal/database",
|
||||
"//internal/extsvc",
|
||||
"//internal/types",
|
||||
"//schema",
|
||||
"@com_github_derision_test_go_mockgen//testutil/require",
|
||||
"@com_github_google_go_github_v41//github",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
||||
@ -1,229 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
gogithub "github.com/google/go-github/v41/github"
|
||||
"github.com/inconshreveable/log15"
|
||||
"github.com/sourcegraph/log"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/frontend/enterprise"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/frontend/envvar"
|
||||
"github.com/sourcegraph/sourcegraph/enterprise/internal/codeintel"
|
||||
"github.com/sourcegraph/sourcegraph/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf/conftypes"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc/github"
|
||||
"github.com/sourcegraph/sourcegraph/internal/jsonc"
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
"github.com/sourcegraph/sourcegraph/internal/types"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
// Init initializes the app endpoints.
|
||||
func Init(
|
||||
ctx context.Context,
|
||||
observationCtx *observation.Context,
|
||||
db database.DB,
|
||||
codeIntelServices codeintel.Services,
|
||||
_ conftypes.UnifiedWatchable,
|
||||
enterpriseServices *enterprise.Services,
|
||||
) error {
|
||||
config, err := conf.GitHubAppConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting GitHubApp config")
|
||||
}
|
||||
if !config.Configured() {
|
||||
enterpriseServices.NewGitHubAppSetupHandler = func() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
_, _ = w.Write([]byte("Sourcegraph GitHub App setup is not enabled"))
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
auther, err := github.NewGitHubAppAuthenticator(config.AppID, config.PrivateKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "new authenticator with GitHub App")
|
||||
}
|
||||
|
||||
apiURL, err := url.Parse("https://github.com")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parse github.com")
|
||||
}
|
||||
client := github.NewV3Client(log.Scoped("app.github.v3", "github v3 client for frontend app"), extsvc.URNGitHubApp, apiURL, auther, nil)
|
||||
|
||||
enterpriseServices.NewGitHubAppSetupHandler = func() http.Handler {
|
||||
return newGitHubAppSetupHandler(db, apiURL, client)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type githubClient interface {
|
||||
GetAppInstallation(ctx context.Context, installationID int64) (*gogithub.Installation, error)
|
||||
}
|
||||
|
||||
func isSetupActionValid(setupAction string) bool {
|
||||
for _, a := range []string{"install", "request"} {
|
||||
if setupAction == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// DescryptWithPrivatekey decrypts a message using a provided private key byte string in PEM format.
|
||||
func DecryptWithPrivateKey(encodedMsg string, privateKey []byte) (string, error) {
|
||||
block, _ := pem.Decode(privateKey)
|
||||
|
||||
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "parse private key")
|
||||
}
|
||||
|
||||
hash := sha256.New()
|
||||
plaintext, err := rsa.DecryptOAEP(hash, rand.Reader, key, []byte(encodedMsg), nil)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "decrypt message")
|
||||
}
|
||||
|
||||
return string(plaintext), nil
|
||||
}
|
||||
|
||||
// EncryptWithPrivatekey encrypts a message using a provided private key byte string in PEM format.
|
||||
// The public key used for encryption is derived from the provided private key.
|
||||
func EncryptWithPrivateKey(msg string, privateKey []byte) ([]byte, error) {
|
||||
block, _ := pem.Decode(privateKey)
|
||||
|
||||
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse private key")
|
||||
}
|
||||
|
||||
hash := sha256.New()
|
||||
ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, &key.PublicKey, []byte(msg), nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "encrypt message")
|
||||
}
|
||||
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
func newGitHubAppSetupHandler(db database.DB, apiURL *url.URL, client githubClient) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
responseServerError := func(statusCode int, msg string, err error) {
|
||||
w.WriteHeader(statusCode)
|
||||
_, _ = w.Write([]byte(msg))
|
||||
log15.Error(msg, "error", err)
|
||||
}
|
||||
|
||||
setupAction := r.URL.Query().Get("setup_action")
|
||||
|
||||
if !isSetupActionValid(setupAction) {
|
||||
err := errors.Newf("Invalid setup action %q", setupAction)
|
||||
responseServerError(http.StatusBadRequest, err.Error(), err)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Double check that this code still works as intended with the
|
||||
// removal org org-based installations on dotcom
|
||||
a := actor.FromContext(r.Context())
|
||||
if !a.IsAuthenticated() || !envvar.SourcegraphDotComMode() {
|
||||
if setupAction == "install" {
|
||||
gitHubAppConfig := conf.SiteConfig().GitHubApp
|
||||
privateKey, err := base64.StdEncoding.DecodeString(gitHubAppConfig.PrivateKey)
|
||||
if err != nil {
|
||||
responseServerError(http.StatusBadRequest, "Error while decoding encryption key", err)
|
||||
return
|
||||
}
|
||||
|
||||
installationID, err := strconv.ParseInt(r.URL.Query().Get("installation_id"), 10, 64)
|
||||
if err != nil {
|
||||
responseServerError(http.StatusBadRequest, `The "installation_id" is not a valid integer`, err)
|
||||
return
|
||||
}
|
||||
|
||||
ins, err := client.GetAppInstallation(r.Context(), installationID)
|
||||
if err != nil {
|
||||
responseServerError(http.StatusInternalServerError, `Failed to get the installation information using the "installation_id"`, err)
|
||||
return
|
||||
}
|
||||
|
||||
var displayName string
|
||||
if ins.Account.Login != nil {
|
||||
displayName = fmt.Sprintf("GitHub (%s)", *ins.Account.Login)
|
||||
}
|
||||
|
||||
err = createCodeHostConnectionForInstallation(r.Context(), db, installationID, displayName, apiURL.String())
|
||||
if err != nil {
|
||||
responseServerError(http.StatusInternalServerError, "Failed to create code host connection", err)
|
||||
return
|
||||
}
|
||||
|
||||
encryptedInstallationID, err := EncryptWithPrivateKey(r.URL.Query().Get("installation_id"), privateKey)
|
||||
if err != nil {
|
||||
responseServerError(http.StatusBadRequest, "Error while encrypting installation ID", err)
|
||||
return
|
||||
}
|
||||
|
||||
base64InstallationID := base64.StdEncoding.EncodeToString(encryptedInstallationID)
|
||||
http.Redirect(w, r, "/install-github-app-success?installation_id="+url.QueryEscape(base64InstallationID), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
if setupAction == "request" {
|
||||
http.Redirect(w, r, "/install-github-app-request", http.StatusFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
state := r.URL.Query().Get("state")
|
||||
if state == "" && setupAction == "install" {
|
||||
http.Redirect(w, r, "/settings", http.StatusFound)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func createCodeHostConnectionForInstallation(ctx context.Context, db database.DB, installationID int64, displayName, apiURL string) error {
|
||||
if displayName == "" {
|
||||
displayName = "GitHub App"
|
||||
}
|
||||
|
||||
config := extsvc.NewUnencryptedConfig(fmt.Sprintf(`{"url": "%s", "repos": []} `, apiURL))
|
||||
svc := &types.ExternalService{
|
||||
Kind: extsvc.KindGitHub,
|
||||
DisplayName: displayName,
|
||||
Config: config,
|
||||
}
|
||||
|
||||
currentConfig, err := svc.Config.Decrypt(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newConfig, err := jsonc.Edit(currentConfig, strconv.FormatInt(installationID, 10), "githubAppInstallationID")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newConfig, err = jsonc.Edit(newConfig, false, "pending")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
svc.Config = extsvc.NewUnencryptedConfig(newConfig)
|
||||
|
||||
return db.ExternalServices().Upsert(ctx, svc)
|
||||
}
|
||||
@ -1,122 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
mockrequire "github.com/derision-test/go-mockgen/testutil/require"
|
||||
gogithub "github.com/google/go-github/v41/github"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
sgactor "github.com/sourcegraph/sourcegraph/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc"
|
||||
"github.com/sourcegraph/sourcegraph/internal/types"
|
||||
"github.com/sourcegraph/sourcegraph/schema"
|
||||
)
|
||||
|
||||
func TestNewGitHubAppSetupHandler(t *testing.T) {
|
||||
const bogusKey = `LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlDWHdJQkFBS0JnUUMvemZJMXRqSlUzbHIxQlFIUHMxYzFvbUNrMFJ0RVVQYXpKTTRYaXEvTmo5ZW13cXhnCmdseVNraEgrU0tKa1hJeXdzTjlBc2hpWm9EOFF1UEtKdy9pQkwrQXNNemU2VmlEa0hoMFMza0hqdGxNVWRlQTMKanMwVFluNnh2TXh1Z3lwTVdKV3BBaS9pdm5Ta3pYNmdtRStjVU4rbDl4aUlWNkx0bGl4M0hla3Nyd0lEQVFBQgpBb0dCQUt3bFp6SVY2RzZMY3c5ZUF4WXJYQ1pqS21KQzJ6b2hnSW1naXVoT0xTTk42cnRkRmVFNG4yVmRmSkRCCkdCOERnYkpEek52Ly9GeEZtdFNqYWV1RDI5QnBBVThvUnQzczBsOXo2K1hkaG5XRzhoNHdDOW83MUJiVTcyUVcKVkIyL0hCTkJMSzBSY1BqV2lvWnp5a3lhQ0dKYnhSemRNV3hMME8xcjJ0MmRtZWRCQWtFQTQ5RmoxVWlWWER5dApKcDVBdkJudk1WUHdjdlI3UnpRNko0RmdydlcwQWRlMzRjSVVPcCtuZm1vaTlZN0dNdGpzS2ZPSWJtZjdnZ3pxCllSWDl1bkQwNXdKQkFOZUlFaDlGSzV3L05lbUpRaXY5bzB6YW9RUXV6WGE3QzdaU3F6RExsaCttWUhVNXBBRFUKalZHS056TnJEaUp6c1NrOWNwb1d0Nk5FdmVHVFNtWkdTUGtDUVFDWFhkQ1BMYUxQbmlFTnY2Z1RVc2Z5Wm1zawpkZnhTMndpb3B2V3VTZUpJTnlRZUErMmM1ZWRMdndsclRtbXg3eDg2NEd5TnJ0a1ZGNi9Dd2ZITHByR1JBa0VBCmxvYnUrUzNxL2szYlRrWlJrNzJwN2tRSERvL05hYTNLeVVSRlVXZnVhaDVkNGFFbkhIbFdWV3R0a0JpbG40UWoKYUFVRlkvNlh0SXlPL050TXE4OU1xUUpCQUpzZ0U4UmlCZXh1aEtLcjZCVjVsSzBMdjU2QlFDaGpkUS84TFFqZAppQWYwYlJ4RE1IS0lzVHFHSW15UzMwVTNvdVkrekxqSVQxb3Fibm0rTFY5VEdtcz0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0=`
|
||||
conf.Mock(&conf.Unified{SiteConfiguration: schema.SiteConfiguration{GitHubApp: &schema.GitHubApp{PrivateKey: bogusKey}}})
|
||||
defer conf.Mock(nil)
|
||||
|
||||
users := database.NewMockUserStore()
|
||||
users.GetByCurrentAuthUserFunc.SetDefaultReturn(&types.User{}, nil)
|
||||
|
||||
externalServices := database.NewMockExternalServiceStore()
|
||||
|
||||
db := database.NewMockDB()
|
||||
db.UsersFunc.SetDefaultReturn(users)
|
||||
db.ExternalServicesFunc.SetDefaultReturn(externalServices)
|
||||
|
||||
apiURL, err := url.Parse("https://github.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
client := NewMockGithubClient()
|
||||
client.GetAppInstallationFunc.SetDefaultReturn(
|
||||
&gogithub.Installation{
|
||||
Account: &gogithub.User{
|
||||
Login: gogithub.String("abc-org"),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "/.setup/github-app-cloud?installation_id=21994992&setup_action=install&state=T3JnOjE%3D", nil)
|
||||
|
||||
require.Nil(t, err)
|
||||
|
||||
h := newGitHubAppSetupHandler(db, apiURL, client)
|
||||
|
||||
t.Run("user not logged in (no actor in context)", func(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
h.ServeHTTP(resp, req)
|
||||
|
||||
assert.Equal(t, http.StatusFound, resp.Code)
|
||||
|
||||
uri, err := url.ParseRequestURI(resp.Header().Get("Location"))
|
||||
require.Nil(t, err)
|
||||
queryVals := uri.Query()
|
||||
|
||||
installationID := queryVals.Get("installation_id")
|
||||
|
||||
decodedKey, err := base64.StdEncoding.DecodeString(bogusKey)
|
||||
require.Nil(t, err)
|
||||
|
||||
installationIDBytes, err := base64.StdEncoding.DecodeString(installationID)
|
||||
require.Nil(t, err)
|
||||
|
||||
decryptedID, err := DecryptWithPrivateKey(string(installationIDBytes), decodedKey)
|
||||
require.Nil(t, err)
|
||||
|
||||
assert.Equal(t, decryptedID, "21994992")
|
||||
})
|
||||
|
||||
t.Run("invalid setup action", func(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
badReq, err := http.NewRequest(http.MethodGet, "/.setup/github-app-cloud?installation_id=21994992&setup_action=incorrect&state=T3JnOjE%3D", nil)
|
||||
require.Nil(t, err)
|
||||
|
||||
h.ServeHTTP(resp, badReq)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, resp.Code)
|
||||
assert.Equal(t, `Invalid setup action "incorrect"`, resp.Body.String())
|
||||
})
|
||||
|
||||
ctx := sgactor.WithActor(req.Context(), &sgactor.Actor{UID: 1})
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
t.Run("create new", func(t *testing.T) {
|
||||
externalServices.UpsertFunc.SetDefaultHook(func(ctx context.Context, svcs ...*types.ExternalService) error {
|
||||
require.Len(t, svcs, 1)
|
||||
|
||||
svc := svcs[0]
|
||||
assert.Equal(t, extsvc.KindGitHub, svc.Kind)
|
||||
assert.Equal(t, "GitHub (abc-org)", svc.DisplayName)
|
||||
|
||||
wantConfig := extsvc.NewUnencryptedConfig(`{
|
||||
"url": "https://github.com",
|
||||
"repos": [],
|
||||
"githubAppInstallationID": "21994992",
|
||||
"pending": false
|
||||
} `)
|
||||
assert.Equal(t, wantConfig, svc.Config)
|
||||
return nil
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
h.ServeHTTP(resp, req)
|
||||
|
||||
assert.Equal(t, http.StatusFound, resp.Code)
|
||||
assert.True(t, strings.Contains(resp.Header().Get("Location"), "/install-github-app-success?installation_id="))
|
||||
|
||||
mockrequire.Called(t, externalServices.UpsertFunc)
|
||||
})
|
||||
}
|
||||
177
enterprise/cmd/frontend/internal/app/mocks_test.go
generated
177
enterprise/cmd/frontend/internal/app/mocks_test.go
generated
@ -1,177 +0,0 @@
|
||||
// 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 app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
github "github.com/google/go-github/v41/github"
|
||||
)
|
||||
|
||||
// MockGithubClient is a mock implementation of the githubClient interface
|
||||
// (from the package
|
||||
// github.com/sourcegraph/sourcegraph/enterprise/cmd/frontend/internal/app)
|
||||
// used for unit testing.
|
||||
type MockGithubClient struct {
|
||||
// GetAppInstallationFunc is an instance of a mock function object
|
||||
// controlling the behavior of the method GetAppInstallation.
|
||||
GetAppInstallationFunc *GithubClientGetAppInstallationFunc
|
||||
}
|
||||
|
||||
// NewMockGithubClient creates a new mock of the githubClient interface. All
|
||||
// methods return zero values for all results, unless overwritten.
|
||||
func NewMockGithubClient() *MockGithubClient {
|
||||
return &MockGithubClient{
|
||||
GetAppInstallationFunc: &GithubClientGetAppInstallationFunc{
|
||||
defaultHook: func(context.Context, int64) (r0 *github.Installation, r1 error) {
|
||||
return
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewStrictMockGithubClient creates a new mock of the githubClient
|
||||
// interface. All methods panic on invocation, unless overwritten.
|
||||
func NewStrictMockGithubClient() *MockGithubClient {
|
||||
return &MockGithubClient{
|
||||
GetAppInstallationFunc: &GithubClientGetAppInstallationFunc{
|
||||
defaultHook: func(context.Context, int64) (*github.Installation, error) {
|
||||
panic("unexpected invocation of MockGithubClient.GetAppInstallation")
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// surrogateMockGithubClient is a copy of the githubClient interface (from
|
||||
// the package
|
||||
// github.com/sourcegraph/sourcegraph/enterprise/cmd/frontend/internal/app).
|
||||
// It is redefined here as it is unexported in the source package.
|
||||
type surrogateMockGithubClient interface {
|
||||
GetAppInstallation(context.Context, int64) (*github.Installation, error)
|
||||
}
|
||||
|
||||
// NewMockGithubClientFrom creates a new mock of the MockGithubClient
|
||||
// interface. All methods delegate to the given implementation, unless
|
||||
// overwritten.
|
||||
func NewMockGithubClientFrom(i surrogateMockGithubClient) *MockGithubClient {
|
||||
return &MockGithubClient{
|
||||
GetAppInstallationFunc: &GithubClientGetAppInstallationFunc{
|
||||
defaultHook: i.GetAppInstallation,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GithubClientGetAppInstallationFunc describes the behavior when the
|
||||
// GetAppInstallation method of the parent MockGithubClient instance is
|
||||
// invoked.
|
||||
type GithubClientGetAppInstallationFunc struct {
|
||||
defaultHook func(context.Context, int64) (*github.Installation, error)
|
||||
hooks []func(context.Context, int64) (*github.Installation, error)
|
||||
history []GithubClientGetAppInstallationFuncCall
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// GetAppInstallation delegates to the next hook function in the queue and
|
||||
// stores the parameter and result values of this invocation.
|
||||
func (m *MockGithubClient) GetAppInstallation(v0 context.Context, v1 int64) (*github.Installation, error) {
|
||||
r0, r1 := m.GetAppInstallationFunc.nextHook()(v0, v1)
|
||||
m.GetAppInstallationFunc.appendCall(GithubClientGetAppInstallationFuncCall{v0, v1, r0, r1})
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SetDefaultHook sets function that is called when the GetAppInstallation
|
||||
// method of the parent MockGithubClient instance is invoked and the hook
|
||||
// queue is empty.
|
||||
func (f *GithubClientGetAppInstallationFunc) SetDefaultHook(hook func(context.Context, int64) (*github.Installation, error)) {
|
||||
f.defaultHook = hook
|
||||
}
|
||||
|
||||
// PushHook adds a function to the end of hook queue. Each invocation of the
|
||||
// GetAppInstallation method of the parent MockGithubClient 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 *GithubClientGetAppInstallationFunc) PushHook(hook func(context.Context, int64) (*github.Installation, 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 *GithubClientGetAppInstallationFunc) SetDefaultReturn(r0 *github.Installation, r1 error) {
|
||||
f.SetDefaultHook(func(context.Context, int64) (*github.Installation, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
// PushReturn calls PushHook with a function that returns the given values.
|
||||
func (f *GithubClientGetAppInstallationFunc) PushReturn(r0 *github.Installation, r1 error) {
|
||||
f.PushHook(func(context.Context, int64) (*github.Installation, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
func (f *GithubClientGetAppInstallationFunc) nextHook() func(context.Context, int64) (*github.Installation, 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 *GithubClientGetAppInstallationFunc) appendCall(r0 GithubClientGetAppInstallationFuncCall) {
|
||||
f.mutex.Lock()
|
||||
f.history = append(f.history, r0)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// History returns a sequence of GithubClientGetAppInstallationFuncCall
|
||||
// objects describing the invocations of this function.
|
||||
func (f *GithubClientGetAppInstallationFunc) History() []GithubClientGetAppInstallationFuncCall {
|
||||
f.mutex.Lock()
|
||||
history := make([]GithubClientGetAppInstallationFuncCall, len(f.history))
|
||||
copy(history, f.history)
|
||||
f.mutex.Unlock()
|
||||
|
||||
return history
|
||||
}
|
||||
|
||||
// GithubClientGetAppInstallationFuncCall is an object that describes an
|
||||
// invocation of method GetAppInstallation on an instance of
|
||||
// MockGithubClient.
|
||||
type GithubClientGetAppInstallationFuncCall 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 int64
|
||||
// Result0 is the value of the 1st result returned from this method
|
||||
// invocation.
|
||||
Result0 *github.Installation
|
||||
// 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 GithubClientGetAppInstallationFuncCall) Args() []interface{} {
|
||||
return []interface{}{c.Arg0, c.Arg1}
|
||||
}
|
||||
|
||||
// Results returns an interface slice containing the results of this
|
||||
// invocation.
|
||||
func (c GithubClientGetAppInstallationFuncCall) Results() []interface{} {
|
||||
return []interface{}{c.Result0, c.Result1}
|
||||
}
|
||||
@ -16,7 +16,6 @@ go_library(
|
||||
"//cmd/frontend/auth/providers",
|
||||
"//cmd/frontend/external/session",
|
||||
"//cmd/frontend/globals",
|
||||
"//enterprise/cmd/frontend/internal/app",
|
||||
"//internal/actor",
|
||||
"//internal/conf",
|
||||
"//internal/cookie",
|
||||
|
||||
@ -3,8 +3,6 @@ package oauth
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@ -20,13 +18,9 @@ import (
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/frontend/auth"
|
||||
"github.com/sourcegraph/sourcegraph/cmd/frontend/auth/providers"
|
||||
"github.com/sourcegraph/sourcegraph/enterprise/cmd/frontend/internal/app"
|
||||
"github.com/sourcegraph/sourcegraph/internal/actor"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/env"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc/github"
|
||||
"github.com/sourcegraph/sourcegraph/internal/httpcli"
|
||||
"github.com/sourcegraph/sourcegraph/internal/trace"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
@ -111,75 +105,6 @@ func newOAuthFlowHandler(serviceType string) http.Handler {
|
||||
}
|
||||
p.Callback(p.OAuth2Config()).ServeHTTP(w, req)
|
||||
}))
|
||||
mux.Handle("/install-github-app", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if !conf.GitHubAppEnabled() {
|
||||
http.NotFound(w, req)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, req, "/install-github-app-success", http.StatusFound)
|
||||
}))
|
||||
mux.Handle("/get-github-app-installation", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
logger := log.Scoped("get-github-app-installation", "handler for getting github app installations")
|
||||
|
||||
var privateKey []byte
|
||||
var appID string
|
||||
var err error
|
||||
|
||||
gitHubAppConfig := conf.SiteConfig().GitHubApp
|
||||
privateKey, err = base64.StdEncoding.DecodeString(gitHubAppConfig.PrivateKey)
|
||||
if err != nil {
|
||||
logger.Error("Unexpected error while decoding GitHub App private key.", log.Error(err))
|
||||
http.Error(w, "Unexpected error while fetching installation data.", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
appID = gitHubAppConfig.AppID
|
||||
|
||||
installationIDQueryUnecoded := req.URL.Query().Get("installation_id")
|
||||
|
||||
installationIDParam, err := base64.StdEncoding.DecodeString(installationIDQueryUnecoded)
|
||||
if err != nil {
|
||||
logger.Error("Unexpected error while decoding base64 encoded installation ID.", log.Error(err))
|
||||
http.Error(w, "Unexpected error while fetching installation data.", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
installationIDDecoded, err := app.DecryptWithPrivateKey(string(installationIDParam), privateKey)
|
||||
if err != nil {
|
||||
logger.Error("Unexpected error while decrypting installation ID.", log.Error(err))
|
||||
http.Error(w, "Unexpected error while fetching installation data.", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
installationID, err := strconv.ParseInt(installationIDDecoded, 10, 64)
|
||||
if err != nil {
|
||||
logger.Error("Unexpected error while creating parsing installation ID.", log.Error(err))
|
||||
http.Error(w, "Unexpected error while fetching installation data.", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
auther, err := github.NewGitHubAppAuthenticator(appID, privateKey)
|
||||
if err != nil {
|
||||
logger.Error("Unexpected error while creating Auth token.", log.Error(err))
|
||||
http.Error(w, "Unexpected error while fetching installation data.", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
client := github.NewV3Client(logger,
|
||||
extsvc.URNGitHubApp, &url.URL{Host: "github.com"}, auther, nil)
|
||||
|
||||
installation, err := client.GetAppInstallation(req.Context(), installationID)
|
||||
if err != nil {
|
||||
logger.Error("Unexpected error while fetching installation.", log.Error(err))
|
||||
http.Error(w, "Unexpected error while fetching installation data.", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.NewEncoder(w).Encode(installation)
|
||||
if err != nil {
|
||||
logger.Error("Failed to encode installation data.", log.Error(err))
|
||||
}
|
||||
}))
|
||||
return mux
|
||||
}
|
||||
|
||||
|
||||
@ -106,7 +106,7 @@ func TestChangesetCountsOverTimeIntegration(t *testing.T) {
|
||||
t.Fatalf("Failed to Upsert external service: %s", err)
|
||||
}
|
||||
|
||||
githubSrc, err := repos.NewGithubSource(ctx, logger, db.ExternalServices(), githubExtSvc, cf)
|
||||
githubSrc, err := repos.NewGithubSource(ctx, logger, githubExtSvc, cf)
|
||||
if err != nil {
|
||||
t.Fatal(t)
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ func testGitHubWebhook(db database.DB, userID int32) func(*testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
githubSrc, err := repos.NewGithubSource(ctx, logtest.Scoped(t), db.ExternalServices(), extSvc, cf)
|
||||
githubSrc, err := repos.NewGithubSource(ctx, logtest.Scoped(t), extSvc, cf)
|
||||
if err != nil {
|
||||
t.Fatal(t)
|
||||
}
|
||||
|
||||
1
enterprise/cmd/frontend/shared/BUILD.bazel
generated
1
enterprise/cmd/frontend/shared/BUILD.bazel
generated
@ -12,7 +12,6 @@ go_library(
|
||||
"//cmd/frontend/enterprise",
|
||||
"//cmd/frontend/registry/api",
|
||||
"//cmd/frontend/shared",
|
||||
"//enterprise/cmd/frontend/internal/app",
|
||||
"//enterprise/cmd/frontend/internal/auth",
|
||||
"//enterprise/cmd/frontend/internal/authz",
|
||||
"//enterprise/cmd/frontend/internal/batches",
|
||||
|
||||
@ -13,7 +13,6 @@ import (
|
||||
"github.com/sourcegraph/log"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/cmd/frontend/enterprise"
|
||||
"github.com/sourcegraph/sourcegraph/enterprise/cmd/frontend/internal/app"
|
||||
"github.com/sourcegraph/sourcegraph/enterprise/cmd/frontend/internal/auth"
|
||||
"github.com/sourcegraph/sourcegraph/enterprise/cmd/frontend/internal/authz"
|
||||
"github.com/sourcegraph/sourcegraph/enterprise/cmd/frontend/internal/batches"
|
||||
@ -46,7 +45,6 @@ import (
|
||||
type EnterpriseInitializer = func(context.Context, *observation.Context, database.DB, codeintel.Services, conftypes.UnifiedWatchable, *enterprise.Services) error
|
||||
|
||||
var initFunctions = map[string]EnterpriseInitializer{
|
||||
"app": app.Init,
|
||||
"authz": authz.Init,
|
||||
"batches": batches.Init,
|
||||
"codeintel": codeintelinit.Init,
|
||||
|
||||
@ -161,7 +161,7 @@ func ProvidersFromConfig(
|
||||
enableGithubInternalRepoVisibility = ef.EnableGithubInternalRepoVisibility
|
||||
}
|
||||
|
||||
initResult := github.NewAuthzProviders(db, gitHubConns, cfg.SiteConfig().AuthProviders, enableGithubInternalRepoVisibility)
|
||||
initResult := github.NewAuthzProviders(gitHubConns, cfg.SiteConfig().AuthProviders, enableGithubInternalRepoVisibility)
|
||||
initResult.Append(gitlab.NewAuthzProviders(db, cfg.SiteConfig(), gitLabConns))
|
||||
initResult.Append(bitbucketserver.NewAuthzProviders(bitbucketServerConns))
|
||||
initResult.Append(perforce.NewAuthzProviders(perforceConns))
|
||||
|
||||
7
enterprise/internal/authz/github/BUILD.bazel
generated
7
enterprise/internal/authz/github/BUILD.bazel
generated
@ -3,7 +3,6 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
go_library(
|
||||
name = "github",
|
||||
srcs = [
|
||||
"app.go",
|
||||
"authz.go",
|
||||
"cache.go",
|
||||
"client.go",
|
||||
@ -15,12 +14,10 @@ go_library(
|
||||
"//enterprise/internal/authz/types",
|
||||
"//enterprise/internal/licensing",
|
||||
"//internal/authz",
|
||||
"//internal/conf",
|
||||
"//internal/database",
|
||||
"//internal/extsvc",
|
||||
"//internal/extsvc/auth",
|
||||
"//internal/extsvc/github",
|
||||
"//internal/httpcli",
|
||||
"//internal/rcache",
|
||||
"//internal/types",
|
||||
"//lib/errors",
|
||||
@ -34,7 +31,6 @@ go_test(
|
||||
name = "github_test",
|
||||
timeout = "short",
|
||||
srcs = [
|
||||
"app_test.go",
|
||||
"authz_test.go",
|
||||
"github_test.go",
|
||||
"mocks_test.go",
|
||||
@ -44,8 +40,6 @@ go_test(
|
||||
"//enterprise/internal/licensing",
|
||||
"//internal/api",
|
||||
"//internal/authz",
|
||||
"//internal/conf",
|
||||
"//internal/database",
|
||||
"//internal/extsvc",
|
||||
"//internal/extsvc/auth",
|
||||
"//internal/extsvc/github",
|
||||
@ -53,7 +47,6 @@ go_test(
|
||||
"//lib/errors",
|
||||
"//schema",
|
||||
"@com_github_google_go_cmp//cmp",
|
||||
"@com_github_google_go_github_v31//github",
|
||||
"@com_github_gregjones_httpcache//:httpcache",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/sourcegraph/log"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc/auth"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc/github"
|
||||
"github.com/sourcegraph/sourcegraph/internal/httpcli"
|
||||
"github.com/sourcegraph/sourcegraph/internal/types"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
// newAppProvider creates a new authz Provider for GitHub App.
|
||||
func newAppProvider(
|
||||
db database.DB,
|
||||
svc *types.ExternalService,
|
||||
urn string,
|
||||
baseURL *url.URL,
|
||||
appID string,
|
||||
privateKey []byte,
|
||||
installationID int64,
|
||||
cli httpcli.Doer,
|
||||
) (*Provider, error) {
|
||||
apiURL, _ := github.APIRoot(baseURL)
|
||||
|
||||
var installationAuther auth.AuthenticatorWithRefresh
|
||||
if db != nil { // should only be nil when called by ValidateAuthz
|
||||
var err error
|
||||
installationAuther, err = database.BuildGitHubAppInstallationAuther(db.ExternalServices(), appID, privateKey, urn, apiURL, cli, installationID, svc)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "new GitHub App installation auther")
|
||||
}
|
||||
}
|
||||
|
||||
return &Provider{
|
||||
urn: urn,
|
||||
codeHost: extsvc.NewCodeHost(baseURL, extsvc.TypeGitHub),
|
||||
client: func() (client, error) {
|
||||
logger := log.Scoped("installation", "github client for installation").
|
||||
With(log.String("appID", appID), log.Int64("installationID", installationID))
|
||||
|
||||
return &ClientAdapter{
|
||||
V3Client: github.NewV3Client(logger, urn, apiURL, installationAuther, cli),
|
||||
}, nil
|
||||
},
|
||||
InstallationID: &installationID,
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
||||
@ -1,118 +0,0 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-github/v31/github"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc"
|
||||
"github.com/sourcegraph/sourcegraph/internal/types"
|
||||
"github.com/sourcegraph/sourcegraph/schema"
|
||||
)
|
||||
|
||||
type mockDoer struct {
|
||||
do func(*http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
func (c *mockDoer) Do(r *http.Request) (*http.Response, error) {
|
||||
return c.do(r)
|
||||
}
|
||||
|
||||
func TestNewAppProvider(t *testing.T) {
|
||||
srvHit := false
|
||||
doer := &mockDoer{
|
||||
do: func(r *http.Request) (*http.Response, error) {
|
||||
if r.URL.Path == "/app/installations/1234/access_tokens" {
|
||||
tokenString := "app-token"
|
||||
tokenExpiry := time.Now()
|
||||
token := github.InstallationToken{
|
||||
Token: &tokenString,
|
||||
ExpiresAt: &tokenExpiry,
|
||||
}
|
||||
|
||||
respJSON, err := json.Marshal(token)
|
||||
require.NoError(t, err)
|
||||
|
||||
return &http.Response{
|
||||
Status: http.StatusText(http.StatusCreated),
|
||||
StatusCode: http.StatusCreated,
|
||||
Body: io.NopCloser(bytes.NewReader(respJSON)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
if r.Header.Get("Authorization") != "Bearer app-token" {
|
||||
return &http.Response{
|
||||
Status: http.StatusText(http.StatusUnauthorized),
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
Body: io.NopCloser(bytes.NewReader([]byte(`invalid access token`))),
|
||||
}, nil
|
||||
}
|
||||
|
||||
srvHit = true
|
||||
assert.Equal(t, "Bearer app-token", r.Header.Get("Authorization"))
|
||||
return &http.Response{
|
||||
Status: http.StatusText(http.StatusOK),
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(bytes.NewReader([]byte(`[]`))),
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
config, err := json.Marshal(
|
||||
&schema.GitHubConnection{
|
||||
Url: schema.DefaultGitHubURL,
|
||||
Authorization: &schema.GitHubAuthorization{},
|
||||
GithubAppInstallationID: "1234",
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
encodedBogusKey := `LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCUEFJQkFBSkJBUEpIaWprdG1UMUlLYUd0YTVFZXAzQVo5Q2VPZUw4alBESUZUN3dRZ0tabXQzRUZxRGhCCk93bitRVUhKdUs5Zm92UkROSmVWTDJvWTVCT0l6NHJ3L0cwQ0F3RUFBUUpCQU1BK0o5Mks0d2NQVllsbWMrM28KcHU5NmlKTkNwMmp5Nm5hK1pEQlQzK0VvSUo1VFJGdnN3R2kvTHUzZThYUWwxTDNTM21ub0xPSlZNcTF0bUxOMgpIY0VDSVFEK3daeS83RlYxUEFtdmlXeWlYVklETzJnNWJOaUJlbmdKQ3hFa3Nia1VtUUloQVBOMlZaczN6UFFwCk1EVG9vTlJXcnl0RW1URERkamdiOFpzTldYL1JPRGIxQWlCZWNKblNVQ05TQllLMXJ5VTFmNURTbitoQU9ZaDkKWDFBMlVnTDE3bWhsS1FJaEFPK2JMNmRDWktpTGZORWxmVnRkTUtxQnFjNlBIK01heFU2VzlkVlFvR1dkQWlFQQptdGZ5cE9zYTFiS2hFTDg0blovaXZFYkJyaVJHalAya3lERHYzUlg0V0JrPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=`
|
||||
bogusKey, err := base64.StdEncoding.DecodeString(encodedBogusKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
baseURL, err := url.Parse(schema.DefaultGitHubURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
db := database.NewMockDB()
|
||||
db.ExternalServicesFunc.SetDefaultReturn(database.NewMockExternalServiceStore())
|
||||
t.Run("with non-nil service", func(t *testing.T) {
|
||||
svc := &types.ExternalService{
|
||||
ID: 1,
|
||||
Kind: extsvc.KindGitHub,
|
||||
Config: extsvc.NewUnencryptedConfig(string(config)),
|
||||
}
|
||||
|
||||
provider, err := newAppProvider(db, svc, "", baseURL, "1234", bogusKey, 1234, doer)
|
||||
require.NoError(t, err)
|
||||
|
||||
cli, err := provider.client()
|
||||
require.NoError(t, err)
|
||||
|
||||
// call any endpoint so that test server can check Authorization header
|
||||
_, _, err = cli.ListTeamMembers(context.Background(), "anyOwner", "anyTeam", 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, srvHit, "hit server endpoint")
|
||||
})
|
||||
|
||||
t.Run("with nil service for validation", func(t *testing.T) {
|
||||
var svc *types.ExternalService
|
||||
|
||||
// just validate that a new provider can be created for validation if svc is nil
|
||||
_, err = newAppProvider(db, svc, "", baseURL, "1234", bogusKey, 1234, doer)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
}
|
||||
@ -3,13 +3,10 @@ package github
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
atypes "github.com/sourcegraph/sourcegraph/enterprise/internal/authz/types"
|
||||
"github.com/sourcegraph/sourcegraph/enterprise/internal/licensing"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc"
|
||||
"github.com/sourcegraph/sourcegraph/internal/types"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
@ -33,7 +30,6 @@ type ExternalConnection struct {
|
||||
// desired, callers should use `(*Provider).ValidateConnection` directly to get warnings related
|
||||
// to connection issues.
|
||||
func NewAuthzProviders(
|
||||
db database.DB,
|
||||
conns []*ExternalConnection,
|
||||
authProviders []schema.AuthProviders,
|
||||
enableGithubInternalRepoVisibility bool,
|
||||
@ -59,7 +55,7 @@ func NewAuthzProviders(
|
||||
|
||||
for _, c := range conns {
|
||||
// Initialize authz (permissions) provider.
|
||||
p, err := newAuthzProvider(db, c)
|
||||
p, err := newAuthzProvider(c)
|
||||
if err != nil {
|
||||
initResults.InvalidConnections = append(initResults.InvalidConnections, extsvc.TypeGitHub)
|
||||
initResults.Problems = append(initResults.Problems, err.Error())
|
||||
@ -104,7 +100,6 @@ func NewAuthzProviders(
|
||||
// newAuthzProvider instantiates a provider, or returns nil if authorization is disabled.
|
||||
// Errors returned are "serious problems".
|
||||
func newAuthzProvider(
|
||||
db database.DB,
|
||||
c *ExternalConnection,
|
||||
) (*Provider, error) {
|
||||
if c.Authorization == nil {
|
||||
@ -120,23 +115,6 @@ func newAuthzProvider(
|
||||
return nil, errors.Errorf("could not parse URL for GitHub instance %q: %s", c.Url, err)
|
||||
}
|
||||
|
||||
if c.GithubAppInstallationID != "" {
|
||||
installationID, err := strconv.ParseInt(c.GithubAppInstallationID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse installation ID")
|
||||
}
|
||||
|
||||
config, err := conf.GitHubAppConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !config.Configured() {
|
||||
return nil, errors.Errorf("connection contains an GitHub App installation ID while GitHub App for Sourcegraph is not enabled")
|
||||
}
|
||||
|
||||
return newAppProvider(db, c.ExternalService, c.GitHubConnection.URN, baseURL, config.AppID, config.PrivateKey, installationID, nil)
|
||||
}
|
||||
|
||||
// Disable by default for now
|
||||
if c.Authorization.GroupsCacheTTL <= 0 {
|
||||
c.Authorization.GroupsCacheTTL = -1
|
||||
@ -153,6 +131,6 @@ func newAuthzProvider(
|
||||
// ValidateAuthz validates the authorization fields of the given GitHub external
|
||||
// service config.
|
||||
func ValidateAuthz(c *types.GitHubConnection) error {
|
||||
_, err := newAuthzProvider(nil, &ExternalConnection{GitHubConnection: c})
|
||||
_, err := newAuthzProvider(&ExternalConnection{GitHubConnection: c})
|
||||
return err
|
||||
}
|
||||
|
||||
@ -8,20 +8,14 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/enterprise/internal/licensing"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc/github"
|
||||
"github.com/sourcegraph/sourcegraph/internal/types"
|
||||
"github.com/sourcegraph/sourcegraph/schema"
|
||||
)
|
||||
|
||||
func TestNewAuthzProviders(t *testing.T) {
|
||||
db := database.NewMockDB()
|
||||
db.ExternalServicesFunc.SetDefaultReturn(database.NewMockExternalServiceStore())
|
||||
t.Run("no authorization", func(t *testing.T) {
|
||||
initResults := NewAuthzProviders(
|
||||
db,
|
||||
[]*ExternalConnection{
|
||||
{
|
||||
GitHubConnection: &types.GitHubConnection{
|
||||
@ -48,7 +42,6 @@ func TestNewAuthzProviders(t *testing.T) {
|
||||
t.Run("no matching auth provider", func(t *testing.T) {
|
||||
t.Cleanup(licensing.TestingSkipFeatureChecks())
|
||||
initResults := NewAuthzProviders(
|
||||
db,
|
||||
[]*ExternalConnection{
|
||||
{
|
||||
GitHubConnection: &types.GitHubConnection{
|
||||
@ -82,7 +75,6 @@ func TestNewAuthzProviders(t *testing.T) {
|
||||
t.Run("default case", func(t *testing.T) {
|
||||
t.Cleanup(licensing.TestingSkipFeatureChecks())
|
||||
initResults := NewAuthzProviders(
|
||||
db,
|
||||
[]*ExternalConnection{
|
||||
{
|
||||
GitHubConnection: &types.GitHubConnection{
|
||||
@ -112,7 +104,6 @@ func TestNewAuthzProviders(t *testing.T) {
|
||||
t.Run("license does not have ACLs feature", func(t *testing.T) {
|
||||
t.Cleanup(licensing.MockCheckFeatureError("failed"))
|
||||
initResults := NewAuthzProviders(
|
||||
db,
|
||||
[]*ExternalConnection{
|
||||
{
|
||||
GitHubConnection: &types.GitHubConnection{
|
||||
@ -141,7 +132,6 @@ func TestNewAuthzProviders(t *testing.T) {
|
||||
t.Run("groups cache enabled, but not allowGroupsPermissionsSync", func(t *testing.T) {
|
||||
t.Cleanup(licensing.TestingSkipFeatureChecks())
|
||||
initResults := NewAuthzProviders(
|
||||
db,
|
||||
[]*ExternalConnection{
|
||||
{
|
||||
GitHubConnection: &types.GitHubConnection{
|
||||
@ -180,10 +170,7 @@ func TestNewAuthzProviders(t *testing.T) {
|
||||
github.MockGetAuthenticatedOAuthScopes = func(context.Context) ([]string, error) {
|
||||
return []string{"read:org"}, nil
|
||||
}
|
||||
db := database.NewMockDB()
|
||||
db.ExternalServicesFunc.SetDefaultReturn(database.NewMockExternalServiceStore())
|
||||
initResults := NewAuthzProviders(
|
||||
db,
|
||||
[]*ExternalConnection{
|
||||
{
|
||||
GitHubConnection: &types.GitHubConnection{
|
||||
@ -214,60 +201,5 @@ func TestNewAuthzProviders(t *testing.T) {
|
||||
assert.Empty(t, initResults.Warnings)
|
||||
assert.Empty(t, initResults.InvalidConnections)
|
||||
})
|
||||
|
||||
t.Run("github app installation id available", func(t *testing.T) {
|
||||
t.Cleanup(licensing.TestingSkipFeatureChecks())
|
||||
const bogusKey = `LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCUEFJQkFBSkJBUEpIaWprdG1UMUlLYUd0YTVFZXAzQVo5Q2VPZUw4alBESUZUN3dRZ0tabXQzRUZxRGhCCk93bitRVUhKdUs5Zm92UkROSmVWTDJvWTVCT0l6NHJ3L0cwQ0F3RUFBUUpCQU1BK0o5Mks0d2NQVllsbWMrM28KcHU5NmlKTkNwMmp5Nm5hK1pEQlQzK0VvSUo1VFJGdnN3R2kvTHUzZThYUWwxTDNTM21ub0xPSlZNcTF0bUxOMgpIY0VDSVFEK3daeS83RlYxUEFtdmlXeWlYVklETzJnNWJOaUJlbmdKQ3hFa3Nia1VtUUloQVBOMlZaczN6UFFwCk1EVG9vTlJXcnl0RW1URERkamdiOFpzTldYL1JPRGIxQWlCZWNKblNVQ05TQllLMXJ5VTFmNURTbitoQU9ZaDkKWDFBMlVnTDE3bWhsS1FJaEFPK2JMNmRDWktpTGZORWxmVnRkTUtxQnFjNlBIK01heFU2VzlkVlFvR1dkQWlFQQptdGZ5cE9zYTFiS2hFTDg0blovaXZFYkJyaVJHalAya3lERHYzUlg0V0JrPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=`
|
||||
|
||||
conf.Mock(&conf.Unified{
|
||||
SiteConfiguration: schema.SiteConfiguration{
|
||||
GitHubApp: &schema.GitHubApp{
|
||||
AppID: "1234",
|
||||
ClientID: "1234",
|
||||
ClientSecret: "1234",
|
||||
Slug: "test-app",
|
||||
PrivateKey: bogusKey,
|
||||
},
|
||||
},
|
||||
})
|
||||
defer conf.Mock(nil)
|
||||
|
||||
db := database.NewMockDB()
|
||||
db.ExternalServicesFunc.SetDefaultReturn(database.NewMockExternalServiceStore())
|
||||
initResults := NewAuthzProviders(
|
||||
db,
|
||||
[]*ExternalConnection{
|
||||
{
|
||||
ExternalService: &types.ExternalService{
|
||||
ID: 1,
|
||||
Kind: extsvc.KindGitHub,
|
||||
Config: extsvc.NewEmptyConfig(),
|
||||
},
|
||||
GitHubConnection: &types.GitHubConnection{
|
||||
URN: "extsvc:github:1",
|
||||
GitHubConnection: &schema.GitHubConnection{
|
||||
Url: schema.DefaultGitHubURL,
|
||||
Authorization: &schema.GitHubAuthorization{
|
||||
GroupsCacheTTL: 72,
|
||||
},
|
||||
GithubAppInstallationID: "1234",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
[]schema.AuthProviders{{
|
||||
// falls back to schema.DefaultGitHubURL
|
||||
Github: &schema.GitHubAuthProvider{},
|
||||
}},
|
||||
false,
|
||||
)
|
||||
|
||||
require.Len(t, initResults.Providers, 1, "expect exactly one provider")
|
||||
assert.NotNil(t, initResults.Providers[0])
|
||||
|
||||
assert.Empty(t, initResults.Problems)
|
||||
assert.Empty(t, initResults.Warnings)
|
||||
assert.Empty(t, initResults.InvalidConnections)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -37,8 +37,6 @@ type Provider struct {
|
||||
// become the default behaviour.
|
||||
enableGithubInternalRepoVisibility bool
|
||||
|
||||
InstallationID *int64
|
||||
|
||||
db database.DB
|
||||
}
|
||||
|
||||
@ -346,12 +344,6 @@ func (p *Provider) FetchUserPerms(ctx context.Context, account *extsvc.Account,
|
||||
Expiry: tok.Expiry,
|
||||
}
|
||||
|
||||
if p.InstallationID != nil && p.db != nil {
|
||||
// Only used if created by newAppProvider
|
||||
oauthToken.RefreshFunc = database.GetAccountRefreshAndStoreOAuthTokenFunc(p.db, account.ID, github.GetOAuthContext(p.codeHost.BaseURL.String()))
|
||||
oauthToken.NeedsRefreshBuffer = 5
|
||||
}
|
||||
|
||||
return p.fetchUserPermsByToken(ctx, extsvc.AccountID(account.AccountID), oauthToken, opts)
|
||||
}
|
||||
|
||||
@ -439,9 +431,6 @@ func (p *Provider) FetchRepoPerms(ctx context.Context, repo *extsvc.Repository,
|
||||
|
||||
for _, u := range users {
|
||||
userID := strconv.FormatInt(u.DatabaseID, 10)
|
||||
if p.InstallationID != nil {
|
||||
userID = strconv.FormatInt(*p.InstallationID, 10) + "/" + userID
|
||||
}
|
||||
|
||||
addUserToRepoPerms(extsvc.AccountID(userID))
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ type EnterpriseDB interface {
|
||||
Perms() PermsStore
|
||||
SubRepoPerms() SubRepoPermsStore
|
||||
Codeowners() CodeownersStore
|
||||
GithubApps() gha.GithubAppsStore
|
||||
GitHubApps() gha.GitHubAppsStore
|
||||
}
|
||||
|
||||
func NewEnterpriseDB(db database.DB) EnterpriseDB {
|
||||
@ -54,8 +54,8 @@ func (edb *enterpriseDB) Codeowners() CodeownersStore {
|
||||
return CodeownersWith(basestore.NewWithHandle(edb.Handle()))
|
||||
}
|
||||
|
||||
func (edb *enterpriseDB) GithubApps() gha.GithubAppsStore {
|
||||
return gha.GithubAppsWith(basestore.NewWithHandle(edb.Handle()))
|
||||
func (edb *enterpriseDB) GitHubApps() gha.GitHubAppsStore {
|
||||
return gha.GitHubAppsWith(basestore.NewWithHandle(edb.Handle()))
|
||||
}
|
||||
|
||||
type InsightsDB interface {
|
||||
|
||||
@ -666,11 +666,11 @@ func TestValidateExternalServiceConfig(t *testing.T) {
|
||||
},
|
||||
{
|
||||
kind: extsvc.KindGitHub,
|
||||
desc: "without url, token, githubAppInstallationID, repositoryQuery, repos nor orgs",
|
||||
desc: "without url, token, repositoryQuery, repos nor orgs",
|
||||
config: `{}`,
|
||||
assert: includes(
|
||||
"url is required",
|
||||
"at least one of token or githubAppInstallationID must be set",
|
||||
"token must be set",
|
||||
"at least one of repositoryQuery, repos or orgs must be set",
|
||||
),
|
||||
},
|
||||
@ -685,17 +685,6 @@ func TestValidateExternalServiceConfig(t *testing.T) {
|
||||
}`,
|
||||
assert: equals(`<nil>`),
|
||||
},
|
||||
{
|
||||
kind: extsvc.KindGitHub,
|
||||
desc: "with url, githubAppInstallationID, repos",
|
||||
config: `
|
||||
{
|
||||
"url": "https://github.corp.com",
|
||||
"githubAppInstallationID": "21994992",
|
||||
"repos": [],
|
||||
}`,
|
||||
assert: equals(`<nil>`),
|
||||
},
|
||||
{
|
||||
kind: extsvc.KindGitHub,
|
||||
desc: "with url, token, repos",
|
||||
|
||||
@ -7881,9 +7881,9 @@ type MockEnterpriseDB struct {
|
||||
// FeatureFlagsFunc is an instance of a mock function object controlling
|
||||
// the behavior of the method FeatureFlags.
|
||||
FeatureFlagsFunc *EnterpriseDBFeatureFlagsFunc
|
||||
// GithubAppsFunc is an instance of a mock function object controlling
|
||||
// the behavior of the method GithubApps.
|
||||
GithubAppsFunc *EnterpriseDBGithubAppsFunc
|
||||
// GitHubAppsFunc is an instance of a mock function object controlling
|
||||
// the behavior of the method GitHubApps.
|
||||
GitHubAppsFunc *EnterpriseDBGitHubAppsFunc
|
||||
// GitserverLocalCloneFunc is an instance of a mock function object
|
||||
// controlling the behavior of the method GitserverLocalClone.
|
||||
GitserverLocalCloneFunc *EnterpriseDBGitserverLocalCloneFunc
|
||||
@ -8080,8 +8080,8 @@ func NewMockEnterpriseDB() *MockEnterpriseDB {
|
||||
return
|
||||
},
|
||||
},
|
||||
GithubAppsFunc: &EnterpriseDBGithubAppsFunc{
|
||||
defaultHook: func() (r0 store.GithubAppsStore) {
|
||||
GitHubAppsFunc: &EnterpriseDBGitHubAppsFunc{
|
||||
defaultHook: func() (r0 store.GitHubAppsStore) {
|
||||
return
|
||||
},
|
||||
},
|
||||
@ -8362,9 +8362,9 @@ func NewStrictMockEnterpriseDB() *MockEnterpriseDB {
|
||||
panic("unexpected invocation of MockEnterpriseDB.FeatureFlags")
|
||||
},
|
||||
},
|
||||
GithubAppsFunc: &EnterpriseDBGithubAppsFunc{
|
||||
defaultHook: func() store.GithubAppsStore {
|
||||
panic("unexpected invocation of MockEnterpriseDB.GithubApps")
|
||||
GitHubAppsFunc: &EnterpriseDBGitHubAppsFunc{
|
||||
defaultHook: func() store.GitHubAppsStore {
|
||||
panic("unexpected invocation of MockEnterpriseDB.GitHubApps")
|
||||
},
|
||||
},
|
||||
GitserverLocalCloneFunc: &EnterpriseDBGitserverLocalCloneFunc{
|
||||
@ -8617,8 +8617,8 @@ func NewMockEnterpriseDBFrom(i EnterpriseDB) *MockEnterpriseDB {
|
||||
FeatureFlagsFunc: &EnterpriseDBFeatureFlagsFunc{
|
||||
defaultHook: i.FeatureFlags,
|
||||
},
|
||||
GithubAppsFunc: &EnterpriseDBGithubAppsFunc{
|
||||
defaultHook: i.GithubApps,
|
||||
GitHubAppsFunc: &EnterpriseDBGitHubAppsFunc{
|
||||
defaultHook: i.GitHubApps,
|
||||
},
|
||||
GitserverLocalCloneFunc: &EnterpriseDBGitserverLocalCloneFunc{
|
||||
defaultHook: i.GitserverLocalClone,
|
||||
@ -10159,35 +10159,35 @@ func (c EnterpriseDBFeatureFlagsFuncCall) Results() []interface{} {
|
||||
return []interface{}{c.Result0}
|
||||
}
|
||||
|
||||
// EnterpriseDBGithubAppsFunc describes the behavior when the GithubApps
|
||||
// EnterpriseDBGitHubAppsFunc describes the behavior when the GitHubApps
|
||||
// method of the parent MockEnterpriseDB instance is invoked.
|
||||
type EnterpriseDBGithubAppsFunc struct {
|
||||
defaultHook func() store.GithubAppsStore
|
||||
hooks []func() store.GithubAppsStore
|
||||
history []EnterpriseDBGithubAppsFuncCall
|
||||
type EnterpriseDBGitHubAppsFunc struct {
|
||||
defaultHook func() store.GitHubAppsStore
|
||||
hooks []func() store.GitHubAppsStore
|
||||
history []EnterpriseDBGitHubAppsFuncCall
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// GithubApps delegates to the next hook function in the queue and stores
|
||||
// GitHubApps delegates to the next hook function in the queue and stores
|
||||
// the parameter and result values of this invocation.
|
||||
func (m *MockEnterpriseDB) GithubApps() store.GithubAppsStore {
|
||||
r0 := m.GithubAppsFunc.nextHook()()
|
||||
m.GithubAppsFunc.appendCall(EnterpriseDBGithubAppsFuncCall{r0})
|
||||
func (m *MockEnterpriseDB) GitHubApps() store.GitHubAppsStore {
|
||||
r0 := m.GitHubAppsFunc.nextHook()()
|
||||
m.GitHubAppsFunc.appendCall(EnterpriseDBGitHubAppsFuncCall{r0})
|
||||
return r0
|
||||
}
|
||||
|
||||
// SetDefaultHook sets function that is called when the GithubApps method of
|
||||
// SetDefaultHook sets function that is called when the GitHubApps method of
|
||||
// the parent MockEnterpriseDB instance is invoked and the hook queue is
|
||||
// empty.
|
||||
func (f *EnterpriseDBGithubAppsFunc) SetDefaultHook(hook func() store.GithubAppsStore) {
|
||||
func (f *EnterpriseDBGitHubAppsFunc) SetDefaultHook(hook func() store.GitHubAppsStore) {
|
||||
f.defaultHook = hook
|
||||
}
|
||||
|
||||
// PushHook adds a function to the end of hook queue. Each invocation of the
|
||||
// GithubApps method of the parent MockEnterpriseDB instance invokes the
|
||||
// GitHubApps method of the parent MockEnterpriseDB 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 *EnterpriseDBGithubAppsFunc) PushHook(hook func() store.GithubAppsStore) {
|
||||
func (f *EnterpriseDBGitHubAppsFunc) PushHook(hook func() store.GitHubAppsStore) {
|
||||
f.mutex.Lock()
|
||||
f.hooks = append(f.hooks, hook)
|
||||
f.mutex.Unlock()
|
||||
@ -10195,20 +10195,20 @@ func (f *EnterpriseDBGithubAppsFunc) PushHook(hook func() store.GithubAppsStore)
|
||||
|
||||
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||
// given values.
|
||||
func (f *EnterpriseDBGithubAppsFunc) SetDefaultReturn(r0 store.GithubAppsStore) {
|
||||
f.SetDefaultHook(func() store.GithubAppsStore {
|
||||
func (f *EnterpriseDBGitHubAppsFunc) SetDefaultReturn(r0 store.GitHubAppsStore) {
|
||||
f.SetDefaultHook(func() store.GitHubAppsStore {
|
||||
return r0
|
||||
})
|
||||
}
|
||||
|
||||
// PushReturn calls PushHook with a function that returns the given values.
|
||||
func (f *EnterpriseDBGithubAppsFunc) PushReturn(r0 store.GithubAppsStore) {
|
||||
f.PushHook(func() store.GithubAppsStore {
|
||||
func (f *EnterpriseDBGitHubAppsFunc) PushReturn(r0 store.GitHubAppsStore) {
|
||||
f.PushHook(func() store.GitHubAppsStore {
|
||||
return r0
|
||||
})
|
||||
}
|
||||
|
||||
func (f *EnterpriseDBGithubAppsFunc) nextHook() func() store.GithubAppsStore {
|
||||
func (f *EnterpriseDBGitHubAppsFunc) nextHook() func() store.GitHubAppsStore {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
@ -10221,40 +10221,40 @@ func (f *EnterpriseDBGithubAppsFunc) nextHook() func() store.GithubAppsStore {
|
||||
return hook
|
||||
}
|
||||
|
||||
func (f *EnterpriseDBGithubAppsFunc) appendCall(r0 EnterpriseDBGithubAppsFuncCall) {
|
||||
func (f *EnterpriseDBGitHubAppsFunc) appendCall(r0 EnterpriseDBGitHubAppsFuncCall) {
|
||||
f.mutex.Lock()
|
||||
f.history = append(f.history, r0)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// History returns a sequence of EnterpriseDBGithubAppsFuncCall objects
|
||||
// History returns a sequence of EnterpriseDBGitHubAppsFuncCall objects
|
||||
// describing the invocations of this function.
|
||||
func (f *EnterpriseDBGithubAppsFunc) History() []EnterpriseDBGithubAppsFuncCall {
|
||||
func (f *EnterpriseDBGitHubAppsFunc) History() []EnterpriseDBGitHubAppsFuncCall {
|
||||
f.mutex.Lock()
|
||||
history := make([]EnterpriseDBGithubAppsFuncCall, len(f.history))
|
||||
history := make([]EnterpriseDBGitHubAppsFuncCall, len(f.history))
|
||||
copy(history, f.history)
|
||||
f.mutex.Unlock()
|
||||
|
||||
return history
|
||||
}
|
||||
|
||||
// EnterpriseDBGithubAppsFuncCall is an object that describes an invocation
|
||||
// of method GithubApps on an instance of MockEnterpriseDB.
|
||||
type EnterpriseDBGithubAppsFuncCall struct {
|
||||
// EnterpriseDBGitHubAppsFuncCall is an object that describes an invocation
|
||||
// of method GitHubApps on an instance of MockEnterpriseDB.
|
||||
type EnterpriseDBGitHubAppsFuncCall struct {
|
||||
// Result0 is the value of the 1st result returned from this method
|
||||
// invocation.
|
||||
Result0 store.GithubAppsStore
|
||||
Result0 store.GitHubAppsStore
|
||||
}
|
||||
|
||||
// Args returns an interface slice containing the arguments of this
|
||||
// invocation.
|
||||
func (c EnterpriseDBGithubAppsFuncCall) Args() []interface{} {
|
||||
func (c EnterpriseDBGitHubAppsFuncCall) Args() []interface{} {
|
||||
return []interface{}{}
|
||||
}
|
||||
|
||||
// Results returns an interface slice containing the results of this
|
||||
// invocation.
|
||||
func (c EnterpriseDBGithubAppsFuncCall) Results() []interface{} {
|
||||
func (c EnterpriseDBGitHubAppsFuncCall) Results() []interface{} {
|
||||
return []interface{}{c.Result0}
|
||||
}
|
||||
|
||||
|
||||
@ -13,8 +13,8 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
// Store handles storing and retrieving GitHub Apps from the database.
|
||||
type GithubAppsStore interface {
|
||||
// GitHubAppsStore handles storing and retrieving GitHub Apps from the database.
|
||||
type GitHubAppsStore interface {
|
||||
// Create inserts a new GitHub App into the database.
|
||||
Create(ctx context.Context, app *types.GitHubApp) error
|
||||
|
||||
@ -34,28 +34,28 @@ type GithubAppsStore interface {
|
||||
GetBySlug(ctx context.Context, slug string, baseURL string) (*types.GitHubApp, error)
|
||||
|
||||
// WithEncryptionKey sets encryption key on store. Returns a new GithubAppStore
|
||||
WithEncryptionKey(key encryption.Key) GithubAppsStore
|
||||
WithEncryptionKey(key encryption.Key) GitHubAppsStore
|
||||
}
|
||||
|
||||
// githubAppStore handles storing and retrieving GitHub Apps from the database.
|
||||
type githubAppsStore struct {
|
||||
// gitHubAppStore handles storing and retrieving GitHub Apps from the database.
|
||||
type gitHubAppsStore struct {
|
||||
*basestore.Store
|
||||
|
||||
key encryption.Key
|
||||
}
|
||||
|
||||
func GithubAppsWith(other *basestore.Store) GithubAppsStore {
|
||||
return &githubAppsStore{
|
||||
func GitHubAppsWith(other *basestore.Store) GitHubAppsStore {
|
||||
return &gitHubAppsStore{
|
||||
Store: basestore.NewWithHandle(other.Handle()),
|
||||
}
|
||||
}
|
||||
|
||||
// WithEncryptionKey sets encryption key on store. Returns a new GithubAppStore
|
||||
func (s *githubAppsStore) WithEncryptionKey(key encryption.Key) GithubAppsStore {
|
||||
return &githubAppsStore{Store: s.Store, key: key}
|
||||
func (s *gitHubAppsStore) WithEncryptionKey(key encryption.Key) GitHubAppsStore {
|
||||
return &gitHubAppsStore{Store: s.Store, key: key}
|
||||
}
|
||||
|
||||
func (s *githubAppsStore) getEncryptionKey() encryption.Key {
|
||||
func (s *gitHubAppsStore) getEncryptionKey() encryption.Key {
|
||||
if s.key != nil {
|
||||
return s.key
|
||||
}
|
||||
@ -63,7 +63,7 @@ func (s *githubAppsStore) getEncryptionKey() encryption.Key {
|
||||
}
|
||||
|
||||
// Create inserts a new GitHub App into the database.
|
||||
func (s *githubAppsStore) Create(ctx context.Context, app *types.GitHubApp) error {
|
||||
func (s *gitHubAppsStore) Create(ctx context.Context, app *types.GitHubApp) error {
|
||||
key := s.getEncryptionKey()
|
||||
clientSecret, _, err := encryption.MaybeEncrypt(ctx, key, app.ClientSecret)
|
||||
if err != nil {
|
||||
@ -82,7 +82,7 @@ func (s *githubAppsStore) Create(ctx context.Context, app *types.GitHubApp) erro
|
||||
}
|
||||
|
||||
// Delete removes a GitHub App from the database by ID.
|
||||
func (s *githubAppsStore) Delete(ctx context.Context, id int) error {
|
||||
func (s *gitHubAppsStore) Delete(ctx context.Context, id int) error {
|
||||
query := sqlf.Sprintf(`DELETE FROM github_apps WHERE id = %s`, id)
|
||||
return s.Exec(ctx, query)
|
||||
}
|
||||
@ -106,7 +106,7 @@ var scanGithubApp = basestore.NewFirstScanner(func(s dbutil.Scanner) (*types.Git
|
||||
return &app, err
|
||||
})
|
||||
|
||||
func (s *githubAppsStore) decrypt(ctx context.Context, app *types.GitHubApp) (*types.GitHubApp, error) {
|
||||
func (s *gitHubAppsStore) decrypt(ctx context.Context, app *types.GitHubApp) (*types.GitHubApp, error) {
|
||||
key := s.getEncryptionKey()
|
||||
var cs, pk string
|
||||
|
||||
@ -125,7 +125,7 @@ func (s *githubAppsStore) decrypt(ctx context.Context, app *types.GitHubApp) (*t
|
||||
}
|
||||
|
||||
// Update updates a GitHub App in the database and returns the updated struct.
|
||||
func (s *githubAppsStore) Update(ctx context.Context, id int, app *types.GitHubApp) (*types.GitHubApp, error) {
|
||||
func (s *gitHubAppsStore) Update(ctx context.Context, id int, app *types.GitHubApp) (*types.GitHubApp, error) {
|
||||
key := s.getEncryptionKey()
|
||||
clientSecret, _, err := encryption.MaybeEncrypt(ctx, key, app.ClientSecret)
|
||||
if err != nil {
|
||||
@ -151,7 +151,7 @@ func (s *githubAppsStore) Update(ctx context.Context, id int, app *types.GitHubA
|
||||
return s.decrypt(ctx, app)
|
||||
}
|
||||
|
||||
func (s *githubAppsStore) get(ctx context.Context, where *sqlf.Query) (*types.GitHubApp, error) {
|
||||
func (s *gitHubAppsStore) get(ctx context.Context, where *sqlf.Query) (*types.GitHubApp, error) {
|
||||
var selectQuery = `SELECT
|
||||
id,
|
||||
app_id,
|
||||
@ -181,16 +181,16 @@ func (s *githubAppsStore) get(ctx context.Context, where *sqlf.Query) (*types.Gi
|
||||
}
|
||||
|
||||
// GetByID retrieves a GitHub App from the database by ID.
|
||||
func (s *githubAppsStore) GetByID(ctx context.Context, id int) (*types.GitHubApp, error) {
|
||||
func (s *gitHubAppsStore) GetByID(ctx context.Context, id int) (*types.GitHubApp, error) {
|
||||
return s.get(ctx, sqlf.Sprintf(`id = %s`, id))
|
||||
}
|
||||
|
||||
// GetByAppID retrieves a GitHub App from the database by appID and base url
|
||||
func (s *githubAppsStore) GetByAppID(ctx context.Context, appID int, baseURL string) (*types.GitHubApp, error) {
|
||||
func (s *gitHubAppsStore) GetByAppID(ctx context.Context, appID int, baseURL string) (*types.GitHubApp, error) {
|
||||
return s.get(ctx, sqlf.Sprintf(`app_id = %s AND base_url = %s`, appID, baseURL))
|
||||
}
|
||||
|
||||
// GetBySlug retrieves a GitHub App from the database by slug and base url
|
||||
func (s *githubAppsStore) GetBySlug(ctx context.Context, slug string, baseURL string) (*types.GitHubApp, error) {
|
||||
func (s *gitHubAppsStore) GetBySlug(ctx context.Context, slug string, baseURL string) (*types.GitHubApp, error) {
|
||||
return s.get(ctx, sqlf.Sprintf(`slug = %s AND base_url = %s`, slug, baseURL))
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ func TestCreateGitHubApp(t *testing.T) {
|
||||
}
|
||||
logger := logtest.Scoped(t)
|
||||
db := database.NewDB(logger, dbtest.NewDB(logger, t))
|
||||
store := &githubAppsStore{Store: basestore.NewWithHandle(db.Handle())}
|
||||
store := &gitHubAppsStore{Store: basestore.NewWithHandle(db.Handle())}
|
||||
ctx := context.Background()
|
||||
|
||||
app := &types.GitHubApp{
|
||||
@ -60,7 +60,7 @@ func TestDeleteGitHubApp(t *testing.T) {
|
||||
}
|
||||
logger := logtest.Scoped(t)
|
||||
db := database.NewDB(logger, dbtest.NewDB(logger, t))
|
||||
store := githubAppsStore{Store: basestore.NewWithHandle(db.Handle())}
|
||||
store := gitHubAppsStore{Store: basestore.NewWithHandle(db.Handle())}
|
||||
ctx := context.Background()
|
||||
|
||||
app := &types.GitHubApp{
|
||||
@ -95,7 +95,7 @@ func TestUpdateGitHubApp(t *testing.T) {
|
||||
}
|
||||
logger := logtest.Scoped(t)
|
||||
db := database.NewDB(logger, dbtest.NewDB(logger, t))
|
||||
store := githubAppsStore{Store: basestore.NewWithHandle(db.Handle())}
|
||||
store := gitHubAppsStore{Store: basestore.NewWithHandle(db.Handle())}
|
||||
ctx := context.Background()
|
||||
|
||||
app := &types.GitHubApp{
|
||||
@ -149,7 +149,7 @@ func TestGetByID(t *testing.T) {
|
||||
}
|
||||
logger := logtest.Scoped(t)
|
||||
db := database.NewDB(logger, dbtest.NewDB(logger, t))
|
||||
store := &githubAppsStore{Store: basestore.NewWithHandle(db.Handle())}
|
||||
store := &gitHubAppsStore{Store: basestore.NewWithHandle(db.Handle())}
|
||||
ctx := context.Background()
|
||||
|
||||
app1 := &types.GitHubApp{
|
||||
@ -207,7 +207,7 @@ func TestGetByAppID(t *testing.T) {
|
||||
}
|
||||
logger := logtest.Scoped(t)
|
||||
db := database.NewDB(logger, dbtest.NewDB(logger, t))
|
||||
store := &githubAppsStore{Store: basestore.NewWithHandle(db.Handle())}
|
||||
store := &gitHubAppsStore{Store: basestore.NewWithHandle(db.Handle())}
|
||||
ctx := context.Background()
|
||||
|
||||
app1 := &types.GitHubApp{
|
||||
@ -266,7 +266,7 @@ func TestGetBySlug(t *testing.T) {
|
||||
}
|
||||
logger := logtest.Scoped(t)
|
||||
db := database.NewDB(logger, dbtest.NewDB(logger, t))
|
||||
store := &githubAppsStore{Store: basestore.NewWithHandle(db.Handle())}
|
||||
store := &gitHubAppsStore{Store: basestore.NewWithHandle(db.Handle())}
|
||||
ctx := context.Background()
|
||||
|
||||
app1 := &types.GitHubApp{
|
||||
|
||||
1
internal/database/BUILD.bazel
generated
1
internal/database/BUILD.bazel
generated
@ -22,7 +22,6 @@ go_library(
|
||||
"external_services.go",
|
||||
"feature_flags.go",
|
||||
"gen.go",
|
||||
"github_app_helper.go",
|
||||
"gitserver_localclone_jobs.go",
|
||||
"gitserver_repos.go",
|
||||
"global_state.go",
|
||||
|
||||
@ -445,8 +445,8 @@ func validateGitHubConnection(githubValidators []func(*types.GitHubConnection) e
|
||||
)
|
||||
}
|
||||
|
||||
if c.Token == "" && c.GithubAppInstallationID == "" {
|
||||
err = errors.Append(err, errors.New("at least one of token or githubAppInstallationID must be set"))
|
||||
if c.Token == "" {
|
||||
err = errors.Append(err, errors.New("token must be set"))
|
||||
}
|
||||
if c.Repos == nil && c.RepositoryQuery == nil && c.Orgs == nil {
|
||||
err = errors.Append(err, errors.New("at least one of repositoryQuery, repos or orgs must be set"))
|
||||
|
||||
@ -76,9 +76,9 @@ func TestExternalServicesListOptions_sqlConditions(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "has after updated_at",
|
||||
updatedAfter: time.Date(2013, 04, 19, 0, 0, 0, 0, time.UTC),
|
||||
updatedAfter: time.Date(2013, 0o4, 19, 0, 0, 0, 0, time.UTC),
|
||||
wantQuery: "deleted_at IS NULL AND updated_at > $1",
|
||||
wantArgs: []any{time.Date(2013, 04, 19, 0, 0, 0, 0, time.UTC)},
|
||||
wantArgs: []any{time.Date(2013, 0o4, 19, 0, 0, 0, 0, time.UTC)},
|
||||
},
|
||||
{
|
||||
name: "has OnlyCloudDefault",
|
||||
@ -148,7 +148,7 @@ func TestExternalServicesStore_ValidateConfig(t *testing.T) {
|
||||
name: "2 errors",
|
||||
kind: extsvc.KindGitHub,
|
||||
config: `{"url": "https://github.com", "repositoryQuery": ["none"], "token": ""}`,
|
||||
wantErr: "2 errors occurred:\n\t* token: String length must be greater than or equal to 1\n\t* at least one of token or githubAppInstallationID must be set",
|
||||
wantErr: "2 errors occurred:\n\t* token: String length must be greater than or equal to 1\n\t* token must be set",
|
||||
},
|
||||
{
|
||||
name: "no conflicting rate limit",
|
||||
@ -1801,7 +1801,6 @@ func TestExternalServicesStore_Upsert(t *testing.T) {
|
||||
t.Fatalf("Wanted an error")
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
t.Run("one external service", func(t *testing.T) {
|
||||
|
||||
@ -1,100 +0,0 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/schema"
|
||||
|
||||
"github.com/sourcegraph/log"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc/auth"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc/github"
|
||||
"github.com/sourcegraph/sourcegraph/internal/httpcli"
|
||||
"github.com/sourcegraph/sourcegraph/internal/jsonc"
|
||||
"github.com/sourcegraph/sourcegraph/internal/types"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
func getGitHubAppInstallationRefreshFunc(externalServiceStore ExternalServiceStore, installationID int64, svc *types.ExternalService, appClient *github.V3Client) func(context.Context, httpcli.Doer) (string, time.Time, error) {
|
||||
return func(ctx context.Context, cli httpcli.Doer) (string, time.Time, error) {
|
||||
token, err := appClient.CreateAppInstallationAccessToken(ctx, installationID)
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
|
||||
rawConfig, err := svc.Config.Decrypt(context.Background())
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
|
||||
rawConfig, err = jsonc.Edit(rawConfig, token.GetToken(), "token")
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
|
||||
externalServiceStore.Update(context.Background(),
|
||||
conf.Get().AuthProviders,
|
||||
svc.ID,
|
||||
&ExternalServiceUpdate{
|
||||
Config: &rawConfig,
|
||||
TokenExpiresAt: token.ExpiresAt,
|
||||
},
|
||||
)
|
||||
|
||||
return *token.Token, token.GetExpiresAt(), nil
|
||||
}
|
||||
}
|
||||
|
||||
// BuildGitHubAppInstallationAuther builds a GitHub App Installation authenticator.
|
||||
// First it creates a GitHub App Authenticator, as it is required to create App Installation
|
||||
// Access Tokens. The App Authenticator is used in the refresh function of the
|
||||
// Installation Authenticator, as installation tokens cannot be refreshed on their own.
|
||||
// The App Authenticator is used to generate a new token once it expires.
|
||||
func BuildGitHubAppInstallationAuther(
|
||||
externalServiceStore ExternalServiceStore,
|
||||
appID string,
|
||||
pkey []byte,
|
||||
urn string,
|
||||
apiURL *url.URL,
|
||||
cli httpcli.Doer,
|
||||
installationID int64,
|
||||
svc *types.ExternalService,
|
||||
) (auth.AuthenticatorWithRefresh, error) {
|
||||
if svc == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rawConfig, err := svc.Config.Decrypt(context.Background())
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("external service id=%d config error: %s", svc.ID, err)
|
||||
}
|
||||
var c schema.GitHubConnection
|
||||
if err := jsonc.Unmarshal(rawConfig, &c); err != nil {
|
||||
return nil, errors.Errorf("external service id=%d config error: %s", svc.ID, err)
|
||||
}
|
||||
|
||||
appAuther, err := github.NewGitHubAppAuthenticator(appID, pkey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "new authenticator with GitHub App")
|
||||
}
|
||||
|
||||
appClient := github.NewV3Client(
|
||||
log.Scoped("app", "github client for github app").
|
||||
With(log.String("appID", appID)),
|
||||
urn, apiURL, appAuther, cli)
|
||||
|
||||
expiry := time.Time{}
|
||||
if svc.TokenExpiresAt != nil {
|
||||
expiry = *svc.TokenExpiresAt
|
||||
}
|
||||
|
||||
return github.NewGitHubAppInstallationAuthenticator(
|
||||
installationID,
|
||||
c.Token,
|
||||
expiry,
|
||||
getGitHubAppInstallationRefreshFunc(externalServiceStore, installationID, svc, appClient),
|
||||
)
|
||||
}
|
||||
@ -99,8 +99,8 @@ func NewRing(ctx context.Context, keyConfig *schema.EncryptionKeys) (*Ring, erro
|
||||
}
|
||||
}
|
||||
|
||||
if keyConfig.GithubAppKey != nil {
|
||||
r.GithubAppKey, err = NewKey(ctx, keyConfig.GithubAppKey, keyConfig)
|
||||
if keyConfig.GitHubAppKey != nil {
|
||||
r.GithubAppKey, err = NewKey(ctx, keyConfig.GitHubAppKey, keyConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
2
internal/extsvc/github/BUILD.bazel
generated
2
internal/extsvc/github/BUILD.bazel
generated
@ -3,7 +3,6 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
go_library(
|
||||
name = "github",
|
||||
srcs = [
|
||||
"authenticators.go",
|
||||
"common.go",
|
||||
"doc.go",
|
||||
"v3.go",
|
||||
@ -25,7 +24,6 @@ go_library(
|
||||
"//internal/ratelimit",
|
||||
"//internal/trace/ot",
|
||||
"//lib/errors",
|
||||
"@com_github_golang_jwt_jwt_v4//:jwt",
|
||||
"@com_github_google_go_github//github",
|
||||
"@com_github_google_go_github_v41//github",
|
||||
"@com_github_graphql_go_graphql//language/ast",
|
||||
|
||||
@ -1,121 +0,0 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc/auth"
|
||||
"github.com/sourcegraph/sourcegraph/internal/httpcli"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
// gitHubAppAuthenticator implements OAuth Bearer Token authentication for
|
||||
// GitHub Apps.
|
||||
type gitHubAppAuthenticator struct {
|
||||
appID string
|
||||
key *rsa.PrivateKey
|
||||
rawKey []byte
|
||||
}
|
||||
|
||||
// NewGitHubAppAuthenticator constructs a new OAuth Bearer Token
|
||||
// authenticator for GitHub Apps using given appID and private key.
|
||||
func NewGitHubAppAuthenticator(appID string, privateKey []byte) (auth.Authenticator, error) {
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(privateKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse private key")
|
||||
}
|
||||
return &gitHubAppAuthenticator{
|
||||
appID: appID,
|
||||
key: key,
|
||||
rawKey: privateKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Authenticate is a modified version of
|
||||
// https://github.com/bradleyfalzon/ghinstallation/blob/24e56b3fb7669f209134a01eff731d7e2ef72a5c/appsTransport.go#L66.
|
||||
func (token *gitHubAppAuthenticator) Authenticate(r *http.Request) error {
|
||||
// The payload computation is following GitHub App's Ruby example shown in
|
||||
// https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#authenticating-as-a-github-app.
|
||||
//
|
||||
// NOTE: GitHub rejects expiry and issue timestamps that are not an integer,
|
||||
// while the jwt-go library serializes to fractional timestamps. Truncate them
|
||||
// before passing to jwt-go.
|
||||
iss := time.Now().Add(-time.Minute).Truncate(time.Second)
|
||||
exp := iss.Add(10 * time.Minute)
|
||||
claims := &jwt.RegisteredClaims{
|
||||
IssuedAt: jwt.NewNumericDate(iss),
|
||||
ExpiresAt: jwt.NewNumericDate(exp),
|
||||
Issuer: token.appID,
|
||||
}
|
||||
bearer := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
||||
|
||||
signedString, err := bearer.SignedString(token.key)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "sign JWT")
|
||||
}
|
||||
|
||||
r.Header.Set("Authorization", "Bearer "+signedString)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (token *gitHubAppAuthenticator) Hash() string {
|
||||
shaSum := sha256.Sum256(token.rawKey)
|
||||
return hex.EncodeToString(shaSum[:])
|
||||
}
|
||||
|
||||
// GitHubAppInstallationAuthenticator implements OAuth Bearer Token authentication for
|
||||
// GitHub Apps.
|
||||
type GitHubAppInstallationAuthenticator struct {
|
||||
installationID int64
|
||||
InstallationAccessToken string
|
||||
Expiry time.Time
|
||||
refreshFunc func(context.Context, httpcli.Doer) (string, time.Time, error)
|
||||
}
|
||||
|
||||
// NewGitHubAppAuthenticator constructs a new OAuth Bearer Token
|
||||
// authenticator for GitHub Apps using given appID and private key.
|
||||
func NewGitHubAppInstallationAuthenticator(installationID int64, installationAccessToken string, expiry time.Time, refreshFunc func(context.Context, httpcli.Doer) (string, time.Time, error)) (auth.AuthenticatorWithRefresh, error) {
|
||||
return &GitHubAppInstallationAuthenticator{
|
||||
installationID: installationID,
|
||||
InstallationAccessToken: installationAccessToken,
|
||||
Expiry: expiry,
|
||||
refreshFunc: refreshFunc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (token *GitHubAppInstallationAuthenticator) Authenticate(r *http.Request) error {
|
||||
r.Header.Set("Authorization", "Bearer "+token.InstallationAccessToken)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (token *GitHubAppInstallationAuthenticator) Hash() string {
|
||||
shaSum := sha256.Sum256([]byte(token.InstallationAccessToken))
|
||||
return hex.EncodeToString(shaSum[:])
|
||||
}
|
||||
|
||||
func (token *GitHubAppInstallationAuthenticator) NeedsRefresh() bool {
|
||||
if !token.Expiry.IsZero() {
|
||||
return time.Until(token.Expiry) < 5*time.Minute
|
||||
}
|
||||
|
||||
// If no expiry is set we default to False
|
||||
return false
|
||||
}
|
||||
|
||||
func (token *GitHubAppInstallationAuthenticator) Refresh(ctx context.Context, cli httpcli.Doer) error {
|
||||
newToken, newExpiry, err := token.refreshFunc(ctx, cli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
token.InstallationAccessToken = newToken
|
||||
token.Expiry = newExpiry
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -219,11 +219,8 @@ func githubCloneURL(logger log.Logger, repo *github.Repository, cfg *schema.GitH
|
||||
return repo.URL, nil
|
||||
}
|
||||
|
||||
if cfg.GithubAppInstallationID != "" {
|
||||
u.User = url.UserPassword("x-access-token", cfg.Token)
|
||||
} else {
|
||||
u.User = url.UserPassword("oauth2", cfg.Token)
|
||||
}
|
||||
u.User = url.UserPassword("oauth2", cfg.Token)
|
||||
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -15,9 +14,7 @@ import (
|
||||
|
||||
"github.com/sourcegraph/log"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf/reposource"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc/auth"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc/github"
|
||||
@ -48,10 +45,6 @@ type GitHubSource struct {
|
||||
// for an originalHostname of github.com).
|
||||
originalHostname string
|
||||
|
||||
// useGitHubApp indicate whether clients are authenticated through GitHub App,
|
||||
// which may need to hit different API endpoints from regular RESTful API.
|
||||
useGitHubApp bool
|
||||
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
@ -63,7 +56,7 @@ var (
|
||||
)
|
||||
|
||||
// NewGithubSource returns a new GitHubSource from the given external service.
|
||||
func NewGithubSource(ctx context.Context, logger log.Logger, externalServicesStore database.ExternalServiceStore, svc *types.ExternalService, cf *httpcli.Factory) (*GitHubSource, error) {
|
||||
func NewGithubSource(ctx context.Context, logger log.Logger, svc *types.ExternalService, cf *httpcli.Factory) (*GitHubSource, error) {
|
||||
rawConfig, err := svc.Config.Decrypt(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("external service id=%d config error: %s", svc.ID, err)
|
||||
@ -72,7 +65,7 @@ func NewGithubSource(ctx context.Context, logger log.Logger, externalServicesSto
|
||||
if err := jsonc.Unmarshal(rawConfig, &c); err != nil {
|
||||
return nil, errors.Errorf("external service id=%d config error: %s", svc.ID, err)
|
||||
}
|
||||
return newGithubSource(logger, externalServicesStore, svc, &c, cf)
|
||||
return newGithubSource(logger, svc, &c, cf)
|
||||
}
|
||||
|
||||
var githubRemainingGauge = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
@ -88,7 +81,6 @@ var githubRatelimitWaitCounter = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
|
||||
func newGithubSource(
|
||||
logger log.Logger,
|
||||
externalServicesStore database.ExternalServiceStore,
|
||||
svc *types.ExternalService,
|
||||
c *schema.GitHubConnection,
|
||||
cf *httpcli.Factory,
|
||||
@ -166,27 +158,6 @@ func newGithubSource(
|
||||
searchClient = github.NewV3SearchClient(searchClientLogger, urn, apiURL, token, cli)
|
||||
)
|
||||
|
||||
useGitHubApp := false
|
||||
config, err := conf.GitHubAppConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.GithubAppInstallationID != "" && config.Configured() {
|
||||
installationID, err := strconv.ParseInt(c.GithubAppInstallationID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse installation ID")
|
||||
}
|
||||
|
||||
installationAuther, err := database.BuildGitHubAppInstallationAuther(externalServicesStore, config.AppID, config.PrivateKey, urn, apiURL, cli, installationID, svc)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating GitHub App installation authenticator")
|
||||
}
|
||||
|
||||
v3Client = github.NewV3Client(v3ClientLogger, urn, apiURL, installationAuther, cli)
|
||||
v4Client = github.NewV4Client(urn, apiURL, installationAuther, cli)
|
||||
useGitHubApp = true
|
||||
}
|
||||
|
||||
for resource, monitor := range map[string]*ratelimit.Monitor{
|
||||
"rest": v3Client.ExternalRateLimiter(),
|
||||
"graphql": v4Client.ExternalRateLimiter(),
|
||||
@ -217,28 +188,17 @@ func newGithubSource(
|
||||
v4Client: v4Client,
|
||||
searchClient: searchClient,
|
||||
originalHostname: originalHostname,
|
||||
useGitHubApp: useGitHubApp,
|
||||
logger: logger.With(
|
||||
log.Object("GitHubSource",
|
||||
log.Bool("excludeForks", excludeForks),
|
||||
log.Bool("githubDotCom", githubDotCom),
|
||||
log.String("originalHostname", originalHostname),
|
||||
log.Bool("useGitHubApp", useGitHubApp),
|
||||
),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *GitHubSource) WithAuthenticator(a auth.Authenticator) (Source, error) {
|
||||
switch a.(type) {
|
||||
case *auth.OAuthBearerToken,
|
||||
*auth.OAuthBearerTokenWithSSH:
|
||||
break
|
||||
|
||||
default:
|
||||
return nil, newUnsupportedAuthenticatorError("GitHubSource", a)
|
||||
}
|
||||
|
||||
sc := *s
|
||||
sc.v3Client = sc.v3Client.WithAuthenticator(a)
|
||||
sc.v4Client = sc.v4Client.WithAuthenticator(a)
|
||||
@ -254,13 +214,7 @@ type githubResult struct {
|
||||
|
||||
func (s *GitHubSource) ValidateAuthenticator(ctx context.Context) error {
|
||||
var err error
|
||||
if s.config.GithubAppInstallationID != "" {
|
||||
// GitHub App does not have an affiliated user, use another
|
||||
// request instead.
|
||||
_, err = s.v3Client.GetAuthenticatedOAuthScopes(ctx)
|
||||
} else {
|
||||
_, err = s.v3Client.GetAuthenticatedUser(ctx)
|
||||
}
|
||||
_, err = s.v3Client.GetAuthenticatedUser(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -768,9 +722,6 @@ func (s *GitHubSource) listAffiliated(ctx context.Context, results chan *githubR
|
||||
log.Duration("retryAfter", retry),
|
||||
)
|
||||
}()
|
||||
if s.useGitHubApp {
|
||||
return s.v3Client.ListInstallationRepositories(ctx, page)
|
||||
}
|
||||
return s.v3Client.ListAffiliatedRepositories(ctx, github.VisibilityAll, page, 100)
|
||||
})
|
||||
}
|
||||
@ -1100,7 +1051,6 @@ func (q *repositoryQuery) doRecursively(ctx context.Context, results chan *githu
|
||||
// to avoid hitting the GitHub search API 1000 results limit, which would cause
|
||||
// use to miss matches.
|
||||
func (s *repositoryQuery) Refine() bool {
|
||||
|
||||
if s.Created.Size() < 2*time.Second {
|
||||
// Can't refine further than 1 second
|
||||
return false
|
||||
@ -1274,11 +1224,7 @@ func (s *GitHubSource) AffiliatedRepositories(ctx context.Context) ([]types.Code
|
||||
}
|
||||
|
||||
var repos []*github.Repository
|
||||
if s.useGitHubApp {
|
||||
repos, hasNextPage, _, err = s.v3Client.ListInstallationRepositories(ctx, page)
|
||||
} else {
|
||||
repos, hasNextPage, _, err = s.v3Client.ListAffiliatedRepositories(ctx, github.VisibilityAll, page, 100)
|
||||
}
|
||||
repos, hasNextPage, _, err = s.v3Client.ListAffiliatedRepositories(ctx, github.VisibilityAll, page, 100)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -23,7 +23,6 @@ import (
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/api"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf"
|
||||
"github.com/sourcegraph/sourcegraph/internal/database"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc/auth"
|
||||
"github.com/sourcegraph/sourcegraph/internal/extsvc/github"
|
||||
@ -205,7 +204,7 @@ func TestGithubSource_GetRepo(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
githubSrc, err := NewGithubSource(ctx, logtest.Scoped(t), database.NewMockExternalServiceStore(), svc, cf)
|
||||
githubSrc, err := NewGithubSource(ctx, logtest.Scoped(t), svc, cf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -315,7 +314,7 @@ func TestGithubSource_GetRepo_Enterprise(t *testing.T) {
|
||||
defer save(t)
|
||||
|
||||
ctx := context.Background()
|
||||
githubSrc, err := NewGithubSource(ctx, logtest.Scoped(t), database.NewMockExternalServiceStore(), svc, cf)
|
||||
githubSrc, err := NewGithubSource(ctx, logtest.Scoped(t), svc, cf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -349,7 +348,7 @@ func TestMakeRepo_NullCharacter(t *testing.T) {
|
||||
schema := &schema.GitHubConnection{
|
||||
Url: "https://github.com",
|
||||
}
|
||||
s, err := newGithubSource(logtest.Scoped(t), database.NewMockExternalServiceStore(), &svc, schema, nil)
|
||||
s, err := newGithubSource(logtest.Scoped(t), &svc, schema, nil)
|
||||
require.NoError(t, err)
|
||||
repo := s.makeRepo(r)
|
||||
|
||||
@ -404,8 +403,7 @@ func TestGithubSource_makeRepo(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
test.name = "GithubSource_makeRepo_" + test.name
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
s, err := newGithubSource(logtest.Scoped(t), database.NewMockExternalServiceStore(), &svc, test.schema, nil)
|
||||
s, err := newGithubSource(logtest.Scoped(t), &svc, test.schema, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -679,7 +677,7 @@ func TestGithubSource_ListRepos(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
githubSrc, err := NewGithubSource(ctx, logtest.Scoped(t), database.NewMockExternalServiceStore(), svc, cf)
|
||||
githubSrc, err := NewGithubSource(ctx, logtest.Scoped(t), svc, cf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -715,7 +713,7 @@ func TestGithubSource_WithAuthenticator(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
githubSrc, err := NewGithubSource(ctx, logtest.Scoped(t), database.NewMockExternalServiceStore(), svc, nil)
|
||||
githubSrc, err := NewGithubSource(ctx, logtest.Scoped(t), svc, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -732,26 +730,6 @@ func TestGithubSource_WithAuthenticator(t *testing.T) {
|
||||
t.Error("unexpected nil Source")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unsupported", func(t *testing.T) {
|
||||
for name, tc := range map[string]auth.Authenticator{
|
||||
"nil": nil,
|
||||
"BasicAuth": &auth.BasicAuth{},
|
||||
"OAuthClient": &auth.OAuthClient{},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
src, err := githubSrc.WithAuthenticator(tc)
|
||||
if err == nil {
|
||||
t.Error("unexpected nil error")
|
||||
} else if !errors.HasType(err, UnsupportedAuthenticatorError{}) {
|
||||
t.Errorf("unexpected error of type %T: %v", err, err)
|
||||
}
|
||||
if src != nil {
|
||||
t.Errorf("expected non-nil Source: %v", src)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGithubSource_excludes_disabledAndLocked(t *testing.T) {
|
||||
@ -764,7 +742,7 @@ func TestGithubSource_excludes_disabledAndLocked(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
githubSrc, err := NewGithubSource(ctx, logtest.Scoped(t), database.NewMockExternalServiceStore(), svc, nil)
|
||||
githubSrc, err := NewGithubSource(ctx, logtest.Scoped(t), svc, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -791,7 +769,7 @@ func TestGithubSource_GetVersion(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
githubSrc, err := NewGithubSource(ctx, logger, database.NewMockExternalServiceStore(), svc, nil)
|
||||
githubSrc, err := NewGithubSource(ctx, logger, svc, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -827,7 +805,7 @@ func TestGithubSource_GetVersion(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
githubSrc, err := NewGithubSource(ctx, logger, database.NewMockExternalServiceStore(), svc, cf)
|
||||
githubSrc, err := NewGithubSource(ctx, logger, svc, cf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -1150,7 +1128,7 @@ func TestGithubSource_SearchRepositories(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
githubSrc, err := NewGithubSource(ctx, logtest.Scoped(t), database.NewMockExternalServiceStore(), svc, cf)
|
||||
githubSrc, err := NewGithubSource(ctx, logtest.Scoped(t), svc, cf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@ -42,11 +42,9 @@ func NewSourcer(logger log.Logger, db database.DB, cf *httpcli.Factory, decs ...
|
||||
|
||||
// NewSource returns a repository yielding Source from the given ExternalService configuration.
|
||||
func NewSource(ctx context.Context, logger log.Logger, db database.DB, svc *types.ExternalService, cf *httpcli.Factory) (Source, error) {
|
||||
externalServicesStore := db.ExternalServices()
|
||||
|
||||
switch strings.ToUpper(svc.Kind) {
|
||||
case extsvc.KindGitHub:
|
||||
return NewGithubSource(ctx, logger.Scoped("GithubSource", "GitHub repo source"), externalServicesStore, svc, cf)
|
||||
return NewGithubSource(ctx, logger.Scoped("GithubSource", "GitHub repo source"), svc, cf)
|
||||
case extsvc.KindGitLab:
|
||||
return NewGitLabSource(ctx, logger.Scoped("GitLabSource", "GitLab repo source"), svc, cf)
|
||||
case extsvc.KindAzureDevOps:
|
||||
|
||||
@ -81,10 +81,6 @@
|
||||
- path: github.com/sourcegraph/sourcegraph/enterprise/cmd/executor/internal/util
|
||||
interfaces:
|
||||
- CmdRunner
|
||||
- filename: enterprise/cmd/frontend/internal/app/mocks_test.go
|
||||
path: github.com/sourcegraph/sourcegraph/enterprise/cmd/frontend/internal/app
|
||||
interfaces:
|
||||
- githubClient
|
||||
- filename: enterprise/internal/codeintel/uploads/transport/http/mocks_test.go
|
||||
path: github.com/sourcegraph/sourcegraph/internal/uploadhandler
|
||||
interfaces:
|
||||
|
||||
@ -173,7 +173,7 @@
|
||||
}
|
||||
},
|
||||
"githubAppInstallationID": {
|
||||
"description": "The installation ID of the GitHub App.",
|
||||
"description": "DEPRECATED: The installation ID of the GitHub App.",
|
||||
"type": "string"
|
||||
},
|
||||
"pending": {
|
||||
|
||||
@ -676,7 +676,7 @@ type EncryptionKeys struct {
|
||||
EnableCache bool `json:"enableCache,omitempty"`
|
||||
ExecutorSecretKey *EncryptionKey `json:"executorSecretKey,omitempty"`
|
||||
ExternalServiceKey *EncryptionKey `json:"externalServiceKey,omitempty"`
|
||||
GithubAppKey *EncryptionKey `json:"githubAppKey,omitempty"`
|
||||
GitHubAppKey *EncryptionKey `json:"gitHubAppKey,omitempty"`
|
||||
OutboundWebhookKey *EncryptionKey `json:"outboundWebhookKey,omitempty"`
|
||||
UserExternalAccountKey *EncryptionKey `json:"userExternalAccountKey,omitempty"`
|
||||
WebhookKey *EncryptionKey `json:"webhookKey,omitempty"`
|
||||
@ -989,7 +989,7 @@ type GitCommitDescription struct {
|
||||
Version int `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// GitHubApp description: The config options for Sourcegraph GitHub App.
|
||||
// GitHubApp description: DEPRECATED: The config options for Sourcegraph GitHub App.
|
||||
type GitHubApp struct {
|
||||
// AppID description: The app ID of the GitHub App for Sourcegraph.
|
||||
AppID string `json:"appID,omitempty"`
|
||||
@ -1054,7 +1054,7 @@ type GitHubConnection struct {
|
||||
//
|
||||
// If "ssh", Sourcegraph will access GitHub repositories using Git URLs of the form git@github.com:myteam/myproject.git. See the documentation for how to provide SSH private keys and known_hosts: https://docs.sourcegraph.com/admin/repo/auth#repositories-that-need-http-s-or-ssh-authentication.
|
||||
GitURLType string `json:"gitURLType,omitempty"`
|
||||
// GithubAppInstallationID description: The installation ID of the GitHub App.
|
||||
// GithubAppInstallationID description: DEPRECATED: The installation ID of the GitHub App.
|
||||
GithubAppInstallationID string `json:"githubAppInstallationID,omitempty"`
|
||||
// InitialRepositoryEnablement description: Deprecated and ignored field which will be removed entirely in the next release. GitHub repositories can no longer be enabled or disabled explicitly. Configure repositories to be mirrored via "repos", "exclude" and "repositoryQuery" instead.
|
||||
InitialRepositoryEnablement bool `json:"initialRepositoryEnablement,omitempty"`
|
||||
@ -2413,7 +2413,7 @@ type SiteConfiguration struct {
|
||||
ExternalURL string `json:"externalURL,omitempty"`
|
||||
// GitCloneURLToRepositoryName description: JSON array of configuration that maps from Git clone URL to repository name. Sourcegraph automatically resolves remote clone URLs to their proper code host. However, there may be non-remote clone URLs (e.g., in submodule declarations) that Sourcegraph cannot automatically map to a code host. In this case, use this field to specify the mapping. The mappings are tried in the order they are specified and take precedence over automatic mappings.
|
||||
GitCloneURLToRepositoryName []*CloneURLToRepositoryName `json:"git.cloneURLToRepositoryName,omitempty"`
|
||||
// GitHubApp description: The config options for Sourcegraph GitHub App.
|
||||
// GitHubApp description: DEPRECATED: The config options for Sourcegraph GitHub App.
|
||||
GitHubApp *GitHubApp `json:"gitHubApp,omitempty"`
|
||||
// GitLongCommandTimeout description: Maximum number of seconds that a long Git command (e.g. clone or remote update) is allowed to execute. The default is 3600 seconds, or 1 hour.
|
||||
GitLongCommandTimeout int `json:"gitLongCommandTimeout,omitempty"`
|
||||
|
||||
@ -1577,7 +1577,7 @@
|
||||
"group": "Sourcegraph Enterprise license"
|
||||
},
|
||||
"gitHubApp": {
|
||||
"description": "The config options for Sourcegraph GitHub App.",
|
||||
"description": "DEPRECATED: The config options for Sourcegraph GitHub App.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"slug": {
|
||||
@ -1931,7 +1931,7 @@
|
||||
"externalServiceKey": {
|
||||
"$ref": "#/definitions/EncryptionKey"
|
||||
},
|
||||
"githubAppKey": {
|
||||
"gitHubAppKey": {
|
||||
"$ref": "#/definitions/EncryptionKey"
|
||||
},
|
||||
"outboundWebhookKey": {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user