Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions internal/rpcserver/rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -5484,10 +5484,9 @@ func (s *Server) checkAuthUserPass(user, pass, remoteAddr string) (bool, bool) {
// of the server (true) or whether the user is limited (false). The second is
// always false if the first is.
func (s *Server) checkAuth(r *http.Request, require bool) (bool, bool, error) {
// If admin-level RPC user and pass options are not set, this always
// succeeds. This will be the case when TLS client certificates are
// being used for authentication.
if s.authsha == ([32]byte{}) {
// If no RPC credentials are set this always succeeds. This is the case when
// TLS client certificates are being used for authentication.
if s.authsha == ([32]byte{}) && s.limitauthsha == ([32]byte{}) {
return true, true, nil
}

Expand Down Expand Up @@ -5915,7 +5914,8 @@ func (s *Server) route(ctx context.Context) *http.Server {
// Keep track of the number of connected clients.
s.incrementClients()
defer s.decrementClients()
_, isAdmin, err := s.checkAuth(r, true)
const required = true
_, isAdmin, err := s.checkAuth(r, required)
if err != nil {
jsonAuthFail(w)
return
Expand All @@ -5927,7 +5927,8 @@ func (s *Server) route(ctx context.Context) *http.Server {

// Websocket endpoint.
rpcServeMux.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
authenticated, isAdmin, err := s.checkAuth(r, false)
const required = false
authenticated, isAdmin, err := s.checkAuth(r, required)
if err != nil {
jsonAuthFail(w)
return
Expand Down
171 changes: 111 additions & 60 deletions internal/rpcserver/rpcserver_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2016 The btcsuite developers
// Copyright (c) 2017-2022 The Decred developers
// Copyright (c) 2017-2026 The Decred developers

// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
Expand All @@ -13,7 +13,9 @@ import (
"github.com/decred/dcrd/rpc/jsonrpc/types/v4"
)

func TestCheckAuthUserPass(t *testing.T) {
// TestAuth_UserPass_AdminAndLimited validates the user/pass authentication when
// both admin and limited credentials are configured.
func TestAuth_UserPass_AdminAndLimited(t *testing.T) {
s, err := New(&Config{
RPCUser: "user",
RPCPass: "pass",
Expand Down Expand Up @@ -67,74 +69,123 @@ func TestCheckAuthUserPass(t *testing.T) {
},
}
for _, test := range tests {
authed, isAdmin := s.checkAuthUserPass(test.user, test.pass, "addr")
if authed != test.wantAuthed {
t.Errorf("%q: unexpected authed -- got %v, want %v", test.name, authed,
test.wantAuthed)
t.Run(test.name, func(t *testing.T) {
authed, isAdmin := s.checkAuthUserPass(test.user, test.pass, "addr")
if authed != test.wantAuthed {
t.Errorf("unexpected authed -- got %v, want %v", authed,
test.wantAuthed)
}
if isAdmin != test.wantAdmin {
t.Errorf("unexpected isAdmin -- got %v, want %v", isAdmin,
test.wantAdmin)
}
})
}
}

// TestAuth_Header_NoCredentials validates the header authentication when no
// credentials are configured.
func TestAuth_Header_NoCredentials(t *testing.T) {
s, err := New(&Config{})
if err != nil {
t.Fatalf("unable to create RPC server: %v", err)
}
for _, require := range []bool{true, false} {
authed, isAdmin, err := s.checkAuth(&http.Request{}, require)
if !authed {
t.Errorf("unexpected authed -- got %v, want %v", authed, true)
}
if isAdmin != test.wantAdmin {
t.Errorf("%q: unexpected isAdmin -- got %v, want %v", test.name, isAdmin,
test.wantAdmin)
if !isAdmin {
t.Errorf("unexpected isAdmin -- got %v, want %v", isAdmin, true)
}
if err != nil {
t.Errorf("unexpected err -- got %v, want %v", err, nil)
}
}
}

func TestCheckAuth(t *testing.T) {
{
s, err := New(&Config{})
if err != nil {
t.Fatalf("unable to create RPC server: %v", err)
// TestAuth_Header_LimitedOnly validates the header authentication when only
// limited credentials are configured.
func TestAuth_Header_LimitedOnly(t *testing.T) {
s, err := New(&Config{
RPCLimitUser: "limit",
RPCLimitPass: "limit",
})
if err != nil {
t.Fatalf("unable to create RPC server: %v", err)
}

// Reject requests with no auth.
authed, isAdmin, err := s.checkAuth(&http.Request{}, true)
if authed {
t.Errorf("unexpected authed -- got %v, want %v", authed, false)
}
if isAdmin {
t.Errorf("unexpected isAdmin -- got %v, want %v", isAdmin, false)
}
if err == nil {
t.Errorf("unexpected err -- got %v, want auth failure", err)
}

// Accept request with correct auth, ensure not admin.
r := &http.Request{Header: make(map[string][]string, 1)}
r.Header["Authorization"] = []string{
"Basic bGltaXQ6bGltaXQ=", // limit:limit
}
authed, isAdmin, err = s.checkAuth(r, true)
if !authed {
t.Errorf("unexpected authed -- got %v, want %v", authed, true)
}
if isAdmin {
t.Errorf("unexpected isAdmin -- got %v, want %v", isAdmin, false)
}
if err != nil {
t.Errorf("unexpected err -- got %v, want %v", err, nil)
}
}

// TestAuth_Header_Require validates that setting the require flag causes an
// error to be returned if the request does not contain an Authorization header.
func TestAuth_Header_Require(t *testing.T) {
s, err := New(&Config{
RPCUser: "user",
RPCPass: "pass",
RPCLimitUser: "limit",
RPCLimitPass: "limit",
})
if err != nil {
t.Fatalf("unable to create RPC server: %v", err)
}

// Test without Authorization header.
for _, require := range []bool{true, false} {
authed, isAdmin, err := s.checkAuth(&http.Request{}, require)
if authed {
t.Errorf("unexpected authed -- got %v, want %v", authed, false)
}
for i := 0; i <= 1; i++ {
authed, isAdmin, err := s.checkAuth(&http.Request{}, i == 0)
if !authed {
t.Errorf(" unexpected authed -- got %v, want %v", authed, true)
}
if !isAdmin {
t.Errorf("unexpected isAdmin -- got %v, want %v", isAdmin, true)
}
if err != nil {
t.Errorf("unexpected err -- got %v, want %v", err, nil)
}
if isAdmin {
t.Errorf("unexpected isAdmin -- got %v, want %v", isAdmin, false)
}
if require && err == nil {
t.Errorf("unexpected err -- got %v, want auth failure", err)
} else if !require && err != nil {
t.Errorf("unexpected err -- got %v, want <nil>", err)
}
}
{
s, err := New(&Config{
RPCUser: "user",
RPCPass: "pass",
RPCLimitUser: "limit",
RPCLimitPass: "limit",
})
if err != nil {
t.Fatalf("unable to create RPC server: %v", err)

// Test with Authorization header and invalid credentials.
for _, require := range []bool{true, false} {
r := &http.Request{Header: make(map[string][]string, 1)}
r.Header["Authorization"] = []string{"Basic badcredentials"}
authed, isAdmin, err := s.checkAuth(r, require)
if authed {
t.Errorf("unexpected authed -- got %v, want %v", authed, false)
}
for i := 0; i <= 1; i++ {
authed, isAdmin, err := s.checkAuth(&http.Request{}, i == 0)
if authed {
t.Errorf(" unexpected authed -- got %v, want %v", authed, false)
}
if isAdmin {
t.Errorf("unexpected isAdmin -- got %v, want %v", isAdmin, false)
}
if i == 0 && err == nil {
t.Errorf("unexpected err -- got %v, want auth failure", err)
} else if i != 0 && err != nil {
t.Errorf("unexpected err -- got %v, want <nil>", err)
}
if isAdmin {
t.Errorf("unexpected isAdmin -- got %v, want %v", isAdmin, false)
}
for i := 0; i <= 1; i++ {
r := &http.Request{Header: make(map[string][]string, 1)}
r.Header["Authorization"] = []string{"Basic Nothing"}
authed, isAdmin, err := s.checkAuth(r, i == 0)
if authed {
t.Errorf(" unexpected authed -- got %v, want %v", authed, false)
}
if isAdmin {
t.Errorf("unexpected isAdmin -- got %v, want %v", isAdmin, false)
}
if err == nil {
t.Errorf("unexpected err -- got %v, want auth failure", err)
}
if err == nil {
t.Errorf("unexpected err -- got %v, want auth failure", err)
}
}
}
Expand Down