Skip to content

Create DB session provider(based on xorm) #13031

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Feb 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
328eea5
Create Xorm session provider
zeripath Oct 4, 2020
62422fe
extraneous l
zeripath Oct 4, 2020
6d15b3e
fix lint
zeripath Oct 5, 2020
895c378
Merge remote-tracking branch 'origin/master' into xorm-session-provider
zeripath Oct 5, 2020
f32c7e4
use key instead of ID to be compatible with go-macaron/session
zeripath Oct 5, 2020
b551f02
And change the migration too.
zeripath Oct 5, 2020
602be9c
Update spacing of imports
zeripath Oct 5, 2020
3715fe4
Merge remote-tracking branch 'origin/master' into xorm-session-provider
zeripath Oct 15, 2020
2a32914
Update modules/session/xorm.go
zeripath Oct 15, 2020
bb69a28
Merge branch 'master' into xorm-session-provider
zeripath Oct 15, 2020
ef6f4ac
Merge remote-tracking branch 'origin/master' into xorm-session-provider
zeripath Oct 24, 2020
f73ac12
add xorm provider to the virtual provider
zeripath Oct 31, 2020
6271a1d
prep for master merge
zeripath Jan 3, 2021
2a806a0
Merge branch 'master' into xorm-session-provider
zeripath Jan 3, 2021
d830fa6
prep for merge master
zeripath Feb 10, 2021
8b85e35
Merge remote-tracking branch 'origin/master' into xorm-session-provider
zeripath Feb 10, 2021
4e699d2
As per @lunny
zeripath Feb 13, 2021
6c19eef
move migration out of the way
zeripath Feb 14, 2021
54f04f4
Merge remote-tracking branch 'origin/master' into xorm-session-provider
zeripath Feb 14, 2021
0ad7db5
Move to call this db session as per @lunny
zeripath Feb 14, 2021
39f1565
Merge branch 'master' into xorm-session-provider
6543 Feb 14, 2021
dcebdfe
Merge branch 'master' into xorm-session-provider
lunny Feb 15, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/content/doc/advanced/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ Define allowed algorithms and their minimum key length (use -1 to disable a type

## Session (`session`)

- `PROVIDER`: **memory**: Session engine provider \[memory, file, redis, mysql, couchbase, memcache, postgres\].
- `PROVIDER`: **memory**: Session engine provider \[memory, file, redis, db, mysql, couchbase, memcache, postgres\].
- `PROVIDER_CONFIG`: **data/sessions**: For file, the root path; for others, the connection string.
- `COOKIE_SECURE`: **false**: Enable this to force using HTTPS for all session access.
- `COOKIE_NAME`: **i\_like\_gitea**: The name of the cookie used for the session ID.
Expand Down
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ var migrations = []Migration{
NewMigration("Add Dismissed to Review table", addDismissedReviewColumn),
// v171 -> v172
NewMigration("Add Sorting to ProjectBoard table", addSortingColToProjectBoard),
// v172 -> v173
NewMigration("Add sessions table for go-chi/session", addSessionTable),
}

// GetCurrentDBVersion returns the current db version
Expand Down
20 changes: 20 additions & 0 deletions models/migrations/v172.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2020 The Gitea 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 migrations

import (
"code.gitea.io/gitea/modules/timeutil"

"xorm.io/xorm"
)

func addSessionTable(x *xorm.Engine) error {
type Session struct {
Key string `xorm:"pk CHAR(16)"`
Data []byte `xorm:"BLOB"`
CreatedUnix timeutil.TimeStamp
}
return x.Sync2(new(Session))
}
1 change: 1 addition & 0 deletions models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func init() {
new(Project),
new(ProjectBoard),
new(ProjectIssue),
new(Session),
)

gonicNames := []string{"SSL", "UID"}
Expand Down
122 changes: 122 additions & 0 deletions models/session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright 2020 The Gitea 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 models

import (
"fmt"

"code.gitea.io/gitea/modules/timeutil"
)

// Session represents a session compatible for go-chi session
type Session struct {
Key string `xorm:"pk CHAR(16)"` // has to be Key to match with go-chi/session
Data []byte `xorm:"BLOB"`
Expiry timeutil.TimeStamp // has to be Expiry to match with go-chi/session
}

// UpdateSession updates the session with provided id
func UpdateSession(key string, data []byte) error {
_, err := x.ID(key).Update(&Session{
Data: data,
Expiry: timeutil.TimeStampNow(),
})
return err
}

// ReadSession reads the data for the provided session
func ReadSession(key string) (*Session, error) {
session := Session{
Key: key,
}
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return nil, err
}

if has, err := sess.Get(&session); err != nil {
return nil, err
} else if !has {
session.Expiry = timeutil.TimeStampNow()
_, err := sess.Insert(&session)
if err != nil {
return nil, err
}
}

return &session, sess.Commit()
}

// ExistSession checks if a session exists
func ExistSession(key string) (bool, error) {
session := Session{
Key: key,
}
return x.Get(&session)
}

// DestroySession destroys a session
func DestroySession(key string) error {
_, err := x.Delete(&Session{
Key: key,
})
return err
}

// RegenerateSession regenerates a session from the old id
func RegenerateSession(oldKey, newKey string) (*Session, error) {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return nil, err
}

if has, err := sess.Get(&Session{
Key: newKey,
}); err != nil {
return nil, err
} else if has {
return nil, fmt.Errorf("session Key: %s already exists", newKey)
}

if has, err := sess.Get(&Session{
Key: oldKey,
}); err != nil {
return nil, err
} else if !has {
_, err := sess.Insert(&Session{
Key: oldKey,
Expiry: timeutil.TimeStampNow(),
})
if err != nil {
return nil, err
}
}

if _, err := sess.Exec("UPDATE "+sess.Engine().TableName(&Session{})+" SET `key` = ? WHERE `key`=?", newKey, oldKey); err != nil {
return nil, err
}

s := Session{
Key: newKey,
}
if _, err := sess.Get(&s); err != nil {
return nil, err
}

return &s, sess.Commit()
}

// CountSessions returns the number of sessions
func CountSessions() (int64, error) {
return x.Count(&Session{})
}

// CleanupSessions cleans up expired sessions
func CleanupSessions(maxLifetime int64) error {
_, err := x.Where("created_unix <= ?", timeutil.TimeStampNow().Add(-maxLifetime)).Delete(&Session{})
return err
}
172 changes: 172 additions & 0 deletions modules/session/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Copyright 2020 The Gitea 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 session

import (
"log"
"sync"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/timeutil"

"gitea.com/go-chi/session"
)

// DBStore represents a session store implementation based on the DB.
type DBStore struct {
sid string
lock sync.RWMutex
data map[interface{}]interface{}
}

// NewDBStore creates and returns a DB session store.
func NewDBStore(sid string, kv map[interface{}]interface{}) *DBStore {
return &DBStore{
sid: sid,
data: kv,
}
}

// Set sets value to given key in session.
func (s *DBStore) Set(key, val interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()

s.data[key] = val
return nil
}

// Get gets value by given key in session.
func (s *DBStore) Get(key interface{}) interface{} {
s.lock.RLock()
defer s.lock.RUnlock()

return s.data[key]
}

// Delete delete a key from session.
func (s *DBStore) Delete(key interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()

delete(s.data, key)
return nil
}

// ID returns current session ID.
func (s *DBStore) ID() string {
return s.sid
}

// Release releases resource and save data to provider.
func (s *DBStore) Release() error {
// Skip encoding if the data is empty
if len(s.data) == 0 {
return nil
}

data, err := session.EncodeGob(s.data)
if err != nil {
return err
}

return models.UpdateSession(s.sid, data)
}

// Flush deletes all session data.
func (s *DBStore) Flush() error {
s.lock.Lock()
defer s.lock.Unlock()

s.data = make(map[interface{}]interface{})
return nil
}

// DBProvider represents a DB session provider implementation.
type DBProvider struct {
maxLifetime int64
}

// Init initializes DB session provider.
// connStr: username:password@protocol(address)/dbname?param=value
func (p *DBProvider) Init(maxLifetime int64, connStr string) error {
p.maxLifetime = maxLifetime
return nil
}

// Read returns raw session store by session ID.
func (p *DBProvider) Read(sid string) (session.RawStore, error) {
s, err := models.ReadSession(sid)
if err != nil {
return nil, err
}

var kv map[interface{}]interface{}
if len(s.Data) == 0 || s.Expiry.Add(p.maxLifetime) <= timeutil.TimeStampNow() {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob(s.Data)
if err != nil {
return nil, err
}
}

return NewDBStore(sid, kv), nil
}

// Exist returns true if session with given ID exists.
func (p *DBProvider) Exist(sid string) bool {
has, err := models.ExistSession(sid)
if err != nil {
panic("session/DB: error checking existence: " + err.Error())
}
return has
}

// Destroy deletes a session by session ID.
func (p *DBProvider) Destroy(sid string) error {
return models.DestroySession(sid)
}

// Regenerate regenerates a session store from old session ID to new one.
func (p *DBProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
s, err := models.RegenerateSession(oldsid, sid)
if err != nil {
return nil, err

}

var kv map[interface{}]interface{}
if len(s.Data) == 0 || s.Expiry.Add(p.maxLifetime) <= timeutil.TimeStampNow() {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob(s.Data)
if err != nil {
return nil, err
}
}

return NewDBStore(sid, kv), nil
}

// Count counts and returns number of sessions.
func (p *DBProvider) Count() int {
total, err := models.CountSessions()
if err != nil {
panic("session/DB: error counting records: " + err.Error())
}
return int(total)
}

// GC calls GC to clean expired sessions.
func (p *DBProvider) GC() {
if err := models.CleanupSessions(p.maxLifetime); err != nil {
log.Printf("session/DB: error garbage collecting: %v", err)
}
}

func init() {
session.Register("db", &DBProvider{})
}
2 changes: 2 additions & 0 deletions modules/session/virtual.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error {
o.provider = &session.FileProvider{}
case "redis":
o.provider = &RedisProvider{}
case "db":
o.provider = &DBProvider{}
case "mysql":
o.provider = &mysql.MysqlProvider{}
case "postgres":
Expand Down
2 changes: 1 addition & 1 deletion modules/setting/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ var (
func newSessionService() {
sec := Cfg.Section("session")
SessionConfig.Provider = sec.Key("PROVIDER").In("memory",
[]string{"memory", "file", "redis", "mysql", "postgres", "couchbase", "memcache"})
[]string{"memory", "file", "redis", "mysql", "postgres", "couchbase", "memcache", "db"})
SessionConfig.ProviderConfig = strings.Trim(sec.Key("PROVIDER_CONFIG").MustString(path.Join(AppDataPath, "sessions")), "\" ")
if SessionConfig.Provider == "file" && !filepath.IsAbs(SessionConfig.ProviderConfig) {
SessionConfig.ProviderConfig = path.Join(AppWorkPath, SessionConfig.ProviderConfig)
Expand Down