view pkg/auth/store.go @ 5591:0011f50cf216 surveysperbottleneckid

Removed no longer used alternative api for surveys/ endpoint. As bottlenecks in the summary for SR imports are now identified by their id and no longer by the (not guarantied to be unique!) name, there is no longer the need to request survey data by the name+date tuple (which isn't reliable anyway). So the workaround was now reversed.
author Sascha Wilde <wilde@sha-bang.de>
date Wed, 06 Apr 2022 13:30:29 +0200
parents 5f47eeea988d
children
line wrap: on
line source

// This is Free Software under GNU Affero General Public License v >= 3.0
// without warranty, see README.md and license for details.
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// License-Filename: LICENSES/AGPL-3.0.txt
//
// Copyright (C) 2018 by via donau
//   – Österreichische Wasserstraßen-Gesellschaft mbH
// Software engineering by Intevation GmbH
//
// Author(s):
//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>

package auth

import (
	"bytes"
	"errors"
	"time"

	bolt "go.etcd.io/bbolt"

	"gemma.intevation.de/gemma/pkg/config"
	"gemma.intevation.de/gemma/pkg/log"
)

// ErrNoSuchToken is returned if a given token does not
// exists th the session store.
var ErrNoSuchToken = errors.New("no such token")

// Sessions is the global connection pool.
var Sessions *SessionStore

// SessionStore encapsulates a set of currently active sessions.
type SessionStore struct {
	storage  *bolt.DB
	sessions map[string]*Session
	cmds     chan func()
}

var sessionsBucket = []byte("sessions")

// NewSessionStore creates a new session store.
// If the filename is empty the session are only hold in memory.
// If the filename is not empty the sessions are mirrored to
// a file with this name. Use the later option if you want
// a persistent session store.
func NewSessionStore(filename string) (*SessionStore, error) {

	ss := &SessionStore{
		sessions: make(map[string]*Session),
		cmds:     make(chan func()),
	}
	if err := ss.openStorage(filename); err != nil {
		return nil, err
	}
	go ss.run()
	return ss, nil
}

// openStorage opens a storage file.
func (ss *SessionStore) openStorage(filename string) error {

	// No file, nothing to restore/persist.
	if filename == "" {
		return nil
	}

	db, err := bolt.Open(filename, 0600, nil)
	if err != nil {
		return err
	}

	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 session Session
			if err := session.deserialize(bytes.NewReader(v)); err != nil {
				return err
			}
			ss.sessions[string(k)] = &session
		}

		return nil
	})

	if err != nil {
		db.Close()
		return err
	}

	ss.storage = db
	return nil
}

func (ss *SessionStore) run() {
	for {
		select {
		case cmd := <-ss.cmds:
			cmd()
		case <-time.After(time.Minute * 5):
			ss.cleanToken()
		}
	}
}

func (ss *SessionStore) cleanToken() {
	now := time.Now()
	for token, session := range ss.sessions {
		expires := time.Unix(session.ExpiresAt, 0)
		if expires.Before(now) {
			delete(ss.sessions, token)
			ss.remove(token)
		}
	}
}

func (ss *SessionStore) remove(token string) {
	if ss.storage == nil {
		return
	}
	err := ss.storage.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(sessionsBucket)
		return b.Delete([]byte(token))
	})
	if err != nil {
		log.Errorf("%v\n", err)
	}
}

// Delete removes a session identified by its token from the
// session store. Returns true if there was such s session.
func (ss *SessionStore) Delete(token string) bool {
	res := make(chan bool)
	ss.cmds <- func() {
		if _, found := ss.sessions[token]; !found {
			res <- false
			return
		}
		delete(ss.sessions, token)
		ss.remove(token)
		res <- true
	}
	return <-res
}

func (ss *SessionStore) store(token string, session *Session) {
	if ss.storage == nil {
		return
	}
	err := ss.storage.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(sessionsBucket)
		var buf bytes.Buffer
		if err := session.serialize(&buf); err != nil {
			return err
		}
		return b.Put([]byte(token), buf.Bytes())
	})
	if err != nil {
		log.Errorf("%v\n", err)
	}
}

// Add puts a session into the session store identified by
// a given token. An old session with the same key will
// be replaced.
func (ss *SessionStore) Add(token string, session *Session) {
	res := make(chan struct{})

	ss.cmds <- func() {
		defer close(res)
		s := ss.sessions[token]
		if s == nil {
			s = session
			ss.sessions[token] = session
		}
		s.touch()
		ss.store(token, s)
	}

	<-res
}

// Renew refreshes a session. It takes an old token to
// identify a session and returns a new token with the
// freshed up one.
func (ss *SessionStore) Renew(token string) (string, error) {

	type result struct {
		newToken string
		err      error
	}

	resCh := make(chan result)

	ss.cmds <- func() {
		session := ss.sessions[token]
		if session == nil {
			resCh <- result{err: ErrNoSuchToken}
		} else {
			delete(ss.sessions, token)
			ss.remove(token)
			newToken := generateSessionKey()
			// TODO: Ensure that this is not racy!
			session.ExpiresAt = time.Now().Add(config.SessionTimeout()).Unix()
			ss.sessions[newToken] = session
			ss.store(newToken, session)
			resCh <- result{newToken: newToken}
		}
	}

	r := <-resCh
	return r.newToken, r.err
}

// Session returns the session associated with given token.
// Returns nil if no matching session was found.
func (ss *SessionStore) Session(token string) *Session {
	res := make(chan *Session)
	ss.cmds <- func() {
		session := ss.sessions[token]
		if session == nil {
			res <- nil
		} else {
			session.touch()
			ss.store(token, session)
			res <- session
		}
	}
	return <-res
}

// Logout removes all sessions of a given user from the session store.
func (ss *SessionStore) Logout(user string) {
	ss.cmds <- func() {
		for token, session := range ss.sessions {
			if session.User == user {
				delete(ss.sessions, token)
				ss.remove(token)
			}
		}
	}
}

// Shutdown closes the session store.
// If using the persistent mode the backing session database is closed.
func (ss *SessionStore) Shutdown() error {
	if db := ss.storage; db != nil {
		log.Infof("shutdown persistent session store.")
		ss.storage = nil
		return db.Close()
	}
	log.Infof("shutdown in-memory session store.")
	return nil
}