mirror of
https://github.com/aclindsa/ofxgo.git
synced 2025-08-01 15:05:19 -04:00
This is unfortunately required to interact with Vanguard because their OFX servers still have not moved to newer ciphers.
143 lines
4.0 KiB
Go
143 lines
4.0 KiB
Go
package ofxgo
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// VanguardClient provides a Client implementation which handles Vanguard's
|
|
// cookie-passing requirements and also enables older, disabled-by-default
|
|
// cipher suites. VanguardClient uses default, non-zero settings, if its fields
|
|
// are not initialized.
|
|
type VanguardClient struct {
|
|
*BasicClient
|
|
}
|
|
|
|
// NewVanguardClient returns a Client interface configured to handle Vanguard's
|
|
// brand of idiosyncrasy
|
|
func NewVanguardClient(bc *BasicClient) Client {
|
|
return &VanguardClient{bc}
|
|
}
|
|
|
|
// vanguardHttpClient returns an http.Client with the default supported
|
|
// ciphers plus the insecure ciphers Vanguard still uses.
|
|
func vanguardHttpClient() *http.Client {
|
|
var clientCiphers []uint16
|
|
|
|
vanguardCiphers := []uint16{
|
|
tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
|
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
|
}
|
|
defaultCiphers := tls.CipherSuites()
|
|
for _, cipher := range defaultCiphers {
|
|
clientCiphers = append(clientCiphers, cipher.ID)
|
|
}
|
|
clientCiphers = append(clientCiphers, vanguardCiphers...)
|
|
|
|
client := &http.Client{
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: &tls.Config{
|
|
CipherSuites: clientCiphers,
|
|
},
|
|
},
|
|
}
|
|
return client
|
|
}
|
|
|
|
// RawRequest is a copy of BasicClient RawRequest with a custom http.Client
|
|
// that enables older cipher suites.
|
|
func (c *VanguardClient) RawRequest(URL string, r io.Reader) (*http.Response, error) {
|
|
if !strings.HasPrefix(URL, "https://") {
|
|
return nil, errors.New("Refusing to send OFX request with possible plain-text password over non-https protocol")
|
|
}
|
|
|
|
request, err := http.NewRequest("POST", URL, r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
request.Header.Set("Content-Type", "application/x-ofx")
|
|
if c.UserAgent != "" {
|
|
request.Header.Set("User-Agent", c.UserAgent)
|
|
}
|
|
client := vanguardHttpClient()
|
|
response, err := client.Do(request)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if response.StatusCode != 200 {
|
|
return response, errors.New("OFXQuery request status: " + response.Status)
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
// rawRequestCookiesInsecureCiphers is RawRequest with the added features of
|
|
// sending cookies and using a custom http.Client that enables older cipher
|
|
// suites which are disabled by default
|
|
func rawRequestCookiesInsecureCiphers(URL string, r io.Reader, cookies []*http.Cookie) (*http.Response, error) {
|
|
if !strings.HasPrefix(URL, "https://") {
|
|
return nil, errors.New("Refusing to send OFX request with possible plain-text password over non-https protocol")
|
|
}
|
|
|
|
request, err := http.NewRequest("POST", URL, r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
request.Header.Set("Content-Type", "application/x-ofx")
|
|
for _, cookie := range cookies {
|
|
request.AddCookie(cookie)
|
|
}
|
|
|
|
client := vanguardHttpClient()
|
|
response, err := client.Do(request)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if response.StatusCode != 200 {
|
|
return nil, errors.New("OFXQuery request status: " + response.Status)
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
// RequestNoParse marshals a Request to XML, makes an HTTP request, and returns
|
|
// the raw HTTP response
|
|
func (c *VanguardClient) RequestNoParse(r *Request) (*http.Response, error) {
|
|
r.SetClientFields(c)
|
|
|
|
b, err := r.Marshal()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
response, err := c.RawRequest(r.URL, b)
|
|
|
|
// Some financial institutions (cough, Vanguard, cough), require a cookie
|
|
// to be set on the http request, or they return empty responses.
|
|
// Fortunately, the initial response contains the cookie we need, so if we
|
|
// detect an empty response with cookies set that didn't have any errors,
|
|
// re-try the request while sending their cookies back to them.
|
|
if response != nil && response.ContentLength <= 0 && len(response.Cookies()) > 0 {
|
|
b, err = r.Marshal()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return rawRequestCookiesInsecureCiphers(r.URL, b, response.Cookies())
|
|
}
|
|
|
|
return response, err
|
|
}
|
|
|
|
// Request marshals a Request to XML, makes an HTTP request, and then
|
|
// unmarshals the response into a Response object.
|
|
func (c *VanguardClient) Request(r *Request) (*Response, error) {
|
|
return clientRequest(c, r)
|
|
}
|