272 lines
8.5 KiB
Go
272 lines
8.5 KiB
Go
package hat
|
|
|
|
import (
|
|
gocontext "context"
|
|
"fmt"
|
|
"net/http"
|
|
"reflect"
|
|
"strings"
|
|
|
|
gitea_api "code.gitea.io/gitea/modules/structs"
|
|
hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
|
|
"gitea.com/go-chi/binding"
|
|
|
|
access_model "code.gitea.io/gitea/models/perm/access"
|
|
repo_model "code.gitea.io/gitea/models/repo"
|
|
"code.gitea.io/gitea/models/unit"
|
|
unit_model "code.gitea.io/gitea/models/unit"
|
|
user_model "code.gitea.io/gitea/models/user"
|
|
"code.gitea.io/gitea/modules/context"
|
|
"code.gitea.io/gitea/modules/log"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
"code.gitea.io/gitea/modules/web"
|
|
"code.gitea.io/gitea/routers/api/v1/misc"
|
|
"code.gitea.io/gitea/services/auth"
|
|
"code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/org"
|
|
"code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/repo"
|
|
"github.com/go-chi/cors"
|
|
)
|
|
|
|
func Routers(ctx gocontext.Context) *web.Route {
|
|
m := web.NewRoute()
|
|
|
|
m.Use(securityHeaders())
|
|
if setting.CORSConfig.Enabled {
|
|
m.Use(cors.Handler(cors.Options{
|
|
// Scheme: setting.CORSConfig.Scheme, // FIXME: the cors middleware needs scheme option
|
|
AllowedOrigins: setting.CORSConfig.AllowDomain,
|
|
// setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option
|
|
AllowedMethods: setting.CORSConfig.Methods,
|
|
AllowCredentials: setting.CORSConfig.AllowCredentials,
|
|
AllowedHeaders: []string{"Authorization", "X-Gitea-OTP"},
|
|
MaxAge: int(setting.CORSConfig.MaxAge.Seconds()),
|
|
}))
|
|
}
|
|
m.Use(context.APIContexter())
|
|
|
|
group := buildAuthGroup()
|
|
if err := group.Init(ctx); err != nil {
|
|
log.Error("Could not initialize '%s' auth method, error: %s", group.Name(), err)
|
|
}
|
|
|
|
// Get user from session if logged in.
|
|
m.Use(context.APIAuth(group))
|
|
|
|
m.Use(context.ToggleAPI(&context.ToggleOptions{
|
|
SignInRequired: setting.Service.RequireSignInView,
|
|
}))
|
|
|
|
reqRepoCodeReader := context.RequireRepoReader(unit_model.TypeCode)
|
|
m.Group("", func() {
|
|
m.Get("/version", misc.Version)
|
|
m.Group("/repos", func() {
|
|
m.Group("/{username}/{reponame}", func() {
|
|
m.Get("/branch_name_set", context.ReferencesGitRepo(), repo.BranchNameSet)
|
|
m.Get("/tag_name_set", context.ReferencesGitRepo(), repo.TagNameSet)
|
|
m.Group("/branches", func() {
|
|
m.Get("/branches_slice", context.ReferencesGitRepo(), repo.ListBranchesSlice)
|
|
}, reqRepoReader(unit_model.TypeCode))
|
|
m.Group("/tags", func() {
|
|
m.Get("", repo.ListTags)
|
|
}, reqRepoReader(unit_model.TypeCode), context.ReferencesGitRepo(true))
|
|
m.Group("/wikies", func() {
|
|
m.Combo("").Get(repo.ListWikiPages).
|
|
Post(bind(hat_api.WikiOption{}), repo.CreateWiki)
|
|
m.Group("/{page}", func() {
|
|
m.Combo("").Get(repo.GetWiki).
|
|
Patch(bind(hat_api.WikiOption{}), repo.EditWiki).
|
|
Delete(repo.DeleteWiki)
|
|
})
|
|
})
|
|
m.Group("/readme", func() {
|
|
m.Get("", repo.GetReadmeContents)
|
|
m.Get("/*", repo.GetReadmeContentsByPath)
|
|
})
|
|
m.Get("/commits_slice", repo.GetAllCommitsSliceByTime)
|
|
m.Get("/compare/*", repo.MustBeNotEmpty, reqRepoCodeReader,
|
|
repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.CompareDiff)
|
|
m.Group("/pulls", func() {
|
|
m.Group("/{index}", func() {
|
|
m.Combo("").Get(repo.GetPullRequest)
|
|
m.Get("/commits", context.RepoRef(), repo.GetPullCommits)
|
|
m.Get("/files", context.RepoRef(), repo.GetPullFiles)
|
|
})
|
|
}, mustAllowPulls, reqRepoReader(unit_model.TypeCode), context.ReferencesGitRepo())
|
|
m.Group("/releases", func() {
|
|
m.Get("/latest", context.ReferencesGitRepo(), repo.GetLatestRelease)
|
|
}, reqRepoReader(unit.TypeReleases))
|
|
m.Group("/contributors", func() {
|
|
m.Get("", repo.GetContributors)
|
|
})
|
|
m.Group("/count", func() {
|
|
m.Get("", context.ReferencesGitRepo(), repo.GetCommitCount)
|
|
})
|
|
m.Group("/file_commits", func() {
|
|
m.Get("/*", context.ReferencesGitRepo(), repo.GetFileAllCommits)
|
|
})
|
|
}, repoAssignment())
|
|
})
|
|
m.Post("/orgs", reqToken(), bind(gitea_api.CreateOrgOption{}), org.Create)
|
|
})
|
|
|
|
return m
|
|
}
|
|
|
|
func securityHeaders() func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
|
// CORB: https://www.chromium.org/Home/chromium-security/corb-for-developers
|
|
// http://stackoverflow.com/a/3146618/244009
|
|
resp.Header().Set("x-content-type-options", "nosniff")
|
|
next.ServeHTTP(resp, req)
|
|
})
|
|
}
|
|
}
|
|
|
|
func buildAuthGroup() *auth.Group {
|
|
group := auth.NewGroup(
|
|
&auth.OAuth2{},
|
|
&auth.HTTPSign{},
|
|
&auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API
|
|
)
|
|
if setting.Service.EnableReverseProxyAuth {
|
|
group.Add(&auth.ReverseProxy{})
|
|
}
|
|
specialAdd(group)
|
|
|
|
return group
|
|
}
|
|
|
|
func repoAssignment() func(ctx *context.APIContext) {
|
|
return func(ctx *context.APIContext) {
|
|
userName := ctx.Params("username")
|
|
repoName := ctx.Params("reponame")
|
|
|
|
var (
|
|
owner *user_model.User
|
|
err error
|
|
)
|
|
|
|
// Check if the user is the same as the repository owner.
|
|
if ctx.IsSigned && ctx.Doer.LowerName == strings.ToLower(userName) {
|
|
owner = ctx.Doer
|
|
} else {
|
|
owner, err = user_model.GetUserByName(ctx, userName)
|
|
if err != nil {
|
|
if user_model.IsErrUserNotExist(err) {
|
|
if redirectUserID, err := user_model.LookupUserRedirect(userName); err == nil {
|
|
context.RedirectToUser(ctx.Context, userName, redirectUserID)
|
|
} else if user_model.IsErrUserRedirectNotExist(err) {
|
|
ctx.NotFound("GetUserByName", err)
|
|
} else {
|
|
ctx.Error(http.StatusInternalServerError, "LookupUserRedirect", err)
|
|
}
|
|
} else {
|
|
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
ctx.Repo.Owner = owner
|
|
ctx.ContextUser = owner
|
|
|
|
// Get repository.
|
|
repo, err := repo_model.GetRepositoryByName(owner.ID, repoName)
|
|
if err != nil {
|
|
if repo_model.IsErrRepoNotExist(err) {
|
|
redirectRepoID, err := repo_model.LookupRedirect(owner.ID, repoName)
|
|
if err == nil {
|
|
context.RedirectToRepo(ctx.Context, redirectRepoID)
|
|
} else if repo_model.IsErrRedirectNotExist(err) {
|
|
ctx.NotFound()
|
|
} else {
|
|
ctx.Error(http.StatusInternalServerError, "LookupRepoRedirect", err)
|
|
}
|
|
} else {
|
|
ctx.Error(http.StatusInternalServerError, "GetRepositoryByName", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
repo.Owner = owner
|
|
ctx.Repo.Repository = repo
|
|
|
|
ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
|
if err != nil {
|
|
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
|
return
|
|
}
|
|
|
|
if !ctx.Repo.HasAccess() {
|
|
ctx.NotFound()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// bind binding an obj to a func(ctx *context.APIContext)
|
|
func bind(obj interface{}) http.HandlerFunc {
|
|
tp := reflect.TypeOf(obj)
|
|
for tp.Kind() == reflect.Ptr {
|
|
tp = tp.Elem()
|
|
}
|
|
return web.Wrap(func(ctx *context.APIContext) {
|
|
theObj := reflect.New(tp).Interface() // create a new form obj for every request but not use obj directly
|
|
errs := binding.Bind(ctx.Req, theObj)
|
|
if len(errs) > 0 {
|
|
ctx.Error(http.StatusUnprocessableEntity, "validationError", fmt.Sprintf("%s: %s", errs[0].FieldNames, errs[0].Error()))
|
|
return
|
|
}
|
|
web.SetForm(ctx, theObj)
|
|
})
|
|
}
|
|
|
|
// reqRepoReader user should have specific read permission or be a repo admin or a site admin
|
|
func reqRepoReader(unitType unit_model.Type) func(ctx *context.APIContext) {
|
|
return func(ctx *context.APIContext) {
|
|
if !ctx.IsUserRepoReaderSpecific(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
|
|
ctx.Error(http.StatusForbidden, "reqRepoReader", "user should have specific read permission or be a repo admin or a site admin")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func mustAllowPulls(ctx *context.APIContext) {
|
|
if !(ctx.Repo.Repository.CanEnablePulls() && ctx.Repo.CanRead(unit.TypePullRequests)) {
|
|
if ctx.Repo.Repository.CanEnablePulls() && log.IsTrace() {
|
|
if ctx.IsSigned {
|
|
log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+
|
|
"User in Repo has Permissions: %-+v",
|
|
ctx.Doer,
|
|
unit.TypePullRequests,
|
|
ctx.Repo.Repository,
|
|
ctx.Repo.Permission)
|
|
} else {
|
|
log.Trace("Permission Denied: Anonymous user cannot read %-v in Repo %-v\n"+
|
|
"Anonymous user in Repo has Permissions: %-+v",
|
|
unit.TypePullRequests,
|
|
ctx.Repo.Repository,
|
|
ctx.Repo.Permission)
|
|
}
|
|
}
|
|
ctx.NotFound()
|
|
return
|
|
}
|
|
}
|
|
|
|
func reqToken() func(ctx *context.APIContext) {
|
|
return func(ctx *context.APIContext) {
|
|
if true == ctx.Data["IsApiToken"] {
|
|
return
|
|
}
|
|
if ctx.Context.IsBasicAuth {
|
|
ctx.CheckForOTP()
|
|
return
|
|
}
|
|
if ctx.IsSigned {
|
|
return
|
|
}
|
|
ctx.Error(http.StatusUnauthorized, "reqToken", "token is required")
|
|
}
|
|
}
|