// // 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)) }) }