github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/backend/webdav/odrvcookie/fetch.go (about)

     1  // Package odrvcookie can fetch authentication cookies for a sharepoint webdav endpoint
     2  package odrvcookie
     3  
     4  import (
     5  	"bytes"
     6  	"encoding/xml"
     7  	"html/template"
     8  	"net/http"
     9  	"net/http/cookiejar"
    10  	"net/url"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/ncw/rclone/fs"
    15  	"github.com/ncw/rclone/fs/fshttp"
    16  	"github.com/pkg/errors"
    17  	"golang.org/x/net/publicsuffix"
    18  )
    19  
    20  // CookieAuth hold the authentication information
    21  // These are username and password as well as the authentication endpoint
    22  type CookieAuth struct {
    23  	user     string
    24  	pass     string
    25  	endpoint string
    26  }
    27  
    28  // CookieResponse contains the requested cookies
    29  type CookieResponse struct {
    30  	RtFa    http.Cookie
    31  	FedAuth http.Cookie
    32  }
    33  
    34  // SuccessResponse hold a response from the sharepoint webdav
    35  type SuccessResponse struct {
    36  	XMLName xml.Name            `xml:"Envelope"`
    37  	Succ    SuccessResponseBody `xml:"Body"`
    38  }
    39  
    40  // SuccessResponseBody is the body of a success response, it holds the token
    41  type SuccessResponseBody struct {
    42  	XMLName xml.Name
    43  	Type    string    `xml:"RequestSecurityTokenResponse>TokenType"`
    44  	Created time.Time `xml:"RequestSecurityTokenResponse>Lifetime>Created"`
    45  	Expires time.Time `xml:"RequestSecurityTokenResponse>Lifetime>Expires"`
    46  	Token   string    `xml:"RequestSecurityTokenResponse>RequestedSecurityToken>BinarySecurityToken"`
    47  }
    48  
    49  // reqString is a template that gets populated with the user data in order to retrieve a "BinarySecurityToken"
    50  const reqString = `<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
    51  xmlns:a="http://www.w3.org/2005/08/addressing"
    52  xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
    53  <s:Header>
    54  <a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
    55  <a:ReplyTo>
    56  <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
    57  </a:ReplyTo>
    58  <a:To s:mustUnderstand="1">https://login.microsoftonline.com/extSTS.srf</a:To>
    59  <o:Security s:mustUnderstand="1"
    60   xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
    61  <o:UsernameToken>
    62    <o:Username>{{ .Username }}</o:Username>
    63    <o:Password>{{ .Password }}</o:Password>
    64  </o:UsernameToken>
    65  </o:Security>
    66  </s:Header>
    67  <s:Body>
    68  <t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
    69  <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
    70    <a:EndpointReference>
    71      <a:Address>{{ .Address }}</a:Address>
    72    </a:EndpointReference>
    73  </wsp:AppliesTo>
    74  <t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
    75  <t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
    76  <t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
    77  </t:RequestSecurityToken>
    78  </s:Body>
    79  </s:Envelope>`
    80  
    81  // New creates a new CookieAuth struct
    82  func New(pUser, pPass, pEndpoint string) CookieAuth {
    83  	retStruct := CookieAuth{
    84  		user:     pUser,
    85  		pass:     pPass,
    86  		endpoint: pEndpoint,
    87  	}
    88  
    89  	return retStruct
    90  }
    91  
    92  // Cookies creates a CookieResponse. It fetches the auth token and then
    93  // retrieves the Cookies
    94  func (ca *CookieAuth) Cookies() (*CookieResponse, error) {
    95  	tokenResp, err := ca.getSPToken()
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	return ca.getSPCookie(tokenResp)
   100  }
   101  
   102  func (ca *CookieAuth) getSPCookie(conf *SuccessResponse) (*CookieResponse, error) {
   103  	spRoot, err := url.Parse(ca.endpoint)
   104  	if err != nil {
   105  		return nil, errors.Wrap(err, "Error while constructing endpoint URL")
   106  	}
   107  
   108  	u, err := url.Parse("https://" + spRoot.Host + "/_forms/default.aspx?wa=wsignin1.0")
   109  	if err != nil {
   110  		return nil, errors.Wrap(err, "Error while constructing login URL")
   111  	}
   112  
   113  	// To authenticate with davfs or anything else we need two cookies (rtFa and FedAuth)
   114  	// In order to get them we use the token we got earlier and a cookieJar
   115  	jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	client := &http.Client{
   121  		Jar: jar,
   122  	}
   123  
   124  	// Send the previously acquired Token as a Post parameter
   125  	if _, err = client.Post(u.String(), "text/xml", strings.NewReader(conf.Succ.Token)); err != nil {
   126  		return nil, errors.Wrap(err, "Error while grabbing cookies from endpoint: %v")
   127  	}
   128  
   129  	cookieResponse := CookieResponse{}
   130  	for _, cookie := range jar.Cookies(u) {
   131  		if (cookie.Name == "rtFa") || (cookie.Name == "FedAuth") {
   132  			switch cookie.Name {
   133  			case "rtFa":
   134  				cookieResponse.RtFa = *cookie
   135  			case "FedAuth":
   136  				cookieResponse.FedAuth = *cookie
   137  			}
   138  		}
   139  	}
   140  	return &cookieResponse, nil
   141  }
   142  
   143  func (ca *CookieAuth) getSPToken() (conf *SuccessResponse, err error) {
   144  	reqData := map[string]interface{}{
   145  		"Username": ca.user,
   146  		"Password": ca.pass,
   147  		"Address":  ca.endpoint,
   148  	}
   149  
   150  	t := template.Must(template.New("authXML").Parse(reqString))
   151  
   152  	buf := &bytes.Buffer{}
   153  	if err := t.Execute(buf, reqData); err != nil {
   154  		return nil, errors.Wrap(err, "Error while filling auth token template")
   155  	}
   156  
   157  	// Create and execute the first request which returns an auth token for the sharepoint service
   158  	// With this token we can authenticate on the login page and save the returned cookies
   159  	req, err := http.NewRequest("POST", "https://login.microsoftonline.com/extSTS.srf", buf)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	client := fshttp.NewClient(fs.Config)
   165  	resp, err := client.Do(req)
   166  	if err != nil {
   167  		return nil, errors.Wrap(err, "Error while logging in to endpoint")
   168  	}
   169  	defer fs.CheckClose(resp.Body, &err)
   170  
   171  	respBuf := bytes.Buffer{}
   172  	_, err = respBuf.ReadFrom(resp.Body)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  	s := respBuf.Bytes()
   177  
   178  	conf = &SuccessResponse{}
   179  	err = xml.Unmarshal(s, conf)
   180  	if err != nil {
   181  		// FIXME: Try to parse with FailedResponse struct (check for server error code)
   182  		return nil, errors.Wrap(err, "Error while reading endpoint response")
   183  	}
   184  
   185  	return
   186  }