mirror of https://github.com/gogs/gogs.git
auth: enable authentication by token from password (#7198)
Co-authored-by: Joe Chen <jc@unknwon.io>pull/7202/head
parent
fd5874b07b
commit
b9f5cfddc1
|
@ -6,6 +6,7 @@ All notable changes to Gogs are documented in this file.
|
|||
|
||||
### Added
|
||||
|
||||
- Support using personal access token in the password field. [#3866](https://github.com/gogs/gogs/issues/3866)
|
||||
- An unlisted option is added when create or migrate a repository. Unlisted repositories are public but not being listed for users without direct access in the UI. [#5733](https://github.com/gogs/gogs/issues/5733)
|
||||
- New configuration option `[git.timeout] DIFF` for customizing operation timeout of `git diff`. [#6315](https://github.com/gogs/gogs/issues/6315)
|
||||
- New configuration option `[server] SSH_SERVER_MACS` for setting list of accepted MACs for connections to builtin SSH server. [#6434](https://github.com/gogs/gogs/issues/6434)
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/go-macaron/csrf"
|
||||
"github.com/go-macaron/session"
|
||||
"github.com/pkg/errors"
|
||||
gouuid "github.com/satori/go.uuid"
|
||||
"gopkg.in/macaron.v1"
|
||||
log "unknwon.dev/clog/v2"
|
||||
|
@ -229,3 +231,23 @@ func authenticatedUser(ctx *macaron.Context, sess session.Store) (_ *db.User, is
|
|||
}
|
||||
return u, false, isTokenAuth
|
||||
}
|
||||
|
||||
// AuthenticateByToken attempts to authenticate a user by the given access
|
||||
// token. It returns db.ErrAccessTokenNotExist when the access token does not
|
||||
// exist.
|
||||
func AuthenticateByToken(ctx context.Context, token string) (*db.User, error) {
|
||||
t, err := db.AccessTokens.GetBySHA1(ctx, token)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get access token by SHA1")
|
||||
}
|
||||
if err = db.AccessTokens.Touch(ctx, t.ID); err != nil {
|
||||
// NOTE: There is no need to fail the auth flow if we can't touch the token.
|
||||
log.Error("Failed to touch access token [id: %d]: %v", t.ID, err)
|
||||
}
|
||||
|
||||
user, err := db.Users.GetByID(ctx, t.UserID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "get user by ID [user_id: %d]", t.UserID)
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
|
|
@ -144,6 +144,11 @@ func (ErrAccessTokenNotExist) NotFound() bool {
|
|||
}
|
||||
|
||||
func (db *accessTokens) GetBySHA1(ctx context.Context, sha1 string) (*AccessToken, error) {
|
||||
// No need to waste a query for an empty SHA1.
|
||||
if sha1 == "" {
|
||||
return nil, ErrAccessTokenNotExist{args: errutil.Args{"sha": sha1}}
|
||||
}
|
||||
|
||||
sha256 := cryptoutil.SHA256(sha1)
|
||||
token := new(AccessToken)
|
||||
err := db.WithContext(ctx).Where("sha256 = ?", sha256).First(token).Error
|
||||
|
|
|
@ -8,12 +8,14 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/macaron.v1"
|
||||
log "unknwon.dev/clog/v2"
|
||||
|
||||
"gogs.io/gogs/internal/auth"
|
||||
"gogs.io/gogs/internal/authutil"
|
||||
"gogs.io/gogs/internal/conf"
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/lfsutil"
|
||||
)
|
||||
|
@ -70,29 +72,26 @@ func authenticate() macaron.Handler {
|
|||
return
|
||||
}
|
||||
|
||||
// If username and password authentication failed, try again using username as an access token.
|
||||
// If username and password combination failed, try again using either username
|
||||
// or password as the token.
|
||||
if auth.IsErrBadCredentials(err) {
|
||||
token, err := db.AccessTokens.GetBySHA1(c.Req.Context(), username)
|
||||
if err != nil {
|
||||
if db.IsErrAccessTokenNotExist(err) {
|
||||
askCredentials(c.Resp)
|
||||
} else {
|
||||
internalServerError(c.Resp)
|
||||
log.Error("Failed to get access token [sha: %s]: %v", username, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err = db.AccessTokens.Touch(c.Req.Context(), token.ID); err != nil {
|
||||
log.Error("Failed to touch access token: %v", err)
|
||||
}
|
||||
|
||||
user, err = db.Users.GetByID(c.Req.Context(), token.UserID)
|
||||
if err != nil {
|
||||
// Once we found the token, we're supposed to find its related user,
|
||||
// thus any error is unexpected.
|
||||
user, err = context.AuthenticateByToken(c.Req.Context(), username)
|
||||
if err != nil && !db.IsErrAccessTokenNotExist(errors.Cause(err)) {
|
||||
internalServerError(c.Resp)
|
||||
log.Error("Failed to get user [id: %d]: %v", token.UserID, err)
|
||||
log.Error("Failed to authenticate by access token via username: %v", err)
|
||||
return
|
||||
} else if db.IsErrAccessTokenNotExist(errors.Cause(err)) {
|
||||
// Try again using the password field as the token.
|
||||
user, err = context.AuthenticateByToken(c.Req.Context(), password)
|
||||
if err != nil {
|
||||
if db.IsErrAccessTokenNotExist(errors.Cause(err)) {
|
||||
askCredentials(c.Resp)
|
||||
} else {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
log.Error("Failed to authenticate by access token via password: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -108,7 +108,7 @@ func Test_authenticate(t *testing.T) {
|
|||
expBody: "ID: 1, Name: unknwon",
|
||||
},
|
||||
{
|
||||
name: "authenticate by access token",
|
||||
name: "authenticate by access token via username",
|
||||
header: http.Header{
|
||||
"Authorization": []string{"Basic dXNlcm5hbWU="},
|
||||
},
|
||||
|
@ -127,6 +127,31 @@ func Test_authenticate(t *testing.T) {
|
|||
expHeader: http.Header{},
|
||||
expBody: "ID: 1, Name: unknwon",
|
||||
},
|
||||
{
|
||||
name: "authenticate by access token via password",
|
||||
header: http.Header{
|
||||
"Authorization": []string{"Basic dXNlcm5hbWU6cGFzc3dvcmQ="},
|
||||
},
|
||||
mockUsersStore: func() db.UsersStore {
|
||||
mock := NewMockUsersStore()
|
||||
mock.AuthenticateFunc.SetDefaultReturn(nil, auth.ErrBadCredentials{})
|
||||
mock.GetByIDFunc.SetDefaultReturn(&db.User{ID: 1, Name: "unknwon"}, nil)
|
||||
return mock
|
||||
},
|
||||
mockAccessTokensStore: func() db.AccessTokensStore {
|
||||
mock := NewMockAccessTokensStore()
|
||||
mock.GetBySHA1Func.SetDefaultHook(func(ctx context.Context, sha1 string) (*db.AccessToken, error) {
|
||||
if sha1 == "password" {
|
||||
return &db.AccessToken{}, nil
|
||||
}
|
||||
return nil, db.ErrAccessTokenNotExist{}
|
||||
})
|
||||
return mock
|
||||
},
|
||||
expStatusCode: http.StatusOK,
|
||||
expHeader: http.Header{},
|
||||
expBody: "ID: 1, Name: unknwon",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
|
|
@ -17,11 +17,13 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/macaron.v1"
|
||||
log "unknwon.dev/clog/v2"
|
||||
|
||||
"gogs.io/gogs/internal/auth"
|
||||
"gogs.io/gogs/internal/conf"
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/lazyregexp"
|
||||
"gogs.io/gogs/internal/pathutil"
|
||||
|
@ -130,29 +132,26 @@ func HTTPContexter() macaron.Handler {
|
|||
return
|
||||
}
|
||||
|
||||
// If username and password combination failed, try again using username as a token.
|
||||
// If username and password combination failed, try again using either username
|
||||
// or password as the token.
|
||||
if authUser == nil {
|
||||
token, err := db.AccessTokens.GetBySHA1(c.Req.Context(), authUsername)
|
||||
if err != nil {
|
||||
if db.IsErrAccessTokenNotExist(err) {
|
||||
askCredentials(c, http.StatusUnauthorized, "")
|
||||
} else {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
log.Error("Failed to get access token [sha: %s]: %v", authUsername, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err = db.AccessTokens.Touch(c.Req.Context(), token.ID); err != nil {
|
||||
log.Error("Failed to touch access token: %v", err)
|
||||
}
|
||||
|
||||
authUser, err = db.Users.GetByID(c.Req.Context(), token.UserID)
|
||||
if err != nil {
|
||||
// Once we found token, we're supposed to find its related user,
|
||||
// thus any error is unexpected.
|
||||
authUser, err = context.AuthenticateByToken(c.Req.Context(), authUsername)
|
||||
if err != nil && !db.IsErrAccessTokenNotExist(errors.Cause(err)) {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
log.Error("Failed to get user [id: %d]: %v", token.UserID, err)
|
||||
log.Error("Failed to authenticate by access token via username: %v", err)
|
||||
return
|
||||
} else if db.IsErrAccessTokenNotExist(errors.Cause(err)) {
|
||||
// Try again using the password field as the token.
|
||||
authUser, err = context.AuthenticateByToken(c.Req.Context(), authPassword)
|
||||
if err != nil {
|
||||
if db.IsErrAccessTokenNotExist(errors.Cause(err)) {
|
||||
askCredentials(c, http.StatusUnauthorized, "")
|
||||
} else {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
log.Error("Failed to authenticate by access token via password: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
} else if authUser.IsEnabledTwoFactor() {
|
||||
askCredentials(c, http.StatusUnauthorized, `User with two-factor authentication enabled cannot perform HTTP/HTTPS operations via plain username and password
|
||||
|
|
Loading…
Reference in New Issue