diff --git a/client.go b/client.go index 993152e..1b236ae 100644 --- a/client.go +++ b/client.go @@ -9,6 +9,12 @@ import ( "github.com/golang/glog" ) +type casVersion string + +const CASVERSION1 casVersion = "1.0" +const CASVERSION2 casVersion = "2.0" +const CASVERSION3 casVersion = "3.0" + // Options : Client configuration options type Options struct { URL *url.URL // URL to the CAS service @@ -18,6 +24,7 @@ type Options struct { URLScheme URLScheme // Custom url scheme, can be used to modify the request urls for the client Cookie *http.Cookie // http.Cookie options, uses Path, Domain, MaxAge, HttpOnly, & Secure SessionStore SessionStore + CasVersion casVersion // cas server version } // Client implements the main protocol @@ -31,6 +38,8 @@ type Client struct { sendService bool stValidator *ServiceTicketValidator + + casVersion casVersion // cas server version } // NewClient creates a Client with the provided Options. @@ -78,6 +87,13 @@ func NewClient(options *Options) *Client { } } + var casVersion casVersion + if options.CasVersion != CASVERSION1 && options.CasVersion != CASVERSION2 && options.CasVersion != CASVERSION3 { + casVersion = CASVERSION2 + } else { + casVersion = options.CasVersion + } + return &Client{ tickets: tickets, client: client, @@ -86,6 +102,7 @@ func NewClient(options *Options) *Client { sessions: sessions, sendService: options.SendService, stValidator: NewServiceTicketValidator(client, options.URL), + casVersion: casVersion, } } @@ -170,7 +187,7 @@ func (c *Client) ServiceValidateUrlForRequest(ticket string, r *http.Request) (s if err != nil { return "", err } - return c.stValidator.ServiceValidateUrl(service, ticket) + return c.stValidator.ServiceValidateUrl(service, ticket, c.casVersion) } // ValidateUrlForRequest determines the CAS validate URL for the ticket and http.Request. @@ -221,7 +238,7 @@ func (c *Client) validateTicket(ticket string, service *http.Request) error { return err } - success, err := c.stValidator.ValidateTicket(serviceURL, ticket) + success, err := c.stValidator.ValidateTicket(serviceURL, ticket, c.casVersion) if err != nil { return err } diff --git a/rest_client.go b/rest_client.go index 35ba3ef..8aef5a2 100644 --- a/rest_client.go +++ b/rest_client.go @@ -24,6 +24,7 @@ type RestOptions struct { ServiceURL *url.URL Client *http.Client URLScheme URLScheme + CasVersion casVersion } // RestClient uses the rest protocol provided by cas @@ -32,6 +33,7 @@ type RestClient struct { serviceURL *url.URL client *http.Client stValidator *ServiceTicketValidator + casversion casVersion } // NewRestClient creates a new client for the cas rest protocol with the provided options @@ -53,12 +55,19 @@ func NewRestClient(options *RestOptions) *RestClient { } else { urlScheme = NewDefaultURLScheme(options.CasURL) } + var casVersion casVersion + if options.CasVersion != CASVERSION1 && options.CasVersion != CASVERSION2 && options.CasVersion != CASVERSION3 { + casVersion = CASVERSION2 + } else { + casVersion = options.CasVersion + } return &RestClient{ urlScheme: urlScheme, serviceURL: options.ServiceURL, client: client, stValidator: NewServiceTicketValidator(client, options.CasURL), + casversion: casVersion, } } @@ -149,7 +158,7 @@ func (c *RestClient) RequestServiceTicket(tgt TicketGrantingTicket) (ServiceTick // ValidateServiceTicket validates the service ticket and returns an AuthenticationResponse func (c *RestClient) ValidateServiceTicket(st ServiceTicket) (*AuthenticationResponse, error) { - return c.stValidator.ValidateTicket(c.serviceURL, string(st)) + return c.stValidator.ValidateTicket(c.serviceURL, string(st), c.casversion) } // Logout destroys the given granting ticket diff --git a/service_response.go b/service_response.go index e3a76e2..4940c04 100644 --- a/service_response.go +++ b/service_response.go @@ -3,12 +3,12 @@ package cas import ( "encoding/xml" "fmt" + "github.com/golang/glog" + "gopkg.in/yaml.v2" "reflect" + "regexp" "strings" "time" - - "github.com/golang/glog" - "gopkg.in/yaml.v2" ) // AuthenticationError Code values @@ -23,6 +23,10 @@ const ( INTERNAL_ERROR = "INTERNAL_ERROR" ) +var dateExpr = regexp.MustCompile("\\[.*\\]") +var date3Layout = "2006-01-02T15:04:05.000+08:00" +var date2Layout = "2006-01-02T15:04:05Z" + // AuthenticationError represents a CAS AuthenticationFailure response type AuthenticationError struct { Code string @@ -95,7 +99,16 @@ func ParseServiceResponse(data []byte) (*AuthenticationResponse, error) { } if a := x.Success.Attributes; a != nil { - r.AuthenticationDate = a.AuthenticationDate + authenticationDateStr := a.AuthenticationDateStr + authenticationDateStr = "2015-02-10T14:28:42Z" + var layout string + if dateExpr.MatchString(authenticationDateStr) { + authenticationDateStr = dateExpr.ReplaceAllString(authenticationDateStr, "") + layout = date3Layout + } else { + layout = date2Layout + } + r.AuthenticationDate, _ = time.Parse(layout, authenticationDateStr) r.IsRememberedLogin = a.LongTermAuthenticationRequestTokenUsed r.IsNewLogin = a.IsFromNewLogin r.MemberOf = a.MemberOf diff --git a/service_validate.go b/service_validate.go index 5754bb2..aeca10f 100644 --- a/service_validate.go +++ b/service_validate.go @@ -27,12 +27,16 @@ type ServiceTicketValidator struct { // ValidateTicket validates the service ticket for the given server. The method will try to use the service validate // endpoint of the cas >= 2 protocol, if the service validate endpoint not available, the function will use the cas 1 // validate endpoint. -func (validator *ServiceTicketValidator) ValidateTicket(serviceURL *url.URL, ticket string) (*AuthenticationResponse, error) { +func (validator *ServiceTicketValidator) ValidateTicket(serviceURL *url.URL, ticket string, casVersion casVersion) (*AuthenticationResponse, error) { + if casVersion == CASVERSION1 { + return validator.validateTicketCas1(serviceURL, ticket) + } + if glog.V(2) { glog.Infof("Validating ticket %v for service %v", ticket, serviceURL) } - u, err := validator.ServiceValidateUrl(serviceURL, ticket) + u, err := validator.ServiceValidateUrl(serviceURL, ticket, casVersion) if err != nil { return nil, err } @@ -60,10 +64,11 @@ func (validator *ServiceTicketValidator) ValidateTicket(serviceURL *url.URL, tic } if resp.StatusCode == http.StatusNotFound { - return validator.validateTicketCas1(serviceURL, ticket) + return nil, fmt.Errorf("cas: url not found(404) :%s", u) } body, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() if err != nil { @@ -92,8 +97,14 @@ func (validator *ServiceTicketValidator) ValidateTicket(serviceURL *url.URL, tic // ServiceValidateUrl creates the service validation url for the cas >= 2 protocol. // TODO the function is only exposed, because of the clients ServiceValidateUrl function -func (validator *ServiceTicketValidator) ServiceValidateUrl(serviceURL *url.URL, ticket string) (string, error) { - u, err := validator.casURL.Parse(path.Join(validator.casURL.Path, "serviceValidate")) +func (validator *ServiceTicketValidator) ServiceValidateUrl(serviceURL *url.URL, ticket string, casVersion casVersion) (string, error) { + var uri string + if casVersion == CASVERSION2 { + uri = "serviceValidate" + } else if casVersion == CASVERSION3 { + uri = "p3/serviceValidate" + } + u, err := validator.casURL.Parse(path.Join(validator.casURL.Path, uri)) if err != nil { return "", err } diff --git a/xml_service_response.go b/xml_service_response.go index 5a55369..bf3e86a 100644 --- a/xml_service_response.go +++ b/xml_service_response.go @@ -37,11 +37,12 @@ func (p *xmlProxies) AddProxy(proxy string) { } 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"` + XMLName xml.Name `xml:"attributes"` + AuthenticationDate time.Time + AuthenticationDateStr string `xml:"authenticationDate"` + LongTermAuthenticationRequestTokenUsed bool `xml:"longTermAuthenticationRequestTokenUsed"` + IsFromNewLogin bool `xml:"isFromNewLogin"` + MemberOf []string `xml:"memberOf"` UserAttributes *xmlUserAttributes ExtraAttributes []*xmlAnyAttribute `xml:",any"` }