diff --git a/nodes/node_balancer/cmd/nodebalancer/access_cache_test.go b/nodes/node_balancer/cmd/nodebalancer/access_cache_test.go new file mode 100644 index 00000000..4772a1f8 --- /dev/null +++ b/nodes/node_balancer/cmd/nodebalancer/access_cache_test.go @@ -0,0 +1,49 @@ +package main + +import ( + "testing" + "time" +) + +func accessCacheSetupSuit(t *testing.T) func(t *testing.T) { + t.Log("Setup suit") + + CreateAccessCache() + + return func(t *testing.T) { + t.Log("Teardown suit") + } +} + +func TestAddAccessToCache(t *testing.T) { + teardownSuit := accessCacheSetupSuit(t) + defer teardownSuit(t) + + tsNow := time.Now().Unix() + + var cases = []struct { + prop ClientAccess + expected bool + }{ + { + prop: ClientAccess{ClientResourceData: ClientResourceData{AccessID: "7378e2b2-b6ac-4738-bf34-fe39aa0d19e9"}}, + expected: true, + }, + { + prop: ClientAccess{ClientResourceData: ClientResourceData{AccessID: "000000000000000000000000000000000000"}}, + expected: false, + }, + { + prop: ClientAccess{ClientResourceData: ClientResourceData{Name: "name-1"}}, + expected: false, + }, + } + for _, c := range cases { + accessId := c.prop.ClientResourceData.AccessID + accessCache.AddAccessToCache(c.prop, tsNow) + if accessCache.isAccessIdInCache(accessId) != c.expected { + t.Logf("Access %s not found in access cache", accessId) + t.Fatal() + } + } +} diff --git a/nodes/node_balancer/cmd/nodebalancer/clients_test.go b/nodes/node_balancer/cmd/nodebalancer/clients_test.go index 4eecd132..5d19d871 100644 --- a/nodes/node_balancer/cmd/nodebalancer/clients_test.go +++ b/nodes/node_balancer/cmd/nodebalancer/clients_test.go @@ -1,4 +1,3 @@ -// TODO(kompotkot): Re-write tests for client package main import ( @@ -7,7 +6,7 @@ import ( "time" ) -func setupSuit(t *testing.T) func(t *testing.T) { +func clientsSetupSuit(t *testing.T) func(t *testing.T) { t.Log("Setup suit") supportedBlockchains = map[string]bool{"ethereum": true} @@ -18,7 +17,7 @@ func setupSuit(t *testing.T) func(t *testing.T) { } func TestAddClientNode(t *testing.T) { - teardownSuit := setupSuit(t) + teardownSuit := clientsSetupSuit(t) defer teardownSuit(t) var cases = []struct { @@ -44,7 +43,7 @@ func TestAddClientNode(t *testing.T) { } func TestGetClientNode(t *testing.T) { - teardownSuit := setupSuit(t) + teardownSuit := clientsSetupSuit(t) defer teardownSuit(t) ts := time.Now().Unix() @@ -75,7 +74,7 @@ func TestGetClientNode(t *testing.T) { } func TestCleanInactiveClientNodes(t *testing.T) { - teardownSuit := setupSuit(t) + teardownSuit := clientsSetupSuit(t) defer teardownSuit(t) ts := time.Now().Unix() diff --git a/nodes/node_balancer/cmd/nodebalancer/middleware.go b/nodes/node_balancer/cmd/nodebalancer/middleware.go index c9a85c88..486e6d97 100644 --- a/nodes/node_balancer/cmd/nodebalancer/middleware.go +++ b/nodes/node_balancer/cmd/nodebalancer/middleware.go @@ -103,7 +103,13 @@ func (ac *AccessCache) UpdateAccessAtCache(accessId, authorizationToken, request } // Add new user access identifier with data to cache -func (ac *AccessCache) AddAccessToCache(clientAccess ClientAccess, tsNow int64) { +func (ac *AccessCache) AddAccessToCache(clientAccess ClientAccess, tsNow int64) error { + _, err := uuid.Parse(clientAccess.ClientResourceData.AccessID) + if err != nil { + log.Printf("Access ID %s is not valid UUID, err: %v", clientAccess.ClientResourceData.AccessID, err) + return fmt.Errorf("access ID is not valid UUID") + } + ac.mux.Lock() access := &ClientAccess{ ResourceID: clientAccess.ResourceID, @@ -137,6 +143,8 @@ func (ac *AccessCache) AddAccessToCache(clientAccess ClientAccess, tsNow int64) ac.authorizationTokens[clientAccess.authorizationToken] = access } ac.mux.Unlock() + + return nil } // Check each access id in cache if it exceeds lifetime @@ -200,10 +208,9 @@ func initCacheCleaning(debug bool) { } } -// fetchResources fetch resources with access ID or authorization token and generate new one if there no one -func fetchResource(accessID, authorizationToken string, tsNow int64) (*brood.Resource, error) { +// fetchClientAccessFromResources get resources with access ID or authorization token and generate new one if there no one +func fetchClientAccessFromResources(accessID, authorizationToken string, tsNow int64) (*ClientAccess, error) { var err error - var resources brood.Resources queryParameters := map[string]string{"type": BUGOUT_RESOURCE_TYPE_NODEBALANCER_ACCESS} if accessID != "" { @@ -215,6 +222,7 @@ func fetchResource(accessID, authorizationToken string, tsNow int64) (*brood.Res token = authorizationToken } + var resources brood.Resources resources, err = bugoutClient.Brood.GetResources( token, NB_APPLICATION_ID, @@ -277,7 +285,20 @@ func fetchResource(accessID, authorizationToken string, tsNow int64) (*brood.Res return nil, fmt.Errorf("there are no provided access identifier") } - return &resources.Resources[0], nil + var clientAccessRaw ClientAccess + resourceData, err := json.Marshal(&resources.Resources[0].ResourceData) + if err != nil { + log.Printf("Unable to parse resource data to access identifier, err: %v", err) + return nil, fmt.Errorf("unable to parse resource data to access identifier") + } + err = json.Unmarshal(resourceData, &clientAccessRaw.ClientResourceData) + if err != nil { + log.Printf("Unable to decode resource data to access identifier, err: %v", err) + return nil, fmt.Errorf("unable to decode resource data to access identifier") + } + clientAccessRaw.ResourceID = resources.Resources[0].Id + + return &clientAccessRaw, nil } // Extract access_id from header and query. Query takes precedence over header. @@ -445,13 +466,13 @@ func accessMiddleware(next http.Handler) http.Handler { authorizationToken = authorizationTokenSlice[1] } - tsNow := time.Now().Unix() - if accessID == "" && authorizationToken == "" { http.Error(w, "No access ID or authorization header passed with request", http.StatusForbidden) return } + tsNow := time.Now().Unix() + // If access id does not belong to internal crawlers, then check cache or find it in Bugout resources if accessID != "" && accessID == NB_CONTROLLER_ACCESS_ID { if stateCLI.enableDebugFlag { @@ -489,31 +510,18 @@ func accessMiddleware(next http.Handler) http.Handler { log.Printf("No access identity found in cache, looking at Brood resources") } - resource, err := fetchResource(accessID, authorizationToken, tsNow) + clientAccessRaw, err := fetchClientAccessFromResources(accessID, authorizationToken, tsNow) if err != nil { http.Error(w, fmt.Sprintf("%v", err), http.StatusForbidden) return } - var clientAccessRaw ClientAccess - resourceData, err := json.Marshal(resource.ResourceData) - if err != nil { - http.Error(w, "Unable to parse resource data to access identifier", http.StatusInternalServerError) - return - } - err = json.Unmarshal(resourceData, &clientAccessRaw.ClientResourceData) - if err != nil { - http.Error(w, "Unable to decode resource data to access identifier", http.StatusInternalServerError) - return - } - isClientAllowedToGetAccess := clientAccessRaw.CheckClientCallPeriodLimits(tsNow) if !isClientAllowedToGetAccess { http.Error(w, "User exceeded limit of calls per period", http.StatusForbidden) return } - currentClientAccess = ClientAccess(clientAccessRaw) - currentClientAccess.ResourceID = resource.Id + currentClientAccess = ClientAccess(*clientAccessRaw) currentClientAccess.authorizationToken = authorizationToken currentClientAccess.requestedDataSource = requestedDataSource @@ -525,7 +533,11 @@ func accessMiddleware(next http.Handler) http.Handler { if stateCLI.enableDebugFlag { log.Printf("Adding new access identifier in cache") } - accessCache.AddAccessToCache(currentClientAccess, tsNow) + err := accessCache.AddAccessToCache(currentClientAccess, tsNow) + if err != nil { + http.Error(w, "Unable to add access ID to cache", http.StatusForbidden) + return + } } } diff --git a/nodes/node_balancer/sample.env b/nodes/node_balancer/sample.env index 1917d023..d4dcb7d1 100644 --- a/nodes/node_balancer/sample.env +++ b/nodes/node_balancer/sample.env @@ -6,3 +6,6 @@ export NB_CONTROLLER_ACCESS_ID="" # Error humbug reporter export HUMBUG_REPORTER_NODE_BALANCER_TOKEN="" + +# Tests +export TEST_NB_AUTH_TOKEN=""