view pkg/auth/session.go @ 1133:dd4071019676

Delete contour lines with their sounding result Contour lines are no independent data sets and thus can safely be deleted with sounding results. In passing, name the primary key as such.
author Tom Gottfried <tom@intevation.de>
date Wed, 07 Nov 2018 18:13:02 +0100
parents a244b18cb916
children 176c42053562
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 (
	"encoding/base64"
	"errors"
	"io"
	"sync"
	"time"

	"gemma.intevation.de/gemma/pkg/common"
	"gemma.intevation.de/gemma/pkg/misc"
)

type Roles []string

type Session struct {
	ExpiresAt int64  `json:"expires"`
	User      string `json:"user"`
	Roles     Roles  `json:"roles"`

	// private fields for managing expiration.
	access time.Time
	mu     sync.Mutex
}

func (r Roles) Has(role string) bool {
	for _, x := range r {
		if x == role {
			return true
		}
	}
	return false
}

func (r Roles) HasAny(roles ...string) bool {
	for _, y := range roles {
		if r.Has(y) {
			return true
		}
	}
	return false
}

const (
	sessionKeyLength = 20
	maxTokenValid    = time.Hour * 3
)

func NewSession(user, password string, roles Roles) *Session {

	// Create the Claims
	return &Session{
		ExpiresAt: time.Now().Add(maxTokenValid).Unix(),
		User:      user,
		Roles:     roles,
	}
}

func (s *Session) serialize(w io.Writer) error {

	access, err := s.last().MarshalText()
	if err != nil {
		return err
	}

	wr := misc.BinWriter{w, nil}
	wr.WriteBin(s.ExpiresAt)
	wr.WriteString(s.User)
	wr.WriteBin(uint32(len(s.Roles)))
	for _, role := range s.Roles {
		wr.WriteString(role)
	}
	wr.WriteBin(uint32(len(access)))
	wr.WriteBin(access)
	return wr.Err
}

func (s *Session) deserialize(r io.Reader) error {

	var session Session

	var n uint32
	rd := misc.BinReader{r, nil}
	rd.ReadBin(&session.ExpiresAt)
	rd.ReadString(&session.User)
	rd.ReadBin(&n)
	session.Roles = make(Roles, n)

	for i := uint32(0); n > 0 && i < n; i++ {
		rd.ReadString(&session.Roles[i])
	}

	if rd.Err != nil {
		return rd.Err
	}

	var l uint32
	rd.ReadBin(&l)
	access := make([]byte, l)
	rd.ReadBin(access)

	if rd.Err != nil {
		return rd.Err
	}

	var t time.Time
	if err := t.UnmarshalText(access); err != nil {
		return err
	}

	session.access = t

	*s = session

	return nil
}

func (c *Session) touch() {
	c.mu.Lock()
	c.access = time.Now()
	c.mu.Unlock()
}

func (c *Session) last() time.Time {
	c.mu.Lock()
	access := c.access
	c.mu.Unlock()
	return access
}

func GenerateSessionKey() string {
	return base64.URLEncoding.EncodeToString(
		common.GenerateRandomKey(sessionKeyLength))
}

var ErrInvalidRole = errors.New("Invalid role")

func GenerateSession(user, password string) (string, *Session, error) {
	roles, err := AllOtherRoles(user, password)
	if err != nil {
		return "", nil, err
	}
	if !roles.HasAny("sys_admin", "waterway_admin", "waterway_user") {
		return "", nil, ErrInvalidRole
	}
	token := GenerateSessionKey()
	session := NewSession(user, password, roles)
	Sessions.Add(token, session)
	return token, session, nil
}