mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-10-27 12:31:02 +00:00
(cherry picked from commit7b0549cd70) (cherry picked from commit13e10a65d9) (cherry picked from commit65bdd73cf2) (cherry picked from commit64eba8bb92) (cherry picked from commit4c49b1a759) (cherry picked from commit93b4d06406) (cherry picked from commite2bc5f36d9) (cherry picked from commit2bee76f9df) (cherry picked from commit3d8a1b4a9f) (cherry picked from commit99dd092cd0) (cherry picked from commit0fdbd02204) (cherry picked from commit70b277a183) (cherry picked from commit3eece7fbb4) (cherry picked from commit4838fc9e11) (cherry picked from commitb76ed541cf) (cherry picked from commitdcdfb5b65c) (cherry picked from commit377dc48cdc) (cherry picked from commitacc862f411) (cherry picked from commitac75ef101f) (cherry picked from commit08f2d9f7c5) (cherry picked from commite4096f0b64) (cherry picked from commitbf5876f062) (cherry picked from commit7dc60637e5) (cherry picked from commitef3101774b) (cherry picked from commitecb9e8867c) (cherry picked from commit64f0ae72fe) (cherry picked from commit8dd6ec7862) (cherry picked from commitb36723e52b) Conflicts: modules/context/api.go https://codeberg.org/forgejo/forgejo/pulls/1466 (cherry picked from commit5c378e0cb8) (cherry picked from commit1d87602819) (cherry picked from commit0f72002d66) (cherry picked from commitda2556eb13) (cherry picked from commitc01688cd90) (cherry picked from commitaf4bba8329) (cherry picked from commit33ca322c2e) Conflicts: modules/context/api.go https://codeberg.org/forgejo/forgejo/pulls/1739 (cherry picked from commitc18e374d44) (cherry picked from commit27c4797c9f) (cherry picked from commit46588e0fea) (cherry picked from commitb8a02ef220) (cherry picked from commit05e2f49b78) (cherry picked from commite4df86d312) (cherry picked from commitf36e06da43) (cherry picked from commit64d336c287) (cherry picked from commit2af1cbb017) (cherry picked from commit920741a9d6) (cherry picked from commit6cbb37c8c4) (cherry picked from commit539cceca39)
169 lines
4.9 KiB
Go
169 lines
4.9 KiB
Go
// Copyright 2014 The Gogs Authors. All rights reserved.
|
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package auth
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
actions_model "code.gitea.io/gitea/models/actions"
|
|
auth_model "code.gitea.io/gitea/models/auth"
|
|
user_model "code.gitea.io/gitea/models/user"
|
|
"code.gitea.io/gitea/modules/base"
|
|
"code.gitea.io/gitea/modules/log"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
"code.gitea.io/gitea/modules/timeutil"
|
|
"code.gitea.io/gitea/modules/util"
|
|
"code.gitea.io/gitea/modules/web/middleware"
|
|
)
|
|
|
|
// Ensure the struct implements the interface.
|
|
var (
|
|
_ Method = &Basic{}
|
|
)
|
|
|
|
// BasicMethodName is the constant name of the basic authentication method
|
|
const BasicMethodName = "basic"
|
|
|
|
// Basic implements the Auth interface and authenticates requests (API requests
|
|
// only) by looking for Basic authentication data or "x-oauth-basic" token in the "Authorization"
|
|
// header.
|
|
type Basic struct{}
|
|
|
|
// Name represents the name of auth method
|
|
func (b *Basic) Name() string {
|
|
return BasicMethodName
|
|
}
|
|
|
|
// Verify extracts and validates Basic data (username and password/token) from the
|
|
// "Authorization" header of the request and returns the corresponding user object for that
|
|
// name/token on successful validation.
|
|
// Returns nil if header is empty or validation fails.
|
|
func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
|
|
// Basic authentication should only fire on API, Download or on Git or LFSPaths
|
|
if !middleware.IsAPIPath(req) && !isContainerPath(req) && !isAttachmentDownload(req) && !isGitRawOrAttachOrLFSPath(req) {
|
|
return nil, nil
|
|
}
|
|
|
|
baHead := req.Header.Get("Authorization")
|
|
if len(baHead) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
auths := strings.SplitN(baHead, " ", 2)
|
|
if len(auths) != 2 || (strings.ToLower(auths[0]) != "basic") {
|
|
return nil, nil
|
|
}
|
|
|
|
uname, passwd, _ := base.BasicAuthDecode(auths[1])
|
|
|
|
// Check if username or password is a token
|
|
isUsernameToken := len(passwd) == 0 || passwd == "x-oauth-basic"
|
|
// Assume username is token
|
|
authToken := uname
|
|
if !isUsernameToken {
|
|
log.Trace("Basic Authorization: Attempting login for: %s", uname)
|
|
// Assume password is token
|
|
authToken = passwd
|
|
} else {
|
|
log.Trace("Basic Authorization: Attempting login with username as token")
|
|
}
|
|
|
|
// check oauth2 token
|
|
uid := CheckOAuthAccessToken(req.Context(), authToken)
|
|
if uid != 0 {
|
|
log.Trace("Basic Authorization: Valid OAuthAccessToken for user[%d]", uid)
|
|
|
|
u, err := user_model.GetUserByID(req.Context(), uid)
|
|
if err != nil {
|
|
log.Error("GetUserByID: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
store.GetData()["IsApiToken"] = true
|
|
return u, nil
|
|
}
|
|
|
|
// check personal access token
|
|
token, err := auth_model.GetAccessTokenBySHA(req.Context(), authToken)
|
|
if err == nil {
|
|
log.Trace("Basic Authorization: Valid AccessToken for user[%d]", uid)
|
|
u, err := user_model.GetUserByID(req.Context(), token.UID)
|
|
if err != nil {
|
|
log.Error("GetUserByID: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
token.UpdatedUnix = timeutil.TimeStampNow()
|
|
if err = auth_model.UpdateAccessToken(req.Context(), token); err != nil {
|
|
log.Error("UpdateAccessToken: %v", err)
|
|
}
|
|
|
|
store.GetData()["IsApiToken"] = true
|
|
store.GetData()["ApiTokenScope"] = token.Scope
|
|
return u, nil
|
|
} else if !auth_model.IsErrAccessTokenNotExist(err) && !auth_model.IsErrAccessTokenEmpty(err) {
|
|
log.Error("GetAccessTokenBySha: %v", err)
|
|
}
|
|
|
|
// check task token
|
|
task, err := actions_model.GetRunningTaskByToken(req.Context(), authToken)
|
|
if err == nil && task != nil {
|
|
log.Trace("Basic Authorization: Valid AccessToken for task[%d]", task.ID)
|
|
|
|
store.GetData()["IsActionsToken"] = true
|
|
store.GetData()["ActionsTaskID"] = task.ID
|
|
|
|
return user_model.NewActionsUser(), nil
|
|
}
|
|
|
|
if !setting.Service.EnableBasicAuth {
|
|
return nil, nil
|
|
}
|
|
|
|
log.Trace("Basic Authorization: Attempting SignIn for %s", uname)
|
|
u, source, err := UserSignIn(req.Context(), uname, passwd)
|
|
if err != nil {
|
|
if !user_model.IsErrUserNotExist(err) {
|
|
log.Error("UserSignIn: %v", err)
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
if skipper, ok := source.Cfg.(LocalTwoFASkipper); !ok || !skipper.IsSkipLocalTwoFA() {
|
|
if err := validateTOTP(req, u); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
log.Trace("Basic Authorization: Logged in user %-v", u)
|
|
|
|
return u, nil
|
|
}
|
|
|
|
func getOtpHeader(header http.Header) string {
|
|
otpHeader := header.Get("X-Gitea-OTP")
|
|
if forgejoHeader := header.Get("X-Forgejo-OTP"); forgejoHeader != "" {
|
|
otpHeader = forgejoHeader
|
|
}
|
|
return otpHeader
|
|
}
|
|
|
|
func validateTOTP(req *http.Request, u *user_model.User) error {
|
|
twofa, err := auth_model.GetTwoFactorByUID(req.Context(), u.ID)
|
|
if err != nil {
|
|
if auth_model.IsErrTwoFactorNotEnrolled(err) {
|
|
// No 2FA enrollment for this user
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
if ok, err := twofa.ValidateTOTP(getOtpHeader(req.Header)); err != nil {
|
|
return err
|
|
} else if !ok {
|
|
return util.NewInvalidArgumentErrorf("invalid provided OTP")
|
|
}
|
|
return nil
|
|
}
|