github.com/core-coin/go-core/v2@v2.1.9/node/rpcstack_test.go (about) 1 // Copyright 2020 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-core library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 package node 18 19 import ( 20 "bytes" 21 "fmt" 22 "net/http" 23 "net/url" 24 "strconv" 25 "strings" 26 "testing" 27 "time" 28 29 "github.com/golang-jwt/jwt/v4" 30 "github.com/gorilla/websocket" 31 "github.com/stretchr/testify/assert" 32 33 "github.com/core-coin/go-core/v2/internal/testlog" 34 "github.com/core-coin/go-core/v2/log" 35 "github.com/core-coin/go-core/v2/rpc" 36 ) 37 38 // TestCorsHandler makes sure CORS are properly handled on the http server. 39 func TestCorsHandler(t *testing.T) { 40 srv := createAndStartServer(t, &httpConfig{CorsAllowedOrigins: []string{"test", "test.com"}}, false, &wsConfig{}) 41 defer srv.stop() 42 url := "http://" + srv.listenAddr() 43 44 resp := rpcRequest(t, url, "origin", "test.com") 45 assert.Equal(t, "test.com", resp.Header.Get("Access-Control-Allow-Origin")) 46 47 resp2 := rpcRequest(t, url, "origin", "bad") 48 assert.Equal(t, "", resp2.Header.Get("Access-Control-Allow-Origin")) 49 } 50 51 // TestVhosts makes sure vhosts are properly handled on the http server. 52 func TestVhosts(t *testing.T) { 53 srv := createAndStartServer(t, &httpConfig{Vhosts: []string{"test"}}, false, &wsConfig{}) 54 defer srv.stop() 55 url := "http://" + srv.listenAddr() 56 57 resp := rpcRequest(t, url, "host", "test") 58 assert.Equal(t, resp.StatusCode, http.StatusOK) 59 60 resp2 := rpcRequest(t, url, "host", "bad") 61 assert.Equal(t, resp2.StatusCode, http.StatusForbidden) 62 } 63 64 type originTest struct { 65 spec string 66 expOk []string 67 expFail []string 68 } 69 70 // splitAndTrim splits input separated by a comma 71 // and trims excessive white space from the substrings. 72 // Copied over from flags.go 73 func splitAndTrim(input string) (ret []string) { 74 l := strings.Split(input, ",") 75 for _, r := range l { 76 r = strings.TrimSpace(r) 77 if len(r) > 0 { 78 ret = append(ret, r) 79 } 80 } 81 return ret 82 } 83 84 // TestWebsocketOrigins makes sure the websocket origins are properly handled on the websocket server. 85 func TestWebsocketOrigins(t *testing.T) { 86 tests := []originTest{ 87 { 88 spec: "*", // allow all 89 expOk: []string{"", "http://test", "https://test", "http://test:8540", "https://test:8540", 90 "http://test.com", "https://foo.test", "http://testa", "http://atestb:8540", "https://atestb:8540"}, 91 }, 92 { 93 spec: "test", 94 expOk: []string{"http://test", "https://test", "http://test:8540", "https://test:8540"}, 95 expFail: []string{"http://test.com", "https://foo.test", "http://testa", "http://atestb:8540", "https://atestb:8540"}, 96 }, 97 // scheme tests 98 { 99 spec: "https://test", 100 expOk: []string{"https://test", "https://test:9999"}, 101 expFail: []string{ 102 "test", // no scheme, required by spec 103 "http://test", // wrong scheme 104 "http://test.foo", "https://a.test.x", // subdomain variatoins 105 "http://testx:8540", "https://xtest:8540"}, 106 }, 107 // ip tests 108 { 109 spec: "https://12.34.56.78", 110 expOk: []string{"https://12.34.56.78", "https://12.34.56.78:8540"}, 111 expFail: []string{ 112 "http://12.34.56.78", // wrong scheme 113 "http://12.34.56.78:443", // wrong scheme 114 "http://1.12.34.56.78", // wrong 'domain name' 115 "http://12.34.56.78.a", // wrong 'domain name' 116 "https://87.65.43.21", "http://87.65.43.21:8540", "https://87.65.43.21:8540"}, 117 }, 118 // port tests 119 { 120 spec: "test:8540", 121 expOk: []string{"http://test:8540", "https://test:8540"}, 122 expFail: []string{ 123 "http://test", "https://test", // spec says port required 124 "http://test:8541", "https://test:8541", // wrong port 125 "http://bad", "https://bad", "http://bad:8540", "https://bad:8540"}, 126 }, 127 // scheme and port 128 { 129 spec: "https://test:8540", 130 expOk: []string{"https://test:8540"}, 131 expFail: []string{ 132 "https://test", // missing port 133 "http://test", // missing port, + wrong scheme 134 "http://test:8540", // wrong scheme 135 "http://test:8541", "https://test:8541", // wrong port 136 "http://bad", "https://bad", "http://bad:8540", "https://bad:8540"}, 137 }, 138 // several allowed origins 139 { 140 spec: "localhost,http://127.0.0.1", 141 expOk: []string{"localhost", "http://localhost", "https://localhost:8443", 142 "http://127.0.0.1", "http://127.0.0.1:8080"}, 143 expFail: []string{ 144 "https://127.0.0.1", // wrong scheme 145 "http://bad", "https://bad", "http://bad:8540", "https://bad:8540"}, 146 }, 147 } 148 for _, tc := range tests { 149 srv := createAndStartServer(t, &httpConfig{}, true, &wsConfig{Origins: splitAndTrim(tc.spec)}) 150 url := fmt.Sprintf("ws://%v", srv.listenAddr()) 151 for _, origin := range tc.expOk { 152 if err := wsRequest(t, url, "Origin", origin); err != nil { 153 t.Errorf("spec '%v', origin '%v': expected ok, got %v", tc.spec, origin, err) 154 } 155 } 156 for _, origin := range tc.expFail { 157 if err := wsRequest(t, url, "Origin", origin); err == nil { 158 t.Errorf("spec '%v', origin '%v': expected not to allow, got ok", tc.spec, origin) 159 } 160 } 161 srv.stop() 162 } 163 } 164 165 // TestIsWebsocket tests if an incoming websocket upgrade request is handled properly. 166 func TestIsWebsocket(t *testing.T) { 167 r, _ := http.NewRequest("GET", "/", nil) 168 169 assert.False(t, isWebsocket(r)) 170 r.Header.Set("upgrade", "websocket") 171 assert.False(t, isWebsocket(r)) 172 r.Header.Set("connection", "upgrade") 173 assert.True(t, isWebsocket(r)) 174 r.Header.Set("connection", "upgrade,keep-alive") 175 assert.True(t, isWebsocket(r)) 176 r.Header.Set("connection", " UPGRADE,keep-alive") 177 assert.True(t, isWebsocket(r)) 178 } 179 180 func Test_checkPath(t *testing.T) { 181 tests := []struct { 182 req *http.Request 183 prefix string 184 expected bool 185 }{ 186 { 187 req: &http.Request{URL: &url.URL{Path: "/test"}}, 188 prefix: "/test", 189 expected: true, 190 }, 191 { 192 req: &http.Request{URL: &url.URL{Path: "/testing"}}, 193 prefix: "/test", 194 expected: true, 195 }, 196 { 197 req: &http.Request{URL: &url.URL{Path: "/"}}, 198 prefix: "/test", 199 expected: false, 200 }, 201 { 202 req: &http.Request{URL: &url.URL{Path: "/fail"}}, 203 prefix: "/test", 204 expected: false, 205 }, 206 { 207 req: &http.Request{URL: &url.URL{Path: "/"}}, 208 prefix: "", 209 expected: true, 210 }, 211 { 212 req: &http.Request{URL: &url.URL{Path: "/fail"}}, 213 prefix: "", 214 expected: false, 215 }, 216 { 217 req: &http.Request{URL: &url.URL{Path: "/"}}, 218 prefix: "/", 219 expected: true, 220 }, 221 { 222 req: &http.Request{URL: &url.URL{Path: "/testing"}}, 223 prefix: "/", 224 expected: true, 225 }, 226 } 227 228 for i, tt := range tests { 229 t.Run(strconv.Itoa(i), func(t *testing.T) { 230 assert.Equal(t, tt.expected, checkPath(tt.req, tt.prefix)) 231 }) 232 } 233 } 234 235 func createAndStartServer(t *testing.T, conf *httpConfig, ws bool, wsConf *wsConfig) *httpServer { 236 t.Helper() 237 238 srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), rpc.DefaultHTTPTimeouts) 239 assert.NoError(t, srv.enableRPC(nil, *conf)) 240 if ws { 241 assert.NoError(t, srv.enableWS(nil, *wsConf)) 242 } 243 assert.NoError(t, srv.setListenAddr("localhost", 0)) 244 assert.NoError(t, srv.start()) 245 return srv 246 } 247 248 // wsRequest attempts to open a WebSocket connection to the given URL. 249 func wsRequest(t *testing.T, url string, extraHeaders ...string) error { 250 t.Helper() 251 //t.Logf("checking WebSocket on %s (origin %q)", url, browserOrigin) 252 253 headers := make(http.Header) 254 // Apply extra headers. 255 if len(extraHeaders)%2 != 0 { 256 panic("odd extraHeaders length") 257 } 258 for i := 0; i < len(extraHeaders); i += 2 { 259 key, value := extraHeaders[i], extraHeaders[i+1] 260 headers.Set(key, value) 261 } 262 conn, _, err := websocket.DefaultDialer.Dial(url, headers) 263 if conn != nil { 264 conn.Close() 265 } 266 return err 267 } 268 269 // rpcRequest performs a JSON-RPC request to the given URL. 270 func rpcRequest(t *testing.T, url string, extraHeaders ...string) *http.Response { 271 t.Helper() 272 273 // Create the request. 274 body := bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":1,"method":"rpc_modules","params":[]}`)) 275 req, err := http.NewRequest("POST", url, body) 276 if err != nil { 277 t.Fatal("could not create http request:", err) 278 } 279 req.Header.Set("content-type", "application/json") 280 281 // Apply extra headers. 282 if len(extraHeaders)%2 != 0 { 283 panic("odd extraHeaders length") 284 } 285 for i := 0; i < len(extraHeaders); i += 2 { 286 key, value := extraHeaders[i], extraHeaders[i+1] 287 if strings.ToLower(key) == "host" { 288 req.Host = value 289 } else { 290 req.Header.Set(key, value) 291 } 292 } 293 294 // Perform the request. 295 t.Logf("checking RPC/HTTP on %s %v", url, extraHeaders) 296 resp, err := http.DefaultClient.Do(req) 297 if err != nil { 298 t.Fatal(err) 299 } 300 if err != nil { 301 t.Fatal(err) 302 } 303 return resp 304 } 305 306 type testClaim map[string]interface{} 307 308 func (testClaim) Valid() error { 309 return nil 310 } 311 312 func TestJWT(t *testing.T) { 313 var secret = []byte("secret") 314 issueToken := func(secret []byte, method jwt.SigningMethod, input map[string]interface{}) string { 315 if method == nil { 316 method = jwt.SigningMethodHS256 317 } 318 ss, _ := jwt.NewWithClaims(method, testClaim(input)).SignedString(secret) 319 return ss 320 } 321 expOk := []string{ 322 fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})), 323 fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() + 4})), 324 fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() - 4})), 325 fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{ 326 "iat": time.Now().Unix(), 327 "exp": time.Now().Unix() + 2, 328 })), 329 fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{ 330 "iat": time.Now().Unix(), 331 "bar": "baz", 332 })), 333 } 334 expFail := []string{ 335 // future 336 fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() + int64(jwtExpiryTimeout.Seconds()) + 1})), 337 // stale 338 fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() - int64(jwtExpiryTimeout.Seconds()) - 1})), 339 // wrong algo 340 fmt.Sprintf("Bearer %v", issueToken(secret, jwt.SigningMethodHS512, testClaim{"iat": time.Now().Unix() + 4})), 341 // expired 342 fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix(), "exp": time.Now().Unix()})), 343 // missing mandatory iat 344 fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{})), 345 // wrong secret 346 fmt.Sprintf("Bearer %v", issueToken([]byte("wrong"), nil, testClaim{"iat": time.Now().Unix()})), 347 fmt.Sprintf("Bearer %v", issueToken([]byte{}, nil, testClaim{"iat": time.Now().Unix()})), 348 fmt.Sprintf("Bearer %v", issueToken(nil, nil, testClaim{"iat": time.Now().Unix()})), 349 // Various malformed syntax 350 fmt.Sprintf("%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})), 351 fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})), 352 fmt.Sprintf("bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})), 353 fmt.Sprintf("Bearer: %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})), 354 fmt.Sprintf("Bearer:%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})), 355 fmt.Sprintf("Bearer\t%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})), 356 fmt.Sprintf("Bearer \t%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})), 357 } 358 srv := createAndStartServer(t, &httpConfig{jwtSecret: []byte("secret")}, 359 true, &wsConfig{Origins: []string{"*"}, jwtSecret: []byte("secret")}) 360 wsUrl := fmt.Sprintf("ws://%v", srv.listenAddr()) 361 htUrl := fmt.Sprintf("http://%v", srv.listenAddr()) 362 363 for i, token := range expOk { 364 if err := wsRequest(t, wsUrl, "Authorization", token); err != nil { 365 t.Errorf("test %d-ws, token '%v': expected ok, got %v", i, token, err) 366 } 367 if resp := rpcRequest(t, htUrl, "Authorization", token); resp.StatusCode != 200 { 368 t.Errorf("test %d-http, token '%v': expected ok, got %v", i, token, resp.StatusCode) 369 } 370 } 371 for i, token := range expFail { 372 if err := wsRequest(t, wsUrl, "Authorization", token); err == nil { 373 t.Errorf("tc %d-ws, token '%v': expected not to allow, got ok", i, token) 374 } 375 if resp := rpcRequest(t, htUrl, "Authorization", token); resp.StatusCode != 403 { 376 t.Errorf("tc %d-http, token '%v': expected not to allow, got %v", i, token, resp.StatusCode) 377 } 378 } 379 srv.stop() 380 }