sessions.go 3.06 KB
Newer Older
1
2
3
4
5
6
package backend

import (
	"math/big"
	"math/rand"
	"sync"
Sietse Ringers's avatar
Sietse Ringers committed
7
	"time"
8
9
10
11
12
13
14
15

	"github.com/go-errors/errors"
	"github.com/mhe/gabi"
	"github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/irmaserver"
)

type session struct {
Sietse Ringers's avatar
Sietse Ringers committed
16
17
	sync.Mutex

18
19
20
21
22
23
	action  irma.Action
	token   string
	version *irma.ProtocolVersion
	request irma.SessionRequest
	status  irmaserver.Status

24
25
26
	lastActive time.Time
	returned   bool
	result     *irmaserver.SessionResult
27
28
29
30
31
32
33

	kssProofs map[irma.SchemeManagerIdentifier]*gabi.ProofP
}

type sessionStore interface {
	get(token string) *session
	add(token string, session *session)
Sietse Ringers's avatar
Sietse Ringers committed
34
	deleteExpired()
35
36
37
38
39
40
41
}

type memorySessionStore struct {
	sync.RWMutex
	m map[string]*session
}

Sietse Ringers's avatar
Sietse Ringers committed
42
43
44
45
46
const (
	maxSessionLifetime = 5 * time.Minute  // After this a session is cancelled
	expiryTicker       = 10 * time.Second // Every so often we check if any session has expired
)

47
48
49
50
51
52
53
54
55
56
57
const sessionChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

var (
	minProtocolVersion = irma.NewVersion(2, 4)
	maxProtocolVersion = irma.NewVersion(2, 4)

	sessions sessionStore = &memorySessionStore{
		m: make(map[string]*session),
	}
)

Sietse Ringers's avatar
Sietse Ringers committed
58
59
60
61
func init() {
	go sessions.deleteExpired()
}

62
63
64
65
66
67
68
69
70
71
72
73
func (s *memorySessionStore) get(token string) *session {
	s.RLock()
	defer s.RUnlock()
	return s.m[token]
}

func (s *memorySessionStore) add(token string, session *session) {
	s.Lock()
	defer s.Unlock()
	s.m[token] = session
}

Sietse Ringers's avatar
Sietse Ringers committed
74
75
76
77
78
79
func (s memorySessionStore) deleteExpired() {
	// First check which sessions have expired
	// We don't need a write lock for this yet, so postpone that for actual deleting
	s.RLock()
	expired := make([]string, 0, len(s.m))
	for token, session := range s.m {
80
		if session.lastActive.Add(5 * time.Minute).Before(time.Now()) {
Sietse Ringers's avatar
Sietse Ringers committed
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
			conf.Logger.Infof("Session %s expired, deleting", token)
			expired = append(expired, token)
		}
	}
	s.RUnlock()

	// Using a write lock, delete the expired sessions
	s.Lock()
	for _, token := range expired {
		delete(s.m, token)
	}
	s.Unlock()

	// Schedule next run
	time.AfterFunc(expiryTicker, func() {
		s.deleteExpired()
	})
}

100
101
102
var one *big.Int = big.NewInt(1)

func newSession(action irma.Action, request irma.SessionRequest) *session {
103
	token := newSessionToken()
104
	s := &session{
105
106
107
108
109
110
111
112
		action:     action,
		request:    request,
		status:     irmaserver.StatusInitialized,
		lastActive: time.Now(),
		token:      token,
		result: &irmaserver.SessionResult{
			Token: token,
		},
113
114
115
116
	}
	nonce, _ := gabi.RandomBigInt(gabi.DefaultSystemParameters[2048].Lstatzk)
	request.SetNonce(nonce)
	request.SetContext(one)
117
	sessions.add(token, s)
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
	return s
}

func newSessionToken() string {
	b := make([]byte, 20)
	for i := range b {
		b[i] = sessionChars[rand.Int63()%int64(len(sessionChars))]
	}
	return string(b)
}

func chooseProtocolVersion(min, max *irma.ProtocolVersion) (*irma.ProtocolVersion, error) {
	if min.AboveVersion(minProtocolVersion) || max.BelowVersion(min) {
		return nil, errors.Errorf("Protocol version negotiation failed, min=%s max=%s", min.String(), max.String())
	}
	if max.AboveVersion(maxProtocolVersion) {
		return maxProtocolVersion, nil
	} else {
		return max, nil
	}
}