// // 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 ( "context" "net/http" oidc "github.com/coreos/go-oidc" "golang.org/x/oauth2" ) type OIDCSession struct { State string Nonce string } func NewOIDCSession() (s *OIDCSession, err error) { s = &OIDCSession{} if s.State, err = generateRandomString(32); err != nil { return } if s.Nonce, err = generateRandomString(32); err != nil { return } return } // TODO: check concurrent usage of provider/verifier etc... type OIDCBackend struct { provider *oidc.Provider verifier *oidc.IDTokenVerifier oauth2Config oauth2.Config } func NewOIDCBackend(cfg *OIDCConfig) (b *OIDCBackend, err error) { // TODO: make ctx a parameter? ctx := context.Background() b = &OIDCBackend{} if b.provider, err = oidc.NewProvider(ctx, cfg.IssuerURL); err != nil { return } oidcConfig := &oidc.Config{ ClientID: cfg.ClientID, } b.verifier = b.provider.Verifier(oidcConfig) b.oauth2Config = oauth2.Config{ ClientID: cfg.ClientID, ClientSecret: cfg.ClientSecret, Endpoint: b.provider.Endpoint(), RedirectURL: cfg.CallbackURL, Scopes: []string{oidc.ScopeOpenID, "username", "aura_shows"}, } return } func (b *OIDCBackend) LoginHandler() http.Handler { return &oidcLoginHandler{backend: b} } func (b *OIDCBackend) CallbackHandler() http.Handler { return &oidcCallbackHandler{backend: b} } type oidcLoginHandler struct { backend *OIDCBackend } // TODO: error handling? func (h *oidcLoginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // TODO: check if we alreay have a session coockie! s, err := NewOIDCSession() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } // TODO: add session to session-manager and create session coockie http.Redirect(w, r, h.backend.oauth2Config.AuthCodeURL(s.State, oidc.Nonce(s.Nonce)), http.StatusFound) } type oidcCallbackHandler struct { backend *OIDCBackend } func (h *oidcCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // TODO: check if we have a session coockie! // TODO: get OIDCSession from session-manager, based on session coockie os := &OIDCSession{} if r.URL.Query().Get("state") != os.State { http.Error(w, "OIDC: state did not match", http.StatusBadRequest) return } oauth2Token, err := h.backend.oauth2Config.Exchange(r.Context(), r.URL.Query().Get("code")) if err != nil { http.Error(w, "OIDC: failed to exchange token: "+err.Error(), http.StatusInternalServerError) return } rawIDToken, ok := oauth2Token.Extra("id_token").(string) if !ok { http.Error(w, "OIDC: no id_token field in oauth2 token.", http.StatusInternalServerError) return } // Verify the ID Token signature and nonce. idToken, err := h.backend.verifier.Verify(r.Context(), rawIDToken) if err != nil { http.Error(w, "OIDC: failed to verify ID Token: "+err.Error(), http.StatusInternalServerError) return } if idToken.Nonce != os.Nonce { http.Error(w, "OIDC: invalid ID token nonce", http.StatusInternalServerError) return } // TODO: parse userInfo, populeate new session and update it inside the session-manager // userInfo, err := h.backend.provider.UserInfo(r.Context(), oauth2.StaticTokenSource(oauth2Token)) // if err != nil { // http.Error(w, "OIDC: failed to get userinfo: "+err.Error(), http.StatusInternalServerError) // return // } }