mirror of
https://github.com/Luzifer/nginx-sso.git
synced 2024-12-21 05:11:17 +00:00
206 lines
5.5 KiB
Go
206 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)
|
||
|
}
|
||
|
}
|