1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-08-08 23:15:29 +02:00

auth with cas

This commit is contained in:
Derek Chen 2019-08-09 13:44:03 +08:00
parent 8c99977fc9
commit 8c2df6178d
150 changed files with 43682 additions and 24175 deletions

27
vendor/gopkg.in/cas.v2/.gitignore generated vendored Normal file
View file

@ -0,0 +1,27 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
.idea
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
.vscode

21
vendor/gopkg.in/cas.v2/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Geoff Garside
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

12
vendor/gopkg.in/cas.v2/README.md generated vendored Normal file
View file

@ -0,0 +1,12 @@
# CAS Client library
CAS provides a http package compatible client implementation for use with
securing http frontends in golang.
import "gopkg.in/cas.v2"
## Examples and Documentation
Documentation is available at: http://godoc.org/gopkg.in/cas.v1
Examples are included in the documentation but are also available in the
`_examples` directory.

503
vendor/gopkg.in/cas.v2/client.go generated vendored Normal file
View file

@ -0,0 +1,503 @@
package cas
import (
"crypto/rand"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path"
"sync"
"github.com/golang/glog"
)
// Client configuration options
type Options struct {
URL *url.URL // URL to the CAS service
Store TicketStore // Custom TicketStore, if nil a MemoryStore will be used
Client *http.Client // Custom http client to allow options for http connections
SendService bool // Custom sendService to determine whether you need to send service param
}
// Client implements the main protocol
type Client struct {
url *url.URL
tickets TicketStore
client *http.Client
mu sync.Mutex
sessions map[string]string
sendService bool
}
// NewClient creates a Client with the provided Options.
func NewClient(options *Options) *Client {
if glog.V(2) {
glog.Infof("cas: new client with options %v", options)
}
var tickets TicketStore
if options.Store != nil {
tickets = options.Store
} else {
tickets = &MemoryStore{}
}
var client *http.Client
if options.Client != nil {
client = options.Client
} else {
client = &http.Client{}
}
return &Client{
url: options.URL,
tickets: tickets,
client: client,
sessions: make(map[string]string),
sendService: options.SendService,
}
}
// Handle wraps a http.Handler to provide CAS authentication for the handler.
func (c *Client) Handle(h http.Handler) http.Handler {
return &clientHandler{
c: c,
h: h,
}
}
// HandleFunc wraps a function to provide CAS authentication for the handler function.
func (c *Client) HandleFunc(h func(http.ResponseWriter, *http.Request)) http.Handler {
return c.Handle(http.HandlerFunc(h))
}
// requestURL determines an absolute URL from the http.Request.
func requestURL(r *http.Request) (*url.URL, error) {
u, err := url.Parse(r.URL.String())
if err != nil {
return nil, err
}
u.Host = r.Host
u.Scheme = "http"
if scheme := r.Header.Get("X-Forwarded-Proto"); scheme != "" {
u.Scheme = scheme
} else if r.TLS != nil {
u.Scheme = "https"
}
return u, nil
}
// LoginUrlForRequest determines the CAS login URL for the http.Request.
func (c *Client) LoginUrlForRequest(r *http.Request) (string, error) {
u, err := c.url.Parse(path.Join(c.url.Path, "login"))
if err != nil {
return "", err
}
service, err := requestURL(r)
if err != nil {
return "", err
}
q := u.Query()
q.Add("service", sanitisedURLString(service))
u.RawQuery = q.Encode()
return u.String(), nil
}
// LogoutUrlForRequest determines the CAS logout URL for the http.Request.
func (c *Client) LogoutUrlForRequest(r *http.Request) (string, error) {
u, err := c.url.Parse(path.Join(c.url.Path, "logout"))
if err != nil {
return "", err
}
if c.sendService {
service, err := requestURL(r)
if err != nil {
return "", err
}
q := u.Query()
q.Add("service", sanitisedURLString(service))
u.RawQuery = q.Encode()
}
return u.String(), nil
}
// ServiceValidateUrlForRequest determines the CAS serviceValidate URL for the ticket and http.Request.
func (c *Client) ServiceValidateUrlForRequest(ticket string, r *http.Request) (string, error) {
u, err := c.url.Parse(path.Join(c.url.Path, "serviceValidate"))
if err != nil {
return "", err
}
service, err := requestURL(r)
if err != nil {
return "", err
}
q := u.Query()
q.Add("service", sanitisedURLString(service))
q.Add("ticket", ticket)
u.RawQuery = q.Encode()
return u.String(), nil
}
// ValidateUrlForRequest determines the CAS validate URL for the ticket and http.Request.
func (c *Client) ValidateUrlForRequest(ticket string, r *http.Request) (string, error) {
u, err := c.url.Parse(path.Join(c.url.Path, "validate"))
if err != nil {
return "", err
}
service, err := requestURL(r)
if err != nil {
return "", err
}
q := u.Query()
q.Add("service", sanitisedURLString(service))
q.Add("ticket", ticket)
u.RawQuery = q.Encode()
return u.String(), nil
}
// RedirectToLogout replies to the request with a redirect URL to log out of CAS.
func (c *Client) RedirectToLogout(w http.ResponseWriter, r *http.Request) {
u, err := c.LogoutUrlForRequest(r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if glog.V(2) {
glog.Info("Logging out, redirecting client to %v with status %v",
u, http.StatusFound)
}
c.clearSession(w, r)
http.Redirect(w, r, u, http.StatusFound)
}
// RedirectToLogout replies to the request with a redirect URL to authenticate with CAS.
func (c *Client) RedirectToLogin(w http.ResponseWriter, r *http.Request) {
u, err := c.LoginUrlForRequest(r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if glog.V(2) {
glog.Infof("Redirecting client to %v with status %v", u, http.StatusFound)
}
http.Redirect(w, r, u, http.StatusFound)
}
// validateTicket performs CAS ticket validation with the given ticket and service.
//
// If the request returns a 404 then validateTicketCas1 will be returned.
func (c *Client) validateTicket(ticket string, service *http.Request) error {
if glog.V(2) {
serviceUrl, _ := requestURL(service)
glog.Infof("Validating ticket %v for service %v", ticket, serviceUrl)
}
u, err := c.ServiceValidateUrlForRequest(ticket, service)
if err != nil {
return err
}
r, err := http.NewRequest("GET", u, nil)
if err != nil {
return err
}
r.Header.Add("User-Agent", "Golang CAS client gopkg.in/cas")
if glog.V(2) {
glog.Infof("Attempting ticket validation with %v", r.URL)
}
resp, err := c.client.Do(r)
if err != nil {
return err
}
if glog.V(2) {
glog.Infof("Request %v %v returned %v",
r.Method, r.URL,
resp.Status)
}
if resp.StatusCode == http.StatusNotFound {
return c.validateTicketCas1(ticket, service)
}
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("cas: validate ticket: %v", string(body))
}
if glog.V(2) {
glog.Infof("Received authentication response\n%v", string(body))
}
success, err := ParseServiceResponse(body)
if err != nil {
return err
}
if glog.V(2) {
glog.Infof("Parsed ServiceResponse: %#v", success)
}
if err := c.tickets.Write(ticket, success); err != nil {
return err
}
return nil
}
// validateTicketCas1 performs CAS protocol 1 ticket validation.
func (c *Client) validateTicketCas1(ticket string, service *http.Request) error {
u, err := c.ValidateUrlForRequest(ticket, service)
if err != nil {
return err
}
r, err := http.NewRequest("GET", u, nil)
if err != nil {
return err
}
r.Header.Add("User-Agent", "Golang CAS client gopkg.in/cas")
if glog.V(2) {
glog.Info("Attempting ticket validation with %v", r.URL)
}
resp, err := c.client.Do(r)
if err != nil {
return err
}
if glog.V(2) {
glog.Info("Request %v %v returned %v",
r.Method, r.URL,
resp.Status)
}
data, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return err
}
body := string(data)
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("cas: validate ticket: %v", body)
}
if glog.V(2) {
glog.Infof("Received authentication response\n%v", body)
}
if body == "no\n\n" {
return nil // not logged in
}
success := &AuthenticationResponse{
User: body[4 : len(body)-1],
}
if glog.V(2) {
glog.Infof("Parsed ServiceResponse: %#v", success)
}
if err := c.tickets.Write(ticket, success); err != nil {
return err
}
return nil
}
// getSession finds or creates a session for the request.
//
// A cookie is set on the response if one is not provided with the request.
// Validates the ticket if the URL parameter is provided.
func (c *Client) getSession(w http.ResponseWriter, r *http.Request) {
cookie := getCookie(w, r)
if s, ok := c.sessions[cookie.Value]; ok {
if t, err := c.tickets.Read(s); err == nil {
if glog.V(1) {
glog.Infof("Re-used ticket %s for %s", s, t.User)
}
setAuthenticationResponse(r, t)
return
} else {
if glog.V(2) {
glog.Infof("Ticket %v not in %T: %v", s, c.tickets, err)
}
if glog.V(1) {
glog.Infof("Clearing ticket %s, no longer exists in ticket store", s)
}
clearCookie(w, cookie)
}
}
if ticket := r.URL.Query().Get("ticket"); ticket != "" {
if err := c.validateTicket(ticket, r); err != nil {
if glog.V(2) {
glog.Infof("Error validating ticket: %v", err)
}
return // allow ServeHTTP()
}
c.setSession(cookie.Value, ticket)
if t, err := c.tickets.Read(ticket); err == nil {
if glog.V(1) {
glog.Infof("Validated ticket %s for %s", ticket, t.User)
}
setAuthenticationResponse(r, t)
return
} else {
if glog.V(2) {
glog.Infof("Ticket %v not in %T: %v", ticket, c.tickets, err)
}
if glog.V(1) {
glog.Infof("Clearing ticket %s, no longer exists in ticket store", ticket)
}
clearCookie(w, cookie)
}
}
}
// getCookie finds or creates the session cookie on the response.
func getCookie(w http.ResponseWriter, r *http.Request) *http.Cookie {
c, err := r.Cookie(sessionCookieName)
if err != nil {
// NOTE: Intentionally not enabling HttpOnly so the cookie can
// still be used by Ajax requests.
c = &http.Cookie{
Name: sessionCookieName,
Value: newSessionId(),
MaxAge: 86400,
HttpOnly: false,
}
if glog.V(2) {
glog.Infof("Setting %v cookie with value: %v", c.Name, c.Value)
}
r.AddCookie(c) // so we can find it later if required
http.SetCookie(w, c)
}
return c
}
// newSessionId generates a new opaque session identifier for use in the cookie.
func newSessionId() string {
const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
// generate 64 character string
bytes := make([]byte, 64)
rand.Read(bytes)
for k, v := range bytes {
bytes[k] = alphabet[v%byte(len(alphabet))]
}
return string(bytes)
}
// clearCookie invalidates and removes the cookie from the client.
func clearCookie(w http.ResponseWriter, c *http.Cookie) {
c.MaxAge = -1
http.SetCookie(w, c)
}
// setSession stores the session id to ticket mapping in the Client.
func (c *Client) setSession(id string, ticket string) {
if glog.V(2) {
glog.Infof("Recording session, %v -> %v", id, ticket)
}
c.mu.Lock()
c.sessions[id] = ticket
c.mu.Unlock()
}
// clearSession removes the session from the client and clears the cookie.
func (c *Client) clearSession(w http.ResponseWriter, r *http.Request) {
cookie := getCookie(w, r)
if s, ok := c.sessions[cookie.Value]; ok {
if err := c.tickets.Delete(s); err != nil {
fmt.Printf("Failed to remove %v from %T: %v\n", cookie.Value, c.tickets, err)
if glog.V(2) {
glog.Errorf("Failed to remove %v from %T: %v", cookie.Value, c.tickets, err)
}
}
c.deleteSession(s)
}
clearCookie(w, cookie)
}
// deleteSession removes the session from the client
func (c *Client) deleteSession(id string) {
c.mu.Lock()
delete(c.sessions, id)
c.mu.Unlock()
}
// findAndDeleteSessionWithTicket removes the session from the client via Single Log Out
//
// When a Single Log Out request is received we receive the service ticket identidier. This
// function loops through the sessions to find the matching session id. Once retrieved the
// session is removed from the client. When the session is next requested the getSession
// function will notice the session is invalid and revalidate the user.
func (c *Client) findAndDeleteSessionWithTicket(ticket string) {
var id string
for s, t := range c.sessions {
if t == ticket {
id = s
break
}
}
if id == "" {
return
}
c.deleteSession(id)
}

10
vendor/gopkg.in/cas.v2/doc.go generated vendored Normal file
View file

@ -0,0 +1,10 @@
/*
Package cas implements a CAS client.
CAS is a protocol which provides authentication and authorisation for securing
typically HTTP based services.
References:
[PROTOCOL]: http://jasig.github.io/cas/4.0.x/protocol/CAS-Protocol.html
*/
package cas

78
vendor/gopkg.in/cas.v2/handler.go generated vendored Normal file
View file

@ -0,0 +1,78 @@
package cas
import (
"fmt"
"net/http"
"github.com/golang/glog"
)
const (
sessionCookieName = "_cas_session"
)
// clientHandler handles CAS Protocol HTTP requests
type clientHandler struct {
c *Client
h http.Handler
}
// ServeHTTP handles HTTP requests, processes CAS requests
// and passes requests up to its child http.Handler.
func (ch *clientHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if glog.V(2) {
glog.Infof("cas: handling %v request for %v", r.Method, r.URL)
}
setClient(r, ch.c)
if isSingleLogoutRequest(r) {
ch.performSingleLogout(w, r)
return
}
ch.c.getSession(w, r)
ch.h.ServeHTTP(w, r)
return
}
// isSingleLogoutRequest determines if the http.Request is a CAS Single Logout Request.
//
// The rules for a SLO request are, HTTP POST urlencoded form with a logoutRequest parameter.
func isSingleLogoutRequest(r *http.Request) bool {
if r.Method != "POST" {
return false
}
contentType := r.Header.Get("Content-Type")
if contentType != "application/x-www-form-urlencoded" {
return false
}
if v := r.FormValue("logoutRequest"); v == "" {
return false
}
return true
}
// performSingleLogout processes a single logout request
func (ch *clientHandler) performSingleLogout(w http.ResponseWriter, r *http.Request) {
rawXML := r.FormValue("logoutRequest")
logoutRequest, err := parseLogoutRequest([]byte(rawXML))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := ch.c.tickets.Delete(logoutRequest.SessionIndex); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
ch.c.deleteSession(logoutRequest.SessionIndex)
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "OK")
}

152
vendor/gopkg.in/cas.v2/http_helpers.go generated vendored Normal file
View file

@ -0,0 +1,152 @@
package cas
import (
"context"
"net/http"
"time"
)
type key int
const ( // emulating enums is actually pretty ugly in go.
clientKey key = iota
authenticationResponseKey
)
// setClient associates a Client with a http.Request.
func setClient(r *http.Request, c *Client) {
ctx := context.WithValue(r.Context(), clientKey, c)
r2 := r.WithContext(ctx)
*r = *r2
}
// getClient retrieves the Client associated with the http.Request.
func getClient(r *http.Request) *Client {
if c := r.Context().Value(clientKey); c != nil {
return c.(*Client)
} else {
return nil // explicitly pass along the nil to caller -- conforms to previous impl
}
}
// RedirectToLogin allows CAS protected handlers to redirect a request
// to the CAS login page.
func RedirectToLogin(w http.ResponseWriter, r *http.Request) {
c := getClient(r)
if c == nil {
err := "cas: redirect to cas failed as no client associated with request"
http.Error(w, err, http.StatusInternalServerError)
return
}
c.RedirectToLogin(w, r)
}
// RedirectToLogout allows CAS protected handlers to redirect a request
// to the CAS logout page.
func RedirectToLogout(w http.ResponseWriter, r *http.Request) {
c := getClient(r)
if c == nil {
err := "cas: redirect to cas failed as no client associated with request"
http.Error(w, err, http.StatusInternalServerError)
return
}
c.RedirectToLogout(w, r)
}
// setAuthenticationResponse associates an AuthenticationResponse with
// a http.Request.
func setAuthenticationResponse(r *http.Request, a *AuthenticationResponse) {
ctx := context.WithValue(r.Context(), authenticationResponseKey, a)
r2 := r.WithContext(ctx)
*r = *r2
}
// getAuthenticationResponse retrieves the AuthenticationResponse associated
// with a http.Request.
func getAuthenticationResponse(r *http.Request) *AuthenticationResponse {
if a := r.Context().Value(authenticationResponseKey); a != nil {
return a.(*AuthenticationResponse)
} else {
return nil // explicitly pass along the nil to caller -- conforms to previous impl
}
}
// IsAuthenticated indicates whether the request has been authenticated with CAS.
func IsAuthenticated(r *http.Request) bool {
if a := getAuthenticationResponse(r); a != nil {
return true
}
return false
}
// Username returns the authenticated users username
func Username(r *http.Request) string {
if a := getAuthenticationResponse(r); a != nil {
return a.User
}
return ""
}
// Attributes returns the authenticated users attributes.
func Attributes(r *http.Request) UserAttributes {
if a := getAuthenticationResponse(r); a != nil {
return a.Attributes
}
return nil
}
// AuthenticationDate returns the date and time that authentication was performed.
//
// This may return time.IsZero if Authentication Date information is not included
// in the CAS service validation response. This will be the case for CAS 2.0
// protocol servers.
func AuthenticationDate(r *http.Request) time.Time {
var t time.Time
if a := getAuthenticationResponse(r); a != nil {
t = a.AuthenticationDate
}
return t
}
// IsNewLogin indicates whether the CAS service ticket was granted following a
// new authentication.
//
// This may incorrectly return false if Is New Login information is not included
// in the CAS service validation response. This will be the case for CAS 2.0
// protocol servers.
func IsNewLogin(r *http.Request) bool {
if a := getAuthenticationResponse(r); a != nil {
return a.IsNewLogin
}
return false
}
// IsRememberedLogin indicates whether the CAS service ticket was granted by the
// presence of a long term authentication token.
//
// This may incorrectly return false if Remembered Login information is not included
// in the CAS service validation response. This will be the case for CAS 2.0
// protocol servers.
func IsRememberedLogin(r *http.Request) bool {
if a := getAuthenticationResponse(r); a != nil {
return a.IsRememberedLogin
}
return false
}
// MemberOf returns the list of groups which the user belongs to.
func MemberOf(r *http.Request) []string {
if a := getAuthenticationResponse(r); a != nil {
return a.MemberOf
}
return nil
}

65
vendor/gopkg.in/cas.v2/logout_request.go generated vendored Normal file
View file

@ -0,0 +1,65 @@
package cas
import (
"crypto/rand"
"encoding/xml"
"strings"
"time"
)
// Represents the XML CAS Single Log Out Request data
type logoutRequest struct {
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol LogoutRequest"`
Version string `xml:"Version,attr"`
IssueInstant time.Time `xml:"-"`
RawIssueInstant string `xml:"IssueInstant,attr"`
ID string `xml:"ID,attr"`
NameID string `xml:"urn:oasis:names:tc:SAML:2.0:assertion NameID"`
SessionIndex string `xml:"SessionIndex"`
}
func parseLogoutRequest(data []byte) (*logoutRequest, error) {
l := &logoutRequest{}
if err := xml.Unmarshal(data, &l); err != nil {
return nil, err
}
t, err := time.Parse(time.RFC1123Z, l.RawIssueInstant)
if err != nil {
return nil, err
}
l.IssueInstant = t
l.NameID = strings.TrimSpace(l.NameID)
l.SessionIndex = strings.TrimSpace(l.SessionIndex)
return l, nil
}
func newLogoutRequestId() string {
const alphabet = "abcdef0123456789"
// generate 64 character string
bytes := make([]byte, 64)
rand.Read(bytes)
for k, v := range bytes {
bytes[k] = alphabet[v%byte(len(alphabet))]
}
return string(bytes)
}
func xmlLogoutRequest(ticket string) ([]byte, error) {
l := &logoutRequest{
Version: "2.0",
IssueInstant: time.Now().UTC(),
ID: newLogoutRequestId(),
NameID: "@NOT_USED@",
SessionIndex: ticket,
}
l.RawIssueInstant = l.IssueInstant.Format(time.RFC1123Z)
return xml.MarshalIndent(l, "", " ")
}

60
vendor/gopkg.in/cas.v2/memory_store.go generated vendored Normal file
View file

@ -0,0 +1,60 @@
package cas
import (
"sync"
)
// MemoryStore implements the TicketStore interface storing ticket data in memory.
type MemoryStore struct {
mu sync.RWMutex
store map[string]*AuthenticationResponse
}
// Read returns the AuthenticationResponse for a ticket
func (s *MemoryStore) Read(id string) (*AuthenticationResponse, error) {
s.mu.RLock()
if s.store == nil {
s.mu.RUnlock()
return nil, ErrInvalidTicket
}
t, ok := s.store[id]
s.mu.RUnlock()
if !ok {
return nil, ErrInvalidTicket
}
return t, nil
}
// Write stores the AuthenticationResponse for a ticket
func (s *MemoryStore) Write(id string, ticket *AuthenticationResponse) error {
s.mu.Lock()
if s.store == nil {
s.store = make(map[string]*AuthenticationResponse)
}
s.store[id] = ticket
s.mu.Unlock()
return nil
}
// Delete removes the AuthenticationResponse for a ticket
func (s *MemoryStore) Delete(id string) error {
s.mu.Lock()
delete(s.store, id)
s.mu.Unlock()
return nil
}
// Clear removes all ticket data
func (s *MemoryStore) Clear() error {
s.mu.Lock()
s.store = nil
s.mu.Unlock()
return nil
}

28
vendor/gopkg.in/cas.v2/middleware.go generated vendored Normal file
View file

@ -0,0 +1,28 @@
package cas
import (
"net/http"
"github.com/golang/glog"
)
func (c *Client) Handler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if glog.V(2) {
glog.Infof("cas: handling %v request for %v", r.Method, r.URL)
}
setClient(r, c)
if !IsAuthenticated(r) {
RedirectToLogin(w, r)
return
}
if r.URL.Path == "/logout" {
RedirectToLogout(w, r)
return
}
h.ServeHTTP(w, r)
})
}

28
vendor/gopkg.in/cas.v2/sanitise.go generated vendored Normal file
View file

@ -0,0 +1,28 @@
package cas
import (
"net/url"
)
var (
urlCleanParameters = []string{"gateway", "renew", "service", "ticket"}
)
// sanitisedURL cleans a URL of CAS specific parameters
func sanitisedURL(unclean *url.URL) *url.URL {
// Shouldn't be any errors parsing an existing *url.URL
u, _ := url.Parse(unclean.String())
q := u.Query()
for _, param := range urlCleanParameters {
q.Del(param)
}
u.RawQuery = q.Encode()
return u
}
// sanitisedURLString cleans a URL and returns its string value
func sanitisedURLString(unclean *url.URL) string {
return sanitisedURL(unclean).String()
}

177
vendor/gopkg.in/cas.v2/service_response.go generated vendored Normal file
View file

@ -0,0 +1,177 @@
package cas
import (
"encoding/xml"
"fmt"
"reflect"
"strings"
"time"
"github.com/golang/glog"
"gopkg.in/yaml.v2"
)
// AuthenticationError Code values
const (
INVALID_REQUEST = "INVALID_REQUEST"
INVALID_TICKET_SPEC = "INVALID_TICKET_SPEC"
UNAUTHORIZED_SERVICE = "UNAUTHORIZED_SERVICE"
UNAUTHORIZED_SERVICE_PROXY = "UNAUTHORIZED_SERVICE_PROXY"
INVALID_PROXY_CALLBACK = "INVALID_PROXY_CALLBACK"
INVALID_TICKET = "INVALID_TICKET"
INVALID_SERVICE = "INVALID_SERVICE"
INTERNAL_ERROR = "INTERNAL_ERROR"
)
// AuthenticationError represents a CAS AuthenticationFailure response
type AuthenticationError struct {
Code string
Message string
}
// AuthenticationError provides a differentiator for casting.
func (e AuthenticationError) AuthenticationError() bool {
return true
}
// Error returns the AuthenticationError as a string
func (e AuthenticationError) Error() string {
return fmt.Sprintf("%s: %s", e.Code, e.Message)
}
// AuthenticationResponse captures authenticated user information
type AuthenticationResponse struct {
User string // Users login name
ProxyGrantingTicket string // Proxy Granting Ticket
Proxies []string // List of proxies
AuthenticationDate time.Time // Time at which authentication was performed
IsNewLogin bool // Whether new authentication was used to grant the service ticket
IsRememberedLogin bool // Whether a long term token was used to grant the service ticket
MemberOf []string // List of groups which the user is a member of
Attributes UserAttributes // Additional information about the user
}
// UserAttributes represents additional data about the user
type UserAttributes map[string][]string
// Get retrieves an attribute by name.
//
// Attributes are stored in arrays. Get will only return the first element.
func (a UserAttributes) Get(name string) string {
if v, ok := a[name]; ok {
return v[0]
}
return ""
}
// Add appends a new attribute.
func (a UserAttributes) Add(name, value string) {
a[name] = append(a[name], value)
}
// ParseServiceResponse returns a successful response or an error
func ParseServiceResponse(data []byte) (*AuthenticationResponse, error) {
var x xmlServiceResponse
if err := xml.Unmarshal(data, &x); err != nil {
return nil, err
}
if x.Failure != nil {
msg := strings.TrimSpace(x.Failure.Message)
err := &AuthenticationError{Code: x.Failure.Code, Message: msg}
return nil, err
}
r := &AuthenticationResponse{
User: x.Success.User,
ProxyGrantingTicket: x.Success.ProxyGrantingTicket,
Attributes: make(UserAttributes),
}
if p := x.Success.Proxies; p != nil {
r.Proxies = p.Proxies
}
if a := x.Success.Attributes; a != nil {
r.AuthenticationDate = a.AuthenticationDate
r.IsRememberedLogin = a.LongTermAuthenticationRequestTokenUsed
r.IsNewLogin = a.IsFromNewLogin
r.MemberOf = a.MemberOf
if a.UserAttributes != nil {
for _, ua := range a.UserAttributes.Attributes {
if ua.Name == "" {
continue
}
r.Attributes.Add(ua.Name, strings.TrimSpace(ua.Value))
}
for _, ea := range a.UserAttributes.AnyAttributes {
r.Attributes.Add(ea.XMLName.Local, strings.TrimSpace(ea.Value))
}
}
if a.ExtraAttributes != nil {
for _, ea := range a.ExtraAttributes {
r.Attributes.Add(ea.XMLName.Local, strings.TrimSpace(ea.Value))
}
}
}
for _, ea := range x.Success.ExtraAttributes {
addRubycasAttribute(r.Attributes, ea.XMLName.Local, strings.TrimSpace(ea.Value))
}
return r, nil
}
// addRubycasAttribute handles RubyCAS style additional attributes.
func addRubycasAttribute(attributes UserAttributes, key, value string) {
if !strings.HasPrefix(value, "---") {
attributes.Add(key, value)
return
}
if value == "--- true" {
attributes.Add(key, "true")
return
}
if value == "--- false" {
attributes.Add(key, "false")
return
}
var decoded interface{}
if err := yaml.Unmarshal([]byte(value), &decoded); err != nil {
attributes.Add(key, err.Error())
return
}
switch reflect.TypeOf(decoded).Kind() {
case reflect.Slice:
s := reflect.ValueOf(decoded)
for i := 0; i < s.Len(); i++ {
e := s.Index(i).Interface()
switch reflect.TypeOf(e).Kind() {
case reflect.String:
attributes.Add(key, e.(string))
}
}
case reflect.String:
s := reflect.ValueOf(decoded).Interface()
attributes.Add(key, s.(string))
default:
if glog.V(2) {
kind := reflect.TypeOf(decoded).Kind()
glog.Warningf("cas: service response: unable to parse %v value: %#v (kind: %v)", key, decoded, kind)
}
}
return
}

27
vendor/gopkg.in/cas.v2/ticket_store.go generated vendored Normal file
View file

@ -0,0 +1,27 @@
package cas
import (
"errors"
)
// TicketStore errors
var (
// Given Ticket is not associated with an AuthenticationResponse
ErrInvalidTicket = errors.New("cas: ticket store: invalid ticket")
)
// TicketStore provides an interface for storing and retrieving service
// ticket data.
type TicketStore interface {
// Read returns the AuthenticationResponse data associated with a ticket identifier.
Read(id string) (*AuthenticationResponse, error)
// Write stores the AuthenticationResponse data received from a ticket validation.
Write(id string, ticket *AuthenticationResponse) error
// Delete removes the AuthenticationResponse data associated with a ticket identifier.
Delete(id string) error
// Clear removes all of the AuthenticationResponse data from the store.
Clear() error
}

95
vendor/gopkg.in/cas.v2/xml_service_response.go generated vendored Normal file
View file

@ -0,0 +1,95 @@
package cas
import (
"encoding/xml"
"time"
)
type xmlServiceResponse struct {
XMLName xml.Name `xml:"http://www.yale.edu/tp/cas serviceResponse"`
Failure *xmlAuthenticationFailure
Success *xmlAuthenticationSuccess
}
type xmlAuthenticationFailure struct {
XMLName xml.Name `xml:"authenticationFailure"`
Code string `xml:"code,attr"`
Message string `xml:",innerxml"`
}
type xmlAuthenticationSuccess struct {
XMLName xml.Name `xml:"authenticationSuccess"`
User string `xml:"user"`
ProxyGrantingTicket string `xml:"proxyGrantingTicket,omitempty"`
Proxies *xmlProxies `xml:"proxies"`
Attributes *xmlAttributes `xml:"attributes"`
ExtraAttributes []*xmlAnyAttribute `xml:",any"`
}
type xmlProxies struct {
XMLName xml.Name `xml:"proxies"`
Proxies []string `xml:"proxy"`
}
func (p *xmlProxies) AddProxy(proxy string) {
p.Proxies = append(p.Proxies, proxy)
}
type xmlAttributes struct {
XMLName xml.Name `xml:"attributes"`
AuthenticationDate time.Time `xml:"authenticationDate"`
LongTermAuthenticationRequestTokenUsed bool `xml:"longTermAuthenticationRequestTokenUsed"`
IsFromNewLogin bool `xml:"isFromNewLogin"`
MemberOf []string `xml:"memberOf"`
UserAttributes *xmlUserAttributes
ExtraAttributes []*xmlAnyAttribute `xml:",any"`
}
type xmlUserAttributes struct {
XMLName xml.Name `xml:"userAttributes"`
Attributes []*xmlNamedAttribute `xml:"attribute"`
AnyAttributes []*xmlAnyAttribute `xml:",any"`
}
type xmlNamedAttribute struct {
XMLName xml.Name `xml:"attribute"`
Name string `xml:"name,attr,omitempty"`
Value string `xml:",innerxml"`
}
type xmlAnyAttribute struct {
XMLName xml.Name
Value string `xml:",chardata"`
}
func (xsr *xmlServiceResponse) marshalXML(indent int) ([]byte, error) {
if indent == 0 {
return xml.Marshal(xsr)
}
indentStr := ""
for i := 0; i < indent; i++ {
indentStr += " "
}
return xml.MarshalIndent(xsr, "", indentStr)
}
func failureServiceResponse(code, message string) *xmlServiceResponse {
return &xmlServiceResponse{
Failure: &xmlAuthenticationFailure{
Code: code,
Message: message,
},
}
}
func successServiceResponse(username, pgt string) *xmlServiceResponse {
return &xmlServiceResponse{
Success: &xmlAuthenticationSuccess{
User: username,
ProxyGrantingTicket: pgt,
},
}
}