Skip to content

Commit

Permalink
Merge pull request #393 from JettScythe/cookie-auth
Browse files Browse the repository at this point in the history
[Chore] Backport Support Cookie Authorization
  • Loading branch information
cpacia authored Jul 23, 2020
2 parents 5aa76f7 + f988150 commit 5a24f00
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 2 deletions.
38 changes: 38 additions & 0 deletions rpcclient/cookiefile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) 2017 The Namecoin developers
// Copyright (c) 2019 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package rpcclient

import (
"bufio"
"fmt"
"os"
"strings"
)

func readCookieFile(path string) (username, password string, err error) {
f, err := os.Open(path)
if err != nil {
return
}
defer f.Close()

scanner := bufio.NewScanner(f)
scanner.Scan()
err = scanner.Err()
if err != nil {
return
}
s := scanner.Text()

parts := strings.SplitN(s, ":", 2)
if len(parts) != 2 {
err = fmt.Errorf("malformed cookie file")
return
}

username, password = parts[0], parts[1]
return
}
62 changes: 60 additions & 2 deletions rpcclient/infrastructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"net"
"net/http"
"net/url"
"os"
"sync"
"sync/atomic"
"time"
Expand Down Expand Up @@ -825,7 +826,12 @@ func (c *Client) sendPost(jReq *jsonRequest) {
httpReq.Header.Set("Content-Type", "application/json")

// Configure basic access authorization.
httpReq.SetBasicAuth(c.config.User, c.config.Pass)
user, pass, err := c.config.getAuth()
if err != nil {
jReq.responseChan <- &response{result: nil, err: err}
return
}
httpReq.SetBasicAuth(user, pass)

log.Tracef("Sending command [%s] with id %d", jReq.method, jReq.id)
c.sendPostRequest(httpReq, jReq)
Expand Down Expand Up @@ -1070,6 +1076,17 @@ type ConnConfig struct {
// Pass is the passphrase to use to authenticate to the RPC server.
Pass string

// CookiePath is the path to a cookie file containing the username and
// passphrase to use to authenticate to the RPC server. It is used
// instead of User and Pass if non-empty.
CookiePath string

cookieLastCheckTime time.Time
cookieLastModTime time.Time
cookieLastUser string
cookieLastPass string
cookieLastErr error

// Params is the string representing the network that the server is running.
Params string

Expand Down Expand Up @@ -1121,6 +1138,43 @@ type ConnConfig struct {
EnableBCInfoHacks bool
}

// getAuth returns the username and passphrase that will actually be used for
// this connection. This will be the result of checking the cookie if a cookie
// path is configured; if not, it will be the user-configured username and
// passphrase.
func (config *ConnConfig) getAuth() (username, passphrase string, err error) {
// Try username+passphrase auth first.
if config.Pass != "" {
return config.User, config.Pass, nil
}

// If no username or passphrase is set, try cookie auth.
return config.retrieveCookie()
}

// retrieveCookie returns the cookie username and passphrase.
func (config *ConnConfig) retrieveCookie() (username, passphrase string, err error) {
if !config.cookieLastCheckTime.IsZero() && time.Now().Before(config.cookieLastCheckTime.Add(30*time.Second)) {
return config.cookieLastUser, config.cookieLastPass, config.cookieLastErr
}

config.cookieLastCheckTime = time.Now()

st, err := os.Stat(config.CookiePath)
if err != nil {
config.cookieLastErr = err
return config.cookieLastUser, config.cookieLastPass, config.cookieLastErr
}

modTime := st.ModTime()
if !modTime.Equal(config.cookieLastModTime) {
config.cookieLastModTime = modTime
config.cookieLastUser, config.cookieLastPass, config.cookieLastErr = readCookieFile(config.CookiePath)
}

return config.cookieLastUser, config.cookieLastPass, config.cookieLastErr
}

// newHTTPClient returns a new http client that is configured according to the
// proxy and TLS settings in the associated connection configuration.
func newHTTPClient(config *ConnConfig) (*http.Client, error) {
Expand Down Expand Up @@ -1190,7 +1244,11 @@ func dial(config *ConnConfig) (*websocket.Conn, error) {

// The RPC server requires basic authorization, so create a custom
// request header with the Authorization header set.
login := config.User + ":" + config.Pass
user, pass, err := config.getAuth()
if err != nil {
return nil, err
}
login := user + ":" + pass
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login))
requestHeader := make(http.Header)
requestHeader.Add("Authorization", auth)
Expand Down

0 comments on commit 5a24f00

Please sign in to comment.