diff --git a/src/calanonsync/Gopkg.lock b/src/calanonsync/Gopkg.lock index 9fa137c..a063d13 100644 --- a/src/calanonsync/Gopkg.lock +++ b/src/calanonsync/Gopkg.lock @@ -1,6 +1,21 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. +[[projects]] + branch = "master" + name = "github.com/ThomsonReutersEikon/go-ntlm" + packages = [ + "ntlm", + "ntlm/md4" + ] + revision = "2a7c173f9e18233a4ae29891da6a0a63637e2d8d" + +[[projects]] + branch = "master" + name = "github.com/vadimi/go-http-ntlm" + packages = ["."] + revision = "bc5a8d8d91a12dd386d3fa1019abb8bb681bdd41" + [[projects]] branch = "master" name = "golang.org/x/crypto" @@ -19,6 +34,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "724028051592219199897d15df4916e95f132caea6497383bbfabbcdb0672123" + inputs-digest = "c47823b171022aea87501cabc3a33c2794c9352672de5255805a03a96f191f7d" solver-name = "gps-cdcl" solver-version = 1 diff --git a/src/calanonsync/Gopkg.toml b/src/calanonsync/Gopkg.toml index 5a11a9b..c86422b 100644 --- a/src/calanonsync/Gopkg.toml +++ b/src/calanonsync/Gopkg.toml @@ -32,3 +32,11 @@ [[constraint]] branch = "master" name = "golang.org/x/crypto" + +[[constraint]] + branch = "master" + name = "github.com/vadimi/go-http-ntlm" + +[[constraint]] + branch = "master" + name = "github.com/Azure/go-ntlmssp" diff --git a/src/calanonsync/ews.go b/src/calanonsync/ews.go index b328f7d..d2f8820 100644 --- a/src/calanonsync/ews.go +++ b/src/calanonsync/ews.go @@ -6,10 +6,12 @@ import ( "encoding/hex" "encoding/xml" "fmt" + "github.com/vadimi/go-http-ntlm" "io" "net/http" "net/http/cookiejar" "strings" + "sync" "text/template" "time" ) @@ -45,8 +47,64 @@ func (ci CalendarItem) Hash() string { type EWSCalendar struct { httpClient *http.Client url string - username string - password string +} + +type authType int + +const ( + authTypeUnknown authType = iota + authTypeBasic + authTypeNTLM +) + +type EWSRoundTripper struct { + initMutex sync.Mutex + authType authType + username string + password string + delegate http.RoundTripper +} + +func (er EWSRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { + if er.authType == authTypeBasic { + r.SetBasicAuth(er.username, er.password) + } + + resp, err := er.delegate.RoundTrip(r) + if err == nil && resp.StatusCode == http.StatusUnauthorized { + er.initMutex.Lock() + defer er.initMutex.Unlock() + + if er.authType == authTypeUnknown { + // This is a good time to find out what the server prefers. + authHeaders := resp.Header["Www-Authenticate"] + if authHeaders != nil { + for _, h := range authHeaders { + if strings.HasPrefix(h, "BASIC") { + er.authType = authTypeBasic + } else if strings.HasPrefix(h, "NTLM") { + er.authType = authTypeNTLM + break // NTLM is the best we could do + } + } + } + + // So, do we know more than before? If so, try again. + if er.authType > authTypeUnknown { + if er.authType == authTypeNTLM { + // We need to replace the delegator. + er.delegate = &httpntlm.NtlmTransport{ + Domain: "", + User: er.username, + Password: er.password, + } + } + return er.RoundTrip(r) + } + } + } + + return resp, err } func NewEWSCalendar(url, username, password string) *EWSCalendar { @@ -58,10 +116,13 @@ func NewEWSCalendar(url, username, password string) *EWSCalendar { return &EWSCalendar{ httpClient: &http.Client{ Jar: jar, + Transport: EWSRoundTripper{ + username: username, + password: password, + delegate: http.DefaultTransport, + }, }, - url: url, - username: username, - password: password, + url: url, } } @@ -72,7 +133,6 @@ func (e *EWSCalendar) prepareRequest(body io.Reader) (req *http.Request, err err req, err = http.NewRequest(http.MethodPost, e.url, body) req.Header.Set("Content-Type", "text/xml") req.Header.Set("Accept", "text/xml") - req.SetBasicAuth(e.username, e.password) return }