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