mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Backport #6677 Signed-off-by: Andrew Thornton <art27@cantab.net>
This commit is contained in:
		
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							@@ -149,7 +149,7 @@ misspell-check:
 | 
			
		||||
	@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
			
		||||
		$(GO) get -u github.com/client9/misspell/cmd/misspell; \
 | 
			
		||||
	fi
 | 
			
		||||
	misspell -error -i unknwon $(GOFILES)
 | 
			
		||||
	misspell -error -i unknwon,destory $(GOFILES)
 | 
			
		||||
 | 
			
		||||
.PHONY: misspell
 | 
			
		||||
misspell:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										115
									
								
								integrations/create_no_session_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								integrations/create_no_session_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
			
		||||
// Copyright 2019 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 integrations
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/routers/routes"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-macaron/session"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func getSessionID(t *testing.T, resp *httptest.ResponseRecorder) string {
 | 
			
		||||
	cookies := resp.Result().Cookies()
 | 
			
		||||
	found := false
 | 
			
		||||
	sessionID := ""
 | 
			
		||||
	for _, cookie := range cookies {
 | 
			
		||||
		if cookie.Name == setting.SessionConfig.CookieName {
 | 
			
		||||
			sessionID = cookie.Value
 | 
			
		||||
			found = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	assert.True(t, found)
 | 
			
		||||
	assert.NotEmpty(t, sessionID)
 | 
			
		||||
	return sessionID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sessionFile(tmpDir, sessionID string) string {
 | 
			
		||||
	return filepath.Join(tmpDir, sessionID[0:1], sessionID[1:2], sessionID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sessionFileExist(t *testing.T, tmpDir, sessionID string) bool {
 | 
			
		||||
	sessionFile := sessionFile(tmpDir, sessionID)
 | 
			
		||||
	_, err := os.Lstat(sessionFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if os.IsNotExist(err) {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSessionFileCreation(t *testing.T) {
 | 
			
		||||
	prepareTestEnv(t)
 | 
			
		||||
 | 
			
		||||
	oldSessionConfig := setting.SessionConfig.ProviderConfig
 | 
			
		||||
	defer func() {
 | 
			
		||||
		setting.SessionConfig.ProviderConfig = oldSessionConfig
 | 
			
		||||
		mac = routes.NewMacaron()
 | 
			
		||||
		routes.RegisterRoutes(mac)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	var config session.Options
 | 
			
		||||
	err := json.Unmarshal([]byte(oldSessionConfig), &config)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	config.Provider = "file"
 | 
			
		||||
 | 
			
		||||
	// Now create a temporaryDirectory
 | 
			
		||||
	tmpDir, err := ioutil.TempDir("", "sessions")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if _, err := os.Stat(tmpDir); !os.IsNotExist(err) {
 | 
			
		||||
			_ = os.RemoveAll(tmpDir)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	config.ProviderConfig = tmpDir
 | 
			
		||||
 | 
			
		||||
	newConfigBytes, err := json.Marshal(config)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	setting.SessionConfig.ProviderConfig = string(newConfigBytes)
 | 
			
		||||
 | 
			
		||||
	mac = routes.NewMacaron()
 | 
			
		||||
	routes.RegisterRoutes(mac)
 | 
			
		||||
 | 
			
		||||
	t.Run("NoSessionOnViewIssue", func(t *testing.T) {
 | 
			
		||||
		req := NewRequest(t, "GET", "/user2/repo1/issues/1")
 | 
			
		||||
		resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
		sessionID := getSessionID(t, resp)
 | 
			
		||||
 | 
			
		||||
		// We're not logged in so there should be no session
 | 
			
		||||
		assert.False(t, sessionFileExist(t, tmpDir, sessionID))
 | 
			
		||||
	})
 | 
			
		||||
	t.Run("CreateSessionOnLogin", func(t *testing.T) {
 | 
			
		||||
		req := NewRequest(t, "GET", "/user/login")
 | 
			
		||||
		resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
		sessionID := getSessionID(t, resp)
 | 
			
		||||
 | 
			
		||||
		// We're not logged in so there should be no session
 | 
			
		||||
		assert.False(t, sessionFileExist(t, tmpDir, sessionID))
 | 
			
		||||
 | 
			
		||||
		doc := NewHTMLParser(t, resp.Body)
 | 
			
		||||
		req = NewRequestWithValues(t, "POST", "/user/login", map[string]string{
 | 
			
		||||
			"_csrf":     doc.GetCSRF(),
 | 
			
		||||
			"user_name": "user2",
 | 
			
		||||
			"password":  userPassword,
 | 
			
		||||
		})
 | 
			
		||||
		resp = MakeRequest(t, req, http.StatusFound)
 | 
			
		||||
		sessionID = getSessionID(t, resp)
 | 
			
		||||
 | 
			
		||||
		assert.FileExists(t, sessionFile(tmpDir, sessionID))
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										193
									
								
								modules/session/virtual.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								modules/session/virtual.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,193 @@
 | 
			
		||||
// Copyright 2019 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 (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-macaron/session"
 | 
			
		||||
	couchbase "github.com/go-macaron/session/couchbase"
 | 
			
		||||
	memcache "github.com/go-macaron/session/memcache"
 | 
			
		||||
	mysql "github.com/go-macaron/session/mysql"
 | 
			
		||||
	nodb "github.com/go-macaron/session/nodb"
 | 
			
		||||
	postgres "github.com/go-macaron/session/postgres"
 | 
			
		||||
	redis "github.com/go-macaron/session/redis"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// VirtualSessionProvider represents a shadowed session provider implementation.
 | 
			
		||||
type VirtualSessionProvider struct {
 | 
			
		||||
	lock        sync.RWMutex
 | 
			
		||||
	maxlifetime int64
 | 
			
		||||
	rootPath    string
 | 
			
		||||
	provider    session.Provider
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Init initializes the cookie session provider with given root path.
 | 
			
		||||
func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error {
 | 
			
		||||
	var opts session.Options
 | 
			
		||||
	if err := json.Unmarshal([]byte(config), &opts); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	// Note that these options are unprepared so we can't just use NewManager here.
 | 
			
		||||
	// Nor can we access the provider map in session.
 | 
			
		||||
	// So we will just have to do this by hand.
 | 
			
		||||
	// This is only slightly more wrong than modules/setting/session.go:23
 | 
			
		||||
	switch opts.Provider {
 | 
			
		||||
	case "memory":
 | 
			
		||||
		o.provider = &session.MemProvider{}
 | 
			
		||||
	case "file":
 | 
			
		||||
		o.provider = &session.FileProvider{}
 | 
			
		||||
	case "redis":
 | 
			
		||||
		o.provider = &redis.RedisProvider{}
 | 
			
		||||
	case "mysql":
 | 
			
		||||
		o.provider = &mysql.MysqlProvider{}
 | 
			
		||||
	case "postgres":
 | 
			
		||||
		o.provider = &postgres.PostgresProvider{}
 | 
			
		||||
	case "couchbase":
 | 
			
		||||
		o.provider = &couchbase.CouchbaseProvider{}
 | 
			
		||||
	case "memcache":
 | 
			
		||||
		o.provider = &memcache.MemcacheProvider{}
 | 
			
		||||
	case "nodb":
 | 
			
		||||
		o.provider = &nodb.NodbProvider{}
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("VirtualSessionProvider: Unknown Provider: %s", opts.Provider)
 | 
			
		||||
	}
 | 
			
		||||
	return o.provider.Init(gclifetime, opts.ProviderConfig)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Read returns raw session store by session ID.
 | 
			
		||||
func (o *VirtualSessionProvider) Read(sid string) (session.RawStore, error) {
 | 
			
		||||
	o.lock.RLock()
 | 
			
		||||
	defer o.lock.RUnlock()
 | 
			
		||||
	if o.provider.Exist(sid) {
 | 
			
		||||
		return o.provider.Read(sid)
 | 
			
		||||
	}
 | 
			
		||||
	kv := make(map[interface{}]interface{})
 | 
			
		||||
	kv["_old_uid"] = "0"
 | 
			
		||||
	return NewVirtualStore(o, sid, kv), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Exist returns true if session with given ID exists.
 | 
			
		||||
func (o *VirtualSessionProvider) Exist(sid string) bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Destory deletes a session by session ID.
 | 
			
		||||
func (o *VirtualSessionProvider) Destory(sid string) error {
 | 
			
		||||
	o.lock.Lock()
 | 
			
		||||
	defer o.lock.Unlock()
 | 
			
		||||
	return o.provider.Destory(sid)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Regenerate regenerates a session store from old session ID to new one.
 | 
			
		||||
func (o *VirtualSessionProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
 | 
			
		||||
	o.lock.Lock()
 | 
			
		||||
	defer o.lock.Unlock()
 | 
			
		||||
	return o.provider.Regenerate(oldsid, sid)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Count counts and returns number of sessions.
 | 
			
		||||
func (o *VirtualSessionProvider) Count() int {
 | 
			
		||||
	o.lock.RLock()
 | 
			
		||||
	defer o.lock.RUnlock()
 | 
			
		||||
	return o.provider.Count()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GC calls GC to clean expired sessions.
 | 
			
		||||
func (o *VirtualSessionProvider) GC() {
 | 
			
		||||
	o.provider.GC()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	session.Register("VirtualSession", &VirtualSessionProvider{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VirtualStore represents a virtual session store implementation.
 | 
			
		||||
type VirtualStore struct {
 | 
			
		||||
	p    *VirtualSessionProvider
 | 
			
		||||
	sid  string
 | 
			
		||||
	lock sync.RWMutex
 | 
			
		||||
	data map[interface{}]interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewVirtualStore creates and returns a virtual session store.
 | 
			
		||||
func NewVirtualStore(p *VirtualSessionProvider, sid string, kv map[interface{}]interface{}) *VirtualStore {
 | 
			
		||||
	return &VirtualStore{
 | 
			
		||||
		p:    p,
 | 
			
		||||
		sid:  sid,
 | 
			
		||||
		data: kv,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Set sets value to given key in session.
 | 
			
		||||
func (s *VirtualStore) 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 *VirtualStore) Get(key interface{}) interface{} {
 | 
			
		||||
	s.lock.RLock()
 | 
			
		||||
	defer s.lock.RUnlock()
 | 
			
		||||
 | 
			
		||||
	return s.data[key]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Delete delete a key from session.
 | 
			
		||||
func (s *VirtualStore) Delete(key interface{}) error {
 | 
			
		||||
	s.lock.Lock()
 | 
			
		||||
	defer s.lock.Unlock()
 | 
			
		||||
 | 
			
		||||
	delete(s.data, key)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ID returns current session ID.
 | 
			
		||||
func (s *VirtualStore) ID() string {
 | 
			
		||||
	return s.sid
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Release releases resource and save data to provider.
 | 
			
		||||
func (s *VirtualStore) Release() error {
 | 
			
		||||
	s.lock.Lock()
 | 
			
		||||
	defer s.lock.Unlock()
 | 
			
		||||
	// Now need to lock the provider
 | 
			
		||||
	s.p.lock.Lock()
 | 
			
		||||
	defer s.p.lock.Unlock()
 | 
			
		||||
	if oldUID, ok := s.data["_old_uid"]; (ok && (oldUID != "0" || len(s.data) > 1)) || (!ok && len(s.data) > 0) {
 | 
			
		||||
		// Now ensure that we don't exist!
 | 
			
		||||
		realProvider := s.p.provider
 | 
			
		||||
 | 
			
		||||
		if realProvider.Exist(s.sid) {
 | 
			
		||||
			// This is an error!
 | 
			
		||||
			return fmt.Errorf("new sid '%s' already exists", s.sid)
 | 
			
		||||
		}
 | 
			
		||||
		realStore, err := realProvider.Read(s.sid)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		for key, value := range s.data {
 | 
			
		||||
			if err := realStore.Set(key, value); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return realStore.Release()
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Flush deletes all session data.
 | 
			
		||||
func (s *VirtualStore) Flush() error {
 | 
			
		||||
	s.lock.Lock()
 | 
			
		||||
	defer s.lock.Unlock()
 | 
			
		||||
 | 
			
		||||
	s.data = make(map[interface{}]interface{})
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -5,11 +5,15 @@
 | 
			
		||||
package setting
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"path"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	// This ensures that VirtualSessionProvider is available
 | 
			
		||||
	_ "code.gitea.io/gitea/modules/session"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-macaron/session"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -31,5 +35,12 @@ func newSessionService() {
 | 
			
		||||
	SessionConfig.Gclifetime = Cfg.Section("session").Key("GC_INTERVAL_TIME").MustInt64(86400)
 | 
			
		||||
	SessionConfig.Maxlifetime = Cfg.Section("session").Key("SESSION_LIFE_TIME").MustInt64(86400)
 | 
			
		||||
 | 
			
		||||
	shadowConfig, err := json.Marshal(SessionConfig)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(4, "Can't shadow session config: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	SessionConfig.ProviderConfig = string(shadowConfig)
 | 
			
		||||
	SessionConfig.Provider = "VirtualSession"
 | 
			
		||||
 | 
			
		||||
	log.Info("Session Service Enabled")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user