Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 79 additions & 9 deletions auth/gitee.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,18 @@ var (
)

var (
allowedRepos = []string{"openeuler", "src-openeuler", "lfs-org", "openeuler-test"}
uploadPermissions = []string{"admin", "developer"}
downloadPermissions = []string{"admin", "developer", "read"}
defaultAllowedRepos = []string{"openeuler", "src-openeuler", "lfs-org", "openeuler-test"}
allowedRepos = []string{"openeuler", "src-openeuler", "lfs-org", "openeuler-test"}
uploadPermissions = []string{"admin", "developer"}
downloadPermissions = []string{"admin", "developer", "read"}
namespacePlatformMap = map[string]string{}
githubDefaultToken string
githubAllowedOrgs []string
)

// supportedPlatforms enumerates the platforms recognised by ResolvePlatform.
var supportedPlatforms = []string{"gitee", "gitcode", "github"}

const (
refer = "referer"
accept = "Accept"
Expand Down Expand Up @@ -113,9 +120,77 @@ func Init(cfg *config.Config) error {
}

gitCodeSwitch = cfg.GitCodeSwitch

buildNamespacePlatformMap(cfg)
githubAllowedOrgs = cfg.GithubAllowedOrgs
githubDefaultToken = cfg.GithubDefaultToken
if githubDefaultToken == "" {
githubDefaultToken = os.Getenv("GITHUB_DEFAULT_TOKEN")
}
return nil
}

// buildNamespacePlatformMap constructs the owner->platform lookup from config,
// keeping the hardcoded openEuler namespaces as backward-compatible defaults so
// existing deployments keep working without any configuration change.
func buildNamespacePlatformMap(cfg *config.Config) {
namespacePlatformMap = make(map[string]string)
allowedRepos = make([]string, len(defaultAllowedRepos))
copy(allowedRepos, defaultAllowedRepos)
for _, ns := range defaultAllowedRepos {
namespacePlatformMap[ns] = "gitee"
}
for _, m := range cfg.AllowedNamespaces {
if m.Namespace == "" {
continue
}
platform := m.Platform
if !isSupportedPlatform(platform) {
platform = "gitee"
}
namespacePlatformMap[m.Namespace] = platform
// gitee/gitcode owners must also be admissible in CheckRepoOwner.
if platform != "github" {
allowedRepos = append(allowedRepos, m.Namespace)
}
}
}

func isSupportedPlatform(p string) bool {
for _, s := range supportedPlatforms {
if s == p {
return true
}
}
return false
}

// ResolvePlatform returns the hosting platform for a given owner namespace.
// It defaults to "gitcode" when gitCodeSwitch is on, otherwise "gitee".
func ResolvePlatform(owner string) string {
if p, ok := namespacePlatformMap[owner]; ok {
return p
}
if gitCodeSwitch {
return "gitcode"
}
return "gitee"
}

// PlatformForMetadata maps a resolved platform to the value stored in the
// lfs_obj.platform column. It keeps the legacy "atomGit" spelling for gitcode
// so existing rows remain queryable.
func PlatformForMetadata(platform string) string {
switch platform {
case "github":
return "github"
case "gitcode":
return "atomGit"
default:
return "gitee"
}
}

func InitOpenEulerParam(cfg *config.Config) error {
openEulerAccountParam.GrantType = tokenParam
openEulerAccountParam.Url = cfg.OpenEulerAccountConfig.UrlPath
Expand Down Expand Up @@ -455,12 +530,7 @@ func GetLFSMapping(userInRepo UserInRepo, pythonScriptPath ...string) (map[strin
repo := userInRepo.Repo
username := userInRepo.Username
token := userInRepo.Token
var platform string
if gitCodeSwitch {
platform = "gitcode"
} else {
platform = "gitee"
}
platform := ResolvePlatform(owner)

// 确定Python脚本路径
scriptPath, err := resolveScriptPath(pythonScriptPath...)
Expand Down
161 changes: 161 additions & 0 deletions auth/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,60 @@ import (
"errors"
"fmt"
"net/http"
"strings"

"github.com/sirupsen/logrus"
)

var (
githubUploadPermissions = []string{"admin", "maintain", "write"}
githubDownloadPermissions = []string{"admin", "maintain", "write", "triage", "read"}
githubDeletePermissions = []string{"admin"}
)

type ghOrg struct {
Login string `json:"login"`
}

// githubRepo mirrors the subset of the GitHub /repos/{owner}/{repo} response
// that BigFiles needs for ownership validation.
type githubRepo struct {
FullName string `json:"full_name"`
Owner ghOrg `json:"owner"`
Parent parent `json:"parent"`
}

// githubPermission mirrors the GitHub collaborator permission response.
type githubPermission struct {
Permission string `json:"permission"`
}

const (
githubAccept = "application/vnd.github+json"
)

// githubAPIBase is the GitHub REST API root. It is a var so unit tests can
// redirect it to an httptest server.
var githubAPIBase = "https://api.github.com"

// githubTokenFor returns the token to use for a GitHub API call: the user PAT
// when present, otherwise the server-side default bot token.
func githubTokenFor(u UserInRepo) string {
if u.Token != "" {
return u.Token
}
return githubDefaultToken
}

func githubHeaders(token string) http.Header {
h := http.Header{
contentType: []string{headerContentType},
accept: []string{githubAccept},
authorization: []string{"Bearer " + token},
}
return h
}

func GithubOrg(org string) func(string, string) error {
return func(_, ghToken string) error {
req, err := http.NewRequest("GET", "https://api.github.com/user/orgs", nil)
Expand Down Expand Up @@ -47,3 +95,116 @@ func Static(u, p string) func(string, string) error {
return nil
}
}

// CheckGithubRepoOwner validates that {owner}/{repo} exists on GitHub and that
// the repository owner is an allowed GitHub organization.
func CheckGithubRepoOwner(u UserInRepo) (Repo, error) {
token := githubTokenFor(u)
path := fmt.Sprintf("%s/repos/%s/%s", githubAPIBase, u.Owner, u.Repo)
repo := new(githubRepo)
if err := getParsedResponse("GET", path, githubHeaders(token), nil, repo); err != nil {
return Repo{}, err
}
for _, allowed := range githubAllowedOrgs {
if repo.Owner.Login == allowed {
return Repo{Fullname: repo.FullName, Parent: repo.Parent}, nil
}
}
// a fork under an allowed organisation is also admissible
if repo.Parent.Fullname != "" {
parentOwner := strings.Split(repo.Parent.Fullname, "/")[0]
for _, allowed := range githubAllowedOrgs {
if parentOwner == allowed {
return Repo{Fullname: repo.FullName, Parent: repo.Parent}, nil
}
}
}
msg := "forbidden: repo has no permission to use this lfs server"
logrus.Error(fmt.Sprintf("CheckGithubRepoOwner | %s | owner:%s repo:%s", msg, u.Owner, u.Repo))
return Repo{Fullname: repo.FullName, Parent: repo.Parent}, errors.New(msg)
}

// VerifyGithubUser checks the collaborator permission of username on the repo
// against the operation being performed.
func VerifyGithubUser(u UserInRepo) error {
token := githubTokenFor(u)
path := fmt.Sprintf("%s/repos/%s/%s/collaborators/%s/permission",
githubAPIBase, u.Owner, u.Repo, u.Username)
perm := new(githubPermission)
if err := getParsedResponse("GET", path, githubHeaders(token), nil, perm); err != nil {
msg := err.Error() + ": verify github user permission failed"
logrus.Error(fmt.Sprintf(formatLogString, verifyLog, msg))
return errors.New(msg)
}

switch u.Operation {
case "upload":
return verifyGithubUserUpload(perm, u)
case "download":
return verifyGithubUserDownload(perm, u)
case "delete":
return verifyGithubUserDelete(perm, u)
default:
msg := "system_error: unknown operation"
logrus.Error(fmt.Sprintf(formatLogString, verifyLog, msg))
return errors.New(msg)
}
}

func verifyGithubUserUpload(perm *githubPermission, u UserInRepo) error {
for _, p := range githubUploadPermissions {
if perm.Permission == p {
return nil
}
}
msg := fmt.Sprintf("forbidden: user %s has no permission to upload to %s/%s",
u.Username, u.Owner, u.Repo)
logrus.Error(fmt.Sprintf(formatLogString, verifyLog, msg))
return errors.New(msg)
}

func verifyGithubUserDownload(perm *githubPermission, u UserInRepo) error {
for _, p := range githubDownloadPermissions {
if perm.Permission == p {
return nil
}
}
msg := fmt.Sprintf("forbidden: user %s has no permission to download from %s/%s",
u.Username, u.Owner, u.Repo)
logrus.Error(fmt.Sprintf(formatLogString, verifyLog, msg))
return errors.New(msg)
}

func verifyGithubUserDelete(perm *githubPermission, u UserInRepo) error {
for _, p := range githubDeletePermissions {
if perm.Permission == p {
return nil
}
}
msg := fmt.Sprintf("forbidden: user %s has no permission to delete in %s/%s",
u.Username, u.Owner, u.Repo)
logrus.Error(fmt.Sprintf(formatLogString, verifyLog, msg))
return errors.New(msg)
}

// GithubAuth mirrors GiteeAuth: first validate repo ownership, then the user's
// collaborator permission. For GitHub the PAT is passed verbatim in Basic Auth
// (password field), no OAuth token exchange is performed.
func GithubAuth() func(UserInRepo) error {
return func(userInRepo UserInRepo) error {
userInRepo.Token = userInRepo.Password
if _, err := CheckGithubRepoOwner(userInRepo); err != nil {
return err
}
return VerifyGithubUser(userInRepo)
}
}

// CheckRepoOwnerByPlatform dispatches repo-ownership validation to the
// platform-specific implementation.
func CheckRepoOwnerByPlatform(platform string, u UserInRepo) (Repo, error) {
if platform == "github" {
return CheckGithubRepoOwner(u)
}
return CheckRepoOwner(u)
}
Loading
Loading