From e2629d7ea6a79b27146a2e9e6c8cead0ff7db290 Mon Sep 17 00:00:00 2001 From: unknwon Date: Wed, 31 Jul 2019 21:15:17 -0700 Subject: [PATCH 1/4] routes/api/srcgraph: initial support for General Protocol --- cmd/web.go | 3 + routes/api/srcgraph/general_protocol.go | 170 ++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 routes/api/srcgraph/general_protocol.go diff --git a/cmd/web.go b/cmd/web.go index 23628289760..2bcd12b85d6 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -39,6 +39,7 @@ import ( "github.com/gogs/gogs/pkg/template" "github.com/gogs/gogs/routes" "github.com/gogs/gogs/routes/admin" + "github.com/gogs/gogs/routes/api/srcgraph" apiv1 "github.com/gogs/gogs/routes/api/v1" "github.com/gogs/gogs/routes/dev" "github.com/gogs/gogs/routes/org" @@ -670,6 +671,8 @@ func runWeb(c *cli.Context) error { c.RequireBasicAuth(setting.Prometheus.BasicAuthUsername, setting.Prometheus.BasicAuthPassword) }, promhttp.Handler()) } + + m.Get("/srcgraph/*", srcgraph.NewHandler()) }) // robots.txt diff --git a/routes/api/srcgraph/general_protocol.go b/routes/api/srcgraph/general_protocol.go new file mode 100644 index 00000000000..fc33469cdd4 --- /dev/null +++ b/routes/api/srcgraph/general_protocol.go @@ -0,0 +1,170 @@ +// Copyright 2019 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package srcgraph + +import ( + "github.com/Unknwon/com" + "net/http" + "time" + + adapter "github.com/sourcegraph/external-service-adapter" + log "gopkg.in/clog.v1" + + "github.com/gogs/gogs/models" + "github.com/gogs/gogs/models/errors" + "github.com/gogs/gogs/pkg/setting" +) + +func NewHandler() http.HandlerFunc { + h := adapter.NewHandler(externalService{}, adapter.Options{ + URL: setting.AppURL, + PathPrefix: "/-/srcgraph", + MaxPageLen: 100000, // Current version returns all repositories at once, does not matter + TokenAsUsername: true, + }) + return h.ServeHTTP +} + +type externalService struct{} + +func (es externalService) ListRepos(ai adapter.AuthInfo, params adapter.Params) ([]*adapter.Repo, adapter.Page, error) { + return es.listUserRepos("", ai, params) +} + +func (es externalService) ListUserRepos(user string, ai adapter.AuthInfo, params adapter.Params) ([]*adapter.Repo, adapter.Page, error) { + return es.listUserRepos(user, ai, params) +} + +func toRepo(r *models.Repository) *adapter.Repo { + var parent *adapter.Repo + if r.IsFork { + parent = toRepo(r.BaseRepo) + } + + cl := r.CloneLink() + return &adapter.Repo{ + ID: com.ToStr(r.ID), + Name: r.Name, + Slug: r.Name, + FullName: r.FullName(), + SCM: "git", + Description: r.Description, + IsPrivate: r.IsPrivate, + Parent: parent, + Links: []adapter.Link{ + {adapter.CloneSSH, cl.SSH}, + {adapter.CloneHTTP, cl.HTTPS}, + }, + } +} + +func (es externalService) listUserRepos(username string, ai adapter.AuthInfo, params adapter.Params) ([]*adapter.Repo, adapter.Page, error) { + authUser, err := userFromAuthInfo(ai) + if err != nil { + if errors.IsUserNotExist(err) { + return nil, adapter.Page{}, errors.New("403 Forbidden") + } + log.Error(2, "Failed to get user from auth info: %v", err) + return nil, adapter.Page{}, errors.New("500 Internal Server Error") + } + + // Fall back to authenticated user + if username == "" { + username = authUser.Name + } + + user, err := models.GetUserByName(username) + if err != nil { + if errors.IsUserNotExist(err) { + return nil, adapter.Page{}, errors.New("404 Not Found") + } + log.Error(2, "Failed to get user by username %q: %v", username, err) + return nil, adapter.Page{}, errors.New("500 Internal Server Error") + } + + // Only list public repositories if user requests someone else's repository list, + // or an organization isn't a member of. + var ownRepos []*models.Repository + if user.IsOrganization() { + ownRepos, _, err = user.GetUserRepositories(authUser.ID, params.Page, user.NumRepos) + } else { + ownRepos, err = models.GetUserRepositories(&models.UserRepoOptions{ + UserID: user.ID, + Private: authUser.ID == user.ID, + Page: params.Page, + PageSize: user.NumRepos, + }) + } + if err != nil { + log.Error(2, "Failed to get repositories of user %q: %v", username, err) + return nil, adapter.Page{}, errors.New("500 Internal Server Error") + } + + if err = models.RepositoryList(ownRepos).LoadAttributes(); err != nil { + log.Error(2, "Failed to load attributes of repositories: %v", err) + return nil, adapter.Page{}, errors.New("500 Internal Server Error") + } + + // Early return for querying other user's repositories + if authUser.ID != user.ID { + repos := make([]*adapter.Repo, len(ownRepos)) + for i := range ownRepos { + repos[i] = toRepo(ownRepos[i]) + } + return repos, adapter.Page{Last: 1}, nil + } + + accessibleRepos, err := user.GetRepositoryAccesses() + if err != nil { + log.Error(2, "Failed to get accessible repositories of user %q: %v", username, err) + return nil, adapter.Page{}, errors.New("500 Internal Server Error") + } + + numOwnRepos := len(ownRepos) + repos := make([]*adapter.Repo, numOwnRepos+len(accessibleRepos)) + for i := range ownRepos { + repos[i] = toRepo(ownRepos[i]) + } + + i := numOwnRepos + for repo := range accessibleRepos { + repos[i] = toRepo(repo) + i++ + } + + return repos, adapter.Page{Last: 1}, nil +} + +func userFromAuthInfo(ai adapter.AuthInfo) (*models.User, error) { + u, err := models.UserLogin(ai.Username, ai.Password, -1) + if err != nil && !errors.IsUserNotExist(err) { + return nil, err + } + + if u != nil { + if u.IsEnabledTwoFactor() { + return nil, errors.New( + "User with two-factor authentication enabled cannot perform HTTP/HTTPS operations via plain username and password." + + " Please create and use personal access token on user settings page.") + } + return u, nil + } + + t, err := models.GetAccessTokenBySHA(ai.Username) + if err != nil { + if models.IsErrAccessTokenEmpty(err) || models.IsErrAccessTokenNotExist(err) { + return nil, errors.UserNotExist{} + } + return nil, err + } + t.Updated = time.Now() + + u, err = models.GetUserByID(t.UID) + if err != nil { + return nil, err + } + + return u, models.UpdateAccessToken(t) +} From e68845782017ecc810225ab314e0ab5ffdbe22f6 Mon Sep 17 00:00:00 2001 From: unknwon Date: Thu, 1 Aug 2019 18:07:21 -0700 Subject: [PATCH 2/4] routes/api/srcgraph: remove option field --- routes/api/srcgraph/general_protocol.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/routes/api/srcgraph/general_protocol.go b/routes/api/srcgraph/general_protocol.go index fc33469cdd4..a9932c39169 100644 --- a/routes/api/srcgraph/general_protocol.go +++ b/routes/api/srcgraph/general_protocol.go @@ -19,10 +19,9 @@ import ( func NewHandler() http.HandlerFunc { h := adapter.NewHandler(externalService{}, adapter.Options{ - URL: setting.AppURL, - PathPrefix: "/-/srcgraph", - MaxPageLen: 100000, // Current version returns all repositories at once, does not matter - TokenAsUsername: true, + URL: setting.AppURL, + PathPrefix: "/-/srcgraph", + MaxPageLen: 100000, // Current version returns all repositories at once, does not matter }) return h.ServeHTTP } From ad927c5950526d82327faf019931f26a8a36bb57 Mon Sep 17 00:00:00 2001 From: unknwon Date: Sat, 3 Aug 2019 15:30:13 -0700 Subject: [PATCH 3/4] routes/api/srcgraph: rename struct to match adapter package --- routes/api/srcgraph/general_protocol.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/routes/api/srcgraph/general_protocol.go b/routes/api/srcgraph/general_protocol.go index a9932c39169..d59fa6468c4 100644 --- a/routes/api/srcgraph/general_protocol.go +++ b/routes/api/srcgraph/general_protocol.go @@ -18,7 +18,7 @@ import ( ) func NewHandler() http.HandlerFunc { - h := adapter.NewHandler(externalService{}, adapter.Options{ + h := adapter.NewHandler(externalServicer{}, adapter.Options{ URL: setting.AppURL, PathPrefix: "/-/srcgraph", MaxPageLen: 100000, // Current version returns all repositories at once, does not matter @@ -26,13 +26,13 @@ func NewHandler() http.HandlerFunc { return h.ServeHTTP } -type externalService struct{} +type externalServicer struct{} -func (es externalService) ListRepos(ai adapter.AuthInfo, params adapter.Params) ([]*adapter.Repo, adapter.Page, error) { +func (es externalServicer) ListRepos(ai adapter.AuthInfo, params adapter.Params) ([]*adapter.Repo, adapter.Page, error) { return es.listUserRepos("", ai, params) } -func (es externalService) ListUserRepos(user string, ai adapter.AuthInfo, params adapter.Params) ([]*adapter.Repo, adapter.Page, error) { +func (es externalServicer) ListUserRepos(user string, ai adapter.AuthInfo, params adapter.Params) ([]*adapter.Repo, adapter.Page, error) { return es.listUserRepos(user, ai, params) } @@ -59,7 +59,7 @@ func toRepo(r *models.Repository) *adapter.Repo { } } -func (es externalService) listUserRepos(username string, ai adapter.AuthInfo, params adapter.Params) ([]*adapter.Repo, adapter.Page, error) { +func (es externalServicer) listUserRepos(username string, ai adapter.AuthInfo, params adapter.Params) ([]*adapter.Repo, adapter.Page, error) { authUser, err := userFromAuthInfo(ai) if err != nil { if errors.IsUserNotExist(err) { From f1a4b8683b2338b198114786a0f4cba14e8d07e8 Mon Sep 17 00:00:00 2001 From: unknwon Date: Mon, 19 Aug 2019 07:48:43 +0800 Subject: [PATCH 4/4] go fmt --- routes/api/srcgraph/general_protocol.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/routes/api/srcgraph/general_protocol.go b/routes/api/srcgraph/general_protocol.go index d59fa6468c4..8b493106af0 100644 --- a/routes/api/srcgraph/general_protocol.go +++ b/routes/api/srcgraph/general_protocol.go @@ -5,10 +5,11 @@ package srcgraph import ( - "github.com/Unknwon/com" "net/http" "time" + "github.com/Unknwon/com" + adapter "github.com/sourcegraph/external-service-adapter" log "gopkg.in/clog.v1" @@ -137,12 +138,12 @@ func (es externalServicer) listUserRepos(username string, ai adapter.AuthInfo, p } func userFromAuthInfo(ai adapter.AuthInfo) (*models.User, error) { - u, err := models.UserLogin(ai.Username, ai.Password, -1) - if err != nil && !errors.IsUserNotExist(err) { - return nil, err - } + if ai.Method == adapter.AuthBasic { + u, err := models.UserLogin(ai.Username, ai.Password, -1) + if err != nil { + return nil, err + } - if u != nil { if u.IsEnabledTwoFactor() { return nil, errors.New( "User with two-factor authentication enabled cannot perform HTTP/HTTPS operations via plain username and password." + @@ -151,7 +152,7 @@ func userFromAuthInfo(ai adapter.AuthInfo) (*models.User, error) { return u, nil } - t, err := models.GetAccessTokenBySHA(ai.Username) + t, err := models.GetAccessTokenBySHA(ai.Token) if err != nil { if models.IsErrAccessTokenEmpty(err) || models.IsErrAccessTokenNotExist(err) { return nil, errors.UserNotExist{} @@ -160,7 +161,7 @@ func userFromAuthInfo(ai adapter.AuthInfo) (*models.User, error) { } t.Updated = time.Now() - u, err = models.GetUserByID(t.UID) + u, err := models.GetUserByID(t.UID) if err != nil { return nil, err }