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:
parent
8c99977fc9
commit
8c2df6178d
150 changed files with 43682 additions and 24175 deletions
27
vendor/gopkg.in/cas.v2/.gitignore
generated
vendored
Normal file
27
vendor/gopkg.in/cas.v2/.gitignore
generated
vendored
Normal 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
21
vendor/gopkg.in/cas.v2/LICENSE
generated
vendored
Normal 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
12
vendor/gopkg.in/cas.v2/README.md
generated
vendored
Normal 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
503
vendor/gopkg.in/cas.v2/client.go
generated
vendored
Normal 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
10
vendor/gopkg.in/cas.v2/doc.go
generated
vendored
Normal 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
78
vendor/gopkg.in/cas.v2/handler.go
generated
vendored
Normal 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
152
vendor/gopkg.in/cas.v2/http_helpers.go
generated
vendored
Normal 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
65
vendor/gopkg.in/cas.v2/logout_request.go
generated
vendored
Normal 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
60
vendor/gopkg.in/cas.v2/memory_store.go
generated
vendored
Normal 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
28
vendor/gopkg.in/cas.v2/middleware.go
generated
vendored
Normal 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
28
vendor/gopkg.in/cas.v2/sanitise.go
generated
vendored
Normal 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
177
vendor/gopkg.in/cas.v2/service_response.go
generated
vendored
Normal 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
27
vendor/gopkg.in/cas.v2/ticket_store.go
generated
vendored
Normal 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
95
vendor/gopkg.in/cas.v2/xml_service_response.go
generated
vendored
Normal 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,
|
||||
},
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue