Skip to content
Snippets Groups Projects
auth.go 4.54 KiB
//
//  tank
//
//  Import and Playlist Daemon for autoradio project
//
//
//  Copyright (C) 2017-2019 Christian Pointner <equinox@helsinki.at>
//
//  This file is part of tank.
//
//  tank is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  any later version.
//
//  tank is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with tank. If not, see <http://www.gnu.org/licenses/>.
//

package auth

import (
	"encoding/json"
	"net/http"
	"strings"

	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
)

var auth = &Auth{}

type Auth struct {
	sessions *SessionManager
	oidc     *OIDCBackend
}

func Init(c *Config) (err error) {
	if c == nil {
		// authentication is disabled
		return
	}

	if auth.sessions, err = NewSessionManager(c.Sessions); err != nil {
		return
	}

	if c.OIDC != nil {
		if auth.oidc, err = NewOIDCBackend(c.OIDC); err != nil {
			return
		}
	}
	return
}

func newSession() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		request := &NewSessionRequest{}
		err := json.NewDecoder(r.Body).Decode(request)
		if err != nil {
			sendHTTPErrorResponse(w, http.StatusBadRequest, "Error decoding request: "+err.Error())
			return
		}

		response := &NewSessionResponse{}
		switch strings.ToLower(request.Backend) {
		case "oidc":
			if auth.oidc == nil {
				sendHTTPErrorResponse(w, http.StatusBadRequest, "OIDC authentication is not configured")
				return
			}
			s, err := NewOIDCSession()
			if err != nil {
				sendHTTPErrorResponse(w, http.StatusBadRequest, "Error creating session: "+err.Error())
				return
			}
			response.Session = s
			response.Token = s.getToken()
		default:
			sendHTTPErrorResponse(w, http.StatusBadRequest, "invalid authentication backend: "+request.Backend)
			return
		}
		sendHTTPResponse(w, http.StatusOK, response)
	})
}

func getSession() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		s := getSessionFromBearerToken(r)
		if s == nil {
			sendHTTPInvalidSessionResponse(w)
			return
		}

		if r.URL.Query().Get("wait-for-login") == "" {
			sendHTTPResponse(w, http.StatusOK, s)
			return
		}

		for {
			s, sub := auth.sessions.getAndSubscribe(s.ID)
			if s == nil {
				sendHTTPErrorResponse(w, http.StatusNotFound, "this session does not exist")
				return
			}
			st := s.State()
			if st == SessionStateLoggedIn || st == SessionStateLoginFailed {
				sendHTTPResponse(w, http.StatusOK, s)
				return
			}
			<-sub
		}
	})
}

func deleteSession() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		s := getSessionFromBearerToken(r)
		if s == nil {
			sendHTTPInvalidSessionResponse(w)
			return
		}
		auth.sessions.remove(s.ID)
		sendHTTPResponse(w, http.StatusOK, "you are now logged out")
	})
}

func listBackends(w http.ResponseWriter, r *http.Request) {
	backends := []AuthBackendInfo{}
	if auth.oidc != nil {
		backend := AuthBackendInfo{Name: "oidc"}
		backend.Description = auth.oidc.String()
		backends = append(backends, backend)
	}
	sendHTTPResponse(w, http.StatusOK, backends)
}

func disabled(w http.ResponseWriter, r *http.Request) {
	sendHTTPErrorResponse(w, http.StatusBadRequest, "authentication is disabled")
}

func InstallHTTPHandler(r *mux.Router) {
	if auth.sessions == nil {
		r.PathPrefix("/").HandlerFunc(disabled)
		return
	}

	sessionHandler := make(handlers.MethodHandler)
	sessionHandler[http.MethodPost] = newSession()
	sessionHandler[http.MethodGet] = getSession()
	sessionHandler[http.MethodDelete] = deleteSession()
	r.Handle("/session", sessionHandler)

	r.HandleFunc("/backends", listBackends)
	if auth.oidc != nil {
		r.Handle("/oidc/login", auth.oidc.LoginHandler())
		r.Handle("/oidc/callback", auth.oidc.CallbackHandler())
	}
}

func Middleware(next http.Handler) http.Handler {
	if auth.sessions == nil {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			next.ServeHTTP(w, attachSessionToRequest(r, anonAllowAll))
		})
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		s := getSessionFromBearerToken(r)
		if s == nil || s.State() != SessionStateLoggedIn {
			sendHTTPInvalidSessionResponse(w)
			return
		}
		next.ServeHTTP(w, attachSessionToRequest(r, s))
	})
}