1
0
Fork 0
mirror of https://github.com/Luzifer/nginx-sso.git synced 2024-12-30 09:41:19 +00:00
nginx-sso/vendor/github.com/jda/go-crowd/crowdauth/middleware.go
Knut Ahlers 6fa934880e
Implement Crowd authentication (#2)
* Re-add example configuration for Crowd
* Implement Crowd authentication
* Fix: Some errors just mean there is no user
* Document crowd provider
* Vendor new dependencies
* Reduce error messages: Check for config details
2018-02-04 14:51:08 +01:00

205 lines
5.5 KiB
Go

// Package crowdauth provides middleware for Crowd SSO logins
//
// Goals:
// 1) drop-in authentication against Crowd SSO
// 2) make it easy to use Crowd SSO as part of your own auth flow
package crowdauth // import "go.jona.me/crowd/crowdauth"
import (
"errors"
"go.jona.me/crowd"
"html/template"
"log"
"net/http"
"time"
)
type SSO struct {
CrowdApp *crowd.Crowd
LoginPage AuthLoginPage
LoginTemplate *template.Template
ClientAddressFinder ClientAddressFinder
CookieConfig crowd.CookieConfig
}
// The AuthLoginPage type extends the normal http.HandlerFunc type
// with a boolean return to indicate login success or failure.
type AuthLoginPage func(http.ResponseWriter, *http.Request, *SSO) bool
// ClientAddressFinder type represents a function that returns the
// end-user's IP address, allowing you to handle cases where the address
// is masked by proxy servers etc by checking headers or whatever to find
// the user's address
type ClientAddressFinder func(*http.Request) (string, error)
var authErr string = "unauthorized, login required"
func DefaultClientAddressFinder(r *http.Request) (addr string, err error) {
return r.RemoteAddr, nil
}
// New creates a new instance of SSO
func New(user string, password string, crowdURL string) (s *SSO, err error) {
s = &SSO{}
s.LoginPage = loginPage
s.ClientAddressFinder = DefaultClientAddressFinder
s.LoginTemplate = template.Must(template.New("authPage").Parse(defLoginPage))
cwd, err := crowd.New(user, password, crowdURL)
if err != nil {
return s, err
}
s.CrowdApp = &cwd
s.CookieConfig, err = s.CrowdApp.GetCookieConfig()
if err != nil {
return s, err
}
return s, nil
}
// Handler provides HTTP middleware using http.Handler chaining
// that requires user authentication via Atlassian Crowd SSO.
func (s *SSO) Handler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if s.loginHandler(w, r) == false {
return
}
h.ServeHTTP(w, r)
})
}
func (s *SSO) loginHandler(w http.ResponseWriter, r *http.Request) bool {
ck, err := r.Cookie(s.CookieConfig.Name)
if err == http.ErrNoCookie {
// no cookie so show login page if GET
// if POST check if login and handle
// if fail, show login page with message
if r.Method == "GET" {
s.LoginPage(w, r, s)
} else if r.Method == "POST" {
authOK := s.LoginPage(w, r, s)
if authOK == true {
// Redirect for fresh pass through auth etc on success
http.Redirect(w, r, r.RequestURI, http.StatusTemporaryRedirect)
return false
} else {
log.Printf("crowdauth: authentication failed\n")
}
} else {
http.Error(w, authErr, http.StatusUnauthorized)
}
return false
} else {
// validate cookie or show login page
host, err := s.ClientAddressFinder(r)
if err != nil {
log.Printf("crowdauth: could not get remote addr: %s\n", err)
return false
}
_, err = s.CrowdApp.ValidateSession(ck.Value, host)
if err != nil {
log.Printf("crowdauth: could not validate cookie, deleting because: %s\n", err)
s.EndSession(w, r)
s.LoginPage(w, r, s)
return false
}
// valid cookie so fallthrough
}
return true
}
func (s *SSO) Login(user string, pass string, addr string) (cs crowd.Session, err error) {
cs, err = s.CrowdApp.NewSession(user, pass, addr)
return cs, err
}
func (s *SSO) Logout(w http.ResponseWriter, r *http.Request, newURL string) {
s.EndSession(w, r)
http.Redirect(w, r, newURL, http.StatusTemporaryRedirect)
}
// StartSession sets a Crowd session cookie.
func (s *SSO) StartSession(w http.ResponseWriter, cs crowd.Session) {
ck := http.Cookie{
Name: s.CookieConfig.Name,
Domain: s.CookieConfig.Domain,
Secure: s.CookieConfig.Secure,
Value: cs.Token,
Expires: cs.Expires,
}
http.SetCookie(w, &ck)
}
// EndSession invalidates the current Crowd session and cookie
func (s *SSO) EndSession(w http.ResponseWriter, r *http.Request) {
currentCookie, _ := r.Cookie(s.CookieConfig.Name)
newCookie := &http.Cookie{
Name: s.CookieConfig.Name,
Domain: s.CookieConfig.Domain,
Secure: s.CookieConfig.Secure,
MaxAge: -1,
Expires: time.Unix(1, 0),
Value: "LOGGED-OUT",
}
s.CrowdApp.InvalidateSession(currentCookie.Value)
log.Printf("Got cookie to remove: %+v\n", currentCookie)
log.Printf("Removal cookie: %+v\n", newCookie)
http.SetCookie(w, newCookie)
}
// Get User information for the current session (by cookie)
func (s *SSO) GetUser(r *http.Request) (u crowd.User, err error) {
currentCookie, err := r.Cookie(s.CookieConfig.Name)
if err == http.ErrNoCookie {
return u, errors.New("no session cookie")
}
userSession, err := s.CrowdApp.GetSession(currentCookie.Value)
if err != nil {
return u, errors.New("session not valid")
}
return userSession.User, nil
}
func loginPage(w http.ResponseWriter, r *http.Request, s *SSO) bool {
if r.Method == "GET" { // show login page and bail
showLoginPage(w, s)
return false
} else if r.Method == "POST" {
user := r.FormValue("username")
pass := r.FormValue("password")
host, err := s.ClientAddressFinder(r)
if err != nil {
log.Printf("crowdauth: could not get remote addr: %s\n", err)
showLoginPage(w, s)
return false
}
sess, err := s.Login(user, pass, host)
if err != nil {
log.Printf("crowdauth: login/new session failed: %s\n", err)
showLoginPage(w, s)
return false
}
s.StartSession(w, sess)
} else {
return false
}
return true
}
func showLoginPage(w http.ResponseWriter, s *SSO) {
err := s.LoginTemplate.ExecuteTemplate(w, "authPage", nil)
if err != nil {
log.Printf("crowdauth: could not exec template: %s\n", err)
}
}