Skip to content

Commit b33f7f7

Browse files
authored
Prevent creating empty sessions (#6677)
* Prevent creating empty sessions Signed-off-by: Andrew Thornton <art27@cantab.net> * Update modules/setting/session.go * Remove unnecessary option Signed-off-by: Andrew Thornton <art27@cantab.net> * Add destory to list of ignored misspellings * rename cookie.go -> virtual.go * Delete old file * Add test to ensure that sessions are not created without being logged in Signed-off-by: Andrew Thornton <art27@cantab.net> * fix tests Signed-off-by: Andrew Thornton <art27@cantab.net> * Update integrations/create_no_session_test.go
1 parent b74dc97 commit b33f7f7

File tree

4 files changed

+324
-1
lines changed

4 files changed

+324
-1
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ misspell-check:
154154
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
155155
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
156156
fi
157-
misspell -error -i unknwon $(GOFILES)
157+
misspell -error -i unknwon,destory $(GOFILES)
158158

159159
.PHONY: misspell
160160
misspell:
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright 2019 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package integrations
6+
7+
import (
8+
"encoding/json"
9+
"io/ioutil"
10+
"net/http"
11+
"net/http/httptest"
12+
"os"
13+
"path/filepath"
14+
"testing"
15+
16+
"code.gitea.io/gitea/modules/setting"
17+
"code.gitea.io/gitea/routers/routes"
18+
19+
"github.com/go-macaron/session"
20+
"github.com/stretchr/testify/assert"
21+
)
22+
23+
func getSessionID(t *testing.T, resp *httptest.ResponseRecorder) string {
24+
cookies := resp.Result().Cookies()
25+
found := false
26+
sessionID := ""
27+
for _, cookie := range cookies {
28+
if cookie.Name == setting.SessionConfig.CookieName {
29+
sessionID = cookie.Value
30+
found = true
31+
}
32+
}
33+
assert.True(t, found)
34+
assert.NotEmpty(t, sessionID)
35+
return sessionID
36+
}
37+
38+
func sessionFile(tmpDir, sessionID string) string {
39+
return filepath.Join(tmpDir, sessionID[0:1], sessionID[1:2], sessionID)
40+
}
41+
42+
func sessionFileExist(t *testing.T, tmpDir, sessionID string) bool {
43+
sessionFile := sessionFile(tmpDir, sessionID)
44+
_, err := os.Lstat(sessionFile)
45+
if err != nil {
46+
if os.IsNotExist(err) {
47+
return false
48+
}
49+
assert.NoError(t, err)
50+
}
51+
return true
52+
}
53+
54+
func TestSessionFileCreation(t *testing.T) {
55+
prepareTestEnv(t)
56+
57+
oldSessionConfig := setting.SessionConfig.ProviderConfig
58+
defer func() {
59+
setting.SessionConfig.ProviderConfig = oldSessionConfig
60+
mac = routes.NewMacaron()
61+
routes.RegisterRoutes(mac)
62+
}()
63+
64+
var config session.Options
65+
err := json.Unmarshal([]byte(oldSessionConfig), &config)
66+
assert.NoError(t, err)
67+
68+
config.Provider = "file"
69+
70+
// Now create a temporaryDirectory
71+
tmpDir, err := ioutil.TempDir("", "sessions")
72+
assert.NoError(t, err)
73+
defer func() {
74+
if _, err := os.Stat(tmpDir); !os.IsNotExist(err) {
75+
_ = os.RemoveAll(tmpDir)
76+
}
77+
}()
78+
config.ProviderConfig = tmpDir
79+
80+
newConfigBytes, err := json.Marshal(config)
81+
assert.NoError(t, err)
82+
83+
setting.SessionConfig.ProviderConfig = string(newConfigBytes)
84+
85+
mac = routes.NewMacaron()
86+
routes.RegisterRoutes(mac)
87+
88+
t.Run("NoSessionOnViewIssue", func(t *testing.T) {
89+
PrintCurrentTest(t)
90+
91+
req := NewRequest(t, "GET", "/user2/repo1/issues/1")
92+
resp := MakeRequest(t, req, http.StatusOK)
93+
sessionID := getSessionID(t, resp)
94+
95+
// We're not logged in so there should be no session
96+
assert.False(t, sessionFileExist(t, tmpDir, sessionID))
97+
})
98+
t.Run("CreateSessionOnLogin", func(t *testing.T) {
99+
PrintCurrentTest(t)
100+
101+
req := NewRequest(t, "GET", "/user/login")
102+
resp := MakeRequest(t, req, http.StatusOK)
103+
sessionID := getSessionID(t, resp)
104+
105+
// We're not logged in so there should be no session
106+
assert.False(t, sessionFileExist(t, tmpDir, sessionID))
107+
108+
doc := NewHTMLParser(t, resp.Body)
109+
req = NewRequestWithValues(t, "POST", "/user/login", map[string]string{
110+
"_csrf": doc.GetCSRF(),
111+
"user_name": "user2",
112+
"password": userPassword,
113+
})
114+
resp = MakeRequest(t, req, http.StatusFound)
115+
sessionID = getSessionID(t, resp)
116+
117+
assert.FileExists(t, sessionFile(tmpDir, sessionID))
118+
})
119+
}

modules/session/virtual.go

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
// Copyright 2019 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package session
6+
7+
import (
8+
"encoding/json"
9+
"fmt"
10+
"sync"
11+
12+
"github.com/go-macaron/session"
13+
couchbase "github.com/go-macaron/session/couchbase"
14+
memcache "github.com/go-macaron/session/memcache"
15+
mysql "github.com/go-macaron/session/mysql"
16+
nodb "github.com/go-macaron/session/nodb"
17+
postgres "github.com/go-macaron/session/postgres"
18+
redis "github.com/go-macaron/session/redis"
19+
)
20+
21+
// VirtualSessionProvider represents a shadowed session provider implementation.
22+
type VirtualSessionProvider struct {
23+
lock sync.RWMutex
24+
maxlifetime int64
25+
rootPath string
26+
provider session.Provider
27+
}
28+
29+
// Init initializes the cookie session provider with given root path.
30+
func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error {
31+
var opts session.Options
32+
if err := json.Unmarshal([]byte(config), &opts); err != nil {
33+
return err
34+
}
35+
// Note that these options are unprepared so we can't just use NewManager here.
36+
// Nor can we access the provider map in session.
37+
// So we will just have to do this by hand.
38+
// This is only slightly more wrong than modules/setting/session.go:23
39+
switch opts.Provider {
40+
case "memory":
41+
o.provider = &session.MemProvider{}
42+
case "file":
43+
o.provider = &session.FileProvider{}
44+
case "redis":
45+
o.provider = &redis.RedisProvider{}
46+
case "mysql":
47+
o.provider = &mysql.MysqlProvider{}
48+
case "postgres":
49+
o.provider = &postgres.PostgresProvider{}
50+
case "couchbase":
51+
o.provider = &couchbase.CouchbaseProvider{}
52+
case "memcache":
53+
o.provider = &memcache.MemcacheProvider{}
54+
case "nodb":
55+
o.provider = &nodb.NodbProvider{}
56+
default:
57+
return fmt.Errorf("VirtualSessionProvider: Unknown Provider: %s", opts.Provider)
58+
}
59+
return o.provider.Init(gclifetime, opts.ProviderConfig)
60+
}
61+
62+
// Read returns raw session store by session ID.
63+
func (o *VirtualSessionProvider) Read(sid string) (session.RawStore, error) {
64+
o.lock.RLock()
65+
defer o.lock.RUnlock()
66+
if o.provider.Exist(sid) {
67+
return o.provider.Read(sid)
68+
}
69+
kv := make(map[interface{}]interface{})
70+
kv["_old_uid"] = "0"
71+
return NewVirtualStore(o, sid, kv), nil
72+
}
73+
74+
// Exist returns true if session with given ID exists.
75+
func (o *VirtualSessionProvider) Exist(sid string) bool {
76+
return true
77+
}
78+
79+
// Destory deletes a session by session ID.
80+
func (o *VirtualSessionProvider) Destory(sid string) error {
81+
o.lock.Lock()
82+
defer o.lock.Unlock()
83+
return o.provider.Destory(sid)
84+
}
85+
86+
// Regenerate regenerates a session store from old session ID to new one.
87+
func (o *VirtualSessionProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
88+
o.lock.Lock()
89+
defer o.lock.Unlock()
90+
return o.provider.Regenerate(oldsid, sid)
91+
}
92+
93+
// Count counts and returns number of sessions.
94+
func (o *VirtualSessionProvider) Count() int {
95+
o.lock.RLock()
96+
defer o.lock.RUnlock()
97+
return o.provider.Count()
98+
}
99+
100+
// GC calls GC to clean expired sessions.
101+
func (o *VirtualSessionProvider) GC() {
102+
o.provider.GC()
103+
}
104+
105+
func init() {
106+
session.Register("VirtualSession", &VirtualSessionProvider{})
107+
}
108+
109+
// VirtualStore represents a virtual session store implementation.
110+
type VirtualStore struct {
111+
p *VirtualSessionProvider
112+
sid string
113+
lock sync.RWMutex
114+
data map[interface{}]interface{}
115+
}
116+
117+
// NewVirtualStore creates and returns a virtual session store.
118+
func NewVirtualStore(p *VirtualSessionProvider, sid string, kv map[interface{}]interface{}) *VirtualStore {
119+
return &VirtualStore{
120+
p: p,
121+
sid: sid,
122+
data: kv,
123+
}
124+
}
125+
126+
// Set sets value to given key in session.
127+
func (s *VirtualStore) Set(key, val interface{}) error {
128+
s.lock.Lock()
129+
defer s.lock.Unlock()
130+
131+
s.data[key] = val
132+
return nil
133+
}
134+
135+
// Get gets value by given key in session.
136+
func (s *VirtualStore) Get(key interface{}) interface{} {
137+
s.lock.RLock()
138+
defer s.lock.RUnlock()
139+
140+
return s.data[key]
141+
}
142+
143+
// Delete delete a key from session.
144+
func (s *VirtualStore) Delete(key interface{}) error {
145+
s.lock.Lock()
146+
defer s.lock.Unlock()
147+
148+
delete(s.data, key)
149+
return nil
150+
}
151+
152+
// ID returns current session ID.
153+
func (s *VirtualStore) ID() string {
154+
return s.sid
155+
}
156+
157+
// Release releases resource and save data to provider.
158+
func (s *VirtualStore) Release() error {
159+
s.lock.Lock()
160+
defer s.lock.Unlock()
161+
// Now need to lock the provider
162+
s.p.lock.Lock()
163+
defer s.p.lock.Unlock()
164+
if oldUID, ok := s.data["_old_uid"]; (ok && (oldUID != "0" || len(s.data) > 1)) || (!ok && len(s.data) > 0) {
165+
// Now ensure that we don't exist!
166+
realProvider := s.p.provider
167+
168+
if realProvider.Exist(s.sid) {
169+
// This is an error!
170+
return fmt.Errorf("new sid '%s' already exists", s.sid)
171+
}
172+
realStore, err := realProvider.Read(s.sid)
173+
if err != nil {
174+
return err
175+
}
176+
for key, value := range s.data {
177+
if err := realStore.Set(key, value); err != nil {
178+
return err
179+
}
180+
}
181+
return realStore.Release()
182+
}
183+
return nil
184+
}
185+
186+
// Flush deletes all session data.
187+
func (s *VirtualStore) Flush() error {
188+
s.lock.Lock()
189+
defer s.lock.Unlock()
190+
191+
s.data = make(map[interface{}]interface{})
192+
return nil
193+
}

modules/setting/session.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@
55
package setting
66

77
import (
8+
"encoding/json"
89
"path"
910
"path/filepath"
1011
"strings"
1112

1213
"code.gitea.io/gitea/modules/log"
14+
// This ensures that VirtualSessionProvider is available
15+
_ "code.gitea.io/gitea/modules/session"
16+
1317
"github.com/go-macaron/session"
1418
)
1519

@@ -31,5 +35,12 @@ func newSessionService() {
3135
SessionConfig.Gclifetime = Cfg.Section("session").Key("GC_INTERVAL_TIME").MustInt64(86400)
3236
SessionConfig.Maxlifetime = Cfg.Section("session").Key("SESSION_LIFE_TIME").MustInt64(86400)
3337

38+
shadowConfig, err := json.Marshal(SessionConfig)
39+
if err != nil {
40+
log.Fatal("Can't shadow session config: %v", err)
41+
}
42+
SessionConfig.ProviderConfig = string(shadowConfig)
43+
SessionConfig.Provider = "VirtualSession"
44+
3445
log.Info("Session Service Enabled")
3546
}

0 commit comments

Comments
 (0)