code.gitea.io/gitea@v1.22.3/services/auth/basic.go (about)

     1  // Copyright 2014 The Gogs Authors. All rights reserved.
     2  // Copyright 2019 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package auth
     6  
     7  import (
     8  	"net/http"
     9  	"strings"
    10  
    11  	actions_model "code.gitea.io/gitea/models/actions"
    12  	auth_model "code.gitea.io/gitea/models/auth"
    13  	user_model "code.gitea.io/gitea/models/user"
    14  	"code.gitea.io/gitea/modules/base"
    15  	"code.gitea.io/gitea/modules/log"
    16  	"code.gitea.io/gitea/modules/setting"
    17  	"code.gitea.io/gitea/modules/timeutil"
    18  	"code.gitea.io/gitea/modules/util"
    19  	"code.gitea.io/gitea/modules/web/middleware"
    20  )
    21  
    22  // Ensure the struct implements the interface.
    23  var (
    24  	_ Method = &Basic{}
    25  )
    26  
    27  // BasicMethodName is the constant name of the basic authentication method
    28  const (
    29  	BasicMethodName       = "basic"
    30  	AccessTokenMethodName = "access_token"
    31  	OAuth2TokenMethodName = "oauth2_token"
    32  	ActionTokenMethodName = "action_token"
    33  )
    34  
    35  // Basic implements the Auth interface and authenticates requests (API requests
    36  // only) by looking for Basic authentication data or "x-oauth-basic" token in the "Authorization"
    37  // header.
    38  type Basic struct{}
    39  
    40  // Name represents the name of auth method
    41  func (b *Basic) Name() string {
    42  	return BasicMethodName
    43  }
    44  
    45  // Verify extracts and validates Basic data (username and password/token) from the
    46  // "Authorization" header of the request and returns the corresponding user object for that
    47  // name/token on successful validation.
    48  // Returns nil if header is empty or validation fails.
    49  func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
    50  	// Basic authentication should only fire on API, Download or on Git or LFSPaths
    51  	if !middleware.IsAPIPath(req) && !isContainerPath(req) && !isAttachmentDownload(req) && !isGitRawOrAttachOrLFSPath(req) {
    52  		return nil, nil
    53  	}
    54  
    55  	baHead := req.Header.Get("Authorization")
    56  	if len(baHead) == 0 {
    57  		return nil, nil
    58  	}
    59  
    60  	auths := strings.SplitN(baHead, " ", 2)
    61  	if len(auths) != 2 || (strings.ToLower(auths[0]) != "basic") {
    62  		return nil, nil
    63  	}
    64  
    65  	uname, passwd, _ := base.BasicAuthDecode(auths[1])
    66  
    67  	// Check if username or password is a token
    68  	isUsernameToken := len(passwd) == 0 || passwd == "x-oauth-basic"
    69  	// Assume username is token
    70  	authToken := uname
    71  	if !isUsernameToken {
    72  		log.Trace("Basic Authorization: Attempting login for: %s", uname)
    73  		// Assume password is token
    74  		authToken = passwd
    75  	} else {
    76  		log.Trace("Basic Authorization: Attempting login with username as token")
    77  	}
    78  
    79  	// check oauth2 token
    80  	uid := CheckOAuthAccessToken(req.Context(), authToken)
    81  	if uid != 0 {
    82  		log.Trace("Basic Authorization: Valid OAuthAccessToken for user[%d]", uid)
    83  
    84  		u, err := user_model.GetUserByID(req.Context(), uid)
    85  		if err != nil {
    86  			log.Error("GetUserByID:  %v", err)
    87  			return nil, err
    88  		}
    89  
    90  		store.GetData()["LoginMethod"] = OAuth2TokenMethodName
    91  		store.GetData()["IsApiToken"] = true
    92  		return u, nil
    93  	}
    94  
    95  	// check personal access token
    96  	token, err := auth_model.GetAccessTokenBySHA(req.Context(), authToken)
    97  	if err == nil {
    98  		log.Trace("Basic Authorization: Valid AccessToken for user[%d]", uid)
    99  		u, err := user_model.GetUserByID(req.Context(), token.UID)
   100  		if err != nil {
   101  			log.Error("GetUserByID:  %v", err)
   102  			return nil, err
   103  		}
   104  
   105  		token.UpdatedUnix = timeutil.TimeStampNow()
   106  		if err = auth_model.UpdateAccessToken(req.Context(), token); err != nil {
   107  			log.Error("UpdateAccessToken:  %v", err)
   108  		}
   109  
   110  		store.GetData()["LoginMethod"] = AccessTokenMethodName
   111  		store.GetData()["IsApiToken"] = true
   112  		store.GetData()["ApiTokenScope"] = token.Scope
   113  		return u, nil
   114  	} else if !auth_model.IsErrAccessTokenNotExist(err) && !auth_model.IsErrAccessTokenEmpty(err) {
   115  		log.Error("GetAccessTokenBySha: %v", err)
   116  	}
   117  
   118  	// check task token
   119  	task, err := actions_model.GetRunningTaskByToken(req.Context(), authToken)
   120  	if err == nil && task != nil {
   121  		log.Trace("Basic Authorization: Valid AccessToken for task[%d]", task.ID)
   122  
   123  		store.GetData()["LoginMethod"] = ActionTokenMethodName
   124  		store.GetData()["IsActionsToken"] = true
   125  		store.GetData()["ActionsTaskID"] = task.ID
   126  
   127  		return user_model.NewActionsUser(), nil
   128  	}
   129  
   130  	if !setting.Service.EnableBasicAuth {
   131  		return nil, nil
   132  	}
   133  
   134  	log.Trace("Basic Authorization: Attempting SignIn for %s", uname)
   135  	u, source, err := UserSignIn(req.Context(), uname, passwd)
   136  	if err != nil {
   137  		if !user_model.IsErrUserNotExist(err) {
   138  			log.Error("UserSignIn: %v", err)
   139  		}
   140  		return nil, err
   141  	}
   142  
   143  	if skipper, ok := source.Cfg.(LocalTwoFASkipper); !ok || !skipper.IsSkipLocalTwoFA() {
   144  		if err := validateTOTP(req, u); err != nil {
   145  			return nil, err
   146  		}
   147  	}
   148  
   149  	store.GetData()["LoginMethod"] = BasicMethodName
   150  	log.Trace("Basic Authorization: Logged in user %-v", u)
   151  
   152  	return u, nil
   153  }
   154  
   155  func validateTOTP(req *http.Request, u *user_model.User) error {
   156  	twofa, err := auth_model.GetTwoFactorByUID(req.Context(), u.ID)
   157  	if err != nil {
   158  		if auth_model.IsErrTwoFactorNotEnrolled(err) {
   159  			// No 2FA enrollment for this user
   160  			return nil
   161  		}
   162  		return err
   163  	}
   164  	if ok, err := twofa.ValidateTOTP(req.Header.Get("X-Gitea-OTP")); err != nil {
   165  		return err
   166  	} else if !ok {
   167  		return util.NewInvalidArgumentErrorf("invalid provided OTP")
   168  	}
   169  	return nil
   170  }
   171  
   172  func GetAccessScope(store DataStore) auth_model.AccessTokenScope {
   173  	if v, ok := store.GetData()["ApiTokenScope"]; ok {
   174  		return v.(auth_model.AccessTokenScope)
   175  	}
   176  	switch store.GetData()["LoginMethod"] {
   177  	case OAuth2TokenMethodName:
   178  		fallthrough
   179  	case BasicMethodName, AccessTokenMethodName:
   180  		return auth_model.AccessTokenScopeAll
   181  	case ActionTokenMethodName:
   182  		fallthrough
   183  	default:
   184  		return ""
   185  	}
   186  }