changeset 193:1585c334e8a7

More on persisting sessions.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Fri, 20 Jul 2018 18:32:11 +0200
parents 3457a60fb12d
children cf5dcc1761df
files auth/connection.go auth/persistent.go auth/session.go
diffstat 3 files changed, 152 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/auth/connection.go	Fri Jul 20 17:11:57 2018 +0200
+++ b/auth/connection.go	Fri Jul 20 18:32:11 2018 +0200
@@ -1,8 +1,11 @@
 package auth
 
 import (
+	"bytes"
 	"database/sql"
+	"encoding/binary"
 	"errors"
+	"io"
 	"log"
 	"sync"
 	"time"
@@ -47,6 +50,38 @@
 	mu sync.Mutex
 }
 
+func (c *Connection) serialize() []byte {
+	var buf bytes.Buffer
+	c.session.serialize(&buf)
+	access, _ := c.last().MarshalText()
+	binary.Write(&buf, binary.BigEndian, string(access))
+	return buf.Bytes()
+}
+
+func (c *Connection) unserialize(r io.Reader) error {
+	session := new(Session)
+	if err := session.unserialize(r); err != nil {
+		return err
+	}
+
+	var access string
+	if err := binary.Read(r, binary.BigEndian, &access); err != nil {
+		return err
+	}
+
+	var t time.Time
+	if err := t.UnmarshalText([]byte(access)); err != nil {
+		return err
+	}
+
+	*c = Connection{
+		session: session,
+		access:  t,
+	}
+
+	return nil
+}
+
 func (c *Connection) set(session *Session) {
 	c.session = session
 	c.touch()
--- a/auth/persistent.go	Fri Jul 20 17:11:57 2018 +0200
+++ b/auth/persistent.go	Fri Jul 20 18:32:11 2018 +0200
@@ -1,26 +1,105 @@
 package auth
 
 import (
+	"bytes"
 	"database/sql"
 	"log"
+	"time"
+
+	bolt "github.com/coreos/bbolt"
 )
 
 type PersistentConnectionPool struct {
-	filename string
+	db    *bolt.DB
+	conns map[string]*Connection
+	cmds  chan func(*PersistentConnectionPool)
 }
 
+var sessionsBucket = []byte("sessions")
+
 func NewPersistentConnectionPool(filename string) (*PersistentConnectionPool, error) {
 
-	log.Println("NewInMemoryConnectionPool: Not implemented, yet.")
+	db, err := bolt.Open(filename, 0600, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	conns := make(map[string]*Connection)
+	err = db.Update(func(tx *bolt.Tx) error {
+		b, err := tx.CreateBucketIfNotExists(sessionsBucket)
+		if err != nil {
+			return err
+		}
+
+		// pre-load sessions
+		c := b.Cursor()
+
+		for k, v := c.First(); k != nil; k, v = c.Next() {
+			var conn Connection
+			if err := conn.unserialize(bytes.NewReader(v)); err != nil {
+				return err
+			}
+			conns[string(k)] = &conn
+		}
+
+		return nil
+	})
+
+	if err != nil {
+		db.Close()
+		return nil, err
+	}
+
 	pcp := &PersistentConnectionPool{
-		filename: filename,
+		db:    db,
+		conns: conns,
+		cmds:  make(chan func(*PersistentConnectionPool)),
 	}
+	go pcp.run()
 	return pcp, nil
 }
 
+func (pcp *PersistentConnectionPool) run() {
+	for {
+		select {
+		case cmd := <-pcp.cmds:
+			cmd(pcp)
+		case <-time.After(time.Minute):
+			pcp.cleanDB()
+		case <-time.After(time.Minute * 5):
+			pcp.cleanToken()
+		}
+	}
+}
+
+func (pcp *PersistentConnectionPool) cleanDB() {
+	log.Println("cleanDB: Not implemented, yet.")
+}
+
+func (pcp *PersistentConnectionPool) cleanToken() {
+	log.Println("cleanToken: Not implemented, yet.")
+}
+
 func (pcp *PersistentConnectionPool) Delete(token string) bool {
-	log.Println("Delete: Not implemented, yet.")
-	return false
+	res := make(chan bool)
+	pcp.cmds <- func(pcp *PersistentConnectionPool) {
+		conn, found := pcp.conns[token]
+		if !found {
+			res <- false
+			return
+		}
+		conn.close()
+		delete(pcp.conns, token)
+		err := pcp.db.Update(func(tx *bolt.Tx) error {
+			b := tx.Bucket(sessionsBucket)
+			return b.Delete([]byte(token))
+		})
+		if err != nil {
+			log.Printf("error: %v\n", err)
+		}
+		res <- true
+	}
+	return <-res
 }
 
 func (pcp *PersistentConnectionPool) Add(token string, session *Session) *Connection {
@@ -44,6 +123,10 @@
 }
 
 func (pcp *PersistentConnectionPool) Shutdown() error {
-	log.Println("Shutdown: Not implemented, yet.")
+	log.Println("info: shutdown persistent connection pool.")
+	if db := pcp.db; db != nil {
+		pcp.db = nil
+		return db.Close()
+	}
 	return nil
 }
--- a/auth/session.go	Fri Jul 20 17:11:57 2018 +0200
+++ b/auth/session.go	Fri Jul 20 18:32:11 2018 +0200
@@ -3,6 +3,7 @@
 import (
 	"crypto/rand"
 	"encoding/base64"
+	"encoding/binary"
 	"io"
 	"time"
 )
@@ -30,6 +31,33 @@
 	}
 }
 
+func (s *Session) serialize(w io.Writer) {
+	binary.Write(w, binary.BigEndian, s.ExpiresAt)
+	binary.Write(w, binary.BigEndian, s.User)
+	binary.Write(w, binary.BigEndian, s.Password)
+	binary.Write(w, binary.BigEndian, uint32(len(s.Roles)))
+	for _, role := range s.Roles {
+		binary.Write(w, binary.BigEndian, role)
+	}
+}
+
+func (s *Session) unserialize(r io.Reader) error {
+	var x Session
+	binary.Read(r, binary.BigEndian, &x.ExpiresAt)
+	binary.Read(r, binary.BigEndian, &x.User)
+	binary.Read(r, binary.BigEndian, &x.Password)
+	var n uint32
+	binary.Read(r, binary.BigEndian, &n)
+	x.Roles = make([]string, n)
+	for i := uint32(0); n > 0 && i < n; i++ {
+		if err := binary.Read(r, binary.BigEndian, &x.Roles[i]); err != nil {
+			return err
+		}
+	}
+	*s = x
+	return nil
+}
+
 func GenerateSessionKey() string {
 	return base64.URLEncoding.EncodeToString(GenerateRandomKey(sessionKeyLength))
 }