Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
dc3365d
commit 573b206
Showing
2 changed files
with
184 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
package gowebdav | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
) | ||
|
||
// BasicAuth structure holds our credentials | ||
type PassportAuth struct { | ||
user string | ||
pw string | ||
header *http.Header | ||
cookies []string | ||
} | ||
|
||
// constructor for PassportAuth automatically creates a | ||
func NewPassportAuth(user, pw, reqUrl string, header *http.Header) (*PassportAuth, error) { | ||
p := &PassportAuth{ | ||
user: user, | ||
pw: pw, | ||
header: header, | ||
} | ||
err := p.genCookies(reqUrl) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return p, nil | ||
} | ||
|
||
// Authorize the current request | ||
func (p *PassportAuth) Authorize(c *http.Client, rq *http.Request, path string) error { | ||
// prevent redirects to detect subsequent authentication requests | ||
rq.Header.Set(XInhibitRedirect, "1") | ||
for _, cookie := range p.cookies { | ||
cookieParts := strings.Split(cookie, ";") | ||
cookieName := strings.Split(cookieParts[0], "=")[0] | ||
cookieValue := strings.Split(cookieParts[0], "=")[1] | ||
|
||
rq.AddCookie(&http.Cookie{ | ||
Name: cookieName, | ||
Value: cookieValue, | ||
}) | ||
} | ||
return nil | ||
} | ||
|
||
// Verify verifies if the authentication is good | ||
func (p *PassportAuth) Verify(c *http.Client, rs *http.Response, path string) (reauth bool, err error) { | ||
if rs.StatusCode == 302 { | ||
if rs.Header.Get("WWW-Authenticate") != "" { | ||
// re-authentication required as we are redirected to the login page | ||
reauth = true | ||
return | ||
} else { | ||
// just a redirect, follow it | ||
header := rs.Request.Header | ||
header.Del(XInhibitRedirect) | ||
|
||
rq := &http.Request{ | ||
Method: rs.Request.Method, | ||
URL: rs.Request.URL, | ||
Body: rs.Request.Body, | ||
Header: header, | ||
} | ||
|
||
rs, err = c.Do(rq) | ||
if err != nil { | ||
return | ||
} | ||
} | ||
} | ||
|
||
// after following any redirects, sanity check the response | ||
if rs.StatusCode == 401 { | ||
err = NewPathError("Authorize", path, rs.StatusCode) | ||
} | ||
return | ||
} | ||
|
||
// Close cleans up all resources | ||
func (p *PassportAuth) Close() error { | ||
return nil | ||
} | ||
|
||
// Clone creates a Copy of itself | ||
func (p *PassportAuth) Clone() Authenticator { | ||
// once created the struct is accessed read-only | ||
// so there is no need to copy it | ||
return p | ||
} | ||
|
||
// String toString | ||
func (p *PassportAuth) String() string { | ||
return fmt.Sprintf("PassportAuth login: %s", p.user) | ||
} | ||
|
||
func (p *PassportAuth) genCookies(partnerUrl string) error { | ||
// For more details refer to: | ||
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pass/2c80637d-438c-4d4b-adc5-903170a779f3 | ||
// Skipping step 1 and 2 as we already have the partner server challenge | ||
|
||
baseAuthenticationServer := p.header.Get("Location") | ||
baseAuthenticationServerURL, err := url.Parse(baseAuthenticationServer) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Skipping step 3 and 4 as we already know that we need and have the user's credentials | ||
// Step 5 (Sign-in request) | ||
authenticationServerUrl := url.URL{ | ||
Scheme: baseAuthenticationServerURL.Scheme, | ||
Host: baseAuthenticationServerURL.Host, | ||
Path: "/login2.srf", | ||
} | ||
|
||
partnerServerChallenge := strings.Split(p.header.Get("WWW-Authenticate"), " ")[1] | ||
|
||
req := http.Request{ | ||
Method: "GET", | ||
URL: &authenticationServerUrl, | ||
Header: http.Header{ | ||
"Authorization": []string{"Passport1.4 sign-in=" + url.QueryEscape(p.user) + ",pwd=" + url.QueryEscape(p.pw) + ",OrgVerb=GET,OrgUrl=" + partnerUrl + "," + partnerServerChallenge}, | ||
}, | ||
} | ||
|
||
client := &http.Client{} | ||
|
||
rs, err := client.Do(&req) | ||
if err != nil { | ||
return err | ||
} | ||
if rs.StatusCode != 200 { | ||
return fmt.Errorf("%d: Incorrect credentials", rs.StatusCode) | ||
} | ||
|
||
// Step 6 (Token Response from Authentication Server) | ||
tokenResponseHeader := rs.Header.Get("Authentication-Info") | ||
if tokenResponseHeader == "" { | ||
return fmt.Errorf("no Authentication-Info header found") | ||
} | ||
tokenResponseHeaderList := strings.Split(tokenResponseHeader, ",") | ||
token := "" | ||
for _, tokenResponseHeader := range tokenResponseHeaderList { | ||
if strings.HasPrefix(tokenResponseHeader, "from-PP='") { | ||
token = tokenResponseHeader | ||
break | ||
} | ||
} | ||
if token == "" { | ||
return fmt.Errorf("no token found in Authentication-Info header") | ||
} | ||
|
||
// Step 7 (First Authentication Request to Partner Server) | ||
origUrl, err := url.Parse(partnerUrl) | ||
if err != nil { | ||
return err | ||
} | ||
req = http.Request{ | ||
Method: "GET", | ||
URL: origUrl, | ||
Header: http.Header{ | ||
"Authorization": []string{"Passport1.4 " + token}, | ||
}, | ||
} | ||
|
||
rs, err = client.Do(&req) | ||
if err != nil { | ||
return err | ||
} | ||
if rs.StatusCode != 200 && rs.StatusCode != 302 { | ||
return fmt.Errorf("%d: authentication failed with Partner Server", rs.StatusCode) | ||
} | ||
|
||
// Step 8 (Set Token Message from Partner Server) | ||
p.cookies = rs.Header.Values("Set-Cookie") | ||
|
||
return nil | ||
} |