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 }