// 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 ( "net/http" "time" "github.com/Unknwon/com" 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(externalServicer{}, adapter.Options{ URL: setting.AppURL, PathPrefix: "/-/srcgraph", MaxPageLen: 100000, // Current version returns all repositories at once, does not matter }) return h.ServeHTTP } type externalServicer struct{} func (es externalServicer) ListRepos(ai adapter.AuthInfo, params adapter.Params) ([]*adapter.Repo, adapter.Page, error) { return es.listUserRepos("", ai, params) } func (es externalServicer) 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 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) { 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) { if ai.Method == adapter.AuthBasic { u, err := models.UserLogin(ai.Username, ai.Password, -1) if err != nil { return nil, err } 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.Token) 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) }