package httphandlers_test import ( "errors" "net/http" "strings" "testing" "time" "github.com/fiskerinc/cloud-services/pkg/adminroles" "github.com/fiskerinc/cloud-services/pkg/cache" "github.com/fiskerinc/cloud-services/pkg/common" "github.com/fiskerinc/cloud-services/pkg/common/authproviders" c "github.com/fiskerinc/cloud-services/pkg/common/context" "github.com/fiskerinc/cloud-services/pkg/db/queries" "github.com/fiskerinc/cloud-services/pkg/db/queries/mocks" "github.com/fiskerinc/cloud-services/pkg/httphandlers" "github.com/fiskerinc/cloud-services/pkg/redis" "github.com/fiskerinc/cloud-services/pkg/testhelper" ) type testCaseAuthAPIToken struct { RedisClient redis.Client Query queries.APITokensInterface APICalls queries.APICallsInterface RequiredRoles map[string][]adminroles.RoleID JWTAuth bool ExpectedProvider string testhelper.BasicHttpTest } func TestAuthAPIToken(t *testing.T) { adminroles.RoleCreate = adminroles.RoleID("efcc3025-e2d8-4212-8227-805c7be39d2c") adminroles.RoleDelete = adminroles.RoleID("8f78dce7-f5f9-4033-a10c-c9c7408bfcfe") someErr := errors.New("some err") validExpiresAt := time.Now().Add(time.Hour) client := &RedisMockAuthAPIToken{ GetCacheError: redis.ErrNilObject, } db := &mocks.MockAPITokens{ DBMockHelper: mocks.DBMockHelper{ Error: errors.New("token not found"), }, } dbCalls := &mocks.MockAPICalls{ DBMockHelper: mocks.DBMockHelper{Error: nil}, } apiToken := "XXXXXXXXXXXX" req := testhelper.MakeTestRequestWithHeaders(http.MethodGet, "/", map[string]string{cache.ApiKeyHeader: apiToken}, nil) roles := map[string][]adminroles.RoleID{ authproviders.Default: {adminroles.RoleCreate}, } tests := []testCaseAuthAPIToken{ { BasicHttpTest: testhelper.BasicHttpTest{ Name: "Good API Token, no required permission", Request: req, ExpectedStatus: http.StatusOK, ExpectedResponse: `OK`, }, RequiredRoles: nil, RedisClient: client, }, { BasicHttpTest: testhelper.BasicHttpTest{ Name: "Good API Token", Request: req, ExpectedStatus: http.StatusOK, ExpectedResponse: `OK`, }, RequiredRoles: roles, RedisClient: client, Query: &mocks.MockAPITokens{ GetResult: &common.APIToken{ Token: apiToken, Roles: strings.Join([]string{string(adminroles.RoleCreate)}, ","), }, }, APICalls: dbCalls, }, { BasicHttpTest: testhelper.BasicHttpTest{ Name: "Good API Token with expiration", Request: req, ExpectedStatus: http.StatusOK, ExpectedResponse: `OK`, }, RequiredRoles: roles, RedisClient: client, Query: &mocks.MockAPITokens{ GetResult: &common.APIToken{ Token: apiToken, Roles: strings.Join([]string{string(adminroles.RoleCreate)}, ","), ExpiresAt: &validExpiresAt, }, }, APICalls: dbCalls, }, { BasicHttpTest: testhelper.BasicHttpTest{ Name: "Good API Token, without permission", Request: req, ExpectedStatus: http.StatusUnauthorized, ExpectedResponse: `{"message":"missing permission","error":"Unauthorized"}`, }, RequiredRoles: roles, RedisClient: client, Query: &mocks.MockAPITokens{ GetResult: &common.APIToken{ Token: apiToken, Roles: strings.Join([]string{string(adminroles.RoleDelete)}, ","), }, }, APICalls: dbCalls, }, { BasicHttpTest: testhelper.BasicHttpTest{ Name: "Bad API Token", Request: req, ExpectedStatus: http.StatusUnauthorized, ExpectedResponse: `{"message":"token not found","error":"Unauthorized"}`, }, RequiredRoles: roles, RedisClient: client, Query: db, APICalls: dbCalls, }, { BasicHttpTest: testhelper.BasicHttpTest{ Name: "Unknown API Token", Request: testhelper.MakeTestRequestWithHeaders(http.MethodGet, "/", map[string]string{cache.ApiKeyHeader: "abc"}, nil), ExpectedStatus: http.StatusUnauthorized, ExpectedResponse: `{"message":"token not found","error":"Unauthorized"}`, }, RequiredRoles: roles, RedisClient: client, Query: db, APICalls: dbCalls, }, { BasicHttpTest: testhelper.BasicHttpTest{ Name: "No headers", Request: testhelper.MakeTestRequest(http.MethodGet, "/", nil), ExpectedStatus: http.StatusUnauthorized, ExpectedResponse: `{"message":"no api token header","error":"Unauthorized"}`, }, RequiredRoles: roles, RedisClient: client, Query: db, APICalls: dbCalls, }, { BasicHttpTest: testhelper.BasicHttpTest{ Name: "No headers, JWT auth", Request: testhelper.MakeTestRequest(http.MethodGet, "/", nil), ExpectedStatus: http.StatusUnauthorized, ExpectedResponse: `{"message":"no authorization header","error":"Unauthorized"}`, }, JWTAuth: true, RequiredRoles: roles, RedisClient: client, Query: db, APICalls: dbCalls, }, { BasicHttpTest: testhelper.BasicHttpTest{ Name: "No headers, JWT auth, no required roles", Request: testhelper.MakeTestRequest(http.MethodGet, "/", nil), ExpectedStatus: http.StatusOK, ExpectedResponse: `OK`, }, JWTAuth: true, RedisClient: client, Query: db, APICalls: dbCalls, }, { BasicHttpTest: testhelper.BasicHttpTest{ Name: "Failed api calls log", Request: req, ExpectedStatus: http.StatusOK, ExpectedResponse: `OK`, }, RequiredRoles: roles, RedisClient: client, Query: &mocks.MockAPITokens{ GetResult: &common.APIToken{ Token: apiToken, Roles: strings.Join([]string{string(adminroles.RoleCreate)}, ","), ExpiresAt: &validExpiresAt, }, }, APICalls: &mocks.MockAPICalls{ DBMockHelper: mocks.DBMockHelper{ Error: someErr, }, }, }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { handler := setupAuthAPIToken(t, test) testhelper.RunBasicHttpTest(t, test.BasicHttpTest, handler) }) } } func setupAuthAPIToken(t *testing.T, test testCaseAuthAPIToken) http.HandlerFunc { testHandler := func(w http.ResponseWriter, r *http.Request) { if test.ExpectedProvider != "" { if provider, ok := r.Context().Value(c.ProviderKey).(string); !ok || provider != test.ExpectedProvider { t.Errorf(testhelper.TestErrorTemplate, test.Name, test.ExpectedProvider, provider) } } w.Write([]byte(expectedOkBody)) } auth := httphandlers.AuthAPIToken{ APITokens: test.Query, APICalls: test.APICalls, JWTAuth: test.JWTAuth, } return auth.GetHandler(test.RequiredRoles, testHandler) } type RedisMockAuthAPIToken struct { GetCacheError error SetCacheError error redis.Connection } func (m *RedisMockAuthAPIToken) GetCache(key string, dest interface{}, expire int) error { return m.GetCacheError } func (m *RedisMockAuthAPIToken) SetCache(string, interface{}, int) error { return m.SetCacheError }