2018-02-04 13:51:08 +00:00
|
|
|
package crowd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/xml"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Session represents a single sign-on (SSO) session in Crowd
|
|
|
|
type Session struct {
|
|
|
|
XMLName struct{} `xml:"session"`
|
|
|
|
Expand string `xml:"expand,attr"`
|
|
|
|
Token string `xml:"token"`
|
|
|
|
User User `xml:"user,omitempty"`
|
|
|
|
Created time.Time `xml:"created-date"`
|
|
|
|
Expires time.Time `xml:"expiry-date"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// session authentication request
|
|
|
|
type sessionAuthReq struct {
|
|
|
|
XMLName struct{} `xml:"authentication-context"`
|
|
|
|
Username string `xml:"username"`
|
|
|
|
Password string `xml:"password"`
|
|
|
|
ValidationFactors []sessionValidationFactor `xml:"validation-factors>validation-factor"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// validation factors for session
|
|
|
|
type sessionValidationFactor struct {
|
|
|
|
XMLName struct{} `xml:"validation-factor"`
|
|
|
|
Name string `xml:"name"`
|
|
|
|
Value string `xml:"value"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// session validation request -> just validation factors
|
|
|
|
type sessionValidationValidationFactor struct {
|
|
|
|
XMLName struct{} `xml:"validation-factors"`
|
|
|
|
ValidationFactors []sessionValidationFactor `xml:"validation-factor"`
|
|
|
|
}
|
|
|
|
|
2019-04-21 17:58:15 +00:00
|
|
|
// NewSession authenticates a user and starts a SSO session if valid.
|
2018-02-04 13:51:08 +00:00
|
|
|
func (c *Crowd) NewSession(user string, pass string, address string) (Session, error) {
|
|
|
|
s := Session{}
|
|
|
|
|
|
|
|
svf := sessionValidationFactor{Name: "remote_address", Value: address}
|
|
|
|
sar := sessionAuthReq{Username: user, Password: pass}
|
|
|
|
sar.ValidationFactors = append(sar.ValidationFactors, svf)
|
|
|
|
|
|
|
|
sarEncoded, err := xml.Marshal(sar)
|
|
|
|
if err != nil {
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
sarBuf := bytes.NewBuffer(sarEncoded)
|
|
|
|
|
|
|
|
url := c.url + "rest/usermanagement/1/session"
|
|
|
|
|
2019-04-21 17:58:15 +00:00
|
|
|
c.Client.Jar = c.cookies
|
2018-02-04 13:51:08 +00:00
|
|
|
req, err := http.NewRequest("POST", url, sarBuf)
|
|
|
|
if err != nil {
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
req.SetBasicAuth(c.user, c.passwd)
|
|
|
|
req.Header.Set("Accept", "application/xml")
|
|
|
|
req.Header.Set("Content-Type", "application/xml")
|
2019-04-21 17:58:15 +00:00
|
|
|
resp, err := c.Client.Do(req)
|
2018-02-04 13:51:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch resp.StatusCode {
|
|
|
|
case 400:
|
|
|
|
er := Error{}
|
|
|
|
err = xml.Unmarshal(body, &er)
|
|
|
|
if err != nil {
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return s, fmt.Errorf("%s", er.Reason)
|
|
|
|
case 201:
|
|
|
|
err = xml.Unmarshal(body, &s)
|
|
|
|
if err != nil {
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
default:
|
2019-04-21 17:58:15 +00:00
|
|
|
return s, fmt.Errorf("request failed: %s", resp.Status)
|
2018-02-04 13:51:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
2019-04-21 17:58:15 +00:00
|
|
|
// ValidateSession validates a SSO token against Crowd. Returns error on failure
|
2018-02-04 13:51:08 +00:00
|
|
|
// or account lockout. Success is a populated Session with nil error.
|
|
|
|
func (c *Crowd) ValidateSession(token string, clientaddr string) (Session, error) {
|
|
|
|
s := Session{}
|
|
|
|
|
|
|
|
svf := sessionValidationFactor{Name: "remote_address", Value: clientaddr}
|
|
|
|
svvf := sessionValidationValidationFactor{}
|
|
|
|
svvf.ValidationFactors = append(svvf.ValidationFactors, svf)
|
|
|
|
|
|
|
|
svvfEncoded, err := xml.Marshal(svvf)
|
|
|
|
if err != nil {
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
svvfBuf := bytes.NewBuffer(svvfEncoded)
|
|
|
|
|
|
|
|
url := c.url + "rest/usermanagement/1/session/" + token
|
|
|
|
|
2019-04-21 17:58:15 +00:00
|
|
|
c.Client.Jar = c.cookies
|
2018-02-04 13:51:08 +00:00
|
|
|
req, err := http.NewRequest("POST", url, svvfBuf)
|
|
|
|
if err != nil {
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
req.SetBasicAuth(c.user, c.passwd)
|
|
|
|
req.Header.Set("Accept", "application/xml")
|
|
|
|
req.Header.Set("Content-Type", "application/xml")
|
2019-04-21 17:58:15 +00:00
|
|
|
resp, err := c.Client.Do(req)
|
2018-02-04 13:51:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch resp.StatusCode {
|
|
|
|
case 400:
|
|
|
|
er := Error{}
|
|
|
|
err = xml.Unmarshal(body, &er)
|
|
|
|
if err != nil {
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return s, fmt.Errorf("%s", er.Reason)
|
|
|
|
case 404:
|
|
|
|
er := Error{}
|
|
|
|
err = xml.Unmarshal(body, &er)
|
|
|
|
if err != nil {
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return s, fmt.Errorf("%s", er.Reason)
|
|
|
|
case 200:
|
|
|
|
err = xml.Unmarshal(body, &s)
|
|
|
|
if err != nil {
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
default:
|
2019-04-21 17:58:15 +00:00
|
|
|
return s, fmt.Errorf("request failed: %s", resp.Status)
|
2018-02-04 13:51:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
2019-04-21 17:58:15 +00:00
|
|
|
// InvalidateSession invalidates SSO session token. Returns error on failure.
|
2018-02-04 13:51:08 +00:00
|
|
|
func (c *Crowd) InvalidateSession(token string) error {
|
2019-04-21 17:58:15 +00:00
|
|
|
c.Client.Jar = c.cookies
|
2018-02-04 13:51:08 +00:00
|
|
|
req, err := http.NewRequest("DELETE", c.url+"rest/usermanagement/1/session/"+token, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
req.SetBasicAuth(c.user, c.passwd)
|
|
|
|
req.Header.Set("Accept", "application/xml")
|
|
|
|
req.Header.Set("Content-Type", "application/xml")
|
2019-04-21 17:58:15 +00:00
|
|
|
resp, err := c.Client.Do(req)
|
2018-02-04 13:51:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != 204 {
|
2019-04-21 17:58:15 +00:00
|
|
|
return fmt.Errorf("request failed: %s", resp.Status)
|
2018-02-04 13:51:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-04-21 17:58:15 +00:00
|
|
|
// GetSession gets SSO session information by token
|
2018-02-04 13:51:08 +00:00
|
|
|
func (c *Crowd) GetSession(token string) (s Session, err error) {
|
2019-04-21 17:58:15 +00:00
|
|
|
c.Client.Jar = c.cookies
|
2018-02-04 13:51:08 +00:00
|
|
|
url := c.url + "rest/usermanagement/1/session/" + token
|
|
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
|
|
if err != nil {
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
req.SetBasicAuth(c.user, c.passwd)
|
|
|
|
req.Header.Set("Accept", "application/xml")
|
|
|
|
req.Header.Set("Content-Type", "application/xml")
|
2019-04-21 17:58:15 +00:00
|
|
|
resp, err := c.Client.Do(req)
|
2018-02-04 13:51:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
switch resp.StatusCode {
|
|
|
|
case 404:
|
|
|
|
return s, fmt.Errorf("session not found")
|
|
|
|
case 200:
|
|
|
|
// fall through switch without returning
|
|
|
|
default:
|
2019-04-21 17:58:15 +00:00
|
|
|
return s, fmt.Errorf("request failed: %s", resp.Status)
|
2018-02-04 13:51:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = xml.Unmarshal(body, &s)
|
|
|
|
if err != nil {
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return s, nil
|
|
|
|
}
|