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