view pkg/auth/middleware.go @ 5736:55892008ec96 default tip

Fixed a bunch of corner cases in WG import.
author Sascha Wilde <wilde@sha-bang.de>
date Wed, 29 May 2024 19:02:42 +0200
parents 97430d442909
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 (
	"context"
	"net/http"
	"strings"
)

type contextType int

const (
	sessionKey contextType = iota
	tokenKey
)

// GetSession returns the session stored in the context of the request.
func GetSession(req *http.Request) (*Session, bool) {
	session, ok := req.Context().Value(sessionKey).(*Session)
	return session, ok
}

// GetToken returns the session token associated with given request.
func GetToken(req *http.Request) (string, bool) {
	token, ok := req.Context().Value(tokenKey).(string)
	return token, ok
}

// SessionMiddleware constructs a middleware to enforce the existence
// of the header X-Gemma-Auth in the incoming request and checks
// if a session is bound to it.
// Ihe the checks fail the constructed handler issues an http.StatusUnauthorized
// back to the invokation stacks and prevents the execution of the
// nested http.Handler next.
// Inside the http.Handler next calls to GetSession and GetToken are valid
// to fetch the respective information.
func SessionMiddleware(next http.Handler) http.Handler {

	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {

		auth := req.Header.Get("X-Gemma-Auth")

		token := strings.TrimSpace(auth)
		if token == "" {
			http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
			return
		}

		session := Sessions.Session(token)
		if session == nil {
			http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
			return
		}

		ctx := req.Context()
		ctx = context.WithValue(ctx, sessionKey, session)
		ctx = context.WithValue(ctx, tokenKey, token)
		req = req.WithContext(ctx)

		next.ServeHTTP(rw, req)
	})
}

// SessionChecker constructs a middleware to check invariants about a session
// before calling the nested http.Handler next.
// This is useful when creating specialized middleware e.g. to enforce
// a role system.
func SessionChecker(next http.Handler, check func(*Session) bool) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		if claims, ok := GetSession(req); !ok || !check(claims) {
			http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
			return
		}
		next.ServeHTTP(rw, req)
	})
}

// HasRole is a checker function fitting into SessionChecker to check
// if the user is logged in with at least one of list of given roles.
func HasRole(roles ...string) func(*Session) bool {
	return func(session *Session) bool {
		return session.Roles.HasAny(roles...)
	}
}

// EnsureRole is a macro function to stitch SessionChecker and HasRole together.
func EnsureRole(roles ...string) func(http.Handler) http.Handler {
	return func(handler http.Handler) http.Handler {
		return SessionMiddleware(SessionChecker(handler, HasRole(roles...)))
	}
}