all: unwrap `database.AccessTokensStore` interface (#7670)

pull/7676/head
Joe Chen 2024-02-20 21:47:32 -05:00 committed by GitHub
parent 917c14f2ce
commit 8054ffc12f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 752 additions and 892 deletions

View File

@ -62,7 +62,7 @@ jobs:
name: Test
strategy:
matrix:
go-version: [ 1.21.x, 1.22.x ]
go-version: [ 1.22.x ]
platform: [ ubuntu-latest, macos-latest ]
runs-on: ${{ matrix.platform }}
steps:
@ -102,7 +102,7 @@ jobs:
name: Test Windows
strategy:
matrix:
go-version: [ 1.21.x, 1.22.x ]
go-version: [ 1.22.x ]
platform: [ windows-latest ]
runs-on: ${{ matrix.platform }}
steps:
@ -140,7 +140,7 @@ jobs:
name: Postgres
strategy:
matrix:
go-version: [ 1.21.x, 1.22.x ]
go-version: [ 1.22.x ]
platform: [ ubuntu-latest ]
runs-on: ${{ matrix.platform }}
services:
@ -176,7 +176,7 @@ jobs:
name: MySQL
strategy:
matrix:
go-version: [ 1.21.x, 1.22.x ]
go-version: [ 1.22.x ]
platform: [ ubuntu-20.04 ]
runs-on: ${{ matrix.platform }}
steps:
@ -201,7 +201,7 @@ jobs:
name: SQLite - Go
strategy:
matrix:
go-version: [ 1.21.x, 1.22.x ]
go-version: [ 1.22.x ]
platform: [ ubuntu-latest ]
runs-on: ${{ matrix.platform }}
steps:

View File

@ -237,9 +237,11 @@ func runWeb(c *cli.Context) error {
m.Get("", user.SettingsOrganizations)
m.Post("/leave", user.SettingsLeaveOrganization)
})
m.Combo("/applications").Get(user.SettingsApplications).
Post(bindIgnErr(form.NewAccessToken{}), user.SettingsApplicationsPost)
m.Post("/applications/delete", user.SettingsDeleteApplication)
settingsHandler := user.NewSettingsHandler(user.NewSettingsStore())
m.Combo("/applications").Get(settingsHandler.Applications()).
Post(bindIgnErr(form.NewAccessToken{}), settingsHandler.ApplicationsPost())
m.Post("/applications/delete", settingsHandler.DeleteApplication())
m.Route("/delete", "GET,POST", user.SettingsDelete)
}, reqSignIn, func(c *context.Context) {
c.Data["PageIsUserSettings"] = true
@ -652,7 +654,7 @@ func runWeb(c *cli.Context) error {
SetCookie: true,
Secure: conf.Server.URL.Scheme == "https",
}),
context.Contexter(),
context.Contexter(context.NewStore()),
)
// ***************************
@ -666,7 +668,7 @@ func runWeb(c *cli.Context) error {
lfs.RegisterRoutes(m.Router)
})
m.Route("/*", "GET,POST,OPTIONS", context.ServeGoGet(), repo.HTTPContexter(), repo.HTTP)
m.Route("/*", "GET,POST,OPTIONS", context.ServeGoGet(), repo.HTTPContexter(repo.NewStore()), repo.HTTP)
})
// ***************************

View File

@ -106,9 +106,18 @@ func isAPIPath(url string) bool {
return strings.HasPrefix(url, "/api/")
}
type AuthStore interface {
// GetAccessTokenBySHA1 returns the access token with given SHA1. It returns
// database.ErrAccessTokenNotExist when not found.
GetAccessTokenBySHA1(ctx context.Context, sha1 string) (*database.AccessToken, error)
// TouchAccessTokenByID updates the updated time of the given access token to
// the current time.
TouchAccessTokenByID(ctx context.Context, id int64) error
}
// authenticatedUserID returns the ID of the authenticated user, along with a bool value
// which indicates whether the user uses token authentication.
func authenticatedUserID(c *macaron.Context, sess session.Store) (_ int64, isTokenAuth bool) {
func authenticatedUserID(store AuthStore, c *macaron.Context, sess session.Store) (_ int64, isTokenAuth bool) {
if !database.HasEngine {
return 0, false
}
@ -132,14 +141,14 @@ func authenticatedUserID(c *macaron.Context, sess session.Store) (_ int64, isTok
// Let's see if token is valid.
if len(tokenSHA) > 0 {
t, err := database.AccessTokens.GetBySHA1(c.Req.Context(), tokenSHA)
t, err := store.GetAccessTokenBySHA1(c.Req.Context(), tokenSHA)
if err != nil {
if !database.IsErrAccessTokenNotExist(err) {
log.Error("GetAccessTokenBySHA: %v", err)
}
return 0, false
}
if err = database.AccessTokens.Touch(c.Req.Context(), t.ID); err != nil {
if err = store.TouchAccessTokenByID(c.Req.Context(), t.ID); err != nil {
log.Error("Failed to touch access token: %v", err)
}
return t.UserID, true
@ -165,12 +174,12 @@ func authenticatedUserID(c *macaron.Context, sess session.Store) (_ int64, isTok
// authenticatedUser returns the user object of the authenticated user, along with two bool values
// which indicate whether the user uses HTTP Basic Authentication or token authentication respectively.
func authenticatedUser(ctx *macaron.Context, sess session.Store) (_ *database.User, isBasicAuth, isTokenAuth bool) {
func authenticatedUser(store AuthStore, ctx *macaron.Context, sess session.Store) (_ *database.User, isBasicAuth, isTokenAuth bool) {
if !database.HasEngine {
return nil, false, false
}
uid, isTokenAuth := authenticatedUserID(ctx, sess)
uid, isTokenAuth := authenticatedUserID(store, ctx, sess)
if uid <= 0 {
if conf.Auth.EnableReverseProxyAuthentication {
@ -235,12 +244,12 @@ func authenticatedUser(ctx *macaron.Context, sess session.Store) (_ *database.Us
// AuthenticateByToken attempts to authenticate a user by the given access
// token. It returns database.ErrAccessTokenNotExist when the access token does not
// exist.
func AuthenticateByToken(ctx context.Context, token string) (*database.User, error) {
t, err := database.AccessTokens.GetBySHA1(ctx, token)
func AuthenticateByToken(store AuthStore, ctx context.Context, token string) (*database.User, error) {
t, err := store.GetAccessTokenBySHA1(ctx, token)
if err != nil {
return nil, errors.Wrap(err, "get access token by SHA1")
}
if err = database.AccessTokens.Touch(ctx, t.ID); err != nil {
if err = store.TouchAccessTokenByID(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)
}

View File

@ -235,7 +235,7 @@ func (c *Context) ServeContent(name string, r io.ReadSeeker, params ...any) {
var csrfTokenExcludePattern = lazyregexp.New(`[^a-zA-Z0-9-_].*`)
// Contexter initializes a classic context for a request.
func Contexter() macaron.Handler {
func Contexter(store Store) macaron.Handler {
return func(ctx *macaron.Context, l i18n.Locale, cache cache.Cache, sess session.Store, f *session.Flash, x csrf.CSRF) {
c := &Context{
Context: ctx,
@ -260,7 +260,7 @@ func Contexter() macaron.Handler {
}
// Get user from session or header when possible
c.User, c.IsBasicAuth, c.IsTokenAuth = authenticatedUser(c.Context, c.Session)
c.User, c.IsBasicAuth, c.IsTokenAuth = authenticatedUser(store, c.Context, c.Session)
if c.User != nil {
c.IsLogged = true

34
internal/context/store.go Normal file
View File

@ -0,0 +1,34 @@
package context
import (
"context"
"gogs.io/gogs/internal/database"
)
// Store is the data layer carrier for context middleware. This interface is
// meant to abstract away and limit the exposure of the underlying data layer to
// the handler through a thin-wrapper.
type Store interface {
// GetAccessTokenBySHA1 returns the access token with given SHA1. It returns
// database.ErrAccessTokenNotExist when not found.
GetAccessTokenBySHA1(ctx context.Context, sha1 string) (*database.AccessToken, error)
// TouchAccessTokenByID updates the updated time of the given access token to
// the current time.
TouchAccessTokenByID(ctx context.Context, id int64) error
}
type store struct{}
// NewStore returns a new Store using the global database handle.
func NewStore() Store {
return &store{}
}
func (*store) GetAccessTokenBySHA1(ctx context.Context, sha1 string) (*database.AccessToken, error) {
return database.Handle.AccessTokens().GetBySHA1(ctx, sha1)
}
func (*store) TouchAccessTokenByID(ctx context.Context, id int64) error {
return database.Handle.AccessTokens().Touch(ctx, id)
}

View File

@ -17,28 +17,6 @@ import (
"gogs.io/gogs/internal/errutil"
)
// AccessTokensStore is the persistent interface for access tokens.
type AccessTokensStore interface {
// Create creates a new access token and persist to database. It returns
// ErrAccessTokenAlreadyExist when an access token with same name already exists
// for the user.
Create(ctx context.Context, userID int64, name string) (*AccessToken, error)
// DeleteByID deletes the access token by given ID.
//
// 🚨 SECURITY: The "userID" is required to prevent attacker deletes arbitrary
// access token that belongs to another user.
DeleteByID(ctx context.Context, userID, id int64) error
// GetBySHA1 returns the access token with given SHA1. It returns
// ErrAccessTokenNotExist when not found.
GetBySHA1(ctx context.Context, sha1 string) (*AccessToken, error)
// List returns all access tokens belongs to given user.
List(ctx context.Context, userID int64) ([]*AccessToken, error)
// Touch updates the updated time of the given access token to the current time.
Touch(ctx context.Context, id int64) error
}
var AccessTokens AccessTokensStore
// AccessToken is a personal access token.
type AccessToken struct {
ID int64 `gorm:"primarykey"`
@ -74,10 +52,13 @@ func (t *AccessToken) AfterFind(tx *gorm.DB) error {
return nil
}
var _ AccessTokensStore = (*accessTokensStore)(nil)
// AccessTokensStore is the storage layer for access tokens.
type AccessTokensStore struct {
db *gorm.DB
}
type accessTokensStore struct {
*gorm.DB
func newAccessTokensStore(db *gorm.DB) *AccessTokensStore {
return &AccessTokensStore{db}
}
type ErrAccessTokenAlreadyExist struct {
@ -85,19 +66,21 @@ type ErrAccessTokenAlreadyExist struct {
}
func IsErrAccessTokenAlreadyExist(err error) bool {
_, ok := err.(ErrAccessTokenAlreadyExist)
return ok
return errors.As(err, &ErrAccessTokenAlreadyExist{})
}
func (err ErrAccessTokenAlreadyExist) Error() string {
return fmt.Sprintf("access token already exists: %v", err.args)
}
func (s *accessTokensStore) Create(ctx context.Context, userID int64, name string) (*AccessToken, error) {
err := s.WithContext(ctx).Where("uid = ? AND name = ?", userID, name).First(new(AccessToken)).Error
// Create creates a new access token and persist to database. It returns
// ErrAccessTokenAlreadyExist when an access token with same name already exists
// for the user.
func (s *AccessTokensStore) Create(ctx context.Context, userID int64, name string) (*AccessToken, error) {
err := s.db.WithContext(ctx).Where("uid = ? AND name = ?", userID, name).First(new(AccessToken)).Error
if err == nil {
return nil, ErrAccessTokenAlreadyExist{args: errutil.Args{"userID": userID, "name": name}}
} else if err != gorm.ErrRecordNotFound {
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
@ -110,7 +93,7 @@ func (s *accessTokensStore) Create(ctx context.Context, userID int64, name strin
Sha1: sha256[:40], // To pass the column unique constraint, keep the length of SHA1.
SHA256: sha256,
}
if err = s.WithContext(ctx).Create(accessToken).Error; err != nil {
if err = s.db.WithContext(ctx).Create(accessToken).Error; err != nil {
return nil, err
}
@ -119,8 +102,12 @@ func (s *accessTokensStore) Create(ctx context.Context, userID int64, name strin
return accessToken, nil
}
func (s *accessTokensStore) DeleteByID(ctx context.Context, userID, id int64) error {
return s.WithContext(ctx).Where("id = ? AND uid = ?", id, userID).Delete(new(AccessToken)).Error
// DeleteByID deletes the access token by given ID.
//
// 🚨 SECURITY: The "userID" is required to prevent attacker deletes arbitrary
// access token that belongs to another user.
func (s *AccessTokensStore) DeleteByID(ctx context.Context, userID, id int64) error {
return s.db.WithContext(ctx).Where("id = ? AND uid = ?", id, userID).Delete(new(AccessToken)).Error
}
var _ errutil.NotFound = (*ErrAccessTokenNotExist)(nil)
@ -132,8 +119,7 @@ type ErrAccessTokenNotExist struct {
// IsErrAccessTokenNotExist returns true if the underlying error has the type
// ErrAccessTokenNotExist.
func IsErrAccessTokenNotExist(err error) bool {
_, ok := errors.Cause(err).(ErrAccessTokenNotExist)
return ok
return errors.As(errors.Cause(err), &ErrAccessTokenNotExist{})
}
func (err ErrAccessTokenNotExist) Error() string {
@ -144,7 +130,9 @@ func (ErrAccessTokenNotExist) NotFound() bool {
return true
}
func (s *accessTokensStore) GetBySHA1(ctx context.Context, sha1 string) (*AccessToken, error) {
// GetBySHA1 returns the access token with given SHA1. It returns
// ErrAccessTokenNotExist when not found.
func (s *AccessTokensStore) 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}}
@ -152,25 +140,26 @@ func (s *accessTokensStore) GetBySHA1(ctx context.Context, sha1 string) (*Access
sha256 := cryptoutil.SHA256(sha1)
token := new(AccessToken)
err := s.WithContext(ctx).Where("sha256 = ?", sha256).First(token).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
err := s.db.WithContext(ctx).Where("sha256 = ?", sha256).First(token).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrAccessTokenNotExist{args: errutil.Args{"sha": sha1}}
}
} else if err != nil {
return nil, err
}
return token, nil
}
func (s *accessTokensStore) List(ctx context.Context, userID int64) ([]*AccessToken, error) {
// List returns all access tokens belongs to given user.
func (s *AccessTokensStore) List(ctx context.Context, userID int64) ([]*AccessToken, error) {
var tokens []*AccessToken
return tokens, s.WithContext(ctx).Where("uid = ?", userID).Order("id ASC").Find(&tokens).Error
return tokens, s.db.WithContext(ctx).Where("uid = ?", userID).Order("id ASC").Find(&tokens).Error
}
func (s *accessTokensStore) Touch(ctx context.Context, id int64) error {
return s.WithContext(ctx).
// Touch updates the updated time of the given access token to the current time.
func (s *AccessTokensStore) Touch(ctx context.Context, id int64) error {
return s.db.WithContext(ctx).
Model(new(AccessToken)).
Where("id = ?", id).
UpdateColumn("updated_unix", s.NowFunc().Unix()).
UpdateColumn("updated_unix", s.db.NowFunc().Unix()).
Error
}

View File

@ -98,13 +98,13 @@ func TestAccessTokens(t *testing.T) {
t.Parallel()
ctx := context.Background()
db := &accessTokensStore{
DB: newTestDB(t, "accessTokensStore"),
s := &AccessTokensStore{
db: newTestDB(t, "AccessTokensStore"),
}
for _, tc := range []struct {
name string
test func(t *testing.T, ctx context.Context, db *accessTokensStore)
test func(t *testing.T, ctx context.Context, s *AccessTokensStore)
}{
{"Create", accessTokensCreate},
{"DeleteByID", accessTokensDeleteByID},
@ -114,10 +114,10 @@ func TestAccessTokens(t *testing.T) {
} {
t.Run(tc.name, func(t *testing.T) {
t.Cleanup(func() {
err := clearTables(t, db.DB)
err := clearTables(t, s.db)
require.NoError(t, err)
})
tc.test(t, ctx, db)
tc.test(t, ctx, s)
})
if t.Failed() {
break
@ -125,9 +125,9 @@ func TestAccessTokens(t *testing.T) {
}
}
func accessTokensCreate(t *testing.T, ctx context.Context, db *accessTokensStore) {
func accessTokensCreate(t *testing.T, ctx context.Context, s *AccessTokensStore) {
// Create first access token with name "Test"
token, err := db.Create(ctx, 1, "Test")
token, err := s.Create(ctx, 1, "Test")
require.NoError(t, err)
assert.Equal(t, int64(1), token.UserID)
@ -135,12 +135,12 @@ func accessTokensCreate(t *testing.T, ctx context.Context, db *accessTokensStore
assert.Equal(t, 40, len(token.Sha1), "sha1 length")
// Get it back and check the Created field
token, err = db.GetBySHA1(ctx, token.Sha1)
token, err = s.GetBySHA1(ctx, token.Sha1)
require.NoError(t, err)
assert.Equal(t, db.NowFunc().Format(time.RFC3339), token.Created.UTC().Format(time.RFC3339))
assert.Equal(t, s.db.NowFunc().Format(time.RFC3339), token.Created.UTC().Format(time.RFC3339))
// Try create second access token with same name should fail
_, err = db.Create(ctx, token.UserID, token.Name)
_, err = s.Create(ctx, token.UserID, token.Name)
wantErr := ErrAccessTokenAlreadyExist{
args: errutil.Args{
"userID": token.UserID,
@ -150,25 +150,25 @@ func accessTokensCreate(t *testing.T, ctx context.Context, db *accessTokensStore
assert.Equal(t, wantErr, err)
}
func accessTokensDeleteByID(t *testing.T, ctx context.Context, db *accessTokensStore) {
func accessTokensDeleteByID(t *testing.T, ctx context.Context, s *AccessTokensStore) {
// Create an access token with name "Test"
token, err := db.Create(ctx, 1, "Test")
token, err := s.Create(ctx, 1, "Test")
require.NoError(t, err)
// Delete a token with mismatched user ID is noop
err = db.DeleteByID(ctx, 2, token.ID)
err = s.DeleteByID(ctx, 2, token.ID)
require.NoError(t, err)
// We should be able to get it back
_, err = db.GetBySHA1(ctx, token.Sha1)
_, err = s.GetBySHA1(ctx, token.Sha1)
require.NoError(t, err)
// Now delete this token with correct user ID
err = db.DeleteByID(ctx, token.UserID, token.ID)
err = s.DeleteByID(ctx, token.UserID, token.ID)
require.NoError(t, err)
// We should get token not found error
_, err = db.GetBySHA1(ctx, token.Sha1)
_, err = s.GetBySHA1(ctx, token.Sha1)
wantErr := ErrAccessTokenNotExist{
args: errutil.Args{
"sha": token.Sha1,
@ -177,17 +177,17 @@ func accessTokensDeleteByID(t *testing.T, ctx context.Context, db *accessTokensS
assert.Equal(t, wantErr, err)
}
func accessTokensGetBySHA(t *testing.T, ctx context.Context, db *accessTokensStore) {
func accessTokensGetBySHA(t *testing.T, ctx context.Context, s *AccessTokensStore) {
// Create an access token with name "Test"
token, err := db.Create(ctx, 1, "Test")
token, err := s.Create(ctx, 1, "Test")
require.NoError(t, err)
// We should be able to get it back
_, err = db.GetBySHA1(ctx, token.Sha1)
_, err = s.GetBySHA1(ctx, token.Sha1)
require.NoError(t, err)
// Try to get a non-existent token
_, err = db.GetBySHA1(ctx, "bad_sha")
_, err = s.GetBySHA1(ctx, "bad_sha")
wantErr := ErrAccessTokenNotExist{
args: errutil.Args{
"sha": "bad_sha",
@ -196,21 +196,21 @@ func accessTokensGetBySHA(t *testing.T, ctx context.Context, db *accessTokensSto
assert.Equal(t, wantErr, err)
}
func accessTokensList(t *testing.T, ctx context.Context, db *accessTokensStore) {
func accessTokensList(t *testing.T, ctx context.Context, s *AccessTokensStore) {
// Create two access tokens for user 1
_, err := db.Create(ctx, 1, "user1_1")
_, err := s.Create(ctx, 1, "user1_1")
require.NoError(t, err)
_, err = db.Create(ctx, 1, "user1_2")
_, err = s.Create(ctx, 1, "user1_2")
require.NoError(t, err)
// Create one access token for user 2
_, err = db.Create(ctx, 2, "user2_1")
_, err = s.Create(ctx, 2, "user2_1")
require.NoError(t, err)
// List all access tokens for user 1
tokens, err := db.List(ctx, 1)
tokens, err := s.List(ctx, 1)
require.NoError(t, err)
assert.Equal(t, 2, len(tokens), "number of tokens")
require.Equal(t, 2, len(tokens), "number of tokens")
assert.Equal(t, int64(1), tokens[0].UserID)
assert.Equal(t, "user1_1", tokens[0].Name)
@ -219,19 +219,19 @@ func accessTokensList(t *testing.T, ctx context.Context, db *accessTokensStore)
assert.Equal(t, "user1_2", tokens[1].Name)
}
func accessTokensTouch(t *testing.T, ctx context.Context, db *accessTokensStore) {
func accessTokensTouch(t *testing.T, ctx context.Context, s *AccessTokensStore) {
// Create an access token with name "Test"
token, err := db.Create(ctx, 1, "Test")
token, err := s.Create(ctx, 1, "Test")
require.NoError(t, err)
// Updated field is zero now
assert.True(t, token.Updated.IsZero())
err = db.Touch(ctx, token.ID)
err = s.Touch(ctx, token.ID)
require.NoError(t, err)
// Get back from DB should have Updated set
token, err = db.GetBySHA1(ctx, token.Sha1)
token, err = s.GetBySHA1(ctx, token.Sha1)
require.NoError(t, err)
assert.Equal(t, db.NowFunc().Format(time.RFC3339), token.Updated.UTC().Format(time.RFC3339))
assert.Equal(t, s.db.NowFunc().Format(time.RFC3339), token.Updated.UTC().Format(time.RFC3339))
}

View File

@ -50,8 +50,8 @@ var Tables = []any{
new(Notice),
}
// Init initializes the database with given logger.
func Init(w logger.Writer) (*gorm.DB, error) {
// NewConnection returns a new database connection with the given logger.
func NewConnection(w logger.Writer) (*gorm.DB, error) {
level := logger.Info
if conf.IsProdMode() {
level = logger.Warn
@ -123,7 +123,6 @@ func Init(w logger.Writer) (*gorm.DB, error) {
}
// Initialize stores, sorted in alphabetical order.
AccessTokens = &accessTokensStore{DB: db}
Actions = NewActionsStore(db)
LoginSources = &loginSourcesStore{DB: db, files: sourceFiles}
LFS = &lfsStore{DB: db}
@ -136,3 +135,28 @@ func Init(w logger.Writer) (*gorm.DB, error) {
return db, nil
}
type DB struct {
db *gorm.DB
}
// Handle is the global database handle. It could be `nil` during the
// installation mode.
//
// NOTE: Because we need to register all the routes even during the installation
// mode (which initially has no database configuration), we have to use a global
// variable since we can't pass a database handler around before it's available.
//
// NOTE: It is not guarded by a mutex because it is only written once either
// during the service start or during the installation process (which is a
// single-thread process).
var Handle *DB
// SetHandle updates the global database handle with the given connection.
func SetHandle(db *gorm.DB) {
Handle = &DB{db: db}
}
func (db *DB) AccessTokens() *AccessTokensStore {
return newAccessTokensStore(db.db)
}

View File

@ -8,14 +8,6 @@ import (
"testing"
)
func SetMockAccessTokensStore(t *testing.T, mock AccessTokensStore) {
before := AccessTokens
AccessTokens = mock
t.Cleanup(func() {
AccessTokens = before
})
}
func SetMockLFSStore(t *testing.T, mock LFSStore) {
before := LFS
LFS = mock

View File

@ -178,24 +178,24 @@ func SetEngine() (*gorm.DB, error) {
return nil, errors.Wrap(err, "new log writer")
}
}
return Init(gormLogger)
return NewConnection(gormLogger)
}
func NewEngine() (err error) {
func NewEngine() (*gorm.DB, error) {
db, err := SetEngine()
if err != nil {
return err
return nil, err
}
if err = migrations.Migrate(db); err != nil {
return fmt.Errorf("migrate: %v", err)
return nil, fmt.Errorf("migrate: %v", err)
}
if err = x.StoreEngine("InnoDB").Sync2(legacyTables...); err != nil {
return errors.Wrap(err, "sync tables")
return nil, errors.Wrap(err, "sync tables")
}
return nil
return db, nil
}
type Statistic struct {

View File

@ -186,9 +186,10 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("", user.GetInfo)
m.Group("/tokens", func() {
accessTokensHandler := user.NewAccessTokensHandler(user.NewAccessTokensStore())
m.Combo("").
Get(user.ListAccessTokens).
Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken)
Get(accessTokensHandler.List()).
Post(bind(api.CreateAccessTokenOption{}), accessTokensHandler.Create())
}, reqBasicAuth())
})
})

View File

@ -0,0 +1,88 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package user
import (
gocontext "context"
"net/http"
api "github.com/gogs/go-gogs-client"
"gopkg.in/macaron.v1"
"gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/database"
)
// AccessTokensHandler is the handler for users access tokens API endpoints.
type AccessTokensHandler struct {
store AccessTokensStore
}
// NewAccessTokensHandler returns a new AccessTokensHandler for users access
// tokens API endpoints.
func NewAccessTokensHandler(s AccessTokensStore) *AccessTokensHandler {
return &AccessTokensHandler{
store: s,
}
}
func (h *AccessTokensHandler) List() macaron.Handler {
return func(c *context.APIContext) {
tokens, err := h.store.ListAccessTokens(c.Req.Context(), c.User.ID)
if err != nil {
c.Error(err, "list access tokens")
return
}
apiTokens := make([]*api.AccessToken, len(tokens))
for i := range tokens {
apiTokens[i] = &api.AccessToken{Name: tokens[i].Name, Sha1: tokens[i].Sha1}
}
c.JSONSuccess(&apiTokens)
}
}
func (h *AccessTokensHandler) Create() macaron.Handler {
return func(c *context.APIContext, form api.CreateAccessTokenOption) {
t, err := h.store.CreateAccessToken(c.Req.Context(), c.User.ID, form.Name)
if err != nil {
if database.IsErrAccessTokenAlreadyExist(err) {
c.ErrorStatus(http.StatusUnprocessableEntity, err)
} else {
c.Error(err, "new access token")
}
return
}
c.JSON(http.StatusCreated, &api.AccessToken{Name: t.Name, Sha1: t.Sha1})
}
}
// AccessTokensStore is the data layer carrier for user access tokens API
// endpoints. This interface is meant to abstract away and limit the exposure of
// the underlying data layer to the handler through a thin-wrapper.
type AccessTokensStore interface {
// CreateAccessToken creates a new access token and persist to database. It
// returns database.ErrAccessTokenAlreadyExist when an access token with same
// name already exists for the user.
CreateAccessToken(ctx gocontext.Context, userID int64, name string) (*database.AccessToken, error)
// ListAccessTokens returns all access tokens belongs to given user.
ListAccessTokens(ctx gocontext.Context, userID int64) ([]*database.AccessToken, error)
}
type accessTokensStore struct{}
// NewAccessTokensStore returns a new AccessTokensStore using the global
// database handle.
func NewAccessTokensStore() AccessTokensStore {
return &accessTokensStore{}
}
func (*accessTokensStore) CreateAccessToken(ctx gocontext.Context, userID int64, name string) (*database.AccessToken, error) {
return database.Handle.AccessTokens().Create(ctx, userID, name)
}
func (*accessTokensStore) ListAccessTokens(ctx gocontext.Context, userID int64) ([]*database.AccessToken, error) {
return database.Handle.AccessTokens().List(ctx, userID)
}

View File

@ -1,41 +0,0 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package user
import (
"net/http"
api "github.com/gogs/go-gogs-client"
"gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/database"
)
func ListAccessTokens(c *context.APIContext) {
tokens, err := database.AccessTokens.List(c.Req.Context(), c.User.ID)
if err != nil {
c.Error(err, "list access tokens")
return
}
apiTokens := make([]*api.AccessToken, len(tokens))
for i := range tokens {
apiTokens[i] = &api.AccessToken{Name: tokens[i].Name, Sha1: tokens[i].Sha1}
}
c.JSONSuccess(&apiTokens)
}
func CreateAccessToken(c *context.APIContext, form api.CreateAccessTokenOption) {
t, err := database.AccessTokens.Create(c.Req.Context(), c.User.ID, form.Name)
if err != nil {
if database.IsErrAccessTokenAlreadyExist(err) {
c.ErrorStatus(http.StatusUnprocessableEntity, err)
} else {
c.Error(err, "new access token")
}
return
}
c.JSON(http.StatusCreated, &api.AccessToken{Name: t.Name, Sha1: t.Sha1})
}

View File

@ -71,9 +71,11 @@ func GlobalInit(customConf string) error {
if conf.Security.InstallLock {
highlight.NewContext()
markup.NewSanitizer()
if err := database.NewEngine(); err != nil {
db, err := database.NewEngine()
if err != nil {
log.Fatal("Failed to initialize ORM engine: %v", err)
}
database.SetHandle(db)
database.HasEngine = true
database.LoadRepoConfig()

View File

@ -14,657 +14,6 @@ import (
lfsutil "gogs.io/gogs/internal/lfsutil"
)
// MockAccessTokensStore is a mock implementation of the AccessTokensStore
// interface (from the package gogs.io/gogs/internal/database) used for unit
// testing.
type MockAccessTokensStore struct {
// CreateFunc is an instance of a mock function object controlling the
// behavior of the method Create.
CreateFunc *AccessTokensStoreCreateFunc
// DeleteByIDFunc is an instance of a mock function object controlling
// the behavior of the method DeleteByID.
DeleteByIDFunc *AccessTokensStoreDeleteByIDFunc
// GetBySHA1Func is an instance of a mock function object controlling
// the behavior of the method GetBySHA1.
GetBySHA1Func *AccessTokensStoreGetBySHA1Func
// ListFunc is an instance of a mock function object controlling the
// behavior of the method List.
ListFunc *AccessTokensStoreListFunc
// TouchFunc is an instance of a mock function object controlling the
// behavior of the method Touch.
TouchFunc *AccessTokensStoreTouchFunc
}
// NewMockAccessTokensStore creates a new mock of the AccessTokensStore
// interface. All methods return zero values for all results, unless
// overwritten.
func NewMockAccessTokensStore() *MockAccessTokensStore {
return &MockAccessTokensStore{
CreateFunc: &AccessTokensStoreCreateFunc{
defaultHook: func(context.Context, int64, string) (r0 *database.AccessToken, r1 error) {
return
},
},
DeleteByIDFunc: &AccessTokensStoreDeleteByIDFunc{
defaultHook: func(context.Context, int64, int64) (r0 error) {
return
},
},
GetBySHA1Func: &AccessTokensStoreGetBySHA1Func{
defaultHook: func(context.Context, string) (r0 *database.AccessToken, r1 error) {
return
},
},
ListFunc: &AccessTokensStoreListFunc{
defaultHook: func(context.Context, int64) (r0 []*database.AccessToken, r1 error) {
return
},
},
TouchFunc: &AccessTokensStoreTouchFunc{
defaultHook: func(context.Context, int64) (r0 error) {
return
},
},
}
}
// NewStrictMockAccessTokensStore creates a new mock of the
// AccessTokensStore interface. All methods panic on invocation, unless
// overwritten.
func NewStrictMockAccessTokensStore() *MockAccessTokensStore {
return &MockAccessTokensStore{
CreateFunc: &AccessTokensStoreCreateFunc{
defaultHook: func(context.Context, int64, string) (*database.AccessToken, error) {
panic("unexpected invocation of MockAccessTokensStore.Create")
},
},
DeleteByIDFunc: &AccessTokensStoreDeleteByIDFunc{
defaultHook: func(context.Context, int64, int64) error {
panic("unexpected invocation of MockAccessTokensStore.DeleteByID")
},
},
GetBySHA1Func: &AccessTokensStoreGetBySHA1Func{
defaultHook: func(context.Context, string) (*database.AccessToken, error) {
panic("unexpected invocation of MockAccessTokensStore.GetBySHA1")
},
},
ListFunc: &AccessTokensStoreListFunc{
defaultHook: func(context.Context, int64) ([]*database.AccessToken, error) {
panic("unexpected invocation of MockAccessTokensStore.List")
},
},
TouchFunc: &AccessTokensStoreTouchFunc{
defaultHook: func(context.Context, int64) error {
panic("unexpected invocation of MockAccessTokensStore.Touch")
},
},
}
}
// NewMockAccessTokensStoreFrom creates a new mock of the
// MockAccessTokensStore interface. All methods delegate to the given
// implementation, unless overwritten.
func NewMockAccessTokensStoreFrom(i database.AccessTokensStore) *MockAccessTokensStore {
return &MockAccessTokensStore{
CreateFunc: &AccessTokensStoreCreateFunc{
defaultHook: i.Create,
},
DeleteByIDFunc: &AccessTokensStoreDeleteByIDFunc{
defaultHook: i.DeleteByID,
},
GetBySHA1Func: &AccessTokensStoreGetBySHA1Func{
defaultHook: i.GetBySHA1,
},
ListFunc: &AccessTokensStoreListFunc{
defaultHook: i.List,
},
TouchFunc: &AccessTokensStoreTouchFunc{
defaultHook: i.Touch,
},
}
}
// AccessTokensStoreCreateFunc describes the behavior when the Create method
// of the parent MockAccessTokensStore instance is invoked.
type AccessTokensStoreCreateFunc struct {
defaultHook func(context.Context, int64, string) (*database.AccessToken, error)
hooks []func(context.Context, int64, string) (*database.AccessToken, error)
history []AccessTokensStoreCreateFuncCall
mutex sync.Mutex
}
// Create delegates to the next hook function in the queue and stores the
// parameter and result values of this invocation.
func (m *MockAccessTokensStore) Create(v0 context.Context, v1 int64, v2 string) (*database.AccessToken, error) {
r0, r1 := m.CreateFunc.nextHook()(v0, v1, v2)
m.CreateFunc.appendCall(AccessTokensStoreCreateFuncCall{v0, v1, v2, r0, r1})
return r0, r1
}
// SetDefaultHook sets function that is called when the Create method of the
// parent MockAccessTokensStore instance is invoked and the hook queue is
// empty.
func (f *AccessTokensStoreCreateFunc) SetDefaultHook(hook func(context.Context, int64, string) (*database.AccessToken, error)) {
f.defaultHook = hook
}
// PushHook adds a function to the end of hook queue. Each invocation of the
// Create method of the parent MockAccessTokensStore instance invokes the
// hook at the front of the queue and discards it. After the queue is empty,
// the default hook function is invoked for any future action.
func (f *AccessTokensStoreCreateFunc) PushHook(hook func(context.Context, int64, string) (*database.AccessToken, error)) {
f.mutex.Lock()
f.hooks = append(f.hooks, hook)
f.mutex.Unlock()
}
// SetDefaultReturn calls SetDefaultHook with a function that returns the
// given values.
func (f *AccessTokensStoreCreateFunc) SetDefaultReturn(r0 *database.AccessToken, r1 error) {
f.SetDefaultHook(func(context.Context, int64, string) (*database.AccessToken, error) {
return r0, r1
})
}
// PushReturn calls PushHook with a function that returns the given values.
func (f *AccessTokensStoreCreateFunc) PushReturn(r0 *database.AccessToken, r1 error) {
f.PushHook(func(context.Context, int64, string) (*database.AccessToken, error) {
return r0, r1
})
}
func (f *AccessTokensStoreCreateFunc) nextHook() func(context.Context, int64, string) (*database.AccessToken, error) {
f.mutex.Lock()
defer f.mutex.Unlock()
if len(f.hooks) == 0 {
return f.defaultHook
}
hook := f.hooks[0]
f.hooks = f.hooks[1:]
return hook
}
func (f *AccessTokensStoreCreateFunc) appendCall(r0 AccessTokensStoreCreateFuncCall) {
f.mutex.Lock()
f.history = append(f.history, r0)
f.mutex.Unlock()
}
// History returns a sequence of AccessTokensStoreCreateFuncCall objects
// describing the invocations of this function.
func (f *AccessTokensStoreCreateFunc) History() []AccessTokensStoreCreateFuncCall {
f.mutex.Lock()
history := make([]AccessTokensStoreCreateFuncCall, len(f.history))
copy(history, f.history)
f.mutex.Unlock()
return history
}
// AccessTokensStoreCreateFuncCall is an object that describes an invocation
// of method Create on an instance of MockAccessTokensStore.
type AccessTokensStoreCreateFuncCall struct {
// Arg0 is the value of the 1st argument passed to this method
// invocation.
Arg0 context.Context
// Arg1 is the value of the 2nd argument passed to this method
// invocation.
Arg1 int64
// Arg2 is the value of the 3rd argument passed to this method
// invocation.
Arg2 string
// Result0 is the value of the 1st result returned from this method
// invocation.
Result0 *database.AccessToken
// Result1 is the value of the 2nd result returned from this method
// invocation.
Result1 error
}
// Args returns an interface slice containing the arguments of this
// invocation.
func (c AccessTokensStoreCreateFuncCall) Args() []interface{} {
return []interface{}{c.Arg0, c.Arg1, c.Arg2}
}
// Results returns an interface slice containing the results of this
// invocation.
func (c AccessTokensStoreCreateFuncCall) Results() []interface{} {
return []interface{}{c.Result0, c.Result1}
}
// AccessTokensStoreDeleteByIDFunc describes the behavior when the
// DeleteByID method of the parent MockAccessTokensStore instance is
// invoked.
type AccessTokensStoreDeleteByIDFunc struct {
defaultHook func(context.Context, int64, int64) error
hooks []func(context.Context, int64, int64) error
history []AccessTokensStoreDeleteByIDFuncCall
mutex sync.Mutex
}
// DeleteByID delegates to the next hook function in the queue and stores
// the parameter and result values of this invocation.
func (m *MockAccessTokensStore) DeleteByID(v0 context.Context, v1 int64, v2 int64) error {
r0 := m.DeleteByIDFunc.nextHook()(v0, v1, v2)
m.DeleteByIDFunc.appendCall(AccessTokensStoreDeleteByIDFuncCall{v0, v1, v2, r0})
return r0
}
// SetDefaultHook sets function that is called when the DeleteByID method of
// the parent MockAccessTokensStore instance is invoked and the hook queue
// is empty.
func (f *AccessTokensStoreDeleteByIDFunc) SetDefaultHook(hook func(context.Context, int64, int64) error) {
f.defaultHook = hook
}
// PushHook adds a function to the end of hook queue. Each invocation of the
// DeleteByID method of the parent MockAccessTokensStore instance invokes
// the hook at the front of the queue and discards it. After the queue is
// empty, the default hook function is invoked for any future action.
func (f *AccessTokensStoreDeleteByIDFunc) PushHook(hook func(context.Context, int64, int64) error) {
f.mutex.Lock()
f.hooks = append(f.hooks, hook)
f.mutex.Unlock()
}
// SetDefaultReturn calls SetDefaultHook with a function that returns the
// given values.
func (f *AccessTokensStoreDeleteByIDFunc) SetDefaultReturn(r0 error) {
f.SetDefaultHook(func(context.Context, int64, int64) error {
return r0
})
}
// PushReturn calls PushHook with a function that returns the given values.
func (f *AccessTokensStoreDeleteByIDFunc) PushReturn(r0 error) {
f.PushHook(func(context.Context, int64, int64) error {
return r0
})
}
func (f *AccessTokensStoreDeleteByIDFunc) nextHook() func(context.Context, int64, int64) error {
f.mutex.Lock()
defer f.mutex.Unlock()
if len(f.hooks) == 0 {
return f.defaultHook
}
hook := f.hooks[0]
f.hooks = f.hooks[1:]
return hook
}
func (f *AccessTokensStoreDeleteByIDFunc) appendCall(r0 AccessTokensStoreDeleteByIDFuncCall) {
f.mutex.Lock()
f.history = append(f.history, r0)
f.mutex.Unlock()
}
// History returns a sequence of AccessTokensStoreDeleteByIDFuncCall objects
// describing the invocations of this function.
func (f *AccessTokensStoreDeleteByIDFunc) History() []AccessTokensStoreDeleteByIDFuncCall {
f.mutex.Lock()
history := make([]AccessTokensStoreDeleteByIDFuncCall, len(f.history))
copy(history, f.history)
f.mutex.Unlock()
return history
}
// AccessTokensStoreDeleteByIDFuncCall is an object that describes an
// invocation of method DeleteByID on an instance of MockAccessTokensStore.
type AccessTokensStoreDeleteByIDFuncCall struct {
// Arg0 is the value of the 1st argument passed to this method
// invocation.
Arg0 context.Context
// Arg1 is the value of the 2nd argument passed to this method
// invocation.
Arg1 int64
// Arg2 is the value of the 3rd argument passed to this method
// invocation.
Arg2 int64
// Result0 is the value of the 1st result returned from this method
// invocation.
Result0 error
}
// Args returns an interface slice containing the arguments of this
// invocation.
func (c AccessTokensStoreDeleteByIDFuncCall) Args() []interface{} {
return []interface{}{c.Arg0, c.Arg1, c.Arg2}
}
// Results returns an interface slice containing the results of this
// invocation.
func (c AccessTokensStoreDeleteByIDFuncCall) Results() []interface{} {
return []interface{}{c.Result0}
}
// AccessTokensStoreGetBySHA1Func describes the behavior when the GetBySHA1
// method of the parent MockAccessTokensStore instance is invoked.
type AccessTokensStoreGetBySHA1Func struct {
defaultHook func(context.Context, string) (*database.AccessToken, error)
hooks []func(context.Context, string) (*database.AccessToken, error)
history []AccessTokensStoreGetBySHA1FuncCall
mutex sync.Mutex
}
// GetBySHA1 delegates to the next hook function in the queue and stores the
// parameter and result values of this invocation.
func (m *MockAccessTokensStore) GetBySHA1(v0 context.Context, v1 string) (*database.AccessToken, error) {
r0, r1 := m.GetBySHA1Func.nextHook()(v0, v1)
m.GetBySHA1Func.appendCall(AccessTokensStoreGetBySHA1FuncCall{v0, v1, r0, r1})
return r0, r1
}
// SetDefaultHook sets function that is called when the GetBySHA1 method of
// the parent MockAccessTokensStore instance is invoked and the hook queue
// is empty.
func (f *AccessTokensStoreGetBySHA1Func) SetDefaultHook(hook func(context.Context, string) (*database.AccessToken, error)) {
f.defaultHook = hook
}
// PushHook adds a function to the end of hook queue. Each invocation of the
// GetBySHA1 method of the parent MockAccessTokensStore instance invokes the
// hook at the front of the queue and discards it. After the queue is empty,
// the default hook function is invoked for any future action.
func (f *AccessTokensStoreGetBySHA1Func) PushHook(hook func(context.Context, string) (*database.AccessToken, error)) {
f.mutex.Lock()
f.hooks = append(f.hooks, hook)
f.mutex.Unlock()
}
// SetDefaultReturn calls SetDefaultHook with a function that returns the
// given values.
func (f *AccessTokensStoreGetBySHA1Func) SetDefaultReturn(r0 *database.AccessToken, r1 error) {
f.SetDefaultHook(func(context.Context, string) (*database.AccessToken, error) {
return r0, r1
})
}
// PushReturn calls PushHook with a function that returns the given values.
func (f *AccessTokensStoreGetBySHA1Func) PushReturn(r0 *database.AccessToken, r1 error) {
f.PushHook(func(context.Context, string) (*database.AccessToken, error) {
return r0, r1
})
}
func (f *AccessTokensStoreGetBySHA1Func) nextHook() func(context.Context, string) (*database.AccessToken, error) {
f.mutex.Lock()
defer f.mutex.Unlock()
if len(f.hooks) == 0 {
return f.defaultHook
}
hook := f.hooks[0]
f.hooks = f.hooks[1:]
return hook
}
func (f *AccessTokensStoreGetBySHA1Func) appendCall(r0 AccessTokensStoreGetBySHA1FuncCall) {
f.mutex.Lock()
f.history = append(f.history, r0)
f.mutex.Unlock()
}
// History returns a sequence of AccessTokensStoreGetBySHA1FuncCall objects
// describing the invocations of this function.
func (f *AccessTokensStoreGetBySHA1Func) History() []AccessTokensStoreGetBySHA1FuncCall {
f.mutex.Lock()
history := make([]AccessTokensStoreGetBySHA1FuncCall, len(f.history))
copy(history, f.history)
f.mutex.Unlock()
return history
}
// AccessTokensStoreGetBySHA1FuncCall is an object that describes an
// invocation of method GetBySHA1 on an instance of MockAccessTokensStore.
type AccessTokensStoreGetBySHA1FuncCall struct {
// Arg0 is the value of the 1st argument passed to this method
// invocation.
Arg0 context.Context
// Arg1 is the value of the 2nd argument passed to this method
// invocation.
Arg1 string
// Result0 is the value of the 1st result returned from this method
// invocation.
Result0 *database.AccessToken
// Result1 is the value of the 2nd result returned from this method
// invocation.
Result1 error
}
// Args returns an interface slice containing the arguments of this
// invocation.
func (c AccessTokensStoreGetBySHA1FuncCall) Args() []interface{} {
return []interface{}{c.Arg0, c.Arg1}
}
// Results returns an interface slice containing the results of this
// invocation.
func (c AccessTokensStoreGetBySHA1FuncCall) Results() []interface{} {
return []interface{}{c.Result0, c.Result1}
}
// AccessTokensStoreListFunc describes the behavior when the List method of
// the parent MockAccessTokensStore instance is invoked.
type AccessTokensStoreListFunc struct {
defaultHook func(context.Context, int64) ([]*database.AccessToken, error)
hooks []func(context.Context, int64) ([]*database.AccessToken, error)
history []AccessTokensStoreListFuncCall
mutex sync.Mutex
}
// List delegates to the next hook function in the queue and stores the
// parameter and result values of this invocation.
func (m *MockAccessTokensStore) List(v0 context.Context, v1 int64) ([]*database.AccessToken, error) {
r0, r1 := m.ListFunc.nextHook()(v0, v1)
m.ListFunc.appendCall(AccessTokensStoreListFuncCall{v0, v1, r0, r1})
return r0, r1
}
// SetDefaultHook sets function that is called when the List method of the
// parent MockAccessTokensStore instance is invoked and the hook queue is
// empty.
func (f *AccessTokensStoreListFunc) SetDefaultHook(hook func(context.Context, int64) ([]*database.AccessToken, error)) {
f.defaultHook = hook
}
// PushHook adds a function to the end of hook queue. Each invocation of the
// List method of the parent MockAccessTokensStore instance invokes the hook
// at the front of the queue and discards it. After the queue is empty, the
// default hook function is invoked for any future action.
func (f *AccessTokensStoreListFunc) PushHook(hook func(context.Context, int64) ([]*database.AccessToken, error)) {
f.mutex.Lock()
f.hooks = append(f.hooks, hook)
f.mutex.Unlock()
}
// SetDefaultReturn calls SetDefaultHook with a function that returns the
// given values.
func (f *AccessTokensStoreListFunc) SetDefaultReturn(r0 []*database.AccessToken, r1 error) {
f.SetDefaultHook(func(context.Context, int64) ([]*database.AccessToken, error) {
return r0, r1
})
}
// PushReturn calls PushHook with a function that returns the given values.
func (f *AccessTokensStoreListFunc) PushReturn(r0 []*database.AccessToken, r1 error) {
f.PushHook(func(context.Context, int64) ([]*database.AccessToken, error) {
return r0, r1
})
}
func (f *AccessTokensStoreListFunc) nextHook() func(context.Context, int64) ([]*database.AccessToken, error) {
f.mutex.Lock()
defer f.mutex.Unlock()
if len(f.hooks) == 0 {
return f.defaultHook
}
hook := f.hooks[0]
f.hooks = f.hooks[1:]
return hook
}
func (f *AccessTokensStoreListFunc) appendCall(r0 AccessTokensStoreListFuncCall) {
f.mutex.Lock()
f.history = append(f.history, r0)
f.mutex.Unlock()
}
// History returns a sequence of AccessTokensStoreListFuncCall objects
// describing the invocations of this function.
func (f *AccessTokensStoreListFunc) History() []AccessTokensStoreListFuncCall {
f.mutex.Lock()
history := make([]AccessTokensStoreListFuncCall, len(f.history))
copy(history, f.history)
f.mutex.Unlock()
return history
}
// AccessTokensStoreListFuncCall is an object that describes an invocation
// of method List on an instance of MockAccessTokensStore.
type AccessTokensStoreListFuncCall struct {
// Arg0 is the value of the 1st argument passed to this method
// invocation.
Arg0 context.Context
// Arg1 is the value of the 2nd argument passed to this method
// invocation.
Arg1 int64
// Result0 is the value of the 1st result returned from this method
// invocation.
Result0 []*database.AccessToken
// Result1 is the value of the 2nd result returned from this method
// invocation.
Result1 error
}
// Args returns an interface slice containing the arguments of this
// invocation.
func (c AccessTokensStoreListFuncCall) Args() []interface{} {
return []interface{}{c.Arg0, c.Arg1}
}
// Results returns an interface slice containing the results of this
// invocation.
func (c AccessTokensStoreListFuncCall) Results() []interface{} {
return []interface{}{c.Result0, c.Result1}
}
// AccessTokensStoreTouchFunc describes the behavior when the Touch method
// of the parent MockAccessTokensStore instance is invoked.
type AccessTokensStoreTouchFunc struct {
defaultHook func(context.Context, int64) error
hooks []func(context.Context, int64) error
history []AccessTokensStoreTouchFuncCall
mutex sync.Mutex
}
// Touch delegates to the next hook function in the queue and stores the
// parameter and result values of this invocation.
func (m *MockAccessTokensStore) Touch(v0 context.Context, v1 int64) error {
r0 := m.TouchFunc.nextHook()(v0, v1)
m.TouchFunc.appendCall(AccessTokensStoreTouchFuncCall{v0, v1, r0})
return r0
}
// SetDefaultHook sets function that is called when the Touch method of the
// parent MockAccessTokensStore instance is invoked and the hook queue is
// empty.
func (f *AccessTokensStoreTouchFunc) SetDefaultHook(hook func(context.Context, int64) error) {
f.defaultHook = hook
}
// PushHook adds a function to the end of hook queue. Each invocation of the
// Touch method of the parent MockAccessTokensStore instance invokes the
// hook at the front of the queue and discards it. After the queue is empty,
// the default hook function is invoked for any future action.
func (f *AccessTokensStoreTouchFunc) PushHook(hook func(context.Context, int64) error) {
f.mutex.Lock()
f.hooks = append(f.hooks, hook)
f.mutex.Unlock()
}
// SetDefaultReturn calls SetDefaultHook with a function that returns the
// given values.
func (f *AccessTokensStoreTouchFunc) SetDefaultReturn(r0 error) {
f.SetDefaultHook(func(context.Context, int64) error {
return r0
})
}
// PushReturn calls PushHook with a function that returns the given values.
func (f *AccessTokensStoreTouchFunc) PushReturn(r0 error) {
f.PushHook(func(context.Context, int64) error {
return r0
})
}
func (f *AccessTokensStoreTouchFunc) nextHook() func(context.Context, int64) error {
f.mutex.Lock()
defer f.mutex.Unlock()
if len(f.hooks) == 0 {
return f.defaultHook
}
hook := f.hooks[0]
f.hooks = f.hooks[1:]
return hook
}
func (f *AccessTokensStoreTouchFunc) appendCall(r0 AccessTokensStoreTouchFuncCall) {
f.mutex.Lock()
f.history = append(f.history, r0)
f.mutex.Unlock()
}
// History returns a sequence of AccessTokensStoreTouchFuncCall objects
// describing the invocations of this function.
func (f *AccessTokensStoreTouchFunc) History() []AccessTokensStoreTouchFuncCall {
f.mutex.Lock()
history := make([]AccessTokensStoreTouchFuncCall, len(f.history))
copy(history, f.history)
f.mutex.Unlock()
return history
}
// AccessTokensStoreTouchFuncCall is an object that describes an invocation
// of method Touch on an instance of MockAccessTokensStore.
type AccessTokensStoreTouchFuncCall struct {
// Arg0 is the value of the 1st argument passed to this method
// invocation.
Arg0 context.Context
// Arg1 is the value of the 2nd argument passed to this method
// invocation.
Arg1 int64
// Result0 is the value of the 1st result returned from this method
// invocation.
Result0 error
}
// Args returns an interface slice containing the arguments of this
// invocation.
func (c AccessTokensStoreTouchFuncCall) Args() []interface{} {
return []interface{}{c.Arg0, c.Arg1}
}
// Results returns an interface slice containing the results of this
// invocation.
func (c AccessTokensStoreTouchFuncCall) Results() []interface{} {
return []interface{}{c.Result0}
}
// MockLFSStore is a mock implementation of the LFSStore interface (from the
// package gogs.io/gogs/internal/database) used for unit testing.
type MockLFSStore struct {
@ -6698,3 +6047,274 @@ func (c UsersStoreUseCustomAvatarFuncCall) Args() []interface{} {
func (c UsersStoreUseCustomAvatarFuncCall) Results() []interface{} {
return []interface{}{c.Result0}
}
// MockStore is a mock implementation of the Store interface (from the
// package gogs.io/gogs/internal/route/lfs) used for unit testing.
type MockStore struct {
// GetAccessTokenBySHA1Func is an instance of a mock function object
// controlling the behavior of the method GetAccessTokenBySHA1.
GetAccessTokenBySHA1Func *StoreGetAccessTokenBySHA1Func
// TouchAccessTokenByIDFunc is an instance of a mock function object
// controlling the behavior of the method TouchAccessTokenByID.
TouchAccessTokenByIDFunc *StoreTouchAccessTokenByIDFunc
}
// NewMockStore creates a new mock of the Store interface. All methods
// return zero values for all results, unless overwritten.
func NewMockStore() *MockStore {
return &MockStore{
GetAccessTokenBySHA1Func: &StoreGetAccessTokenBySHA1Func{
defaultHook: func(context.Context, string) (r0 *database.AccessToken, r1 error) {
return
},
},
TouchAccessTokenByIDFunc: &StoreTouchAccessTokenByIDFunc{
defaultHook: func(context.Context, int64) (r0 error) {
return
},
},
}
}
// NewStrictMockStore creates a new mock of the Store interface. All methods
// panic on invocation, unless overwritten.
func NewStrictMockStore() *MockStore {
return &MockStore{
GetAccessTokenBySHA1Func: &StoreGetAccessTokenBySHA1Func{
defaultHook: func(context.Context, string) (*database.AccessToken, error) {
panic("unexpected invocation of MockStore.GetAccessTokenBySHA1")
},
},
TouchAccessTokenByIDFunc: &StoreTouchAccessTokenByIDFunc{
defaultHook: func(context.Context, int64) error {
panic("unexpected invocation of MockStore.TouchAccessTokenByID")
},
},
}
}
// NewMockStoreFrom creates a new mock of the MockStore interface. All
// methods delegate to the given implementation, unless overwritten.
func NewMockStoreFrom(i Store) *MockStore {
return &MockStore{
GetAccessTokenBySHA1Func: &StoreGetAccessTokenBySHA1Func{
defaultHook: i.GetAccessTokenBySHA1,
},
TouchAccessTokenByIDFunc: &StoreTouchAccessTokenByIDFunc{
defaultHook: i.TouchAccessTokenByID,
},
}
}
// StoreGetAccessTokenBySHA1Func describes the behavior when the
// GetAccessTokenBySHA1 method of the parent MockStore instance is invoked.
type StoreGetAccessTokenBySHA1Func struct {
defaultHook func(context.Context, string) (*database.AccessToken, error)
hooks []func(context.Context, string) (*database.AccessToken, error)
history []StoreGetAccessTokenBySHA1FuncCall
mutex sync.Mutex
}
// GetAccessTokenBySHA1 delegates to the next hook function in the queue and
// stores the parameter and result values of this invocation.
func (m *MockStore) GetAccessTokenBySHA1(v0 context.Context, v1 string) (*database.AccessToken, error) {
r0, r1 := m.GetAccessTokenBySHA1Func.nextHook()(v0, v1)
m.GetAccessTokenBySHA1Func.appendCall(StoreGetAccessTokenBySHA1FuncCall{v0, v1, r0, r1})
return r0, r1
}
// SetDefaultHook sets function that is called when the GetAccessTokenBySHA1
// method of the parent MockStore instance is invoked and the hook queue is
// empty.
func (f *StoreGetAccessTokenBySHA1Func) SetDefaultHook(hook func(context.Context, string) (*database.AccessToken, error)) {
f.defaultHook = hook
}
// PushHook adds a function to the end of hook queue. Each invocation of the
// GetAccessTokenBySHA1 method of the parent MockStore instance invokes the
// hook at the front of the queue and discards it. After the queue is empty,
// the default hook function is invoked for any future action.
func (f *StoreGetAccessTokenBySHA1Func) PushHook(hook func(context.Context, string) (*database.AccessToken, error)) {
f.mutex.Lock()
f.hooks = append(f.hooks, hook)
f.mutex.Unlock()
}
// SetDefaultReturn calls SetDefaultHook with a function that returns the
// given values.
func (f *StoreGetAccessTokenBySHA1Func) SetDefaultReturn(r0 *database.AccessToken, r1 error) {
f.SetDefaultHook(func(context.Context, string) (*database.AccessToken, error) {
return r0, r1
})
}
// PushReturn calls PushHook with a function that returns the given values.
func (f *StoreGetAccessTokenBySHA1Func) PushReturn(r0 *database.AccessToken, r1 error) {
f.PushHook(func(context.Context, string) (*database.AccessToken, error) {
return r0, r1
})
}
func (f *StoreGetAccessTokenBySHA1Func) nextHook() func(context.Context, string) (*database.AccessToken, error) {
f.mutex.Lock()
defer f.mutex.Unlock()
if len(f.hooks) == 0 {
return f.defaultHook
}
hook := f.hooks[0]
f.hooks = f.hooks[1:]
return hook
}
func (f *StoreGetAccessTokenBySHA1Func) appendCall(r0 StoreGetAccessTokenBySHA1FuncCall) {
f.mutex.Lock()
f.history = append(f.history, r0)
f.mutex.Unlock()
}
// History returns a sequence of StoreGetAccessTokenBySHA1FuncCall objects
// describing the invocations of this function.
func (f *StoreGetAccessTokenBySHA1Func) History() []StoreGetAccessTokenBySHA1FuncCall {
f.mutex.Lock()
history := make([]StoreGetAccessTokenBySHA1FuncCall, len(f.history))
copy(history, f.history)
f.mutex.Unlock()
return history
}
// StoreGetAccessTokenBySHA1FuncCall is an object that describes an
// invocation of method GetAccessTokenBySHA1 on an instance of MockStore.
type StoreGetAccessTokenBySHA1FuncCall struct {
// Arg0 is the value of the 1st argument passed to this method
// invocation.
Arg0 context.Context
// Arg1 is the value of the 2nd argument passed to this method
// invocation.
Arg1 string
// Result0 is the value of the 1st result returned from this method
// invocation.
Result0 *database.AccessToken
// Result1 is the value of the 2nd result returned from this method
// invocation.
Result1 error
}
// Args returns an interface slice containing the arguments of this
// invocation.
func (c StoreGetAccessTokenBySHA1FuncCall) Args() []interface{} {
return []interface{}{c.Arg0, c.Arg1}
}
// Results returns an interface slice containing the results of this
// invocation.
func (c StoreGetAccessTokenBySHA1FuncCall) Results() []interface{} {
return []interface{}{c.Result0, c.Result1}
}
// StoreTouchAccessTokenByIDFunc describes the behavior when the
// TouchAccessTokenByID method of the parent MockStore instance is invoked.
type StoreTouchAccessTokenByIDFunc struct {
defaultHook func(context.Context, int64) error
hooks []func(context.Context, int64) error
history []StoreTouchAccessTokenByIDFuncCall
mutex sync.Mutex
}
// TouchAccessTokenByID delegates to the next hook function in the queue and
// stores the parameter and result values of this invocation.
func (m *MockStore) TouchAccessTokenByID(v0 context.Context, v1 int64) error {
r0 := m.TouchAccessTokenByIDFunc.nextHook()(v0, v1)
m.TouchAccessTokenByIDFunc.appendCall(StoreTouchAccessTokenByIDFuncCall{v0, v1, r0})
return r0
}
// SetDefaultHook sets function that is called when the TouchAccessTokenByID
// method of the parent MockStore instance is invoked and the hook queue is
// empty.
func (f *StoreTouchAccessTokenByIDFunc) SetDefaultHook(hook func(context.Context, int64) error) {
f.defaultHook = hook
}
// PushHook adds a function to the end of hook queue. Each invocation of the
// TouchAccessTokenByID method of the parent MockStore instance invokes the
// hook at the front of the queue and discards it. After the queue is empty,
// the default hook function is invoked for any future action.
func (f *StoreTouchAccessTokenByIDFunc) PushHook(hook func(context.Context, int64) error) {
f.mutex.Lock()
f.hooks = append(f.hooks, hook)
f.mutex.Unlock()
}
// SetDefaultReturn calls SetDefaultHook with a function that returns the
// given values.
func (f *StoreTouchAccessTokenByIDFunc) SetDefaultReturn(r0 error) {
f.SetDefaultHook(func(context.Context, int64) error {
return r0
})
}
// PushReturn calls PushHook with a function that returns the given values.
func (f *StoreTouchAccessTokenByIDFunc) PushReturn(r0 error) {
f.PushHook(func(context.Context, int64) error {
return r0
})
}
func (f *StoreTouchAccessTokenByIDFunc) nextHook() func(context.Context, int64) error {
f.mutex.Lock()
defer f.mutex.Unlock()
if len(f.hooks) == 0 {
return f.defaultHook
}
hook := f.hooks[0]
f.hooks = f.hooks[1:]
return hook
}
func (f *StoreTouchAccessTokenByIDFunc) appendCall(r0 StoreTouchAccessTokenByIDFuncCall) {
f.mutex.Lock()
f.history = append(f.history, r0)
f.mutex.Unlock()
}
// History returns a sequence of StoreTouchAccessTokenByIDFuncCall objects
// describing the invocations of this function.
func (f *StoreTouchAccessTokenByIDFunc) History() []StoreTouchAccessTokenByIDFuncCall {
f.mutex.Lock()
history := make([]StoreTouchAccessTokenByIDFuncCall, len(f.history))
copy(history, f.history)
f.mutex.Unlock()
return history
}
// StoreTouchAccessTokenByIDFuncCall is an object that describes an
// invocation of method TouchAccessTokenByID on an instance of MockStore.
type StoreTouchAccessTokenByIDFuncCall struct {
// Arg0 is the value of the 1st argument passed to this method
// invocation.
Arg0 context.Context
// Arg1 is the value of the 2nd argument passed to this method
// invocation.
Arg1 int64
// Result0 is the value of the 1st result returned from this method
// invocation.
Result0 error
}
// Args returns an interface slice containing the arguments of this
// invocation.
func (c StoreTouchAccessTokenByIDFuncCall) Args() []interface{} {
return []interface{}{c.Arg0, c.Arg1}
}
// Results returns an interface slice containing the results of this
// invocation.
func (c StoreTouchAccessTokenByIDFuncCall) Results() []interface{} {
return []interface{}{c.Result0}
}

View File

@ -19,12 +19,14 @@ import (
"gogs.io/gogs/internal/lfsutil"
)
// RegisterRoutes registers LFS routes using given router, and inherits all groups and middleware.
// RegisterRoutes registers LFS routes using given router, and inherits all
// groups and middleware.
func RegisterRoutes(r *macaron.Router) {
verifyAccept := verifyHeader("Accept", contentType, http.StatusNotAcceptable)
verifyContentTypeJSON := verifyHeader("Content-Type", contentType, http.StatusBadRequest)
verifyContentTypeStream := verifyHeader("Content-Type", "application/octet-stream", http.StatusBadRequest)
store := NewStore()
r.Group("", func() {
r.Post("/objects/batch", authorize(database.AccessModeRead), verifyAccept, verifyContentTypeJSON, serveBatch)
r.Group("/objects/basic", func() {
@ -39,12 +41,12 @@ func RegisterRoutes(r *macaron.Router) {
Put(authorize(database.AccessModeWrite), verifyContentTypeStream, basic.serveUpload)
r.Post("/verify", authorize(database.AccessModeWrite), verifyAccept, verifyContentTypeJSON, basic.serveVerify)
})
}, authenticate())
}, authenticate(store))
}
// authenticate tries to authenticate user via HTTP Basic Auth. It first tries to authenticate
// as plain username and password, then use username as access token if previous step failed.
func authenticate() macaron.Handler {
func authenticate(store Store) macaron.Handler {
askCredentials := func(w http.ResponseWriter) {
w.Header().Set("Lfs-Authenticate", `Basic realm="Git LFS"`)
responseJSON(w, http.StatusUnauthorized, responseError{
@ -74,14 +76,14 @@ func authenticate() macaron.Handler {
// If username and password combination failed, try again using either username
// or password as the token.
if auth.IsErrBadCredentials(err) {
user, err = context.AuthenticateByToken(c.Req.Context(), username)
user, err = context.AuthenticateByToken(store, c.Req.Context(), username)
if err != nil && !database.IsErrAccessTokenNotExist(err) {
internalServerError(c.Resp)
log.Error("Failed to authenticate by access token via username: %v", err)
return
} else if database.IsErrAccessTokenNotExist(err) {
// Try again using the password field as the token.
user, err = context.AuthenticateByToken(c.Req.Context(), password)
user, err = context.AuthenticateByToken(store, c.Req.Context(), password)
if err != nil {
if database.IsErrAccessTokenNotExist(err) {
askCredentials(c.Resp)

View File

@ -20,19 +20,13 @@ import (
"gogs.io/gogs/internal/lfsutil"
)
func Test_authenticate(t *testing.T) {
m := macaron.New()
m.Use(macaron.Renderer())
m.Get("/", authenticate(), func(w http.ResponseWriter, user *database.User) {
_, _ = fmt.Fprintf(w, "ID: %d, Name: %s", user.ID, user.Name)
})
func TestAuthenticate(t *testing.T) {
tests := []struct {
name string
header http.Header
mockUsersStore func() database.UsersStore
mockTwoFactorsStore func() database.TwoFactorsStore
mockAccessTokensStore func() database.AccessTokensStore
mockStore func() *MockStore
expStatusCode int
expHeader http.Header
expBody string
@ -75,10 +69,10 @@ func Test_authenticate(t *testing.T) {
mock.AuthenticateFunc.SetDefaultReturn(nil, auth.ErrBadCredentials{})
return mock
},
mockAccessTokensStore: func() database.AccessTokensStore {
mock := NewMockAccessTokensStore()
mock.GetBySHA1Func.SetDefaultReturn(nil, database.ErrAccessTokenNotExist{})
return mock
mockStore: func() *MockStore {
mockStore := NewMockStore()
mockStore.GetAccessTokenBySHA1Func.SetDefaultReturn(nil, database.ErrAccessTokenNotExist{})
return mockStore
},
expStatusCode: http.StatusUnauthorized,
expHeader: http.Header{
@ -118,10 +112,10 @@ func Test_authenticate(t *testing.T) {
mock.GetByIDFunc.SetDefaultReturn(&database.User{ID: 1, Name: "unknwon"}, nil)
return mock
},
mockAccessTokensStore: func() database.AccessTokensStore {
mock := NewMockAccessTokensStore()
mock.GetBySHA1Func.SetDefaultReturn(&database.AccessToken{}, nil)
return mock
mockStore: func() *MockStore {
mockStore := NewMockStore()
mockStore.GetAccessTokenBySHA1Func.SetDefaultReturn(&database.AccessToken{}, nil)
return mockStore
},
expStatusCode: http.StatusOK,
expHeader: http.Header{},
@ -138,15 +132,15 @@ func Test_authenticate(t *testing.T) {
mock.GetByIDFunc.SetDefaultReturn(&database.User{ID: 1, Name: "unknwon"}, nil)
return mock
},
mockAccessTokensStore: func() database.AccessTokensStore {
mock := NewMockAccessTokensStore()
mock.GetBySHA1Func.SetDefaultHook(func(ctx context.Context, sha1 string) (*database.AccessToken, error) {
mockStore: func() *MockStore {
mockStore := NewMockStore()
mockStore.GetAccessTokenBySHA1Func.SetDefaultHook(func(_ context.Context, sha1 string) (*database.AccessToken, error) {
if sha1 == "password" {
return &database.AccessToken{}, nil
}
return nil, database.ErrAccessTokenNotExist{}
})
return mock
return mockStore
},
expStatusCode: http.StatusOK,
expHeader: http.Header{},
@ -161,10 +155,16 @@ func Test_authenticate(t *testing.T) {
if test.mockTwoFactorsStore != nil {
database.SetMockTwoFactorsStore(t, test.mockTwoFactorsStore())
}
if test.mockAccessTokensStore != nil {
database.SetMockAccessTokensStore(t, test.mockAccessTokensStore())
if test.mockStore == nil {
test.mockStore = NewMockStore
}
m := macaron.New()
m.Use(macaron.Renderer())
m.Get("/", authenticate(test.mockStore()), func(w http.ResponseWriter, user *database.User) {
_, _ = fmt.Fprintf(w, "ID: %d, Name: %s", user.ID, user.Name)
})
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)

View File

@ -0,0 +1,34 @@
package lfs
import (
"context"
"gogs.io/gogs/internal/database"
)
// Store is the data layer carrier for LFS endpoints. This interface is meant to
// abstract away and limit the exposure of the underlying data layer to the
// handler through a thin-wrapper.
type Store interface {
// GetAccessTokenBySHA1 returns the access token with given SHA1. It returns
// database.ErrAccessTokenNotExist when not found.
GetAccessTokenBySHA1(ctx context.Context, sha1 string) (*database.AccessToken, error)
// TouchAccessTokenByID updates the updated time of the given access token to
// the current time.
TouchAccessTokenByID(ctx context.Context, id int64) error
}
type store struct{}
// NewStore returns a new Store using the global database handle.
func NewStore() Store {
return &store{}
}
func (*store) GetAccessTokenBySHA1(ctx context.Context, sha1 string) (*database.AccessToken, error) {
return database.Handle.AccessTokens().GetBySHA1(ctx, sha1)
}
func (*store) TouchAccessTokenByID(ctx context.Context, id int64) error {
return database.Handle.AccessTokens().Touch(ctx, id)
}

View File

@ -44,7 +44,7 @@ func askCredentials(c *macaron.Context, status int, text string) {
c.Error(status, text)
}
func HTTPContexter() macaron.Handler {
func HTTPContexter(store Store) macaron.Handler {
return func(c *macaron.Context) {
if len(conf.HTTP.AccessControlAllowOrigin) > 0 {
// Set CORS headers for browser-based git clients
@ -134,14 +134,14 @@ func HTTPContexter() macaron.Handler {
// If username and password combination failed, try again using either username
// or password as the token.
if authUser == nil {
authUser, err = context.AuthenticateByToken(c.Req.Context(), authUsername)
authUser, err = context.AuthenticateByToken(store, c.Req.Context(), authUsername)
if err != nil && !database.IsErrAccessTokenNotExist(err) {
c.Status(http.StatusInternalServerError)
log.Error("Failed to authenticate by access token via username: %v", err)
return
} else if database.IsErrAccessTokenNotExist(err) {
// Try again using the password field as the token.
authUser, err = context.AuthenticateByToken(c.Req.Context(), authPassword)
authUser, err = context.AuthenticateByToken(store, c.Req.Context(), authPassword)
if err != nil {
if database.IsErrAccessTokenNotExist(err) {
askCredentials(c, http.StatusUnauthorized, "")

View File

@ -0,0 +1,34 @@
package repo
import (
"context"
"gogs.io/gogs/internal/database"
)
// Store is the data layer carrier for context middleware. This interface is
// meant to abstract away and limit the exposure of the underlying data layer to
// the handler through a thin-wrapper.
type Store interface {
// GetAccessTokenBySHA1 returns the access token with given SHA1. It returns
// database.ErrAccessTokenNotExist when not found.
GetAccessTokenBySHA1(ctx context.Context, sha1 string) (*database.AccessToken, error)
// TouchAccessTokenByID updates the updated time of the given access token to
// the current time.
TouchAccessTokenByID(ctx context.Context, id int64) error
}
type store struct{}
// NewStore returns a new Store using the global database handle.
func NewStore() Store {
return &store{}
}
func (*store) GetAccessTokenBySHA1(ctx context.Context, sha1 string) (*database.AccessToken, error) {
return database.Handle.AccessTokens().GetBySHA1(ctx, sha1)
}
func (*store) TouchAccessTokenByID(ctx context.Context, id int64) error {
return database.Handle.AccessTokens().Touch(ctx, id)
}

View File

@ -6,6 +6,7 @@ package user
import (
"bytes"
gocontext "context"
"encoding/base64"
"fmt"
"html/template"
@ -15,6 +16,7 @@ import (
"github.com/pkg/errors"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
"gopkg.in/macaron.v1"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/auth"
@ -28,6 +30,18 @@ import (
"gogs.io/gogs/internal/userutil"
)
// SettingsHandler is the handler for users settings endpoints.
type SettingsHandler struct {
store SettingsStore
}
// NewSettingsHandler returns a new SettingsHandler for users settings endpoints.
func NewSettingsHandler(s SettingsStore) *SettingsHandler {
return &SettingsHandler{
store: s,
}
}
const (
SETTINGS_PROFILE = "user/settings/profile"
SETTINGS_AVATAR = "user/settings/avatar"
@ -580,11 +594,12 @@ func SettingsLeaveOrganization(c *context.Context) {
})
}
func SettingsApplications(c *context.Context) {
func (h *SettingsHandler) Applications() macaron.Handler {
return func(c *context.Context) {
c.Title("settings.applications")
c.PageIs("SettingsApplications")
tokens, err := database.AccessTokens.List(c.Req.Context(), c.User.ID)
tokens, err := h.store.ListAccessTokens(c.Req.Context(), c.User.ID)
if err != nil {
c.Errorf(err, "list access tokens")
return
@ -593,13 +608,15 @@ func SettingsApplications(c *context.Context) {
c.Success(SETTINGS_APPLICATIONS)
}
}
func SettingsApplicationsPost(c *context.Context, f form.NewAccessToken) {
func (h *SettingsHandler) ApplicationsPost() macaron.Handler {
return func(c *context.Context, f form.NewAccessToken) {
c.Title("settings.applications")
c.PageIs("SettingsApplications")
if c.HasError() {
tokens, err := database.AccessTokens.List(c.Req.Context(), c.User.ID)
tokens, err := h.store.ListAccessTokens(c.Req.Context(), c.User.ID)
if err != nil {
c.Errorf(err, "list access tokens")
return
@ -610,7 +627,7 @@ func SettingsApplicationsPost(c *context.Context, f form.NewAccessToken) {
return
}
t, err := database.AccessTokens.Create(c.Req.Context(), c.User.ID, f.Name)
t, err := h.store.CreateAccessToken(c.Req.Context(), c.User.ID, f.Name)
if err != nil {
if database.IsErrAccessTokenAlreadyExist(err) {
c.Flash.Error(c.Tr("settings.token_name_exists"))
@ -625,9 +642,11 @@ func SettingsApplicationsPost(c *context.Context, f form.NewAccessToken) {
c.Flash.Info(t.Sha1)
c.RedirectSubpath("/user/settings/applications")
}
}
func SettingsDeleteApplication(c *context.Context) {
if err := database.AccessTokens.DeleteByID(c.Req.Context(), c.User.ID, c.QueryInt64("id")); err != nil {
func (h *SettingsHandler) DeleteApplication() macaron.Handler {
return func(c *context.Context) {
if err := h.store.DeleteAccessTokenByID(c.Req.Context(), c.User.ID, c.QueryInt64("id")); err != nil {
c.Flash.Error("DeleteAccessTokenByID: " + err.Error())
} else {
c.Flash.Success(c.Tr("settings.delete_token_success"))
@ -637,6 +656,7 @@ func SettingsDeleteApplication(c *context.Context) {
"redirect": conf.Server.Subpath + "/user/settings/applications",
})
}
}
func SettingsDelete(c *context.Context) {
c.Title("settings.delete")
@ -672,3 +692,51 @@ func SettingsDelete(c *context.Context) {
c.Success(SETTINGS_DELETE)
}
// SettingsStore is the data layer carrier for user settings endpoints. This
// interface is meant to abstract away and limit the exposure of the underlying
// data layer to the handler through a thin-wrapper.
type SettingsStore interface {
// CreateAccessToken creates a new access token and persist to database. It
// returns database.ErrAccessTokenAlreadyExist when an access token with same
// name already exists for the user.
CreateAccessToken(ctx gocontext.Context, userID int64, name string) (*database.AccessToken, error)
// GetAccessTokenBySHA1 returns the access token with given SHA1. It returns
// database.ErrAccessTokenNotExist when not found.
GetAccessTokenBySHA1(ctx gocontext.Context, sha1 string) (*database.AccessToken, error)
// TouchAccessTokenByID updates the updated time of the given access token to
// the current time.
TouchAccessTokenByID(ctx gocontext.Context, id int64) error
// ListAccessTokens returns all access tokens belongs to given user.
ListAccessTokens(ctx gocontext.Context, userID int64) ([]*database.AccessToken, error)
// DeleteAccessTokenByID deletes the access token by given ID.
DeleteAccessTokenByID(ctx gocontext.Context, userID, id int64) error
}
type settingsStore struct{}
// NewSettingsStore returns a new SettingsStore using the global database
// handle.
func NewSettingsStore() SettingsStore {
return &settingsStore{}
}
func (*settingsStore) CreateAccessToken(ctx gocontext.Context, userID int64, name string) (*database.AccessToken, error) {
return database.Handle.AccessTokens().Create(ctx, userID, name)
}
func (*settingsStore) GetAccessTokenBySHA1(ctx gocontext.Context, sha1 string) (*database.AccessToken, error) {
return database.Handle.AccessTokens().GetBySHA1(ctx, sha1)
}
func (*settingsStore) TouchAccessTokenByID(ctx gocontext.Context, id int64) error {
return database.Handle.AccessTokens().Touch(ctx, id)
}
func (*settingsStore) ListAccessTokens(ctx gocontext.Context, userID int64) ([]*database.AccessToken, error) {
return database.Handle.AccessTokens().List(ctx, userID)
}
func (*settingsStore) DeleteAccessTokenByID(ctx gocontext.Context, userID, id int64) error {
return database.Handle.AccessTokens().DeleteByID(ctx, userID, id)
}

View File

@ -39,6 +39,8 @@ mocks:
- LFSStore
- UsersStore
- TwoFactorsStore
- AccessTokensStore
- ReposStore
- PermsStore
- path: gogs.io/gogs/internal/route/lfs
interfaces:
- Store