Commit ef9ce4ba authored by ale's avatar ale

Add logout page

parent f430169e
Pipeline #601 passed with stages
in 1 minute and 10 seconds
......@@ -10,6 +10,7 @@
// templates/login_otp.html
// templates/login_password.html
// templates/login_u2f.html
// templates/logout.html
// templates/page.html
// DO NOT EDIT!
......@@ -72,13 +73,13 @@ func staticCssBootstrapMinCss() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "static/css/bootstrap.min.css", size: 124962, mode: os.FileMode(420), modTime: time.Unix(1509120975, 0)}
info := bindataFileInfo{name: "static/css/bootstrap.min.css", size: 124962, mode: os.FileMode(436), modTime: time.Unix(1510996183, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _staticCssSigninCss = []byte(`body {
padding-top: 20%;
padding-top: 15%;
padding-bottom: 20%;
background-color: #eee;
}
......@@ -115,6 +116,10 @@ var _staticCssSigninCss = []byte(`body {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.error {
font-weight: bold;
color: red;
}
`)
func staticCssSigninCssBytes() ([]byte, error) {
......@@ -127,7 +132,7 @@ func staticCssSigninCss() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "static/css/signin.css", size: 756, mode: os.FileMode(420), modTime: time.Unix(1509120975, 0)}
info := bindataFileInfo{name: "static/css/signin.css", size: 802, mode: os.FileMode(436), modTime: time.Unix(1511081405, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
......@@ -149,7 +154,7 @@ func staticJsBootstrap400BetaMinJs() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "static/js/bootstrap-4.0.0-beta.min.js", size: 51143, mode: os.FileMode(420), modTime: time.Unix(1509120962, 0)}
info := bindataFileInfo{name: "static/js/bootstrap-4.0.0-beta.min.js", size: 51143, mode: os.FileMode(436), modTime: time.Unix(1510996183, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
......@@ -170,7 +175,7 @@ func staticJsJquery321MinJs() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "static/js/jquery-3.2.1.min.js", size: 86659, mode: os.FileMode(420), modTime: time.Unix(1509120962, 0)}
info := bindataFileInfo{name: "static/js/jquery-3.2.1.min.js", size: 86659, mode: os.FileMode(436), modTime: time.Unix(1510996183, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
......@@ -192,7 +197,7 @@ func staticJsPopper1110MinJs() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "static/js/popper-1.11.0.min.js", size: 19033, mode: os.FileMode(420), modTime: time.Unix(1509120962, 0)}
info := bindataFileInfo{name: "static/js/popper-1.11.0.min.js", size: 19033, mode: os.FileMode(436), modTime: time.Unix(1510996183, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
......@@ -957,7 +962,7 @@ func staticJsU2fApiJs() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "static/js/u2f-api.js", size: 20880, mode: os.FileMode(420), modTime: time.Unix(1509120962, 0)}
info := bindataFileInfo{name: "static/js/u2f-api.js", size: 20880, mode: os.FileMode(436), modTime: time.Unix(1510996183, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
......@@ -1026,7 +1031,7 @@ func staticJsU2fJs() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "static/js/u2f.js", size: 1281, mode: os.FileMode(420), modTime: time.Unix(1509260310, 0)}
info := bindataFileInfo{name: "static/js/u2f.js", size: 1281, mode: os.FileMode(436), modTime: time.Unix(1510996183, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
......@@ -1064,7 +1069,7 @@ func templatesLogin_otpHtml() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "templates/login_otp.html", size: 529, mode: os.FileMode(420), modTime: time.Unix(1509218738, 0)}
info := bindataFileInfo{name: "templates/login_otp.html", size: 529, mode: os.FileMode(436), modTime: time.Unix(1510996183, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
......@@ -1124,7 +1129,7 @@ func templatesLogin_passwordHtml() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "templates/login_password.html", size: 1074, mode: os.FileMode(420), modTime: time.Unix(1509218731, 0)}
info := bindataFileInfo{name: "templates/login_password.html", size: 1074, mode: os.FileMode(436), modTime: time.Unix(1510996183, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
......@@ -1164,7 +1169,65 @@ func templatesLogin_u2fHtml() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "templates/login_u2f.html", size: 498, mode: os.FileMode(420), modTime: time.Unix(1509260387, 0)}
info := bindataFileInfo{name: "templates/login_u2f.html", size: 498, mode: os.FileMode(436), modTime: time.Unix(1510996183, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _templatesLogoutHtml = []byte(`{{template "header" .}}
{{if .IsPOST}}
<div class="form-signin">
<h1 class="form-signin-heading>">Sign Out</h1>
<p>
Signing you out from all services...
</p>
<ul>
{{range .Services}}
<li>
<img src="{{.URL}}" class="logout-img"> {{.Name}}
</li>
{{end}}
</ul>
</div>
{{else}}
<form class="form-signin" action="/logout" method="post">
{{.CSRFField}}
<h1 class="form-signin-heading">Sign Out</h1>
<p>
You are about to sign out from the following services:
</p>
<ul>
{{range .Services}}
<li>{{.Name}}</li>
{{end}}
</ul>
<button type="submit" class="btn btn-lg btn-primary btn-block">Logout</button>
</form>
{{end}}
{{template "footer" .}}
`)
func templatesLogoutHtmlBytes() ([]byte, error) {
return _templatesLogoutHtml, nil
}
func templatesLogoutHtml() (*asset, error) {
bytes, err := templatesLogoutHtmlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "templates/logout.html", size: 820, mode: os.FileMode(436), modTime: time.Unix(1511083629, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
......@@ -1209,7 +1272,7 @@ func templatesPageHtml() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "templates/page.html", size: 1493, mode: os.FileMode(420), modTime: time.Unix(1509174553, 0)}
info := bindataFileInfo{name: "templates/page.html", size: 1493, mode: os.FileMode(436), modTime: time.Unix(1510996183, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
......@@ -1276,6 +1339,7 @@ var _bindata = map[string]func() (*asset, error){
"templates/login_otp.html": templatesLogin_otpHtml,
"templates/login_password.html": templatesLogin_passwordHtml,
"templates/login_u2f.html": templatesLogin_u2fHtml,
"templates/logout.html": templatesLogoutHtml,
"templates/page.html": templatesPageHtml,
}
......@@ -1336,6 +1400,7 @@ var _bintree = &bintree{nil, map[string]*bintree{
"login_otp.html": &bintree{templatesLogin_otpHtml, map[string]*bintree{}},
"login_password.html": &bintree{templatesLogin_passwordHtml, map[string]*bintree{}},
"login_u2f.html": &bintree{templatesLogin_u2fHtml, map[string]*bintree{}},
"logout.html": &bintree{templatesLogoutHtml, map[string]*bintree{}},
"page.html": &bintree{templatesPageHtml, map[string]*bintree{}},
}},
}}
......
......@@ -5,6 +5,7 @@ package server
import (
"encoding/gob"
"fmt"
"html/template"
"io"
"log"
"net/http"
......@@ -66,6 +67,19 @@ func init() {
prometheus.MustRegister(totalRequests, inFlightRequests)
}
// Returns the URL of the login handler on the target service.
func serviceLoginCallback(service, destination, token string) string {
v := make(url.Values)
v.Set("t", token)
v.Set("d", destination)
return fmt.Sprintf("https://%ssso_login?%s", service, v.Encode())
}
// Returns the URL of the logout handler on the target service.
func serviceLogoutCallback(service string) string {
return fmt.Sprintf("https://%ssso_logout", service)
}
// Server for the SSO protocol. Provides the HTTP interface to a
// LoginService.
type Server struct {
......@@ -74,6 +88,7 @@ type Server struct {
loginHandler *loginHandler
loginService *LoginService
csrfSecret []byte
tpl *template.Template
}
func sl2bl(sl []string) [][]byte {
......@@ -98,6 +113,7 @@ func New(loginService *LoginService, authClient authclient.Client, config *Confi
authSessionLifetime: defaultAuthSessionLifetime,
authSessionStore: store,
loginService: loginService,
tpl: parseEmbeddedTemplates(),
}
if config.CSRFSecret != "" {
s.csrfSecret = []byte(config.CSRFSecret)
......@@ -110,7 +126,7 @@ func New(loginService *LoginService, authClient authclient.Client, config *Confi
if err != nil {
return nil, err
}
s.loginHandler = newLoginHandler(s.loginCallback, devMgr, authClient, config.AuthService, sessionSecrets...)
s.loginHandler = newLoginHandler(s.loginCallback, devMgr, authClient, config.AuthService, s.tpl, sessionSecrets...)
return s, nil
}
......@@ -186,16 +202,39 @@ func (h *Server) handleHomepage(w http.ResponseWriter, req *http.Request, sessio
_ = sessions.Save(req, w)
// Redirect to service callback.
callbackURL := serviceCallback(service, destination, token)
callbackURL := serviceLoginCallback(service, destination, token)
http.Redirect(w, req, callbackURL, http.StatusFound)
}
// Returns the URL of the login handler on the target service.
func serviceCallback(service, destination, token string) string {
v := make(url.Values)
v.Set("t", token)
v.Set("d", destination)
return fmt.Sprintf("https://%ssso_login?%s", service, v.Encode())
type logoutServiceInfo struct {
URL string
Name string
}
func (h *Server) handleLogout(w http.ResponseWriter, req *http.Request, session *authSession) {
var svcs []logoutServiceInfo
for _, svc := range session.Services {
svcs = append(svcs, logoutServiceInfo{
Name: svc,
URL: serviceLogoutCallback(svc),
})
}
data := map[string]interface{}{
"CSRFField": csrf.TemplateField(req),
"Services": svcs,
"IsPOST": false,
}
if req.Method == "POST" {
data["IsPOST"] = true
// Clear the local session.
httpSession, _ := h.authSessionStore.Get(req, authSessionKey)
httpSession.Options.MaxAge = -1
_ = httpSession.Save(req, w)
}
h.tpl.ExecuteTemplate(w, "logout.html", data)
}
func (h *Server) handleExchange(w http.ResponseWriter, req *http.Request) {
......@@ -221,13 +260,16 @@ func (h *Server) handleExchange(w http.ResponseWriter, req *http.Request) {
func (h *Server) Handler() http.Handler {
m := mux.NewRouter()
var lh http.Handler
var lih, loh http.Handler
lih = h.loginHandler
loh = h.withAuth(h.handleLogout)
if h.csrfSecret != nil {
lh = csrf.Protect(h.csrfSecret)(h.loginHandler)
} else {
lh = h.loginHandler
csrfW := csrf.Protect(h.csrfSecret)
lih = csrfW(lih)
loh = csrfW(loh)
}
m.Handle("/login", withDynamicHeaders(lh))
m.Handle("/login", withDynamicHeaders(lih))
m.Handle("/logout", withDynamicHeaders(loh))
m.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(&assetfs.AssetFS{
Asset: Asset,
......@@ -276,3 +318,24 @@ var (
},
)
)
// Parse the templates that are embedded with the binary (in bindata.go).
func parseEmbeddedTemplates() *template.Template {
root := template.New("").Funcs(template.FuncMap{
"json": toJSON,
})
files, err := AssetDir("templates")
if err != nil {
log.Fatalf("no asset dir for templates: %v", err)
}
for _, f := range files {
b, err := Asset("templates/" + f)
if err != nil {
log.Fatalf("could not read embedded template %s: %v", f, err)
}
if _, err := root.New(f).Parse(string(b)); err != nil {
log.Fatalf("error parsing template %s: %v", f, err)
}
}
return root
}
......@@ -79,7 +79,7 @@ type loginHandler struct {
// NewLoginHandler will wrap an http.Handler with the login workflow,
// invoking it only on successful login.
func newLoginHandler(okHandler loginCallbackFunc, devMgr *device.Manager, authClient authclient.Client, authService string, keyPairs ...[]byte) *loginHandler {
func newLoginHandler(okHandler loginCallbackFunc, devMgr *device.Manager, authClient authclient.Client, authService string, tpl *template.Template, keyPairs ...[]byte) *loginHandler {
store := sessions.NewCookieStore(keyPairs...)
store.Options = &sessions.Options{
HttpOnly: true,
......@@ -288,27 +288,6 @@ func (l *loginHandler) executeTemplateToBuffer(req *http.Request, templateName s
return loginStateNone, buf.Bytes(), nil
}
// Parse the templates that are embedded with the binary (in bindata.go).
func parseEmbeddedTemplates() *template.Template {
root := template.New("").Funcs(template.FuncMap{
"json": toJSON,
})
files, err := AssetDir("templates")
if err != nil {
log.Fatalf("no asset dir for templates: %v", err)
}
for _, f := range files {
b, err := Asset("templates/" + f)
if err != nil {
log.Fatalf("could not read embedded template %s: %v", f, err)
}
if _, err := root.New(f).Parse(string(b)); err != nil {
log.Fatalf("error parsing template %s: %v", f, err)
}
}
return root
}
// Template helper function that encodes its input as JSON.
func toJSON(obj interface{}) string {
data, err := json.Marshal(obj)
......
body {
padding-top: 20%;
padding-top: 15%;
padding-bottom: 20%;
background-color: #eee;
}
......@@ -36,3 +36,7 @@ body {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.error {
font-weight: bold;
color: red;
}
{{template "header" .}}
{{if .IsPOST}}
<div class="form-signin">
<h1 class="form-signin-heading>">Sign Out</h1>
<p>
Signing you out from all services...
</p>
<ul>
{{range .Services}}
<li>
<img src="{{.URL}}" class="logout-img"> {{.Name}}
</li>
{{end}}
</ul>
</div>
{{else}}
<form class="form-signin" action="/logout" method="post">
{{.CSRFField}}
<h1 class="form-signin-heading">Sign Out</h1>
<p>
You are about to sign out from the following services:
</p>
<ul>
{{range .Services}}
<li>{{.Name}}</li>
{{end}}
</ul>
<button type="submit" class="btn btn-lg btn-primary btn-block">Logout</button>
</form>
{{end}}
{{template "footer" .}}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment