mirror of
https://github.com/go-gitea/gitea
synced 2025-12-07 13:28:25 +00:00
Merge branch 'main' into Badge
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/user"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -150,3 +151,32 @@ func ActivateEmail(ctx *context.Context) {
|
||||
redirect.RawQuery = q.Encode()
|
||||
ctx.Redirect(redirect.String())
|
||||
}
|
||||
|
||||
// DeleteEmail serves a POST request for delete a user's email
|
||||
func DeleteEmail(ctx *context.Context) {
|
||||
u, err := user_model.GetUserByID(ctx, ctx.FormInt64("Uid"))
|
||||
if err != nil || u == nil {
|
||||
ctx.ServerError("GetUserByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
email, err := user_model.GetEmailAddressByID(ctx, u.ID, ctx.FormInt64("id"))
|
||||
if err != nil || email == nil {
|
||||
ctx.ServerError("GetEmailAddressByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := user.DeleteEmailAddresses(ctx, u, []string{email.Email}); err != nil {
|
||||
if user_model.IsErrPrimaryEmailCannotDelete(err) {
|
||||
ctx.Flash.Error(ctx.Tr("admin.emails.delete_primary_email_error"))
|
||||
ctx.JSONRedirect("")
|
||||
return
|
||||
}
|
||||
ctx.ServerError("DeleteEmailAddresses", err)
|
||||
return
|
||||
}
|
||||
log.Trace("Email address deleted: %s %s", u.Name, email.Email)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("admin.emails.deletion_success"))
|
||||
ctx.JSONRedirect("")
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func DefaultOrSystemWebhooks(ctx *context.Context) {
|
||||
}
|
||||
|
||||
sys["Title"] = ctx.Tr("admin.systemhooks")
|
||||
sys["Description"] = ctx.Tr("admin.systemhooks.desc")
|
||||
sys["Description"] = ctx.Tr("admin.systemhooks.desc", "https://docs.gitea.com/usage/webhooks")
|
||||
sys["Webhooks"], err = webhook.GetSystemWebhooks(ctx, optional.None[bool]())
|
||||
sys["BaseLink"] = setting.AppSubURL + "/admin/hooks"
|
||||
sys["BaseLinkNew"] = setting.AppSubURL + "/admin/system-hooks"
|
||||
@@ -44,7 +44,7 @@ func DefaultOrSystemWebhooks(ctx *context.Context) {
|
||||
}
|
||||
|
||||
def["Title"] = ctx.Tr("admin.defaulthooks")
|
||||
def["Description"] = ctx.Tr("admin.defaulthooks.desc")
|
||||
def["Description"] = ctx.Tr("admin.defaulthooks.desc", "https://docs.gitea.com/usage/webhooks")
|
||||
def["Webhooks"], err = webhook.GetDefaultWebhooks(ctx)
|
||||
def["BaseLink"] = setting.AppSubURL + "/admin/hooks"
|
||||
def["BaseLinkNew"] = setting.AppSubURL + "/admin/default-hooks"
|
||||
|
||||
@@ -166,7 +166,7 @@ func NewUserPost(ctx *context.Context) {
|
||||
}
|
||||
if err := password.IsPwned(ctx, form.Password); err != nil {
|
||||
ctx.Data["Err_Password"] = true
|
||||
errMsg := ctx.Tr("auth.password_pwned")
|
||||
errMsg := ctx.Tr("auth.password_pwned", "https://haveibeenpwned.com/Passwords")
|
||||
if password.IsErrIsPwnedRequest(err) {
|
||||
log.Error(err.Error())
|
||||
errMsg = ctx.Tr("auth.password_pwned_err")
|
||||
@@ -401,7 +401,7 @@ func EditUserPost(ctx *context.Context) {
|
||||
ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplUserEdit, &form)
|
||||
case errors.Is(err, password.ErrIsPwned):
|
||||
ctx.Data["Err_Password"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("auth.password_pwned"), tplUserEdit, &form)
|
||||
ctx.RenderWithErr(ctx.Tr("auth.password_pwned", "https://haveibeenpwned.com/Passwords"), tplUserEdit, &form)
|
||||
case password.IsErrIsPwnedRequest(err):
|
||||
ctx.Data["Err_Password"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplUserEdit, &form)
|
||||
|
||||
@@ -504,7 +504,7 @@ func SignUpPost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
if err := password.IsPwned(ctx, form.Password); err != nil {
|
||||
errMsg := ctx.Tr("auth.password_pwned")
|
||||
errMsg := ctx.Tr("auth.password_pwned", "https://haveibeenpwned.com/Passwords")
|
||||
if password.IsErrIsPwnedRequest(err) {
|
||||
log.Error(err.Error())
|
||||
errMsg = ctx.Tr("auth.password_pwned_err")
|
||||
@@ -622,10 +622,8 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
|
||||
|
||||
// update external user information
|
||||
if gothUser != nil {
|
||||
if err := externalaccount.UpdateExternalUser(ctx, u, *gothUser); err != nil {
|
||||
if !errors.Is(err, util.ErrNotExist) {
|
||||
log.Error("UpdateExternalUser failed: %v", err)
|
||||
}
|
||||
if err := externalaccount.EnsureLinkExternalToUser(ctx, u, *gothUser); err != nil {
|
||||
log.Error("EnsureLinkExternalToUser failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ func LinkAccount(ctx *context.Context) {
|
||||
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
|
||||
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
|
||||
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
|
||||
ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
|
||||
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
|
||||
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
|
||||
ctx.Data["ShowRegistrationButton"] = false
|
||||
@@ -132,6 +133,7 @@ func LinkAccountPostSignIn(ctx *context.Context) {
|
||||
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
|
||||
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
|
||||
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
|
||||
ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
|
||||
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
|
||||
ctx.Data["ShowRegistrationButton"] = false
|
||||
|
||||
@@ -219,6 +221,7 @@ func LinkAccountPostRegister(ctx *context.Context) {
|
||||
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
|
||||
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
|
||||
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
|
||||
ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
|
||||
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
|
||||
ctx.Data["ShowRegistrationButton"] = false
|
||||
|
||||
|
||||
+68
-57
@@ -5,7 +5,6 @@ package auth
|
||||
|
||||
import (
|
||||
go_context "context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
@@ -27,7 +26,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
auth_service "code.gitea.io/gitea/services/auth"
|
||||
@@ -327,17 +325,37 @@ func getOAuthGroupsForUser(ctx go_context.Context, user *user_model.User) ([]str
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func parseBasicAuth(ctx *context.Context) (username, password string, err error) {
|
||||
authHeader := ctx.Req.Header.Get("Authorization")
|
||||
if authType, authData, ok := strings.Cut(authHeader, " "); ok && strings.EqualFold(authType, "Basic") {
|
||||
return base.BasicAuthDecode(authData)
|
||||
}
|
||||
return "", "", errors.New("invalid basic authentication")
|
||||
}
|
||||
|
||||
// IntrospectOAuth introspects an oauth token
|
||||
func IntrospectOAuth(ctx *context.Context) {
|
||||
if ctx.Doer == nil {
|
||||
ctx.Resp.Header().Set("WWW-Authenticate", `Bearer realm=""`)
|
||||
clientIDValid := false
|
||||
if clientID, clientSecret, err := parseBasicAuth(ctx); err == nil {
|
||||
app, err := auth.GetOAuth2ApplicationByClientID(ctx, clientID)
|
||||
if err != nil && !auth.IsErrOauthClientIDInvalid(err) {
|
||||
// this is likely a database error; log it and respond without details
|
||||
log.Error("Error retrieving client_id: %v", err)
|
||||
ctx.Error(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
clientIDValid = err == nil && app.ValidateClientSecret([]byte(clientSecret))
|
||||
}
|
||||
if !clientIDValid {
|
||||
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm=""`)
|
||||
ctx.PlainText(http.StatusUnauthorized, "no valid authorization")
|
||||
return
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Active bool `json:"active"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
Active bool `json:"active"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
@@ -354,6 +372,9 @@ func IntrospectOAuth(ctx *context.Context) {
|
||||
response.Audience = []string{app.ClientID}
|
||||
response.Subject = fmt.Sprint(grant.UserID)
|
||||
}
|
||||
if user, err := user_model.GetUserByID(ctx, grant.UserID); err == nil {
|
||||
response.Username = user.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,9 +491,9 @@ func AuthorizeOAuth(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect if user already granted access and the application is confidential.
|
||||
// I.e. always require authorization for public clients as recommended by RFC 6749 Section 10.2
|
||||
if app.ConfidentialClient && grant != nil {
|
||||
// Redirect if user already granted access and the application is confidential or trusted otherwise
|
||||
// I.e. always require authorization for untrusted public clients as recommended by RFC 6749 Section 10.2
|
||||
if (app.ConfidentialClient || app.SkipSecondaryAuthorization) && grant != nil {
|
||||
code, err := grant.GenerateNewAuthorizationCode(ctx, form.RedirectURI, form.CodeChallenge, form.CodeChallengeMethod)
|
||||
if err != nil {
|
||||
handleServerError(ctx, form.State, form.RedirectURI)
|
||||
@@ -640,9 +661,8 @@ func AccessTokenOAuth(ctx *context.Context) {
|
||||
// if there is no ClientID or ClientSecret in the request body, fill these fields by the Authorization header and ensure the provided field matches the Authorization header
|
||||
if form.ClientID == "" || form.ClientSecret == "" {
|
||||
authHeader := ctx.Req.Header.Get("Authorization")
|
||||
authContent := strings.SplitN(authHeader, " ", 2)
|
||||
if len(authContent) == 2 && authContent[0] == "Basic" {
|
||||
payload, err := base64.StdEncoding.DecodeString(authContent[1])
|
||||
if authType, authData, ok := strings.Cut(authHeader, " "); ok && strings.EqualFold(authType, "Basic") {
|
||||
clientID, clientSecret, err := base.BasicAuthDecode(authData)
|
||||
if err != nil {
|
||||
handleAccessTokenError(ctx, AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidRequest,
|
||||
@@ -650,30 +670,23 @@ func AccessTokenOAuth(ctx *context.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
pair := strings.SplitN(string(payload), ":", 2)
|
||||
if len(pair) != 2 {
|
||||
handleAccessTokenError(ctx, AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidRequest,
|
||||
ErrorDescription: "cannot parse basic auth header",
|
||||
})
|
||||
return
|
||||
}
|
||||
if form.ClientID != "" && form.ClientID != pair[0] {
|
||||
// validate that any fields present in the form match the Basic auth header
|
||||
if form.ClientID != "" && form.ClientID != clientID {
|
||||
handleAccessTokenError(ctx, AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidRequest,
|
||||
ErrorDescription: "client_id in request body inconsistent with Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
form.ClientID = pair[0]
|
||||
if form.ClientSecret != "" && form.ClientSecret != pair[1] {
|
||||
form.ClientID = clientID
|
||||
if form.ClientSecret != "" && form.ClientSecret != clientSecret {
|
||||
handleAccessTokenError(ctx, AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidRequest,
|
||||
ErrorDescription: "client_secret in request body inconsistent with Authorization header",
|
||||
})
|
||||
return
|
||||
}
|
||||
form.ClientSecret = pair[1]
|
||||
form.ClientSecret = clientSecret
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1148,9 +1161,39 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
|
||||
|
||||
groups := getClaimedGroups(oauth2Source, &gothUser)
|
||||
|
||||
opts := &user_service.UpdateOptions{}
|
||||
|
||||
// Reactivate user if they are deactivated
|
||||
if !u.IsActive {
|
||||
opts.IsActive = optional.Some(true)
|
||||
}
|
||||
|
||||
// Update GroupClaims
|
||||
opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser)
|
||||
|
||||
if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval {
|
||||
if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil {
|
||||
ctx.ServerError("SyncGroupsToTeams", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := externalaccount.EnsureLinkExternalToUser(ctx, u, gothUser); err != nil {
|
||||
ctx.ServerError("EnsureLinkExternalToUser", err)
|
||||
return
|
||||
}
|
||||
|
||||
// If this user is enrolled in 2FA and this source doesn't override it,
|
||||
// we can't sign the user in just yet. Instead, redirect them to the 2FA authentication page.
|
||||
if !needs2FA {
|
||||
// Register last login
|
||||
opts.SetLastLogin = true
|
||||
|
||||
if err := user_service.UpdateUser(ctx, u, opts); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := updateSession(ctx, nil, map[string]any{
|
||||
"uid": u.ID,
|
||||
"uname": u.Name,
|
||||
@@ -1162,29 +1205,6 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
|
||||
// Clear whatever CSRF cookie has right now, force to generate a new one
|
||||
ctx.Csrf.DeleteCookie(ctx)
|
||||
|
||||
opts := &user_service.UpdateOptions{
|
||||
SetLastLogin: true,
|
||||
}
|
||||
opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser)
|
||||
if err := user_service.UpdateUser(ctx, u, opts); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
|
||||
if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval {
|
||||
if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil {
|
||||
ctx.ServerError("SyncGroupsToTeams", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// update external user information
|
||||
if err := externalaccount.UpdateExternalUser(ctx, u, gothUser); err != nil {
|
||||
if !errors.Is(err, util.ErrNotExist) {
|
||||
log.Error("UpdateExternalUser failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := resetLocale(ctx, u); err != nil {
|
||||
ctx.ServerError("resetLocale", err)
|
||||
return
|
||||
@@ -1200,22 +1220,13 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
|
||||
return
|
||||
}
|
||||
|
||||
opts := &user_service.UpdateOptions{}
|
||||
opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser)
|
||||
if opts.IsAdmin.Has() || opts.IsRestricted.Has() {
|
||||
if opts.IsActive.Has() || opts.IsAdmin.Has() || opts.IsRestricted.Has() {
|
||||
if err := user_service.UpdateUser(ctx, u, opts); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval {
|
||||
if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil {
|
||||
ctx.ServerError("SyncGroupsToTeams", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := updateSession(ctx, nil, map[string]any{
|
||||
// User needs to use 2FA, save data and redirect to 2FA page.
|
||||
"twofaUid": u.ID,
|
||||
|
||||
@@ -307,6 +307,7 @@ func RegisterOpenID(ctx *context.Context) {
|
||||
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
|
||||
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
|
||||
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
|
||||
ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
|
||||
ctx.Data["OpenID"] = oid
|
||||
userName, _ := ctx.Session.Get("openid_determined_username").(string)
|
||||
if userName != "" {
|
||||
|
||||
@@ -212,7 +212,7 @@ func ResetPasswdPost(ctx *context.Context) {
|
||||
case errors.Is(err, password.ErrComplexity):
|
||||
ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplResetPassword, nil)
|
||||
case errors.Is(err, password.ErrIsPwned):
|
||||
ctx.RenderWithErr(ctx.Tr("auth.password_pwned"), tplResetPassword, nil)
|
||||
ctx.RenderWithErr(ctx.Tr("auth.password_pwned", "https://haveibeenpwned.com/Passwords"), tplResetPassword, nil)
|
||||
case password.IsErrIsPwnedRequest(err):
|
||||
ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplResetPassword, nil)
|
||||
default:
|
||||
@@ -295,7 +295,7 @@ func MustChangePasswordPost(ctx *context.Context) {
|
||||
ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplMustChangePassword, &form)
|
||||
case errors.Is(err, password.ErrIsPwned):
|
||||
ctx.Data["Err_Password"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("auth.password_pwned"), tplMustChangePassword, &form)
|
||||
ctx.RenderWithErr(ctx.Tr("auth.password_pwned", "https://haveibeenpwned.com/Passwords"), tplMustChangePassword, &form)
|
||||
case password.IsErrIsPwnedRequest(err):
|
||||
ctx.Data["Err_Password"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplMustChangePassword, &form)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
@@ -47,6 +48,104 @@ func WebAuthn(ctx *context.Context) {
|
||||
ctx.HTML(http.StatusOK, tplWebAuthn)
|
||||
}
|
||||
|
||||
// WebAuthnPasskeyAssertion submits a WebAuthn challenge for the passkey login to the browser
|
||||
func WebAuthnPasskeyAssertion(ctx *context.Context) {
|
||||
assertion, sessionData, err := wa.WebAuthn.BeginDiscoverableLogin()
|
||||
if err != nil {
|
||||
ctx.ServerError("webauthn.BeginDiscoverableLogin", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := ctx.Session.Set("webauthnPasskeyAssertion", sessionData); err != nil {
|
||||
ctx.ServerError("Session.Set", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, assertion)
|
||||
}
|
||||
|
||||
// WebAuthnPasskeyLogin handles the WebAuthn login process using a Passkey
|
||||
func WebAuthnPasskeyLogin(ctx *context.Context) {
|
||||
sessionData, okData := ctx.Session.Get("webauthnPasskeyAssertion").(*webauthn.SessionData)
|
||||
if !okData || sessionData == nil {
|
||||
ctx.ServerError("ctx.Session.Get", errors.New("not in WebAuthn session"))
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = ctx.Session.Delete("webauthnPasskeyAssertion")
|
||||
}()
|
||||
|
||||
// Validate the parsed response.
|
||||
var user *user_model.User
|
||||
cred, err := wa.WebAuthn.FinishDiscoverableLogin(func(rawID, userHandle []byte) (webauthn.User, error) {
|
||||
userID, n := binary.Varint(userHandle)
|
||||
if n <= 0 {
|
||||
return nil, errors.New("invalid rawID")
|
||||
}
|
||||
|
||||
var err error
|
||||
user, err = user_model.GetUserByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return (*wa.User)(user), nil
|
||||
}, *sessionData, ctx.Req)
|
||||
if err != nil {
|
||||
// Failed authentication attempt.
|
||||
log.Info("Failed authentication attempt for passkey from %s: %v", ctx.RemoteAddr(), err)
|
||||
ctx.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if !cred.Flags.UserPresent {
|
||||
ctx.Status(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
ctx.Status(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure that the credential wasn't cloned by checking if CloneWarning is set.
|
||||
// (This is set if the sign counter is less than the one we have stored.)
|
||||
if cred.Authenticator.CloneWarning {
|
||||
log.Info("Failed authentication attempt for %s from %s: cloned credential", user.Name, ctx.RemoteAddr())
|
||||
ctx.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// Success! Get the credential and update the sign count with the new value we received.
|
||||
dbCred, err := auth.GetWebAuthnCredentialByCredID(ctx, user.ID, cred.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetWebAuthnCredentialByCredID", err)
|
||||
return
|
||||
}
|
||||
|
||||
dbCred.SignCount = cred.Authenticator.SignCount
|
||||
if err := dbCred.UpdateSignCount(ctx); err != nil {
|
||||
ctx.ServerError("UpdateSignCount", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Now handle account linking if that's requested
|
||||
if ctx.Session.Get("linkAccount") != nil {
|
||||
if err := externalaccount.LinkAccountFromStore(ctx, ctx.Session, user); err != nil {
|
||||
ctx.ServerError("LinkAccountFromStore", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
remember := false // TODO: implement remember me
|
||||
redirect := handleSignInFull(ctx, user, remember, false)
|
||||
if redirect == "" {
|
||||
redirect = setting.AppSubURL + "/"
|
||||
}
|
||||
|
||||
ctx.JSONRedirect(redirect)
|
||||
}
|
||||
|
||||
// WebAuthnLoginAssertion submits a WebAuthn challenge to the browser
|
||||
func WebAuthnLoginAssertion(ctx *context.Context) {
|
||||
// Ensure user is in a WebAuthn session.
|
||||
|
||||
@@ -6,7 +6,6 @@ package explore
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
@@ -58,7 +57,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
|
||||
orderBy db.SearchOrderBy
|
||||
)
|
||||
|
||||
sortOrder := strings.ToLower(ctx.FormString("sort"))
|
||||
sortOrder := ctx.FormString("sort")
|
||||
if sortOrder == "" {
|
||||
sortOrder = setting.UI.ExploreDefaultSort
|
||||
}
|
||||
@@ -144,6 +143,21 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
|
||||
pager.AddParamString("topic", fmt.Sprint(topicOnly))
|
||||
pager.AddParamString("language", language)
|
||||
pager.AddParamString(relevantReposOnlyParam, fmt.Sprint(opts.OnlyShowRelevant))
|
||||
if archived.Has() {
|
||||
pager.AddParamString("archived", fmt.Sprint(archived.Value()))
|
||||
}
|
||||
if fork.Has() {
|
||||
pager.AddParamString("fork", fmt.Sprint(fork.Value()))
|
||||
}
|
||||
if mirror.Has() {
|
||||
pager.AddParamString("mirror", fmt.Sprint(mirror.Value()))
|
||||
}
|
||||
if template.Has() {
|
||||
pager.AddParamString("template", fmt.Sprint(template.Value()))
|
||||
}
|
||||
if private.Has() {
|
||||
pager.AddParamString("private", fmt.Sprint(private.Value()))
|
||||
}
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.HTML(http.StatusOK, opts.TplName)
|
||||
|
||||
@@ -83,7 +83,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
|
||||
link := &feeds.Link{Href: act.GetCommentHTMLURL(ctx)}
|
||||
|
||||
// title
|
||||
title = act.ActUser.DisplayName() + " "
|
||||
title = act.ActUser.GetDisplayName() + " "
|
||||
var titleExtra template.HTML
|
||||
switch act.OpType {
|
||||
case activities_model.ActionCreateRepo:
|
||||
@@ -252,7 +252,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
|
||||
Description: desc,
|
||||
IsPermaLink: "false",
|
||||
Author: &feeds.Author{
|
||||
Name: act.ActUser.DisplayName(),
|
||||
Name: act.ActUser.GetDisplayName(),
|
||||
Email: act.ActUser.GetEmail(),
|
||||
},
|
||||
Id: fmt.Sprintf("%v: %v", strconv.FormatInt(act.ID, 10), link.Href),
|
||||
@@ -313,7 +313,7 @@ func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release) (
|
||||
Link: link,
|
||||
Created: rel.CreatedUnix.AsTime(),
|
||||
Author: &feeds.Author{
|
||||
Name: rel.Publisher.DisplayName(),
|
||||
Name: rel.Publisher.GetDisplayName(),
|
||||
Email: rel.Publisher.GetEmail(),
|
||||
},
|
||||
Id: fmt.Sprintf("%v: %v", strconv.FormatInt(rel.ID, 10), link.Href),
|
||||
|
||||
+64
-57
@@ -4,6 +4,7 @@
|
||||
package org
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
@@ -12,7 +13,6 @@ import (
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
@@ -41,38 +41,26 @@ func Home(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
home(ctx, false)
|
||||
}
|
||||
|
||||
func Repositories(ctx *context.Context) {
|
||||
home(ctx, true)
|
||||
}
|
||||
|
||||
func home(ctx *context.Context, viewRepositories bool) {
|
||||
org := ctx.Org.Organization
|
||||
|
||||
ctx.Data["PageIsUserProfile"] = true
|
||||
ctx.Data["Title"] = org.DisplayName()
|
||||
|
||||
var orderBy db.SearchOrderBy
|
||||
ctx.Data["SortType"] = ctx.FormString("sort")
|
||||
switch ctx.FormString("sort") {
|
||||
case "newest":
|
||||
orderBy = db.SearchOrderByNewest
|
||||
case "oldest":
|
||||
orderBy = db.SearchOrderByOldest
|
||||
case "recentupdate":
|
||||
orderBy = db.SearchOrderByRecentUpdated
|
||||
case "leastupdate":
|
||||
orderBy = db.SearchOrderByLeastUpdated
|
||||
case "reversealphabetically":
|
||||
orderBy = db.SearchOrderByAlphabeticallyReverse
|
||||
case "alphabetically":
|
||||
orderBy = db.SearchOrderByAlphabetically
|
||||
case "moststars":
|
||||
orderBy = db.SearchOrderByStarsReverse
|
||||
case "feweststars":
|
||||
orderBy = db.SearchOrderByStars
|
||||
case "mostforks":
|
||||
orderBy = db.SearchOrderByForksReverse
|
||||
case "fewestforks":
|
||||
orderBy = db.SearchOrderByForks
|
||||
default:
|
||||
ctx.Data["SortType"] = "recentupdate"
|
||||
orderBy = db.SearchOrderByRecentUpdated
|
||||
sortOrder := ctx.FormString("sort")
|
||||
if _, ok := repo_model.OrderByFlatMap[sortOrder]; !ok {
|
||||
sortOrder = setting.UI.ExploreDefaultSort // TODO: add new default sort order for org home?
|
||||
}
|
||||
ctx.Data["SortType"] = sortOrder
|
||||
orderBy = repo_model.OrderByFlatMap[sortOrder]
|
||||
|
||||
keyword := ctx.FormTrim("q")
|
||||
ctx.Data["Keyword"] = keyword
|
||||
@@ -100,10 +88,34 @@ func Home(ctx *context.Context) {
|
||||
private := ctx.FormOptionalBool("private")
|
||||
ctx.Data["IsPrivate"] = private
|
||||
|
||||
err := shared_user.LoadHeaderCount(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadHeaderCount", err)
|
||||
return
|
||||
}
|
||||
|
||||
opts := &organization.FindOrgMembersOpts{
|
||||
OrgID: org.ID,
|
||||
PublicOnly: ctx.Org.PublicMemberOnly,
|
||||
ListOptions: db.ListOptions{Page: 1, PageSize: 25},
|
||||
}
|
||||
members, _, err := organization.FindOrgMembers(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.ServerError("FindOrgMembers", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Members"] = members
|
||||
ctx.Data["Teams"] = ctx.Org.Teams
|
||||
ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull
|
||||
ctx.Data["ShowMemberAndTeamTab"] = ctx.Org.IsMember || len(members) > 0
|
||||
|
||||
if !prepareOrgProfileReadme(ctx, viewRepositories) {
|
||||
ctx.Data["PageIsViewRepositories"] = true
|
||||
}
|
||||
|
||||
var (
|
||||
repos []*repo_model.Repository
|
||||
count int64
|
||||
err error
|
||||
)
|
||||
repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
@@ -128,47 +140,39 @@ func Home(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
opts := &organization.FindOrgMembersOpts{
|
||||
OrgID: org.ID,
|
||||
PublicOnly: ctx.Org.PublicMemberOnly,
|
||||
ListOptions: db.ListOptions{Page: 1, PageSize: 25},
|
||||
}
|
||||
members, _, err := organization.FindOrgMembers(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.ServerError("FindOrgMembers", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Repos"] = repos
|
||||
ctx.Data["Total"] = count
|
||||
ctx.Data["Members"] = members
|
||||
ctx.Data["Teams"] = ctx.Org.Teams
|
||||
ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull
|
||||
ctx.Data["PageIsViewRepositories"] = true
|
||||
|
||||
err = shared_user.LoadHeaderCount(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadHeaderCount", err)
|
||||
return
|
||||
}
|
||||
|
||||
pager := context.NewPagination(int(count), setting.UI.User.RepoPagingNum, page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
pager.AddParamString("language", language)
|
||||
if archived.Has() {
|
||||
pager.AddParamString("archived", fmt.Sprint(archived.Value()))
|
||||
}
|
||||
if fork.Has() {
|
||||
pager.AddParamString("fork", fmt.Sprint(fork.Value()))
|
||||
}
|
||||
if mirror.Has() {
|
||||
pager.AddParamString("mirror", fmt.Sprint(mirror.Value()))
|
||||
}
|
||||
if template.Has() {
|
||||
pager.AddParamString("template", fmt.Sprint(template.Value()))
|
||||
}
|
||||
if private.Has() {
|
||||
pager.AddParamString("private", fmt.Sprint(private.Value()))
|
||||
}
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.Data["ShowMemberAndTeamTab"] = ctx.Org.IsMember || len(members) > 0
|
||||
|
||||
profileDbRepo, profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer)
|
||||
defer profileClose()
|
||||
prepareOrgProfileReadme(ctx, profileGitRepo, profileDbRepo, profileReadmeBlob)
|
||||
|
||||
ctx.HTML(http.StatusOK, tplOrgHome)
|
||||
}
|
||||
|
||||
func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repository, profileDbRepo *repo_model.Repository, profileReadme *git.Blob) {
|
||||
if profileGitRepo == nil || profileReadme == nil {
|
||||
return
|
||||
func prepareOrgProfileReadme(ctx *context.Context, viewRepositories bool) bool {
|
||||
profileDbRepo, profileGitRepo, profileReadme, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer)
|
||||
defer profileClose()
|
||||
ctx.Data["HasProfileReadme"] = profileReadme != nil
|
||||
|
||||
if profileGitRepo == nil || profileReadme == nil || viewRepositories {
|
||||
return false
|
||||
}
|
||||
|
||||
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
|
||||
@@ -190,4 +194,7 @@ func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repositor
|
||||
ctx.Data["ProfileReadme"] = profileContent
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["PageIsViewOverview"] = true
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -54,9 +54,9 @@ func Members(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
err = shared_user.LoadHeaderCount(ctx)
|
||||
err = shared_user.RenderOrgHeader(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadHeaderCount", err)
|
||||
ctx.ServerError("RenderOrgHeader", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
project_service "code.gitea.io/gitea/services/projects"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -601,7 +602,7 @@ func MoveIssues(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if err = project_model.MoveIssuesOnProjectColumn(ctx, column, sortedIssueIDs); err != nil {
|
||||
if err = project_service.MoveIssuesOnProjectColumn(ctx, ctx.Doer, column, sortedIssueIDs); err != nil {
|
||||
ctx.ServerError("MoveIssuesOnProjectColumn", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -59,9 +59,9 @@ func Teams(ctx *context.Context) {
|
||||
}
|
||||
ctx.Data["Teams"] = ctx.Org.Teams
|
||||
|
||||
err := shared_user.LoadHeaderCount(ctx)
|
||||
err := shared_user.RenderOrgHeader(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadHeaderCount", err)
|
||||
ctx.ServerError("RenderOrgHeader", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -7,22 +7,28 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/actions"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/routers/web/repo"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
|
||||
"github.com/nektos/act/pkg/model"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -58,8 +64,13 @@ func MustEnableActions(ctx *context.Context) {
|
||||
func List(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("actions.actions")
|
||||
ctx.Data["PageIsActions"] = true
|
||||
workflowID := ctx.FormString("workflow")
|
||||
actorID := ctx.FormInt64("actor")
|
||||
status := ctx.FormInt("status")
|
||||
ctx.Data["CurWorkflow"] = workflowID
|
||||
|
||||
var workflows []Workflow
|
||||
var curWorkflow *model.Workflow
|
||||
if empty, err := ctx.Repo.GitRepo.IsEmpty(); err != nil {
|
||||
ctx.ServerError("IsEmpty", err)
|
||||
return
|
||||
@@ -140,6 +151,10 @@ func List(ctx *context.Context) {
|
||||
workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job")
|
||||
}
|
||||
workflows = append(workflows, workflow)
|
||||
|
||||
if workflow.Entry.Name() == workflowID {
|
||||
curWorkflow = wf
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.Data["workflows"] = workflows
|
||||
@@ -150,17 +165,46 @@ func List(ctx *context.Context) {
|
||||
page = 1
|
||||
}
|
||||
|
||||
workflow := ctx.FormString("workflow")
|
||||
actorID := ctx.FormInt64("actor")
|
||||
status := ctx.FormInt("status")
|
||||
ctx.Data["CurWorkflow"] = workflow
|
||||
|
||||
actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
|
||||
ctx.Data["ActionsConfig"] = actionsConfig
|
||||
|
||||
if len(workflow) > 0 && ctx.Repo.IsAdmin() {
|
||||
if len(workflowID) > 0 && ctx.Repo.IsAdmin() {
|
||||
ctx.Data["AllowDisableOrEnableWorkflow"] = true
|
||||
ctx.Data["CurWorkflowDisabled"] = actionsConfig.IsWorkflowDisabled(workflow)
|
||||
isWorkflowDisabled := actionsConfig.IsWorkflowDisabled(workflowID)
|
||||
ctx.Data["CurWorkflowDisabled"] = isWorkflowDisabled
|
||||
|
||||
if !isWorkflowDisabled && curWorkflow != nil {
|
||||
workflowDispatchConfig := workflowDispatchConfig(curWorkflow)
|
||||
if workflowDispatchConfig != nil {
|
||||
ctx.Data["WorkflowDispatchConfig"] = workflowDispatchConfig
|
||||
|
||||
branchOpts := git_model.FindBranchOptions{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
IsDeletedBranch: optional.Some(false),
|
||||
ListOptions: db.ListOptions{
|
||||
ListAll: true,
|
||||
},
|
||||
}
|
||||
branches, err := git_model.FindBranchNames(ctx, branchOpts)
|
||||
if err != nil {
|
||||
ctx.ServerError("FindBranchNames", err)
|
||||
return
|
||||
}
|
||||
// always put default branch on the top if it exists
|
||||
if slices.Contains(branches, ctx.Repo.Repository.DefaultBranch) {
|
||||
branches = util.SliceRemoveAll(branches, ctx.Repo.Repository.DefaultBranch)
|
||||
branches = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...)
|
||||
}
|
||||
ctx.Data["Branches"] = branches
|
||||
|
||||
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTagNamesByRepoID", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Tags"] = tags
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if status or actor query param is not given to frontend href, (href="/<repoLink>/actions")
|
||||
@@ -177,7 +221,7 @@ func List(ctx *context.Context) {
|
||||
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
||||
},
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
WorkflowID: workflow,
|
||||
WorkflowID: workflowID,
|
||||
TriggerUserID: actorID,
|
||||
}
|
||||
|
||||
@@ -214,7 +258,7 @@ func List(ctx *context.Context) {
|
||||
|
||||
pager := context.NewPagination(int(total), opts.PageSize, opts.Page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
pager.AddParamString("workflow", workflow)
|
||||
pager.AddParamString("workflow", workflowID)
|
||||
pager.AddParamString("actor", fmt.Sprint(actorID))
|
||||
pager.AddParamString("status", fmt.Sprint(status))
|
||||
ctx.Data["Page"] = pager
|
||||
@@ -222,3 +266,86 @@ func List(ctx *context.Context) {
|
||||
|
||||
ctx.HTML(http.StatusOK, tplListActions)
|
||||
}
|
||||
|
||||
type WorkflowDispatchInput struct {
|
||||
Name string `yaml:"name"`
|
||||
Description string `yaml:"description"`
|
||||
Required bool `yaml:"required"`
|
||||
Default string `yaml:"default"`
|
||||
Type string `yaml:"type"`
|
||||
Options []string `yaml:"options"`
|
||||
}
|
||||
|
||||
type WorkflowDispatch struct {
|
||||
Inputs []WorkflowDispatchInput
|
||||
}
|
||||
|
||||
func workflowDispatchConfig(w *model.Workflow) *WorkflowDispatch {
|
||||
switch w.RawOn.Kind {
|
||||
case yaml.ScalarNode:
|
||||
var val string
|
||||
if !decodeNode(w.RawOn, &val) {
|
||||
return nil
|
||||
}
|
||||
if val == "workflow_dispatch" {
|
||||
return &WorkflowDispatch{}
|
||||
}
|
||||
case yaml.SequenceNode:
|
||||
var val []string
|
||||
if !decodeNode(w.RawOn, &val) {
|
||||
return nil
|
||||
}
|
||||
for _, v := range val {
|
||||
if v == "workflow_dispatch" {
|
||||
return &WorkflowDispatch{}
|
||||
}
|
||||
}
|
||||
case yaml.MappingNode:
|
||||
var val map[string]yaml.Node
|
||||
if !decodeNode(w.RawOn, &val) {
|
||||
return nil
|
||||
}
|
||||
|
||||
workflowDispatchNode, found := val["workflow_dispatch"]
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
var workflowDispatch WorkflowDispatch
|
||||
var workflowDispatchVal map[string]yaml.Node
|
||||
if !decodeNode(workflowDispatchNode, &workflowDispatchVal) {
|
||||
return &workflowDispatch
|
||||
}
|
||||
|
||||
inputsNode, found := workflowDispatchVal["inputs"]
|
||||
if !found || inputsNode.Kind != yaml.MappingNode {
|
||||
return &workflowDispatch
|
||||
}
|
||||
|
||||
i := 0
|
||||
for {
|
||||
if i+1 >= len(inputsNode.Content) {
|
||||
break
|
||||
}
|
||||
var input WorkflowDispatchInput
|
||||
if decodeNode(*inputsNode.Content[i+1], &input) {
|
||||
input.Name = inputsNode.Content[i].Value
|
||||
workflowDispatch.Inputs = append(workflowDispatch.Inputs, input)
|
||||
}
|
||||
i += 2
|
||||
}
|
||||
return &workflowDispatch
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeNode(node yaml.Node, out any) bool {
|
||||
if err := node.Decode(out); err != nil {
|
||||
log.Warn("Failed to decode node %v into %T: %v", node, out, err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
act_model "github.com/nektos/act/pkg/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestReadWorkflow_WorkflowDispatchConfig(t *testing.T) {
|
||||
yaml := `
|
||||
name: local-action-docker-url
|
||||
`
|
||||
workflow, err := act_model.ReadWorkflow(strings.NewReader(yaml))
|
||||
assert.NoError(t, err, "read workflow should succeed")
|
||||
workflowDispatch := workflowDispatchConfig(workflow)
|
||||
assert.Nil(t, workflowDispatch)
|
||||
|
||||
yaml = `
|
||||
name: local-action-docker-url
|
||||
on: push
|
||||
`
|
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml))
|
||||
assert.NoError(t, err, "read workflow should succeed")
|
||||
workflowDispatch = workflowDispatchConfig(workflow)
|
||||
assert.Nil(t, workflowDispatch)
|
||||
|
||||
yaml = `
|
||||
name: local-action-docker-url
|
||||
on: workflow_dispatch
|
||||
`
|
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml))
|
||||
assert.NoError(t, err, "read workflow should succeed")
|
||||
workflowDispatch = workflowDispatchConfig(workflow)
|
||||
assert.NotNil(t, workflowDispatch)
|
||||
assert.Nil(t, workflowDispatch.Inputs)
|
||||
|
||||
yaml = `
|
||||
name: local-action-docker-url
|
||||
on: [push, pull_request]
|
||||
`
|
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml))
|
||||
assert.NoError(t, err, "read workflow should succeed")
|
||||
workflowDispatch = workflowDispatchConfig(workflow)
|
||||
assert.Nil(t, workflowDispatch)
|
||||
|
||||
yaml = `
|
||||
name: local-action-docker-url
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
`
|
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml))
|
||||
assert.NoError(t, err, "read workflow should succeed")
|
||||
workflowDispatch = workflowDispatchConfig(workflow)
|
||||
assert.Nil(t, workflowDispatch)
|
||||
|
||||
yaml = `
|
||||
name: local-action-docker-url
|
||||
on: [push, workflow_dispatch]
|
||||
`
|
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml))
|
||||
assert.NoError(t, err, "read workflow should succeed")
|
||||
workflowDispatch = workflowDispatchConfig(workflow)
|
||||
assert.NotNil(t, workflowDispatch)
|
||||
assert.Nil(t, workflowDispatch.Inputs)
|
||||
|
||||
yaml = `
|
||||
name: local-action-docker-url
|
||||
on:
|
||||
- push
|
||||
- workflow_dispatch
|
||||
`
|
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml))
|
||||
assert.NoError(t, err, "read workflow should succeed")
|
||||
workflowDispatch = workflowDispatchConfig(workflow)
|
||||
assert.NotNil(t, workflowDispatch)
|
||||
assert.Nil(t, workflowDispatch.Inputs)
|
||||
|
||||
yaml = `
|
||||
name: local-action-docker-url
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
`
|
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml))
|
||||
assert.NoError(t, err, "read workflow should succeed")
|
||||
workflowDispatch = workflowDispatchConfig(workflow)
|
||||
assert.NotNil(t, workflowDispatch)
|
||||
assert.Nil(t, workflowDispatch.Inputs)
|
||||
|
||||
yaml = `
|
||||
name: local-action-docker-url
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
logLevel:
|
||||
description: 'Log level'
|
||||
required: true
|
||||
default: 'warning'
|
||||
type: choice
|
||||
options:
|
||||
- info
|
||||
- warning
|
||||
- debug
|
||||
boolean_default_true:
|
||||
description: 'Test scenario tags'
|
||||
required: true
|
||||
type: boolean
|
||||
default: true
|
||||
boolean_default_false:
|
||||
description: 'Test scenario tags'
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
`
|
||||
|
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml))
|
||||
assert.NoError(t, err, "read workflow should succeed")
|
||||
workflowDispatch = workflowDispatchConfig(workflow)
|
||||
assert.NotNil(t, workflowDispatch)
|
||||
assert.Equal(t, WorkflowDispatchInput{
|
||||
Name: "logLevel",
|
||||
Default: "warning",
|
||||
Description: "Log level",
|
||||
Options: []string{
|
||||
"info",
|
||||
"warning",
|
||||
"debug",
|
||||
},
|
||||
Required: true,
|
||||
Type: "choice",
|
||||
}, workflowDispatch.Inputs[0])
|
||||
assert.Equal(t, WorkflowDispatchInput{
|
||||
Name: "boolean_default_true",
|
||||
Default: "true",
|
||||
Description: "Test scenario tags",
|
||||
Required: true,
|
||||
Type: "boolean",
|
||||
}, workflowDispatch.Inputs[1])
|
||||
assert.Equal(t, WorkflowDispatchInput{
|
||||
Name: "boolean_default_false",
|
||||
Default: "false",
|
||||
Description: "Test scenario tags",
|
||||
Required: true,
|
||||
Type: "boolean",
|
||||
}, workflowDispatch.Inputs[2])
|
||||
}
|
||||
@@ -18,24 +18,42 @@ import (
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/actions"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
context_module "code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
|
||||
"github.com/nektos/act/pkg/jobparser"
|
||||
"github.com/nektos/act/pkg/model"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func getRunIndex(ctx *context_module.Context) int64 {
|
||||
// if run param is "latest", get the latest run index
|
||||
if ctx.PathParam("run") == "latest" {
|
||||
if run, _ := actions_model.GetLatestRun(ctx, ctx.Repo.Repository.ID); run != nil {
|
||||
return run.Index
|
||||
}
|
||||
}
|
||||
return ctx.PathParamInt64("run")
|
||||
}
|
||||
|
||||
func View(ctx *context_module.Context) {
|
||||
ctx.Data["PageIsActions"] = true
|
||||
runIndex := ctx.PathParamInt64("run")
|
||||
runIndex := getRunIndex(ctx)
|
||||
jobIndex := ctx.PathParamInt64("job")
|
||||
ctx.Data["RunIndex"] = runIndex
|
||||
ctx.Data["JobIndex"] = jobIndex
|
||||
@@ -130,7 +148,7 @@ type ViewStepLogLine struct {
|
||||
|
||||
func ViewPost(ctx *context_module.Context) {
|
||||
req := web.GetForm(ctx).(*ViewRequest)
|
||||
runIndex := ctx.PathParamInt64("run")
|
||||
runIndex := getRunIndex(ctx)
|
||||
jobIndex := ctx.PathParamInt64("job")
|
||||
|
||||
current, jobs := getRunJobs(ctx, runIndex, jobIndex)
|
||||
@@ -222,6 +240,27 @@ func ViewPost(ctx *context_module.Context) {
|
||||
|
||||
step := steps[cursor.Step]
|
||||
|
||||
// if task log is expired, return a consistent log line
|
||||
if task.LogExpired {
|
||||
if cursor.Cursor == 0 {
|
||||
resp.Logs.StepsLog = append(resp.Logs.StepsLog, &ViewStepLog{
|
||||
Step: cursor.Step,
|
||||
Cursor: 1,
|
||||
Lines: []*ViewStepLogLine{
|
||||
{
|
||||
Index: 1,
|
||||
Message: ctx.Locale.TrString("actions.runs.expire_log_message"),
|
||||
// Timestamp doesn't mean anything when the log is expired.
|
||||
// Set it to the task's updated time since it's probably the time when the log has expired.
|
||||
Timestamp: float64(task.Updated.AsTime().UnixNano()) / float64(time.Second),
|
||||
},
|
||||
},
|
||||
Started: int64(step.Started),
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
logLines := make([]*ViewStepLogLine, 0) // marshal to '[]' instead fo 'null' in json
|
||||
|
||||
index := step.LogIndex + cursor.Cursor
|
||||
@@ -268,7 +307,7 @@ func ViewPost(ctx *context_module.Context) {
|
||||
// Rerun will rerun jobs in the given run
|
||||
// If jobIndexStr is a blank string, it means rerun all jobs
|
||||
func Rerun(ctx *context_module.Context) {
|
||||
runIndex := ctx.PathParamInt64("run")
|
||||
runIndex := getRunIndex(ctx)
|
||||
jobIndexStr := ctx.PathParam("job")
|
||||
var jobIndex int64
|
||||
if jobIndexStr != "" {
|
||||
@@ -358,7 +397,7 @@ func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob, shou
|
||||
}
|
||||
|
||||
func Logs(ctx *context_module.Context) {
|
||||
runIndex := ctx.PathParamInt64("run")
|
||||
runIndex := getRunIndex(ctx)
|
||||
jobIndex := ctx.PathParamInt64("job")
|
||||
|
||||
job, _ := getRunJobs(ctx, runIndex, jobIndex)
|
||||
@@ -407,7 +446,7 @@ func Logs(ctx *context_module.Context) {
|
||||
}
|
||||
|
||||
func Cancel(ctx *context_module.Context) {
|
||||
runIndex := ctx.PathParamInt64("run")
|
||||
runIndex := getRunIndex(ctx)
|
||||
|
||||
_, jobs := getRunJobs(ctx, runIndex, -1)
|
||||
if ctx.Written() {
|
||||
@@ -448,7 +487,7 @@ func Cancel(ctx *context_module.Context) {
|
||||
}
|
||||
|
||||
func Approve(ctx *context_module.Context) {
|
||||
runIndex := ctx.PathParamInt64("run")
|
||||
runIndex := getRunIndex(ctx)
|
||||
|
||||
current, jobs := getRunJobs(ctx, runIndex, -1)
|
||||
if ctx.Written() {
|
||||
@@ -497,7 +536,6 @@ func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (*actions
|
||||
return nil, nil
|
||||
}
|
||||
run.Repo = ctx.Repo.Repository
|
||||
|
||||
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
@@ -529,7 +567,7 @@ type ArtifactsViewItem struct {
|
||||
}
|
||||
|
||||
func ArtifactsView(ctx *context_module.Context) {
|
||||
runIndex := ctx.PathParamInt64("run")
|
||||
runIndex := getRunIndex(ctx)
|
||||
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
@@ -567,7 +605,7 @@ func ArtifactsDeleteView(ctx *context_module.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
runIndex := ctx.PathParamInt64("run")
|
||||
runIndex := getRunIndex(ctx)
|
||||
artifactName := ctx.PathParam("artifact_name")
|
||||
|
||||
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
|
||||
@@ -585,7 +623,7 @@ func ArtifactsDeleteView(ctx *context_module.Context) {
|
||||
}
|
||||
|
||||
func ArtifactsDownloadView(ctx *context_module.Context) {
|
||||
runIndex := ctx.PathParamInt64("run")
|
||||
runIndex := getRunIndex(ctx)
|
||||
artifactName := ctx.PathParam("artifact_name")
|
||||
|
||||
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
|
||||
@@ -715,3 +753,164 @@ func disableOrEnableWorkflowFile(ctx *context_module.Context, isEnable bool) {
|
||||
url.QueryEscape(ctx.FormString("actor")), url.QueryEscape(ctx.FormString("status")))
|
||||
ctx.JSONRedirect(redirectURL)
|
||||
}
|
||||
|
||||
func Run(ctx *context_module.Context) {
|
||||
redirectURL := fmt.Sprintf("%s/actions?workflow=%s&actor=%s&status=%s", ctx.Repo.RepoLink, url.QueryEscape(ctx.FormString("workflow")),
|
||||
url.QueryEscape(ctx.FormString("actor")), url.QueryEscape(ctx.FormString("status")))
|
||||
|
||||
workflowID := ctx.FormString("workflow")
|
||||
if len(workflowID) == 0 {
|
||||
ctx.ServerError("workflow", nil)
|
||||
return
|
||||
}
|
||||
|
||||
ref := ctx.FormString("ref")
|
||||
if len(ref) == 0 {
|
||||
ctx.ServerError("ref", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// can not rerun job when workflow is disabled
|
||||
cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions)
|
||||
cfg := cfgUnit.ActionsConfig()
|
||||
if cfg.IsWorkflowDisabled(workflowID) {
|
||||
ctx.Flash.Error(ctx.Tr("actions.workflow.disabled"))
|
||||
ctx.Redirect(redirectURL)
|
||||
return
|
||||
}
|
||||
|
||||
// get target commit of run from specified ref
|
||||
refName := git.RefName(ref)
|
||||
var runTargetCommit *git.Commit
|
||||
var err error
|
||||
if refName.IsTag() {
|
||||
runTargetCommit, err = ctx.Repo.GitRepo.GetTagCommit(refName.TagName())
|
||||
} else if refName.IsBranch() {
|
||||
runTargetCommit, err = ctx.Repo.GitRepo.GetBranchCommit(refName.BranchName())
|
||||
} else {
|
||||
ctx.Flash.Error(ctx.Tr("form.git_ref_name_error", ref))
|
||||
ctx.Redirect(redirectURL)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
ctx.Flash.Error(ctx.Tr("form.target_ref_not_exist", ref))
|
||||
ctx.Redirect(redirectURL)
|
||||
return
|
||||
}
|
||||
|
||||
// get workflow entry from default branch commit
|
||||
defaultBranchCommit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
entries, err := actions.ListWorkflows(defaultBranchCommit)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// find workflow from commit
|
||||
var workflows []*jobparser.SingleWorkflow
|
||||
for _, entry := range entries {
|
||||
if entry.Name() == workflowID {
|
||||
content, err := actions.GetContentFromEntry(entry)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
workflows, err = jobparser.Parse(content)
|
||||
if err != nil {
|
||||
ctx.ServerError("workflow", err)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(workflows) == 0 {
|
||||
ctx.Flash.Error(ctx.Tr("actions.workflow.not_found", workflowID))
|
||||
ctx.Redirect(redirectURL)
|
||||
return
|
||||
}
|
||||
|
||||
// get inputs from post
|
||||
workflow := &model.Workflow{
|
||||
RawOn: workflows[0].RawOn,
|
||||
}
|
||||
inputs := make(map[string]any)
|
||||
if workflowDispatch := workflow.WorkflowDispatchConfig(); workflowDispatch != nil {
|
||||
for name, config := range workflowDispatch.Inputs {
|
||||
value := ctx.Req.PostForm.Get(name)
|
||||
if config.Type == "boolean" {
|
||||
// https://www.w3.org/TR/html401/interact/forms.html
|
||||
// https://stackoverflow.com/questions/11424037/do-checkbox-inputs-only-post-data-if-theyre-checked
|
||||
// Checkboxes (and radio buttons) are on/off switches that may be toggled by the user.
|
||||
// A switch is "on" when the control element's checked attribute is set.
|
||||
// When a form is submitted, only "on" checkbox controls can become successful.
|
||||
inputs[name] = strconv.FormatBool(value == "on")
|
||||
} else if value != "" {
|
||||
inputs[name] = value
|
||||
} else {
|
||||
inputs[name] = config.Default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ctx.Req.PostForm -> WorkflowDispatchPayload.Inputs -> ActionRun.EventPayload -> runner: ghc.Event
|
||||
// https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
|
||||
// https://docs.github.com/en/webhooks/webhook-events-and-payloads#workflow_dispatch
|
||||
workflowDispatchPayload := &api.WorkflowDispatchPayload{
|
||||
Workflow: workflowID,
|
||||
Ref: ref,
|
||||
Repository: convert.ToRepo(ctx, ctx.Repo.Repository, access_model.Permission{AccessMode: perm.AccessModeNone}),
|
||||
Inputs: inputs,
|
||||
Sender: convert.ToUserWithAccessMode(ctx, ctx.Doer, perm.AccessModeNone),
|
||||
}
|
||||
var eventPayload []byte
|
||||
if eventPayload, err = workflowDispatchPayload.JSONPayload(); err != nil {
|
||||
ctx.ServerError("JSONPayload", err)
|
||||
return
|
||||
}
|
||||
|
||||
run := &actions_model.ActionRun{
|
||||
Title: strings.SplitN(runTargetCommit.CommitMessage, "\n", 2)[0],
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
OwnerID: ctx.Repo.Repository.OwnerID,
|
||||
WorkflowID: workflowID,
|
||||
TriggerUserID: ctx.Doer.ID,
|
||||
Ref: ref,
|
||||
CommitSHA: runTargetCommit.ID.String(),
|
||||
IsForkPullRequest: false,
|
||||
Event: "workflow_dispatch",
|
||||
TriggerEvent: "workflow_dispatch",
|
||||
EventPayload: string(eventPayload),
|
||||
Status: actions_model.StatusWaiting,
|
||||
}
|
||||
|
||||
// cancel running jobs of the same workflow
|
||||
if err := actions_model.CancelPreviousJobs(
|
||||
ctx,
|
||||
run.RepoID,
|
||||
run.Ref,
|
||||
run.WorkflowID,
|
||||
run.Event,
|
||||
); err != nil {
|
||||
log.Error("CancelRunningJobs: %v", err)
|
||||
}
|
||||
|
||||
// Insert the action run and its associated jobs into the database
|
||||
if err := actions_model.InsertRun(ctx, run, workflows); err != nil {
|
||||
ctx.ServerError("workflow", err)
|
||||
return
|
||||
}
|
||||
|
||||
alljobs, err := db.Find[actions_model.ActionRunJob](ctx, actions_model.FindRunJobOptions{RunID: run.ID})
|
||||
if err != nil {
|
||||
log.Error("FindRunJobs: %v", err)
|
||||
}
|
||||
actions_service.CreateCommitStatus(ctx, alljobs...)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("actions.workflow.run_success", workflowID))
|
||||
ctx.Redirect(redirectURL)
|
||||
}
|
||||
|
||||
@@ -70,6 +70,11 @@ func Branches(ctx *context.Context) {
|
||||
ctx.ServerError("LoadBranches", err)
|
||||
return
|
||||
}
|
||||
if !ctx.Repo.CanRead(unit.TypeActions) {
|
||||
for key := range commitStatuses {
|
||||
git_model.CommitStatusesHideActionsURL(ctx, commitStatuses[key])
|
||||
}
|
||||
}
|
||||
|
||||
commitStatus := make(map[string]*git_model.CommitStatus)
|
||||
for commitID, cs := range commitStatuses {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/charset"
|
||||
@@ -81,8 +82,18 @@ func Commits(ctx *context.Context) {
|
||||
ctx.ServerError("CommitsByRange", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository)
|
||||
|
||||
ctx.Data["Commits"] = processGitCommits(ctx, commits)
|
||||
commitIDs := make([]string, 0, len(commits))
|
||||
for _, c := range commits {
|
||||
commitIDs = append(commitIDs, c.ID.String())
|
||||
}
|
||||
commitsTagsMap, err := repo_model.FindTagsByCommitIDs(ctx, ctx.Repo.Repository.ID, commitIDs...)
|
||||
if err != nil {
|
||||
log.Error("FindTagsByCommitIDs: %v", err)
|
||||
ctx.Flash.Error(ctx.Tr("repo.commit.load_tags_failed"))
|
||||
} else {
|
||||
ctx.Data["CommitsTagsMap"] = commitsTagsMap
|
||||
}
|
||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
|
||||
ctx.Data["CommitCount"] = commitsCount
|
||||
@@ -199,7 +210,7 @@ func SearchCommits(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
ctx.Data["CommitCount"] = len(commits)
|
||||
ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository)
|
||||
ctx.Data["Commits"] = processGitCommits(ctx, commits)
|
||||
|
||||
ctx.Data["Keyword"] = query
|
||||
if all {
|
||||
@@ -242,7 +253,7 @@ func FileHistory(ctx *context.Context) {
|
||||
ctx.ServerError("CommitsByFileAndRange", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository)
|
||||
ctx.Data["Commits"] = processGitCommits(ctx, commits)
|
||||
|
||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
|
||||
@@ -353,6 +364,9 @@ func Diff(ctx *context.Context) {
|
||||
if err != nil {
|
||||
log.Error("GetLatestCommitStatus: %v", err)
|
||||
}
|
||||
if !ctx.Repo.CanRead(unit_model.TypeActions) {
|
||||
git_model.CommitStatusesHideActionsURL(ctx, statuses)
|
||||
}
|
||||
|
||||
ctx.Data["CommitStatus"] = git_model.CalcCommitStatus(statuses)
|
||||
ctx.Data["CommitStatuses"] = statuses
|
||||
@@ -433,3 +447,14 @@ func RawDiff(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func processGitCommits(ctx *context.Context, gitCommits []*git.Commit) []*git_model.SignCommitWithStatuses {
|
||||
commits := git_model.ConvertFromGitCommit(ctx, gitCommits, ctx.Repo.Repository)
|
||||
if !ctx.Repo.CanRead(unit_model.TypeActions) {
|
||||
for _, commit := range commits {
|
||||
commit.Status.HideActionsURL(ctx)
|
||||
git_model.CommitStatusesHideActionsURL(ctx, commit.Statuses)
|
||||
}
|
||||
}
|
||||
return commits
|
||||
}
|
||||
|
||||
@@ -643,7 +643,7 @@ func PrepareCompareDiff(
|
||||
return false
|
||||
}
|
||||
|
||||
commits := git_model.ConvertFromGitCommit(ctx, ci.CompareInfo.Commits, ci.HeadRepo)
|
||||
commits := processGitCommits(ctx, ci.CompareInfo.Commits)
|
||||
ctx.Data["Commits"] = commits
|
||||
ctx.Data["CommitCount"] = len(commits)
|
||||
|
||||
|
||||
@@ -339,6 +339,11 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
||||
ctx.ServerError("GetIssuesAllCommitStatus", err)
|
||||
return
|
||||
}
|
||||
if !ctx.Repo.CanRead(unit.TypeActions) {
|
||||
for key := range commitStatuses {
|
||||
git_model.CommitStatusesHideActionsURL(ctx, commitStatuses[key])
|
||||
}
|
||||
}
|
||||
|
||||
if err := issues.LoadAttributes(ctx); err != nil {
|
||||
ctx.ServerError("issues.LoadAttributes", err)
|
||||
@@ -934,12 +939,23 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles
|
||||
}
|
||||
}
|
||||
}
|
||||
selectedAssigneeIDs := make([]int64, 0, len(template.Assignees))
|
||||
selectedAssigneeIDStrings := make([]string, 0, len(template.Assignees))
|
||||
if userIDs, err := user_model.GetUserIDsByNames(ctx, template.Assignees, false); err == nil {
|
||||
for _, userID := range userIDs {
|
||||
selectedAssigneeIDs = append(selectedAssigneeIDs, userID)
|
||||
selectedAssigneeIDStrings = append(selectedAssigneeIDStrings, strconv.FormatInt(userID, 10))
|
||||
}
|
||||
}
|
||||
|
||||
if template.Ref != "" && !strings.HasPrefix(template.Ref, "refs/") { // Assume that the ref intended is always a branch - for tags users should use refs/tags/<ref>
|
||||
template.Ref = git.BranchPrefix + template.Ref
|
||||
}
|
||||
ctx.Data["HasSelectedLabel"] = len(labelIDs) > 0
|
||||
ctx.Data["label_ids"] = strings.Join(labelIDs, ",")
|
||||
ctx.Data["HasSelectedAssignee"] = len(selectedAssigneeIDs) > 0
|
||||
ctx.Data["assignee_ids"] = strings.Join(selectedAssigneeIDStrings, ",")
|
||||
ctx.Data["SelectedAssigneeIDs"] = selectedAssigneeIDs
|
||||
ctx.Data["Reference"] = template.Ref
|
||||
ctx.Data["RefEndName"] = git.RefName(template.Ref).ShortName()
|
||||
return true, templateErrs
|
||||
@@ -1671,7 +1687,7 @@ func ViewIssue(ctx *context.Context) {
|
||||
}
|
||||
|
||||
ghostProject := &project_model.Project{
|
||||
ID: -1,
|
||||
ID: project_model.GhostProjectID,
|
||||
Title: ctx.Locale.TrString("repo.issues.deleted_project"),
|
||||
}
|
||||
|
||||
@@ -1682,6 +1698,11 @@ func ViewIssue(ctx *context.Context) {
|
||||
if comment.ProjectID > 0 && comment.Project == nil {
|
||||
comment.Project = ghostProject
|
||||
}
|
||||
} else if comment.Type == issues_model.CommentTypeProjectColumn {
|
||||
if err = comment.LoadProject(ctx); err != nil {
|
||||
ctx.ServerError("LoadProject", err)
|
||||
return
|
||||
}
|
||||
} else if comment.Type == issues_model.CommentTypeAssignees || comment.Type == issues_model.CommentTypeReviewRequest {
|
||||
if err = comment.LoadAssigneeUserAndTeam(ctx); err != nil {
|
||||
ctx.ServerError("LoadAssigneeUserAndTeam", err)
|
||||
@@ -1757,6 +1778,12 @@ func ViewIssue(ctx *context.Context) {
|
||||
ctx.ServerError("LoadPushCommits", err)
|
||||
return
|
||||
}
|
||||
if !ctx.Repo.CanRead(unit.TypeActions) {
|
||||
for _, commit := range comment.Commits {
|
||||
commit.Status.HideActionsURL(ctx)
|
||||
git_model.CommitStatusesHideActionsURL(ctx, commit.Statuses)
|
||||
}
|
||||
}
|
||||
} else if comment.Type == issues_model.CommentTypeAddTimeManual ||
|
||||
comment.Type == issues_model.CommentTypeStopTracking ||
|
||||
comment.Type == issues_model.CommentTypeDeleteTimeManual {
|
||||
@@ -1853,6 +1880,8 @@ func ViewIssue(ctx *context.Context) {
|
||||
}
|
||||
prConfig := prUnit.PullRequestsConfig()
|
||||
|
||||
ctx.Data["AutodetectManualMerge"] = prConfig.AutodetectManualMerge
|
||||
|
||||
var mergeStyle repo_model.MergeStyle
|
||||
// Check correct values and select default
|
||||
if ms, ok := ctx.Data["MergeStyle"].(repo_model.MergeStyle); !ok ||
|
||||
@@ -1911,6 +1940,7 @@ func ViewIssue(ctx *context.Context) {
|
||||
ctx.Data["ChangedProtectedFiles"] = pull.ChangedProtectedFiles
|
||||
ctx.Data["IsBlockedByChangedProtectedFiles"] = len(pull.ChangedProtectedFiles) != 0
|
||||
ctx.Data["ChangedProtectedFilesNum"] = len(pull.ChangedProtectedFiles)
|
||||
ctx.Data["RequireApprovalsWhitelist"] = pb.EnableApprovalsWhitelist
|
||||
}
|
||||
ctx.Data["WillSign"] = false
|
||||
if ctx.Doer != nil {
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
project_service "code.gitea.io/gitea/services/projects"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -664,7 +665,7 @@ func MoveIssues(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if err = project_model.MoveIssuesOnProjectColumn(ctx, column, sortedIssueIDs); err != nil {
|
||||
if err = project_service.MoveIssuesOnProjectColumn(ctx, ctx.Doer, column, sortedIssueIDs); err != nil {
|
||||
ctx.ServerError("MoveIssuesOnProjectColumn", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -283,6 +283,10 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue)
|
||||
ctx.ServerError("GetLatestCommitStatus", err)
|
||||
return nil
|
||||
}
|
||||
if !ctx.Repo.CanRead(unit.TypeActions) {
|
||||
git_model.CommitStatusesHideActionsURL(ctx, commitStatuses)
|
||||
}
|
||||
|
||||
if len(commitStatuses) != 0 {
|
||||
ctx.Data["LatestCommitStatuses"] = commitStatuses
|
||||
ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses)
|
||||
@@ -345,6 +349,10 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
|
||||
ctx.ServerError("GetLatestCommitStatus", err)
|
||||
return nil
|
||||
}
|
||||
if !ctx.Repo.CanRead(unit.TypeActions) {
|
||||
git_model.CommitStatusesHideActionsURL(ctx, commitStatuses)
|
||||
}
|
||||
|
||||
if len(commitStatuses) > 0 {
|
||||
ctx.Data["LatestCommitStatuses"] = commitStatuses
|
||||
ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses)
|
||||
@@ -437,6 +445,10 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
|
||||
ctx.ServerError("GetLatestCommitStatus", err)
|
||||
return nil
|
||||
}
|
||||
if !ctx.Repo.CanRead(unit.TypeActions) {
|
||||
git_model.CommitStatusesHideActionsURL(ctx, commitStatuses)
|
||||
}
|
||||
|
||||
if len(commitStatuses) > 0 {
|
||||
ctx.Data["LatestCommitStatuses"] = commitStatuses
|
||||
ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses)
|
||||
@@ -603,7 +615,7 @@ func ViewPullCommits(ctx *context.Context) {
|
||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
|
||||
|
||||
commits := git_model.ConvertFromGitCommit(ctx, prInfo.Commits, ctx.Repo.Repository)
|
||||
commits := processGitCommits(ctx, prInfo.Commits)
|
||||
ctx.Data["Commits"] = commits
|
||||
ctx.Data["CommitCount"] = len(commits)
|
||||
|
||||
@@ -1296,9 +1308,10 @@ func CompareAndPullRequestPost(ctx *context.Context) {
|
||||
// instead of 500.
|
||||
|
||||
if err := pull_service.NewPullRequest(ctx, repo, pullIssue, labelIDs, attachments, pullRequest, assigneeIDs); err != nil {
|
||||
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) {
|
||||
switch {
|
||||
case repo_model.IsErrUserDoesNotHaveAccessToRepo(err):
|
||||
ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error())
|
||||
} else if git.IsErrPushRejected(err) {
|
||||
case git.IsErrPushRejected(err):
|
||||
pushrejErr := err.(*git.ErrPushRejected)
|
||||
message := pushrejErr.Message
|
||||
if len(message) == 0 {
|
||||
@@ -1315,7 +1328,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
ctx.JSONError(flashError)
|
||||
} else if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
case errors.Is(err, user_model.ErrBlockedUser):
|
||||
flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
|
||||
"Message": ctx.Tr("repo.pulls.push_rejected"),
|
||||
"Summary": ctx.Tr("repo.pulls.new.blocked_user"),
|
||||
@@ -1325,6 +1338,21 @@ func CompareAndPullRequestPost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
ctx.JSONError(flashError)
|
||||
case errors.Is(err, issues_model.ErrMustCollaborator):
|
||||
flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
|
||||
"Message": ctx.Tr("repo.pulls.push_rejected"),
|
||||
"Summary": ctx.Tr("repo.pulls.new.must_collaborator"),
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("CompareAndPullRequest.HTMLString", err)
|
||||
return
|
||||
}
|
||||
ctx.JSONError(flashError)
|
||||
default:
|
||||
// It's an unexpected error.
|
||||
// If it happens, we should add another case to handle it.
|
||||
log.Error("Unexpected error of NewPullRequest: %T %s", err, err)
|
||||
ctx.ServerError("CompareAndPullRequest", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -656,6 +656,9 @@ func SearchRepo(ctx *context.Context) {
|
||||
ctx.JSON(http.StatusInternalServerError, nil)
|
||||
return
|
||||
}
|
||||
if !ctx.Repo.CanRead(unit.TypeActions) {
|
||||
git_model.CommitStatusesHideActionsURL(ctx, latestCommitStatuses)
|
||||
}
|
||||
|
||||
results := make([]*repo_service.WebSearchRepository, len(repos))
|
||||
for i, repo := range repos {
|
||||
@@ -668,7 +671,7 @@ func SearchRepo(ctx *context.Context) {
|
||||
Template: repo.IsTemplate,
|
||||
Mirror: repo.IsMirror,
|
||||
Stars: repo.NumStars,
|
||||
HTMLURL: repo.HTMLURL(),
|
||||
HTMLURL: repo.HTMLURL(ctx),
|
||||
Link: repo.Link(),
|
||||
Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate,
|
||||
},
|
||||
|
||||
@@ -95,6 +95,11 @@ func LFSLocks(ctx *context.Context) {
|
||||
ctx.ServerError("LFSLocks", err)
|
||||
return
|
||||
}
|
||||
if err := lfsLocks.LoadAttributes(ctx); err != nil {
|
||||
ctx.ServerError("LFSLocks", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["LFSLocks"] = lfsLocks
|
||||
|
||||
if len(lfsLocks) == 0 {
|
||||
|
||||
@@ -77,6 +77,7 @@ func SettingsProtectedBranch(c *context.Context) {
|
||||
}
|
||||
c.Data["Users"] = users
|
||||
c.Data["whitelist_users"] = strings.Join(base.Int64sToStrings(rule.WhitelistUserIDs), ",")
|
||||
c.Data["force_push_allowlist_users"] = strings.Join(base.Int64sToStrings(rule.ForcePushAllowlistUserIDs), ",")
|
||||
c.Data["merge_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistUserIDs), ",")
|
||||
c.Data["approvals_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistUserIDs), ",")
|
||||
c.Data["status_check_contexts"] = strings.Join(rule.StatusCheckContexts, "\n")
|
||||
@@ -91,6 +92,7 @@ func SettingsProtectedBranch(c *context.Context) {
|
||||
}
|
||||
c.Data["Teams"] = teams
|
||||
c.Data["whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.WhitelistTeamIDs), ",")
|
||||
c.Data["force_push_allowlist_teams"] = strings.Join(base.Int64sToStrings(rule.ForcePushAllowlistTeamIDs), ",")
|
||||
c.Data["merge_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistTeamIDs), ",")
|
||||
c.Data["approvals_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistTeamIDs), ",")
|
||||
}
|
||||
@@ -149,7 +151,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
var whitelistUsers, whitelistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams []int64
|
||||
var whitelistUsers, whitelistTeams, forcePushAllowlistUsers, forcePushAllowlistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams []int64
|
||||
protectBranch.RuleName = f.RuleName
|
||||
if f.RequiredApprovals < 0 {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_approvals_min"))
|
||||
@@ -178,6 +180,27 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
|
||||
protectBranch.WhitelistDeployKeys = false
|
||||
}
|
||||
|
||||
switch f.EnableForcePush {
|
||||
case "all":
|
||||
protectBranch.CanForcePush = true
|
||||
protectBranch.EnableForcePushAllowlist = false
|
||||
protectBranch.ForcePushAllowlistDeployKeys = false
|
||||
case "whitelist":
|
||||
protectBranch.CanForcePush = true
|
||||
protectBranch.EnableForcePushAllowlist = true
|
||||
protectBranch.ForcePushAllowlistDeployKeys = f.ForcePushAllowlistDeployKeys
|
||||
if strings.TrimSpace(f.ForcePushAllowlistUsers) != "" {
|
||||
forcePushAllowlistUsers, _ = base.StringsToInt64s(strings.Split(f.ForcePushAllowlistUsers, ","))
|
||||
}
|
||||
if strings.TrimSpace(f.ForcePushAllowlistTeams) != "" {
|
||||
forcePushAllowlistTeams, _ = base.StringsToInt64s(strings.Split(f.ForcePushAllowlistTeams, ","))
|
||||
}
|
||||
default:
|
||||
protectBranch.CanForcePush = false
|
||||
protectBranch.EnableForcePushAllowlist = false
|
||||
protectBranch.ForcePushAllowlistDeployKeys = false
|
||||
}
|
||||
|
||||
protectBranch.EnableMergeWhitelist = f.EnableMergeWhitelist
|
||||
if f.EnableMergeWhitelist {
|
||||
if strings.TrimSpace(f.MergeWhitelistUsers) != "" {
|
||||
@@ -237,6 +260,8 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
|
||||
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
|
||||
UserIDs: whitelistUsers,
|
||||
TeamIDs: whitelistTeams,
|
||||
ForcePushUserIDs: forcePushAllowlistUsers,
|
||||
ForcePushTeamIDs: forcePushAllowlistTeams,
|
||||
MergeUserIDs: mergeWhitelistUsers,
|
||||
MergeTeamIDs: mergeWhitelistTeams,
|
||||
ApprovalsUserIDs: approvalsWhitelistUsers,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
shared "code.gitea.io/gitea/routers/web/shared/secrets"
|
||||
@@ -74,6 +75,7 @@ func Secrets(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("actions.actions")
|
||||
ctx.Data["PageType"] = "secrets"
|
||||
ctx.Data["PageIsSharedSettingsSecrets"] = true
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
|
||||
sCtx, err := getSecretsCtx(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -170,15 +170,7 @@ func SettingsPost(ctx *context.Context) {
|
||||
form.Private = repo.BaseRepo.IsPrivate || repo.BaseRepo.Owner.Visibility == structs.VisibleTypePrivate
|
||||
}
|
||||
|
||||
visibilityChanged := repo.IsPrivate != form.Private
|
||||
// when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public
|
||||
if visibilityChanged && setting.Repository.ForcePrivate && !form.Private && !ctx.Doer.IsAdmin {
|
||||
ctx.RenderWithErr(ctx.Tr("form.repository_force_private"), tplSettingsOptions, form)
|
||||
return
|
||||
}
|
||||
|
||||
repo.IsPrivate = form.Private
|
||||
if err := repo_service.UpdateRepository(ctx, repo, visibilityChanged); err != nil {
|
||||
if err := repo_service.UpdateRepository(ctx, repo, false); err != nil {
|
||||
ctx.ServerError("UpdateRepository", err)
|
||||
return
|
||||
}
|
||||
@@ -248,7 +240,8 @@ func SettingsPost(ctx *context.Context) {
|
||||
|
||||
remoteAddress, err := util.SanitizeURL(form.MirrorAddress)
|
||||
if err != nil {
|
||||
ctx.ServerError("SanitizeURL", err)
|
||||
ctx.Data["Err_MirrorAddress"] = true
|
||||
handleSettingRemoteAddrError(ctx, err, form)
|
||||
return
|
||||
}
|
||||
pullMirror.RemoteAddress = remoteAddress
|
||||
@@ -409,7 +402,8 @@ func SettingsPost(ctx *context.Context) {
|
||||
|
||||
remoteAddress, err := util.SanitizeURL(form.PushMirrorAddress)
|
||||
if err != nil {
|
||||
ctx.ServerError("SanitizeURL", err)
|
||||
ctx.Data["Err_PushMirrorAddress"] = true
|
||||
handleSettingRemoteAddrError(ctx, err, form)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -940,6 +934,39 @@ func SettingsPost(ctx *context.Context) {
|
||||
log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
|
||||
|
||||
case "visibility":
|
||||
if repo.IsFork {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.visibility.fork_error"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public
|
||||
if setting.Repository.ForcePrivate && repo.IsPrivate && !ctx.Doer.IsAdmin {
|
||||
ctx.RenderWithErr(ctx.Tr("form.repository_force_private"), tplSettingsOptions, form)
|
||||
return
|
||||
}
|
||||
|
||||
if repo.IsPrivate {
|
||||
err = repo_service.MakeRepoPublic(ctx, repo)
|
||||
} else {
|
||||
err = repo_service.MakeRepoPrivate(ctx, repo)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Error("Tried to change the visibility of the repo: %s", err)
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.visibility.error"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.visibility.success"))
|
||||
|
||||
log.Trace("Repository visibility changed: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
|
||||
|
||||
default:
|
||||
ctx.NotFound("", nil)
|
||||
}
|
||||
|
||||
@@ -234,14 +234,12 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte,
|
||||
}
|
||||
|
||||
meta, err := git_model.GetLFSMetaObjectByOid(ctx, repoID, pointer.Oid)
|
||||
if err != nil && err != git_model.ErrLFSObjectNotExist { // fallback to plain file
|
||||
if err != nil { // fallback to plain file
|
||||
log.Warn("Unable to access LFS pointer %s in repo %d: %v", pointer.Oid, repoID, err)
|
||||
return buf, dataRc, &fileInfo{isTextFile, false, blob.Size(), nil, st}, nil
|
||||
}
|
||||
|
||||
dataRc.Close()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
dataRc, err = lfs.ReadMetaObject(pointer)
|
||||
if err != nil {
|
||||
@@ -363,6 +361,9 @@ func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool {
|
||||
if err != nil {
|
||||
log.Error("GetLatestCommitStatus: %v", err)
|
||||
}
|
||||
if !ctx.Repo.CanRead(unit_model.TypeActions) {
|
||||
git_model.CommitStatusesHideActionsURL(ctx, statuses)
|
||||
}
|
||||
|
||||
ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(statuses)
|
||||
ctx.Data["LatestCommitStatuses"] = statuses
|
||||
|
||||
+55
-10
@@ -138,18 +138,41 @@ func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte {
|
||||
return content
|
||||
}
|
||||
|
||||
// wikiContentsByName returns the contents of a wiki page, along with a boolean
|
||||
// indicating whether the page exists. Writes to ctx if an error occurs.
|
||||
func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName wiki_service.WebPath) ([]byte, *git.TreeEntry, string, bool) {
|
||||
// wikiEntryByName returns the entry of a wiki page, along with a boolean
|
||||
// indicating whether the entry exists. Writes to ctx if an error occurs.
|
||||
// The last return value indicates whether the file should be returned as a raw file
|
||||
func wikiEntryByName(ctx *context.Context, commit *git.Commit, wikiName wiki_service.WebPath) (*git.TreeEntry, string, bool, bool) {
|
||||
isRaw := false
|
||||
gitFilename := wiki_service.WebPathToGitPath(wikiName)
|
||||
entry, err := findEntryForFile(commit, gitFilename)
|
||||
if err != nil && !git.IsErrNotExist(err) {
|
||||
ctx.ServerError("findEntryForFile", err)
|
||||
return nil, nil, "", false
|
||||
} else if entry == nil {
|
||||
return nil, "", false, false
|
||||
}
|
||||
if entry == nil {
|
||||
// check if the file without ".md" suffix exists
|
||||
gitFilename := strings.TrimSuffix(gitFilename, ".md")
|
||||
entry, err = findEntryForFile(commit, gitFilename)
|
||||
if err != nil && !git.IsErrNotExist(err) {
|
||||
ctx.ServerError("findEntryForFile", err)
|
||||
return nil, "", false, false
|
||||
}
|
||||
isRaw = true
|
||||
}
|
||||
if entry == nil {
|
||||
return nil, "", true, false
|
||||
}
|
||||
return entry, gitFilename, false, isRaw
|
||||
}
|
||||
|
||||
// wikiContentsByName returns the contents of a wiki page, along with a boolean
|
||||
// indicating whether the page exists. Writes to ctx if an error occurs.
|
||||
func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName wiki_service.WebPath) ([]byte, *git.TreeEntry, string, bool) {
|
||||
entry, gitFilename, noEntry, _ := wikiEntryByName(ctx, commit, wikiName)
|
||||
if entry == nil {
|
||||
return nil, nil, "", true
|
||||
}
|
||||
return wikiContentsByEntry(ctx, entry), entry, gitFilename, false
|
||||
return wikiContentsByEntry(ctx, entry), entry, gitFilename, noEntry
|
||||
}
|
||||
|
||||
func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
|
||||
@@ -215,11 +238,14 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
|
||||
isSideBar := pageName == "_Sidebar"
|
||||
isFooter := pageName == "_Footer"
|
||||
|
||||
// lookup filename in wiki - get filecontent, gitTree entry , real filename
|
||||
data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName)
|
||||
// lookup filename in wiki - get gitTree entry , real filename
|
||||
entry, pageFilename, noEntry, isRaw := wikiEntryByName(ctx, commit, pageName)
|
||||
if noEntry {
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages")
|
||||
}
|
||||
if isRaw {
|
||||
ctx.Redirect(util.URLJoin(ctx.Repo.RepoLink, "wiki/raw", string(pageName)))
|
||||
}
|
||||
if entry == nil || ctx.Written() {
|
||||
if wikiRepo != nil {
|
||||
wikiRepo.Close()
|
||||
@@ -227,6 +253,15 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// get filecontent
|
||||
data := wikiContentsByEntry(ctx, entry)
|
||||
if ctx.Written() {
|
||||
if wikiRepo != nil {
|
||||
wikiRepo.Close()
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var sidebarContent []byte
|
||||
if !isSideBar {
|
||||
sidebarContent, _, _, _ = wikiContentsByName(ctx, commit, "_Sidebar")
|
||||
@@ -410,6 +445,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry)
|
||||
|
||||
pager := context.NewPagination(int(commitsCount), setting.Git.CommitsRangeSize, page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
pager.AddParamString("action", "_revision")
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
return wikiRepo, entry
|
||||
@@ -441,15 +477,24 @@ func renderEditPage(ctx *context.Context) {
|
||||
ctx.Data["Title"] = displayName
|
||||
ctx.Data["title"] = displayName
|
||||
|
||||
// lookup filename in wiki - get filecontent, gitTree entry , real filename
|
||||
data, entry, _, noEntry := wikiContentsByName(ctx, commit, pageName)
|
||||
// lookup filename in wiki - gitTree entry , real filename
|
||||
entry, _, noEntry, isRaw := wikiEntryByName(ctx, commit, pageName)
|
||||
if noEntry {
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages")
|
||||
}
|
||||
if isRaw {
|
||||
ctx.Error(http.StatusForbidden, "Editing of raw wiki files is not allowed")
|
||||
}
|
||||
if entry == nil || ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
// get filecontent
|
||||
data := wikiContentsByEntry(ctx, entry)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["content"] = string(data)
|
||||
ctx.Data["sidebarPresent"] = false
|
||||
ctx.Data["sidebarContent"] = ""
|
||||
|
||||
@@ -87,6 +87,13 @@ func TestWiki(t *testing.T) {
|
||||
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
|
||||
assert.EqualValues(t, "Home", ctx.Data["Title"])
|
||||
assertPagesMetas(t, []string{"Home", "Page With Image", "Page With Spaced Name", "Unescaped File"}, ctx.Data["Pages"])
|
||||
|
||||
ctx, _ = contexttest.MockContext(t, "user2/repo1/jpeg.jpg")
|
||||
ctx.SetPathParam("*", "jpeg.jpg")
|
||||
contexttest.LoadRepo(t, ctx, 1)
|
||||
Wiki(ctx)
|
||||
assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status())
|
||||
assert.Equal(t, "/user2/repo1/wiki/raw/jpeg.jpg", ctx.Resp.Header().Get("Location"))
|
||||
}
|
||||
|
||||
func TestWikiPages(t *testing.T) {
|
||||
@@ -160,6 +167,13 @@ func TestEditWiki(t *testing.T) {
|
||||
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
|
||||
assert.EqualValues(t, "Home", ctx.Data["Title"])
|
||||
assert.Equal(t, wikiContent(t, ctx.Repo.Repository, "Home"), ctx.Data["content"])
|
||||
|
||||
ctx, _ = contexttest.MockContext(t, "user2/repo1/wiki/jpeg.jpg?action=_edit")
|
||||
ctx.SetPathParam("*", "jpeg.jpg")
|
||||
contexttest.LoadUser(t, ctx, 2)
|
||||
contexttest.LoadRepo(t, ctx, 1)
|
||||
EditWiki(ctx)
|
||||
assert.EqualValues(t, http.StatusForbidden, ctx.Resp.Status())
|
||||
}
|
||||
|
||||
func TestEditWikiPost(t *testing.T) {
|
||||
|
||||
@@ -162,3 +162,15 @@ func LoadHeaderCount(ctx *context.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RenderOrgHeader(ctx *context.Context) error {
|
||||
if err := LoadHeaderCount(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, profileReadmeBlob, profileClose := FindUserProfileReadme(ctx, ctx.Doer)
|
||||
defer profileClose()
|
||||
ctx.Data["HasProfileReadme"] = profileReadmeBlob != nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
@@ -568,6 +569,11 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
ctx.ServerError("GetIssuesLastCommitStatus", err)
|
||||
return
|
||||
}
|
||||
if !ctx.Repo.CanRead(unit.TypeActions) {
|
||||
for key := range commitStatuses {
|
||||
git_model.CommitStatusesHideActionsURL(ctx, commitStatuses[key])
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// Fill stats to post to ctx.Data.
|
||||
|
||||
@@ -13,8 +13,10 @@ import (
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
@@ -303,6 +305,11 @@ func NotificationSubscriptions(ctx *context.Context) {
|
||||
ctx.ServerError("GetIssuesAllCommitStatus", err)
|
||||
return
|
||||
}
|
||||
if !ctx.Repo.CanRead(unit.TypeActions) {
|
||||
for key := range commitStatuses {
|
||||
git_model.CommitStatusesHideActionsURL(ctx, commitStatuses[key])
|
||||
}
|
||||
}
|
||||
ctx.Data["CommitLastStatus"] = lastStatus
|
||||
ctx.Data["CommitStatuses"] = commitStatuses
|
||||
ctx.Data["Issues"] = issues
|
||||
@@ -439,6 +446,21 @@ func NotificationWatching(ctx *context.Context) {
|
||||
// redirect to last page if request page is more than total pages
|
||||
pager := context.NewPagination(total, setting.UI.User.RepoPagingNum, page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
if archived.Has() {
|
||||
pager.AddParamString("archived", fmt.Sprint(archived.Value()))
|
||||
}
|
||||
if fork.Has() {
|
||||
pager.AddParamString("fork", fmt.Sprint(fork.Value()))
|
||||
}
|
||||
if mirror.Has() {
|
||||
pager.AddParamString("mirror", fmt.Sprint(mirror.Value()))
|
||||
}
|
||||
if template.Has() {
|
||||
pager.AddParamString("template", fmt.Sprint(template.Value()))
|
||||
}
|
||||
if private.Has() {
|
||||
pager.AddParamString("private", fmt.Sprint(private.Value()))
|
||||
}
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.Data["Status"] = 2
|
||||
|
||||
+20
-25
@@ -110,32 +110,12 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
|
||||
orderBy db.SearchOrderBy
|
||||
)
|
||||
|
||||
ctx.Data["SortType"] = ctx.FormString("sort")
|
||||
switch ctx.FormString("sort") {
|
||||
case "newest":
|
||||
orderBy = db.SearchOrderByNewest
|
||||
case "oldest":
|
||||
orderBy = db.SearchOrderByOldest
|
||||
case "recentupdate":
|
||||
orderBy = db.SearchOrderByRecentUpdated
|
||||
case "leastupdate":
|
||||
orderBy = db.SearchOrderByLeastUpdated
|
||||
case "reversealphabetically":
|
||||
orderBy = db.SearchOrderByAlphabeticallyReverse
|
||||
case "alphabetically":
|
||||
orderBy = db.SearchOrderByAlphabetically
|
||||
case "moststars":
|
||||
orderBy = db.SearchOrderByStarsReverse
|
||||
case "feweststars":
|
||||
orderBy = db.SearchOrderByStars
|
||||
case "mostforks":
|
||||
orderBy = db.SearchOrderByForksReverse
|
||||
case "fewestforks":
|
||||
orderBy = db.SearchOrderByForks
|
||||
default:
|
||||
ctx.Data["SortType"] = "recentupdate"
|
||||
orderBy = db.SearchOrderByRecentUpdated
|
||||
sortOrder := ctx.FormString("sort")
|
||||
if _, ok := repo_model.OrderByFlatMap[sortOrder]; !ok {
|
||||
sortOrder = setting.UI.ExploreDefaultSort // TODO: add new default sort order for user home?
|
||||
}
|
||||
ctx.Data["SortType"] = sortOrder
|
||||
orderBy = repo_model.OrderByFlatMap[sortOrder]
|
||||
|
||||
keyword := ctx.FormTrim("q")
|
||||
ctx.Data["Keyword"] = keyword
|
||||
@@ -333,6 +313,21 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
|
||||
pager.AddParamString("date", fmt.Sprint(ctx.Data["Date"]))
|
||||
}
|
||||
}
|
||||
if archived.Has() {
|
||||
pager.AddParamString("archived", fmt.Sprint(archived.Value()))
|
||||
}
|
||||
if fork.Has() {
|
||||
pager.AddParamString("fork", fmt.Sprint(fork.Value()))
|
||||
}
|
||||
if mirror.Has() {
|
||||
pager.AddParamString("mirror", fmt.Sprint(mirror.Value()))
|
||||
}
|
||||
if template.Has() {
|
||||
pager.AddParamString("template", fmt.Sprint(template.Value()))
|
||||
}
|
||||
if private.Has() {
|
||||
pager.AddParamString("private", fmt.Sprint(private.Value()))
|
||||
}
|
||||
ctx.Data["Page"] = pager
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ package setting
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@@ -33,6 +34,11 @@ const (
|
||||
|
||||
// Account renders change user's password, user's email and user suicide page
|
||||
func Account(ctx *context.Context) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials, setting.UserFeatureDeletion) && !setting.Service.EnableNotifyMail {
|
||||
ctx.NotFound("Not Found", fmt.Errorf("account setting are not allowed to be changed"))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("settings.account")
|
||||
ctx.Data["PageIsSettingsAccount"] = true
|
||||
ctx.Data["Email"] = ctx.Doer.Email
|
||||
@@ -45,9 +51,16 @@ func Account(ctx *context.Context) {
|
||||
|
||||
// AccountPost response for change user's password
|
||||
func AccountPost(ctx *context.Context) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) {
|
||||
ctx.NotFound("Not Found", fmt.Errorf("password setting is not allowed to be changed"))
|
||||
return
|
||||
}
|
||||
|
||||
form := web.GetForm(ctx).(*forms.ChangePasswordForm)
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsAccount"] = true
|
||||
ctx.Data["Email"] = ctx.Doer.Email
|
||||
ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail
|
||||
|
||||
if ctx.HasError() {
|
||||
loadAccountData(ctx)
|
||||
@@ -72,7 +85,7 @@ func AccountPost(ctx *context.Context) {
|
||||
case errors.Is(err, password.ErrComplexity):
|
||||
ctx.Flash.Error(password.BuildComplexityError(ctx.Locale))
|
||||
case errors.Is(err, password.ErrIsPwned):
|
||||
ctx.Flash.Error(ctx.Tr("auth.password_pwned"))
|
||||
ctx.Flash.Error(ctx.Tr("auth.password_pwned", "https://haveibeenpwned.com/Passwords"))
|
||||
case password.IsErrIsPwnedRequest(err):
|
||||
ctx.Flash.Error(ctx.Tr("auth.password_pwned_err"))
|
||||
default:
|
||||
@@ -89,9 +102,16 @@ func AccountPost(ctx *context.Context) {
|
||||
|
||||
// EmailPost response for change user's email
|
||||
func EmailPost(ctx *context.Context) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) {
|
||||
ctx.NotFound("Not Found", fmt.Errorf("emails are not allowed to be changed"))
|
||||
return
|
||||
}
|
||||
|
||||
form := web.GetForm(ctx).(*forms.AddEmailForm)
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsAccount"] = true
|
||||
ctx.Data["Email"] = ctx.Doer.Email
|
||||
ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail
|
||||
|
||||
// Make email address primary.
|
||||
if ctx.FormString("_method") == "PRIMARY" {
|
||||
@@ -216,6 +236,10 @@ func EmailPost(ctx *context.Context) {
|
||||
|
||||
// DeleteEmail response for delete user's email
|
||||
func DeleteEmail(ctx *context.Context) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) {
|
||||
ctx.NotFound("Not Found", fmt.Errorf("emails are not allowed to be changed"))
|
||||
return
|
||||
}
|
||||
email, err := user_model.GetEmailAddressByID(ctx, ctx.Doer.ID, ctx.FormInt64("id"))
|
||||
if err != nil || email == nil {
|
||||
ctx.ServerError("GetEmailAddressByID", err)
|
||||
@@ -241,6 +265,8 @@ func DeleteAccount(ctx *context.Context) {
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsAccount"] = true
|
||||
ctx.Data["Email"] = ctx.Doer.Email
|
||||
ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail
|
||||
|
||||
if _, _, err := auth.UserSignIn(ctx, ctx.Doer.Name, ctx.FormString("password")); err != nil {
|
||||
switch {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
@@ -24,6 +25,7 @@ const (
|
||||
func Applications(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings.applications")
|
||||
ctx.Data["PageIsSettingsApplications"] = true
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
|
||||
loadApplicationsData(ctx)
|
||||
|
||||
@@ -35,6 +37,7 @@ func ApplicationsPost(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.NewAccessTokenForm)
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsApplications"] = true
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
|
||||
if ctx.HasError() {
|
||||
loadApplicationsData(ctx)
|
||||
|
||||
@@ -6,6 +6,7 @@ package setting
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||
@@ -19,6 +20,7 @@ const (
|
||||
func BlockedUsers(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("user.block.list")
|
||||
ctx.Data["PageIsSettingsBlockedUsers"] = true
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
|
||||
shared_user.BlockedUsers(ctx, ctx.Doer)
|
||||
if ctx.Written() {
|
||||
|
||||
@@ -25,11 +25,17 @@ const (
|
||||
|
||||
// Keys render user's SSH/GPG public keys page
|
||||
func Keys(ctx *context.Context) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys, setting.UserFeatureManageGPGKeys) {
|
||||
ctx.NotFound("Not Found", fmt.Errorf("keys setting is not allowed to be changed"))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("settings.ssh_gpg_keys")
|
||||
ctx.Data["PageIsSettingsKeys"] = true
|
||||
ctx.Data["DisableSSH"] = setting.SSH.Disabled
|
||||
ctx.Data["BuiltinSSH"] = setting.SSH.StartBuiltinServer
|
||||
ctx.Data["AllowPrincipals"] = setting.SSH.AuthorizedPrincipalsEnabled
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
|
||||
loadKeysData(ctx)
|
||||
|
||||
@@ -44,6 +50,7 @@ func KeysPost(ctx *context.Context) {
|
||||
ctx.Data["DisableSSH"] = setting.SSH.Disabled
|
||||
ctx.Data["BuiltinSSH"] = setting.SSH.StartBuiltinServer
|
||||
ctx.Data["AllowPrincipals"] = setting.SSH.AuthorizedPrincipalsEnabled
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
|
||||
if ctx.HasError() {
|
||||
loadKeysData(ctx)
|
||||
|
||||
@@ -49,10 +49,11 @@ func (oa *OAuth2CommonHandlers) AddApp(ctx *context.Context) {
|
||||
|
||||
// TODO validate redirect URI
|
||||
app, err := auth.CreateOAuth2Application(ctx, auth.CreateOAuth2ApplicationOptions{
|
||||
Name: form.Name,
|
||||
RedirectURIs: util.SplitTrimSpace(form.RedirectURIs, "\n"),
|
||||
UserID: oa.OwnerID,
|
||||
ConfidentialClient: form.ConfidentialClient,
|
||||
Name: form.Name,
|
||||
RedirectURIs: util.SplitTrimSpace(form.RedirectURIs, "\n"),
|
||||
UserID: oa.OwnerID,
|
||||
ConfidentialClient: form.ConfidentialClient,
|
||||
SkipSecondaryAuthorization: form.SkipSecondaryAuthorization,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("CreateOAuth2Application", err)
|
||||
@@ -102,11 +103,12 @@ func (oa *OAuth2CommonHandlers) EditSave(ctx *context.Context) {
|
||||
// TODO validate redirect URI
|
||||
var err error
|
||||
if ctx.Data["App"], err = auth.UpdateOAuth2Application(ctx, auth.UpdateOAuth2ApplicationOptions{
|
||||
ID: ctx.PathParamInt64("id"),
|
||||
Name: form.Name,
|
||||
RedirectURIs: util.SplitTrimSpace(form.RedirectURIs, "\n"),
|
||||
UserID: oa.OwnerID,
|
||||
ConfidentialClient: form.ConfidentialClient,
|
||||
ID: ctx.PathParamInt64("id"),
|
||||
Name: form.Name,
|
||||
RedirectURIs: util.SplitTrimSpace(form.RedirectURIs, "\n"),
|
||||
UserID: oa.OwnerID,
|
||||
ConfidentialClient: form.ConfidentialClient,
|
||||
SkipSecondaryAuthorization: form.SkipSecondaryAuthorization,
|
||||
}); err != nil {
|
||||
ctx.ServerError("UpdateOAuth2Application", err)
|
||||
return
|
||||
|
||||
@@ -25,6 +25,7 @@ const (
|
||||
func Packages(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("packages.title")
|
||||
ctx.Data["PageIsSettingsPackages"] = true
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
|
||||
shared.SetPackagesContext(ctx, ctx.Doer)
|
||||
|
||||
@@ -34,6 +35,7 @@ func Packages(ctx *context.Context) {
|
||||
func PackagesRuleAdd(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("packages.title")
|
||||
ctx.Data["PageIsSettingsPackages"] = true
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
|
||||
shared.SetRuleAddContext(ctx)
|
||||
|
||||
@@ -43,6 +45,7 @@ func PackagesRuleAdd(ctx *context.Context) {
|
||||
func PackagesRuleEdit(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("packages.title")
|
||||
ctx.Data["PageIsSettingsPackages"] = true
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
|
||||
shared.SetRuleEditContext(ctx, ctx.Doer)
|
||||
|
||||
@@ -52,6 +55,7 @@ func PackagesRuleEdit(ctx *context.Context) {
|
||||
func PackagesRuleAddPost(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsPackages"] = true
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
|
||||
shared.PerformRuleAddPost(
|
||||
ctx,
|
||||
@@ -64,6 +68,7 @@ func PackagesRuleAddPost(ctx *context.Context) {
|
||||
func PackagesRuleEditPost(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("packages.title")
|
||||
ctx.Data["PageIsSettingsPackages"] = true
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
|
||||
shared.PerformRuleEditPost(
|
||||
ctx,
|
||||
@@ -76,6 +81,7 @@ func PackagesRuleEditPost(ctx *context.Context) {
|
||||
func PackagesRulePreview(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("packages.title")
|
||||
ctx.Data["PageIsSettingsPackages"] = true
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
|
||||
shared.SetRulePreviewContext(ctx, ctx.Doer)
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ func Profile(ctx *context.Context) {
|
||||
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
|
||||
ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx)
|
||||
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
|
||||
ctx.HTML(http.StatusOK, tplSettingsProfile)
|
||||
}
|
||||
|
||||
@@ -57,6 +59,7 @@ func ProfilePost(ctx *context.Context) {
|
||||
ctx.Data["PageIsSettingsProfile"] = true
|
||||
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
|
||||
ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx)
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(http.StatusOK, tplSettingsProfile)
|
||||
@@ -182,6 +185,7 @@ func DeleteAvatar(ctx *context.Context) {
|
||||
func Organization(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings.organization")
|
||||
ctx.Data["PageIsSettingsOrganization"] = true
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
|
||||
opts := organization.FindOrgOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
@@ -213,6 +217,7 @@ func Organization(ctx *context.Context) {
|
||||
func Repos(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings.repos")
|
||||
ctx.Data["PageIsSettingsRepos"] = true
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
ctx.Data["allowAdopt"] = ctx.IsUserSiteAdmin() || setting.Repository.AllowAdoptionOfUnadoptedRepositories
|
||||
ctx.Data["allowDelete"] = ctx.IsUserSiteAdmin() || setting.Repository.AllowDeleteOfUnadoptedRepositories
|
||||
|
||||
@@ -326,6 +331,7 @@ func Appearance(ctx *context.Context) {
|
||||
allThemes = append([]string{setting.UI.DefaultTheme}, allThemes...) // move the default theme to the top
|
||||
}
|
||||
ctx.Data["AllThemes"] = allThemes
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
|
||||
var hiddenCommentTypes *big.Int
|
||||
val, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyHiddenCommentTypes)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
@@ -25,6 +26,11 @@ import (
|
||||
|
||||
// RegenerateScratchTwoFactor regenerates the user's 2FA scratch code.
|
||||
func RegenerateScratchTwoFactor(ctx *context.Context) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) {
|
||||
ctx.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsSecurity"] = true
|
||||
|
||||
@@ -55,6 +61,11 @@ func RegenerateScratchTwoFactor(ctx *context.Context) {
|
||||
|
||||
// DisableTwoFactor deletes the user's 2FA settings.
|
||||
func DisableTwoFactor(ctx *context.Context) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) {
|
||||
ctx.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsSecurity"] = true
|
||||
|
||||
@@ -142,6 +153,11 @@ func twofaGenerateSecretAndQr(ctx *context.Context) bool {
|
||||
|
||||
// EnrollTwoFactor shows the page where the user can enroll into 2FA.
|
||||
func EnrollTwoFactor(ctx *context.Context) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) {
|
||||
ctx.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsSecurity"] = true
|
||||
|
||||
@@ -167,6 +183,11 @@ func EnrollTwoFactor(ctx *context.Context) {
|
||||
|
||||
// EnrollTwoFactorPost handles enrolling the user into 2FA.
|
||||
func EnrollTwoFactorPost(ctx *context.Context) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) {
|
||||
ctx.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
form := web.GetForm(ctx).(*forms.TwoFactorAuthForm)
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsSecurity"] = true
|
||||
|
||||
@@ -17,6 +17,11 @@ import (
|
||||
|
||||
// OpenIDPost response for change user's openid
|
||||
func OpenIDPost(ctx *context.Context) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) {
|
||||
ctx.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
form := web.GetForm(ctx).(*forms.AddOpenIDForm)
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsSecurity"] = true
|
||||
@@ -105,6 +110,11 @@ func settingsOpenIDVerify(ctx *context.Context) {
|
||||
|
||||
// DeleteOpenID response for delete user's openid
|
||||
func DeleteOpenID(ctx *context.Context) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) {
|
||||
ctx.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err := user_model.DeleteUserOpenID(ctx, &user_model.UserOpenID{ID: ctx.FormInt64("id"), UID: ctx.Doer.ID}); err != nil {
|
||||
ctx.ServerError("DeleteUserOpenID", err)
|
||||
return
|
||||
@@ -117,6 +127,11 @@ func DeleteOpenID(ctx *context.Context) {
|
||||
|
||||
// ToggleOpenIDVisibility response for toggle visibility of user's openid
|
||||
func ToggleOpenIDVisibility(ctx *context.Context) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) {
|
||||
ctx.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err := user_model.ToggleUserOpenIDVisibility(ctx, ctx.FormInt64("id")); err != nil {
|
||||
ctx.ServerError("ToggleUserOpenIDVisibility", err)
|
||||
return
|
||||
|
||||
@@ -25,6 +25,12 @@ const (
|
||||
|
||||
// Security render change user's password page and 2FA
|
||||
func Security(ctx *context.Context) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer,
|
||||
setting.UserFeatureManageMFA, setting.UserFeatureManageCredentials) {
|
||||
ctx.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("settings.security")
|
||||
ctx.Data["PageIsSettingsSecurity"] = true
|
||||
|
||||
@@ -40,6 +46,11 @@ func Security(ctx *context.Context) {
|
||||
|
||||
// DeleteAccountLink delete a single account link
|
||||
func DeleteAccountLink(ctx *context.Context) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) {
|
||||
ctx.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
id := ctx.FormInt64("id")
|
||||
if id <= 0 {
|
||||
ctx.Flash.Error("Account link id is not given")
|
||||
@@ -145,4 +156,5 @@ func loadSecurityData(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
ctx.Data["OpenIDs"] = openid
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
wa "code.gitea.io/gitea/modules/auth/webauthn"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@@ -23,6 +24,11 @@ import (
|
||||
|
||||
// WebAuthnRegister initializes the webauthn registration procedure
|
||||
func WebAuthnRegister(ctx *context.Context) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) {
|
||||
ctx.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
form := web.GetForm(ctx).(*forms.WebauthnRegistrationForm)
|
||||
if form.Name == "" {
|
||||
// Set name to the hexadecimal of the current time
|
||||
@@ -45,7 +51,9 @@ func WebAuthnRegister(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
credentialOptions, sessionData, err := wa.WebAuthn.BeginRegistration((*wa.User)(ctx.Doer))
|
||||
credentialOptions, sessionData, err := wa.WebAuthn.BeginRegistration((*wa.User)(ctx.Doer), webauthn.WithAuthenticatorSelection(protocol.AuthenticatorSelection{
|
||||
ResidentKey: protocol.ResidentKeyRequirementRequired,
|
||||
}))
|
||||
if err != nil {
|
||||
ctx.ServerError("Unable to BeginRegistration", err)
|
||||
return
|
||||
@@ -62,6 +70,11 @@ func WebAuthnRegister(ctx *context.Context) {
|
||||
|
||||
// WebauthnRegisterPost receives the response of the security key
|
||||
func WebauthnRegisterPost(ctx *context.Context) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) {
|
||||
ctx.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
name, ok := ctx.Session.Get("webauthnName").(string)
|
||||
if !ok || name == "" {
|
||||
ctx.ServerError("Get webauthnName", errors.New("no webauthnName"))
|
||||
@@ -111,6 +124,11 @@ func WebauthnRegisterPost(ctx *context.Context) {
|
||||
|
||||
// WebauthnDelete deletes an security key by id
|
||||
func WebauthnDelete(ctx *context.Context) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) {
|
||||
ctx.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
form := web.GetForm(ctx).(*forms.WebauthnDeleteForm)
|
||||
if _, err := auth.DeleteCredential(ctx, form.ID, ctx.Doer.ID); err != nil {
|
||||
ctx.ServerError("GetWebAuthnCredentialByID", err)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/models/webhook"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@@ -24,6 +25,7 @@ func Webhooks(ctx *context.Context) {
|
||||
ctx.Data["BaseLink"] = setting.AppSubURL + "/user/settings/hooks"
|
||||
ctx.Data["BaseLinkNew"] = setting.AppSubURL + "/user/settings/hooks"
|
||||
ctx.Data["Description"] = ctx.Tr("settings.hooks.desc")
|
||||
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
|
||||
|
||||
ws, err := db.Find[webhook.Webhook](ctx, webhook.ListWebhookOptions{OwnerID: ctx.Doer.ID})
|
||||
if err != nil {
|
||||
|
||||
+12
-1
@@ -535,6 +535,8 @@ func registerRoutes(m *web.Router) {
|
||||
})
|
||||
m.Group("/webauthn", func() {
|
||||
m.Get("", auth.WebAuthn)
|
||||
m.Get("/passkey/assertion", auth.WebAuthnPasskeyAssertion)
|
||||
m.Post("/passkey/login", auth.WebAuthnPasskeyLogin)
|
||||
m.Get("/assertion", auth.WebAuthnLoginAssertion)
|
||||
m.Post("/assertion", auth.WebAuthnLoginAssertionPost)
|
||||
})
|
||||
@@ -733,6 +735,7 @@ func registerRoutes(m *web.Router) {
|
||||
m.Group("/emails", func() {
|
||||
m.Get("", admin.Emails)
|
||||
m.Post("/activate", admin.ActivateEmail)
|
||||
m.Post("/delete", admin.DeleteEmail)
|
||||
})
|
||||
|
||||
m.Group("/orgs", func() {
|
||||
@@ -890,10 +893,15 @@ func registerRoutes(m *web.Router) {
|
||||
m.Post("/teams/{team}/action/repo/{action}", org.TeamsRepoAction)
|
||||
}, context.OrgAssignment(true, false, true))
|
||||
|
||||
// require admin permission
|
||||
m.Group("/{org}", func() {
|
||||
m.Get("/teams/-/search", org.SearchTeam)
|
||||
}, context.OrgAssignment(true, false, false, true))
|
||||
|
||||
// require owner permission
|
||||
m.Group("/{org}", func() {
|
||||
m.Get("/teams/new", org.NewTeam)
|
||||
m.Post("/teams/new", web.Bind(forms.CreateTeamForm{}), org.NewTeamPost)
|
||||
m.Get("/teams/-/search", org.SearchTeam)
|
||||
m.Get("/teams/{team}/edit", org.EditTeam)
|
||||
m.Post("/teams/{team}/edit", web.Bind(forms.CreateTeamForm{}), org.EditTeamPost)
|
||||
m.Post("/teams/{team}/delete", org.DeleteTeam)
|
||||
@@ -1002,6 +1010,8 @@ func registerRoutes(m *web.Router) {
|
||||
}, context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead))
|
||||
}
|
||||
|
||||
m.Get("/repositories", org.Repositories)
|
||||
|
||||
m.Group("/projects", func() {
|
||||
m.Group("", func() {
|
||||
m.Get("", org.Projects)
|
||||
@@ -1391,6 +1401,7 @@ func registerRoutes(m *web.Router) {
|
||||
m.Get("", actions.List)
|
||||
m.Post("/disable", reqRepoAdmin, actions.DisableWorkflowFile)
|
||||
m.Post("/enable", reqRepoAdmin, actions.EnableWorkflowFile)
|
||||
m.Post("/run", reqRepoAdmin, actions.Run)
|
||||
|
||||
m.Group("/runs/{run}", func() {
|
||||
m.Combo("").
|
||||
|
||||
Reference in New Issue
Block a user