Skip to content

Commit

Permalink
feat(auth): add support for MS-PASS
Browse files Browse the repository at this point in the history
  • Loading branch information
Gamer92000 committed Feb 9, 2023
1 parent dc3365d commit 573b206
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 0 deletions.
4 changes: 4 additions & 0 deletions auth.go
Expand Up @@ -75,6 +75,10 @@ func NewAutoAuth(login string, secret string) Authorizer {
return &DigestAuth{login, secret, digestParts(rs)}, nil
})

az.AddAuthenticator("passport1.4", func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) {
return NewPassportAuth(login, secret, rs.Request.URL.String(), &rs.Header)
})

return az
}

Expand Down
180 changes: 180 additions & 0 deletions passportAuth.go
@@ -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
}

0 comments on commit 573b206

Please sign in to comment.