Refactor app, implement logout

This commit is contained in:
Jan Dittberner 2020-12-31 13:19:21 +01:00
parent ce1fac0e68
commit 27e225795c
14 changed files with 647 additions and 349 deletions

View file

@ -0,0 +1,33 @@
package handlers
import (
"net/http"
"github.com/sirupsen/logrus"
"git.cacert.org/oidc_login/app/services"
)
type afterLogoutHandler struct {
logger *logrus.Logger
}
func (h *afterLogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
session, err := services.GetSessionStore().Get(r, sessionName)
if err != nil {
h.logger.Errorf("could not get session: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
session.Options.MaxAge = -1
if err = session.Save(r, w); err != nil {
h.logger.Errorf("could not save session: %v", err)
}
w.Header().Set("Location", "/")
w.WriteHeader(http.StatusFound)
}
func NewAfterLogoutHandler(logger *logrus.Logger) *afterLogoutHandler {
return &afterLogoutHandler{logger: logger}
}

49
app/handlers/common.go Normal file
View file

@ -0,0 +1,49 @@
package handlers
import (
"encoding/base64"
"net/http"
"net/url"
"golang.org/x/oauth2"
"git.cacert.org/oidc_login/app/services"
commonServices "git.cacert.org/oidc_login/common/services"
)
const sessionName = "resource_session"
func Authenticate(oauth2Config *oauth2.Config, clientId string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session, err := services.GetSessionStore().Get(r, sessionName)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, ok := session.Values[sessionKeyUserId]; ok {
next.ServeHTTP(w, r)
return
}
session.Values[sessionRedirectTarget] = r.URL.String()
if err = session.Save(r, w); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var authUrl *url.URL
if authUrl, err = url.Parse(oauth2Config.Endpoint.AuthURL); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
queryValues := authUrl.Query()
queryValues.Set("client_id", clientId)
queryValues.Set("response_type", "code")
queryValues.Set("scope", "openid offline_access profile email")
queryValues.Set("state", base64.URLEncoding.EncodeToString(commonServices.GenerateKey(8)))
authUrl.RawQuery = queryValues.Encode()
w.Header().Set("Location", authUrl.String())
w.WriteHeader(http.StatusFound)
})
}
}

79
app/handlers/index.go Normal file
View file

@ -0,0 +1,79 @@
package handlers
import (
"fmt"
"html/template"
"net/http"
"net/url"
"git.cacert.org/oidc_login/app/services"
)
type indexHandler struct {
logoutUrl string
serverAddr string
}
func (h *indexHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
if request.Method != http.MethodGet {
http.Error(writer, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
if request.URL.Path != "/" {
http.NotFound(writer, request)
return
}
writer.WriteHeader(http.StatusOK)
page, err := template.New("").Parse(`
<!DOCTYPE html>
<html lang="en">
<head><title>Auth test</title></head>
<body>
<h1>Hello {{ .User }}</h1>
<p>This is an authorization protected resource</p>
<a href="{{ .LogoutURL }}">Logout</a>
</body>
</html>
`)
if err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError)
return
}
session, err := services.GetSessionStore().Get(request, sessionName)
if err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError)
return
}
logoutUrl, err := url.Parse(h.logoutUrl)
if err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError)
return
}
var user string
var ok bool
if user, ok = session.Values[sessionKeyUsername].(string); ok {
}
if idToken, ok := session.Values[sessionKeyIdToken].(string); ok {
logoutUrl.RawQuery = url.Values{
"id_token_hint": []string{idToken},
"post_logout_redirect_uri": []string{fmt.Sprintf("https://%s/after-logout", h.serverAddr)},
}.Encode()
}
writer.Header().Add("Content-Type", "text/html")
err = page.Execute(writer, map[string]interface{}{
"User": user,
"LogoutURL": logoutUrl.String(),
})
if err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError)
return
}
}
func NewIndexHandler(logoutUrl string, serverAddr string) *indexHandler {
return &indexHandler{logoutUrl: logoutUrl, serverAddr: serverAddr}
}

View file

@ -0,0 +1,117 @@
package handlers
import (
"context"
"net/http"
"github.com/go-openapi/runtime/client"
"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/jwt"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"git.cacert.org/oidc_login/app/services"
)
const (
sessionKeyAccessToken = iota
sessionKeyRefreshToken
sessionKeyIdToken
sessionKeyUserId
sessionKeyRoles
sessionKeyEmail
sessionKeyUsername
sessionRedirectTarget
)
type oidcCallbackHandler struct {
keySet *jwk.Set
oauth2Config *oauth2.Config
}
func (c *oidcCallbackHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
if request.Method != http.MethodGet {
http.Error(writer, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
if request.URL.Path != "/callback" {
http.NotFound(writer, request)
return
}
code := request.URL.Query().Get("code")
ctx := context.Background()
httpClient, err := client.TLSClient(client.TLSClientOptions{InsecureSkipVerify: true})
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
tok, err := c.oauth2Config.Exchange(ctx, code)
if err != nil {
logrus.Error(err)
http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
session, err := services.GetSessionStore().Get(request, "resource_session")
if err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError)
return
}
session.Values[sessionKeyAccessToken] = tok.AccessToken
session.Values[sessionKeyRefreshToken] = tok.RefreshToken
session.Values[sessionKeyIdToken] = tok.Extra("id_token").(string)
idToken := tok.Extra("id_token")
if parsedIdToken, err := jwt.ParseString(idToken.(string), jwt.WithKeySet(c.keySet), jwt.WithOpenIDClaims()); err != nil {
logrus.Error(err)
http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
} else {
logrus.Infof(`
ID Token
========
Subject: %s
Audience: %s
Issued at: %s
Issued by: %s
Not valid before: %s
Not valid after: %s
`,
parsedIdToken.Subject(),
parsedIdToken.Audience(),
parsedIdToken.IssuedAt(),
parsedIdToken.Issuer(),
parsedIdToken.NotBefore(),
parsedIdToken.Expiration(),
)
session.Values[sessionKeyUserId] = parsedIdToken.Subject()
if roles, ok := parsedIdToken.Get("Groups"); ok {
session.Values[sessionKeyRoles] = roles
}
if username, ok := parsedIdToken.Get("Username"); ok {
session.Values[sessionKeyUsername] = username
}
if email, ok := parsedIdToken.Get("Email"); ok {
session.Values[sessionKeyEmail] = email
}
}
if err = session.Save(request, writer); err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError)
}
if redirectTarget, ok := session.Values[sessionRedirectTarget]; ok {
writer.Header().Set("Location", redirectTarget.(string))
} else {
writer.Header().Set("Location", "/")
}
writer.WriteHeader(http.StatusFound)
}
func NewCallbackHandler(keySet *jwk.Set, oauth2Config *oauth2.Config) *oidcCallbackHandler {
return &oidcCallbackHandler{keySet: keySet, oauth2Config: oauth2Config}
}