Skip to content
Snippets Groups Projects
Commit bc46ae3e authored by Christian Pointner's avatar Christian Pointner
Browse files

some more refactoring of auth session management

parent 557d937f
No related branches found
No related tags found
No related merge requests found
......@@ -27,6 +27,7 @@ package auth
import (
"encoding/json"
"net/http"
"strings"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
......@@ -61,32 +62,55 @@ func Init(c *Config) (err error) {
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
}
if response.SessionID, err = NewOIDCSession(); err != nil {
sendHTTPErrorResponse(w, http.StatusBadRequest, "Error creating session: "+err.Error())
return
}
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 || s.Username == "" {
sendHTTPResponseInvalidSession(w)
sID := mux.Vars(r)["session-id"]
s := auth.sessions.get(sID)
if s == nil {
sendHTTPErrorResponse(w, http.StatusNotFound, "this session does not exist")
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(s)
sendHTTPResponse(w, http.StatusOK, s)
})
}
func deleteSession() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sid, s := getSessionFromBearerToken(r)
sID := mux.Vars(r)["session-id"]
s := auth.sessions.get(sID)
if s == nil {
sendHTTPResponseInvalidSession(w)
sendHTTPErrorResponse(w, http.StatusNotFound, "this session does not exist")
return
}
auth.sessions.remove(sid)
sendHTTPResponse(w, http.StatusOK, HTTPResponse{Message: "you are now logged out"})
auth.sessions.remove(sID)
sendHTTPResponse(w, http.StatusOK, "you are now logged out")
})
}
......@@ -110,7 +134,7 @@ func listBackends(w http.ResponseWriter, r *http.Request) {
}
func disabled(w http.ResponseWriter, r *http.Request) {
sendHTTPResponse(w, http.StatusBadRequest, HTTPResponse{Error: "authentication is disabled"})
sendHTTPErrorResponse(w, http.StatusBadRequest, "authentication is disabled")
}
func InstallHTTPHandler(r *mux.Router) {
......@@ -119,11 +143,14 @@ func InstallHTTPHandler(r *mux.Router) {
return
}
sessionsHandler := make(handlers.MethodHandler)
sessionsHandler["POST"] = newSession()
r.Handle("/sessions", sessionsHandler)
sessionHandler := make(handlers.MethodHandler)
sessionHandler["POST"] = newSession()
sessionHandler["GET"] = getSession()
sessionHandler["DELETE"] = deleteSession()
r.Handle("/session", sessionHandler)
r.Handle("/sessions/{session-id}", sessionHandler)
r.HandleFunc("/backends", listBackends)
if auth.oidc != nil {
......@@ -142,7 +169,7 @@ func Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, s := getSessionFromBearerToken(r)
if s == nil || s.Username == "" {
sendHTTPResponseInvalidSession(w)
sendHTTPInvalidSessionResponse(w)
return
}
next.ServeHTTP(w, attachSessionToRequest(r, s))
......
......@@ -39,12 +39,17 @@ type OIDCSession struct {
token *oauth2.Token
}
func NewOIDCSession() (s *OIDCSession, err error) {
s = &OIDCSession{}
if s.State, err = generateRandomString(32); err != nil {
func NewOIDCSession() (sID string, err error) {
os := &OIDCSession{}
if os.State, err = generateRandomString(16); err != nil {
return
}
if s.Nonce, err = generateRandomString(32); err != nil {
if os.Nonce, err = generateRandomString(16); err != nil {
return
}
s := &Session{oidc: os}
if sID, err = auth.sessions.insert(s); err != nil {
return
}
return
......@@ -100,24 +105,13 @@ func (h *oidcLoginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if s != nil {
msg := ""
if s.Username != "" {
msg = "You are still logged in, please logout first"
msg = "This session is already logged in."
} else if s.oidc == nil {
msg = "This is not an OIDC session."
} else {
msg = "OIDC login already in progress, retry later."
msg = "OIDC login already in progress."
}
sendHTTPResponse(w, http.StatusConflict, HTTPResponse{Error: msg})
return
}
os, err := NewOIDCSession()
if err != nil {
sendHTTPResponse(w, http.StatusInternalServerError, HTTPResponse{Error: "Failed to generate new OIDC session: " + err.Error()})
return
}
s = &Session{oidc: os, Expires: time.Now().Add(time.Minute)}
if _, err = auth.sessions.insert(s); err != nil {
sendHTTPResponse(w, http.StatusInternalServerError, HTTPResponse{Error: "Failed to generate new session: " + err.Error()})
sendHTTPErrorResponse(w, http.StatusConflict, msg)
return
}
http.Redirect(w, r, h.backend.oauth2Config.AuthCodeURL(s.oidc.State, oidc.Nonce(s.oidc.Nonce)), http.StatusFound)
......@@ -128,61 +122,62 @@ type oidcCallbackHandler struct {
}
func (h *oidcCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
sid, s := getSessionFromBearerToken(r)
// TODO: this won't work, we need to use `state` to find the right session
sID, s := getSessionFromBearerToken(r)
if s == nil {
sendHTTPResponseInvalidSession(w)
sendHTTPInvalidSessionResponse(w)
return
}
if s.oidc == nil {
sendHTTPResponse(w, http.StatusConflict, HTTPResponse{Error: "This is not an OIDC session."})
sendHTTPErrorResponse(w, http.StatusConflict, "This is not an OIDC session.")
return
}
if s.Username != "" {
sendHTTPResponse(w, http.StatusConflict, HTTPResponse{Error: "This session is already logged in."})
sendHTTPErrorResponse(w, http.StatusConflict, "This session is already logged in.")
return
}
if r.URL.Query().Get("state") != s.oidc.State {
sendHTTPResponse(w, http.StatusBadRequest, HTTPResponse{Error: "OIDC verification failed: state did not match"})
sendHTTPErrorResponse(w, http.StatusBadRequest, "OIDC verification failed: state did not match")
return
}
oauth2Token, err := h.backend.oauth2Config.Exchange(r.Context(), r.URL.Query().Get("code"))
if err != nil {
auth.sessions.remove(sid)
sendHTTPResponse(w, http.StatusBadRequest, HTTPResponse{Error: "OAuth2 token exchange failed: " + err.Error()})
auth.sessions.remove(sID)
sendHTTPErrorResponse(w, http.StatusBadRequest, "OAuth2 token exchange failed: "+err.Error())
return
}
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
auth.sessions.remove(sid)
sendHTTPResponse(w, http.StatusBadRequest, HTTPResponse{Error: "OIDC verification failed: no id_token field in oauth2 token."})
auth.sessions.remove(sID)
sendHTTPErrorResponse(w, http.StatusBadRequest, "OIDC verification failed: no id_token field in oauth2 token.")
return
}
// Verify the ID Token signature and nonce.
idToken, err := h.backend.verifier.Verify(r.Context(), rawIDToken)
if err != nil {
auth.sessions.remove(sid)
sendHTTPResponse(w, http.StatusInternalServerError, HTTPResponse{Error: "OAuth2 ID token verification failed: " + err.Error()})
auth.sessions.remove(sID)
sendHTTPErrorResponse(w, http.StatusInternalServerError, "OAuth2 ID token verification failed: "+err.Error())
return
}
if idToken.Nonce != s.oidc.Nonce {
auth.sessions.remove(sid)
sendHTTPResponse(w, http.StatusInternalServerError, HTTPResponse{Error: "OAuth2 ID token verification failed: invalid nonce"})
auth.sessions.remove(sID)
sendHTTPErrorResponse(w, http.StatusInternalServerError, "OAuth2 ID token verification failed: invalid nonce")
return
}
userInfo, err := h.backend.provider.UserInfo(r.Context(), oauth2.StaticTokenSource(oauth2Token))
if err != nil {
auth.sessions.remove(sid)
sendHTTPResponse(w, http.StatusInternalServerError, HTTPResponse{Error: "Fetching OIDC UserInfo failed: " + err.Error()})
auth.sessions.remove(sID)
sendHTTPErrorResponse(w, http.StatusInternalServerError, "Fetching OIDC UserInfo failed: "+err.Error())
return
}
newS := &Session{}
if err := userInfo.Claims(newS); err != nil {
sendHTTPResponse(w, http.StatusInternalServerError, HTTPResponse{Error: "Parsing OIDC UserInfo failed: " + err.Error()})
sendHTTPErrorResponse(w, http.StatusInternalServerError, "Parsing OIDC UserInfo failed: "+err.Error())
return
}
maxAge := defaultAge
......@@ -192,10 +187,10 @@ func (h *oidcCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
newS.Expires = time.Now().Add(maxAge)
newS.oidc = &OIDCSession{State: s.oidc.State, Nonce: s.oidc.Nonce}
newS.oidc.token = oauth2Token
if err = auth.sessions.update(sid, newS); err != nil {
auth.sessions.remove(sid)
sendHTTPResponse(w, http.StatusInternalServerError, HTTPResponse{Error: "Updating session failed: " + err.Error()})
if err = auth.sessions.update(sID, newS); err != nil {
auth.sessions.remove(sID)
sendHTTPErrorResponse(w, http.StatusInternalServerError, "Updating session failed: "+err.Error())
return
}
sendHTTPResponse(w, http.StatusOK, HTTPResponse{Message: "You are now logged in as: " + newS.Username})
sendHTTPResponse(w, http.StatusOK, "You are now logged in as: "+newS.Username)
}
......@@ -73,21 +73,21 @@ func getSessionFromBearerToken(r *http.Request) (string, *Session) {
return "", nil
}
sid, ok := parseBearerAuthHeader(authHeader)
sID, ok := parseBearerAuthHeader(authHeader)
if !ok {
return "", nil
}
s := auth.sessions.get(sid)
s := auth.sessions.get(sID)
if s == nil {
return "", nil
}
if s.Expired() {
auth.sessions.remove(sid)
auth.sessions.remove(sID)
return "", nil
}
return sid, s
return sID, s
}
func attachSessionToRequest(r *http.Request, s *Session) *http.Request {
......@@ -125,6 +125,8 @@ func (sm *SessionManager) insert(s *Session) (id string, err error) {
return
}
// TODO: set session expiry
sm.mutex.Lock()
defer sm.mutex.Unlock()
sm.sessions[id] = s
......@@ -146,6 +148,8 @@ func (sm *SessionManager) update(id string, s *Session) error {
sm.mutex.Lock()
defer sm.mutex.Unlock()
// TODO: set session expiry
if _, ok := sm.sessions[id]; !ok {
return errors.New("session not found.")
}
......
......@@ -45,18 +45,28 @@ func generateRandomString(len int) (string, error) {
return base64.RawURLEncoding.EncodeToString(b[:]), nil
}
type HTTPResponse struct {
Message string `json:"message,omitempty"`
Error string `json:"error,omitempty"`
type NewSessionRequest struct {
Backend string `json:"backend"`
}
func sendHTTPResponse(w http.ResponseWriter, status int, resp HTTPResponse) {
type NewSessionResponse struct {
SessionID string `json:"session-id"`
}
func sendHTTPResponse(w http.ResponseWriter, status int, resp interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(resp) // TODO: Error Handling?
}
func sendHTTPResponseInvalidSession(w http.ResponseWriter) {
resp := HTTPResponse{Error: "Request does not contain a valid token or session is already expired."}
sendHTTPResponse(w, http.StatusUnauthorized, resp)
type HTTPErrorResponse struct {
Error string `json:"error,omitempty"`
}
func sendHTTPErrorResponse(w http.ResponseWriter, status int, error string) {
sendHTTPResponse(w, status, HTTPErrorResponse{Error: error})
}
func sendHTTPInvalidSessionResponse(w http.ResponseWriter) {
sendHTTPErrorResponse(w, http.StatusUnauthorized, "Request does not contain a valid token or session is already expired.")
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment