mirror of
https://github.com/project-zot/zot.git
synced 2026-06-16 04:17:55 +08:00
refactor(extensions)!: refactor the extensions URLs and errors (#1636)
BREAKING CHANGE: The functionality provided by the mgmt endpoint has beed redesigned - see details below BREAKING CHANGE: The API keys endpoint has been moved - see details below BREAKING CHANGE: The mgmt extension config has been removed - endpoint is now enabled by having both the search and the ui extensions enabled BREAKING CHANGE: The API keys configuration has been moved from extensions to http>auth>apikey mgmt and imagetrust extensions: - separate the _zot/ext/mgmt into 3 separate endpoints: _zot/ext/auth, _zot/ext/notation, _zot/ext/cosign - signature verification logic is in a separate `imagetrust` extension - better hanling or errors in case of signature uploads: logging and error codes (more 400 and less 500 errors) - add authz on signature uploads (and add a new middleware in common for this purpose) - remove the mgmt extension configuration - it is now enabled if the UI and the search extensions are enabled userprefs estension: - userprefs are enabled if both search and ui extensions are enabled (as opposed to just search) apikey extension is removed and logic moved into the api folder - Move apikeys code out of pkg/extensions and into pkg/api - Remove apikey configuration options from the extensions configuration and move it inside the http auth section - remove the build label apikeys other changes: - move most of the logic adding handlers to the extensions endpoints out of routes.go and into the extensions files. - add warnings in case the users are still using configurations with the obsolete settings for mgmt and api keys - add a new function in the extension package which could be a single point of starting backgroud tasks for all extensions - more clear methods for verifying specific extensions are enabled - fix http methods paired with the UI handlers - rebuild swagger docs Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
This commit is contained in:
@@ -0,0 +1,617 @@
|
||||
//go:build mgmt
|
||||
// +build mgmt
|
||||
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
guuid "github.com/gofrs/uuid"
|
||||
"github.com/project-zot/mockoidc"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"gopkg.in/resty.v1"
|
||||
|
||||
"zotregistry.io/zot/pkg/api"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
"zotregistry.io/zot/pkg/api/constants"
|
||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
mTypes "zotregistry.io/zot/pkg/meta/types"
|
||||
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
||||
"zotregistry.io/zot/pkg/test"
|
||||
"zotregistry.io/zot/pkg/test/mocks"
|
||||
)
|
||||
|
||||
var ErrUnexpectedError = errors.New("error: unexpected error")
|
||||
|
||||
type (
|
||||
apiKeyResponse struct {
|
||||
mTypes.APIKeyDetails
|
||||
APIKey string `json:"apiKey"`
|
||||
}
|
||||
)
|
||||
|
||||
func TestAllowedMethodsHeaderAPIKey(t *testing.T) {
|
||||
defaultVal := true
|
||||
|
||||
Convey("Test http options response", t, func() {
|
||||
conf := config.New()
|
||||
port := test.GetFreePort()
|
||||
conf.HTTP.Port = port
|
||||
conf.HTTP.Auth.APIKey = defaultVal
|
||||
baseURL := test.GetBaseURL(port)
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
ctlr.Config.Storage.RootDirectory = t.TempDir()
|
||||
|
||||
ctrlManager := test.NewControllerManager(ctlr)
|
||||
|
||||
ctrlManager.StartAndWait(port)
|
||||
defer ctrlManager.StopServer()
|
||||
|
||||
resp, _ := resty.R().Options(baseURL + constants.APIKeyPath)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.Header().Get("Access-Control-Allow-Methods"), ShouldResemble, "POST,DELETE,OPTIONS")
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusNoContent)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIKeys(t *testing.T) {
|
||||
Convey("Make a new controller", t, func() {
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
|
||||
htpasswdPath := test.MakeHtpasswdFile()
|
||||
defer os.Remove(htpasswdPath)
|
||||
|
||||
mockOIDCServer, err := test.MockOIDCRun()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := mockOIDCServer.Shutdown()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
mockOIDCConfig := mockOIDCServer.Config()
|
||||
defaultVal := true
|
||||
|
||||
conf.HTTP.Auth = &config.AuthConfig{
|
||||
HTPasswd: config.AuthHTPasswd{
|
||||
Path: htpasswdPath,
|
||||
},
|
||||
OpenID: &config.OpenIDConfig{
|
||||
Providers: map[string]config.OpenIDProviderConfig{
|
||||
"dex": {
|
||||
ClientID: mockOIDCConfig.ClientID,
|
||||
ClientSecret: mockOIDCConfig.ClientSecret,
|
||||
KeyPath: "",
|
||||
Issuer: mockOIDCConfig.Issuer,
|
||||
Scopes: []string{"openid", "email", "groups"},
|
||||
},
|
||||
},
|
||||
},
|
||||
APIKey: defaultVal,
|
||||
}
|
||||
|
||||
conf.HTTP.AccessControl = &config.AccessControlConfig{}
|
||||
|
||||
conf.Extensions = &extconf.ExtensionConfig{}
|
||||
conf.Extensions.Search = &extconf.SearchConfig{}
|
||||
conf.Extensions.Search.Enable = &defaultVal
|
||||
conf.Extensions.Search.CVE = nil
|
||||
conf.Extensions.UI = &extconf.UIConfig{}
|
||||
conf.Extensions.UI.Enable = &defaultVal
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
dir := t.TempDir()
|
||||
|
||||
ctlr.Config.Storage.RootDirectory = dir
|
||||
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
|
||||
cm.StartServer()
|
||||
defer cm.StopServer()
|
||||
test.WaitTillServerReady(baseURL)
|
||||
|
||||
payload := api.APIKeyPayload{
|
||||
Label: "test",
|
||||
Scopes: []string{"test"},
|
||||
}
|
||||
reqBody, err := json.Marshal(payload)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("API key retrieved with basic auth", func() {
|
||||
// call endpoint with session ( added to client after previous request)
|
||||
resp, err := resty.R().
|
||||
SetBody(reqBody).
|
||||
SetBasicAuth("test", "test").
|
||||
Post(baseURL + constants.APIKeyPath)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||
|
||||
user := mockoidc.DefaultUser()
|
||||
|
||||
// get API key and email from apikey route response
|
||||
var apiKeyResponse apiKeyResponse
|
||||
err = json.Unmarshal(resp.Body(), &apiKeyResponse)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
email := user.Email
|
||||
So(email, ShouldNotBeEmpty)
|
||||
|
||||
resp, err = resty.R().
|
||||
SetBasicAuth("test", apiKeyResponse.APIKey).
|
||||
Get(baseURL + "/v2/_catalog")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
// add another one
|
||||
resp, err = resty.R().
|
||||
SetBody(reqBody).
|
||||
SetBasicAuth("test", "test").
|
||||
Post(baseURL + constants.APIKeyPath)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||
|
||||
err = json.Unmarshal(resp.Body(), &apiKeyResponse)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
resp, err = resty.R().
|
||||
SetBasicAuth("test", apiKeyResponse.APIKey).
|
||||
Get(baseURL + "/v2/_catalog")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
})
|
||||
|
||||
Convey("API key retrieved with openID", func() {
|
||||
client := resty.New()
|
||||
client.SetRedirectPolicy(test.CustomRedirectPolicy(20))
|
||||
|
||||
// first login user
|
||||
resp, err := client.R().
|
||||
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
|
||||
SetQueryParam("provider", "dex").
|
||||
Get(baseURL + constants.LoginPath)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
|
||||
cookies := resp.Cookies()
|
||||
|
||||
// call endpoint without session
|
||||
resp, err = client.R().
|
||||
SetBody(reqBody).
|
||||
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
|
||||
Post(baseURL + constants.APIKeyPath)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
|
||||
client.SetCookies(cookies)
|
||||
|
||||
// call endpoint with session ( added to client after previous request)
|
||||
resp, err = client.R().
|
||||
SetBody(reqBody).
|
||||
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
|
||||
Post(baseURL + constants.APIKeyPath)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||
|
||||
user := mockoidc.DefaultUser()
|
||||
|
||||
// get API key and email from apikey route response
|
||||
var apiKeyResponse apiKeyResponse
|
||||
err = json.Unmarshal(resp.Body(), &apiKeyResponse)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
email := user.Email
|
||||
So(email, ShouldNotBeEmpty)
|
||||
|
||||
resp, err = client.R().
|
||||
SetBasicAuth(email, apiKeyResponse.APIKey).
|
||||
Get(baseURL + "/v2/_catalog")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
// trigger errors
|
||||
ctlr.MetaDB = mocks.MetaDBMock{
|
||||
GetUserAPIKeyInfoFn: func(hashedKey string) (string, error) {
|
||||
return "", ErrUnexpectedError
|
||||
},
|
||||
}
|
||||
|
||||
resp, err = client.R().
|
||||
SetBasicAuth(email, apiKeyResponse.APIKey).
|
||||
Get(baseURL + "/v2/_catalog")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError)
|
||||
|
||||
ctlr.MetaDB = mocks.MetaDBMock{
|
||||
GetUserAPIKeyInfoFn: func(hashedKey string) (string, error) {
|
||||
return user.Email, nil
|
||||
},
|
||||
GetUserGroupsFn: func(ctx context.Context) ([]string, error) {
|
||||
return []string{}, ErrUnexpectedError
|
||||
},
|
||||
}
|
||||
|
||||
resp, err = client.R().
|
||||
SetBasicAuth(email, apiKeyResponse.APIKey).
|
||||
Get(baseURL + "/v2/_catalog")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError)
|
||||
|
||||
ctlr.MetaDB = mocks.MetaDBMock{
|
||||
GetUserAPIKeyInfoFn: func(hashedKey string) (string, error) {
|
||||
return user.Email, nil
|
||||
},
|
||||
UpdateUserAPIKeyLastUsedFn: func(ctx context.Context, hashedKey string) error {
|
||||
return ErrUnexpectedError
|
||||
},
|
||||
}
|
||||
|
||||
resp, err = client.R().
|
||||
SetBasicAuth(email, apiKeyResponse.APIKey).
|
||||
Get(baseURL + "/v2/_catalog")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError)
|
||||
|
||||
client = resty.New()
|
||||
|
||||
// call endpoint without session
|
||||
resp, err = client.R().
|
||||
SetBody(reqBody).
|
||||
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
|
||||
Post(baseURL + constants.APIKeyPath)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
})
|
||||
|
||||
Convey("Login with openid and create API key", func() {
|
||||
client := resty.New()
|
||||
|
||||
// mgmt should work both unauthenticated and authenticated
|
||||
resp, err := client.R().Get(baseURL + constants.FullMgmt)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
client.SetRedirectPolicy(test.CustomRedirectPolicy(20))
|
||||
// first login user
|
||||
resp, err = client.R().
|
||||
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
|
||||
SetQueryParam("provider", "dex").
|
||||
Get(baseURL + constants.LoginPath)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||
|
||||
client.SetCookies(resp.Cookies())
|
||||
|
||||
// call endpoint with session ( added to client after previous request)
|
||||
resp, err = client.R().
|
||||
SetBody(reqBody).
|
||||
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
|
||||
Post(baseURL + constants.APIKeyPath)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||
|
||||
var apiKeyResponse apiKeyResponse
|
||||
err = json.Unmarshal(resp.Body(), &apiKeyResponse)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
user := mockoidc.DefaultUser()
|
||||
email := user.Email
|
||||
So(email, ShouldNotBeEmpty)
|
||||
|
||||
resp, err = client.R().
|
||||
SetBasicAuth(email, apiKeyResponse.APIKey).
|
||||
Get(baseURL + "/v2/_catalog")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
// auth with API key
|
||||
// we need new client without session cookie set
|
||||
client = resty.New()
|
||||
client.SetRedirectPolicy(test.CustomRedirectPolicy(20))
|
||||
|
||||
resp, err = client.R().
|
||||
SetBasicAuth(email, apiKeyResponse.APIKey).
|
||||
Get(baseURL + "/v2/_catalog")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
resp, err = client.R().
|
||||
SetBasicAuth(email, apiKeyResponse.APIKey).
|
||||
Get(baseURL + constants.FullMgmt)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
// invalid api keys
|
||||
resp, err = client.R().
|
||||
SetBasicAuth("invalidEmail", apiKeyResponse.APIKey).
|
||||
Get(baseURL + constants.FullMgmt)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
|
||||
resp, err = client.R().
|
||||
SetBasicAuth(email, "noprefixAPIKey").
|
||||
Get(baseURL + "/v2/_catalog")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
|
||||
resp, err = client.R().
|
||||
SetBasicAuth(email, "zak_notworkingAPIKey").
|
||||
Get(baseURL + "/v2/_catalog")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
|
||||
authzCtxKey := localCtx.GetContextKey()
|
||||
|
||||
acCtx := localCtx.AccessControlContext{
|
||||
Username: email,
|
||||
}
|
||||
|
||||
ctx := context.WithValue(context.Background(), authzCtxKey, acCtx)
|
||||
|
||||
err = ctlr.MetaDB.DeleteUserData(ctx)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
resp, err = client.R().
|
||||
SetBasicAuth(email, apiKeyResponse.APIKey).
|
||||
Get(baseURL + constants.FullMgmt)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError)
|
||||
|
||||
client = resty.New()
|
||||
client.SetRedirectPolicy(test.CustomRedirectPolicy(20))
|
||||
|
||||
// without creds should work
|
||||
resp, err = client.R().Get(baseURL + constants.FullMgmt)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
// login again
|
||||
resp, err = client.R().
|
||||
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
|
||||
SetQueryParam("provider", "dex").
|
||||
Get(baseURL + constants.LoginPath)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||
|
||||
client.SetCookies(resp.Cookies())
|
||||
|
||||
resp, err = client.R().
|
||||
SetBody(reqBody).
|
||||
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
|
||||
Post(baseURL + constants.APIKeyPath)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
|
||||
|
||||
err = json.Unmarshal(resp.Body(), &apiKeyResponse)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// should work with session
|
||||
resp, err = client.R().
|
||||
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
|
||||
Get(baseURL + constants.FullMgmt)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
// should work with api key
|
||||
resp, err = client.R().
|
||||
SetBasicAuth(email, apiKeyResponse.APIKey).
|
||||
Get(baseURL + constants.FullMgmt)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
resp, err = client.R().
|
||||
SetBasicAuth(email, apiKeyResponse.APIKey).
|
||||
Get(baseURL + "/v2/_catalog")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
err = json.Unmarshal(resp.Body(), &apiKeyResponse)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// delete api key
|
||||
resp, err = client.R().
|
||||
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
|
||||
SetQueryParam("id", apiKeyResponse.UUID).
|
||||
Delete(baseURL + constants.APIKeyPath)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
resp, err = client.R().
|
||||
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
|
||||
Delete(baseURL + constants.APIKeyPath)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
|
||||
|
||||
resp, err = client.R().
|
||||
SetBasicAuth(email, apiKeyResponse.APIKey).
|
||||
Get(baseURL + "/v2/_catalog")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
|
||||
resp, err = client.R().
|
||||
SetBasicAuth("test", "test").
|
||||
SetQueryParam("id", apiKeyResponse.UUID).
|
||||
Delete(baseURL + constants.APIKeyPath)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
// unsupported method
|
||||
resp, err = client.R().
|
||||
Put(baseURL + constants.APIKeyPath)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusMethodNotAllowed)
|
||||
})
|
||||
|
||||
Convey("Test error handling when API Key handler reads the request body", func() {
|
||||
request, _ := http.NewRequestWithContext(context.TODO(),
|
||||
http.MethodPost, "baseURL", errReader(0))
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
rthdlr := api.NewRouteHandler(ctlr)
|
||||
rthdlr.CreateAPIKey(response, request)
|
||||
|
||||
resp := response.Result()
|
||||
defer resp.Body.Close()
|
||||
So(resp.StatusCode, ShouldEqual, http.StatusInternalServerError)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIKeysOpenDBError(t *testing.T) {
|
||||
Convey("Test API keys - unable to create database", t, func() {
|
||||
conf := config.New()
|
||||
htpasswdPath := test.MakeHtpasswdFile()
|
||||
defer os.Remove(htpasswdPath)
|
||||
|
||||
mockOIDCServer, err := test.MockOIDCRun()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := mockOIDCServer.Shutdown()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
mockOIDCConfig := mockOIDCServer.Config()
|
||||
defaultVal := true
|
||||
|
||||
conf.HTTP.Auth = &config.AuthConfig{
|
||||
HTPasswd: config.AuthHTPasswd{
|
||||
Path: htpasswdPath,
|
||||
},
|
||||
|
||||
OpenID: &config.OpenIDConfig{
|
||||
Providers: map[string]config.OpenIDProviderConfig{
|
||||
"dex": {
|
||||
ClientID: mockOIDCConfig.ClientID,
|
||||
ClientSecret: mockOIDCConfig.ClientSecret,
|
||||
KeyPath: "",
|
||||
Issuer: mockOIDCConfig.Issuer,
|
||||
Scopes: []string{"openid", "email"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
APIKey: defaultVal,
|
||||
}
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
dir := t.TempDir()
|
||||
|
||||
err = os.Chmod(dir, 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
ctlr.Config.Storage.RootDirectory = dir
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
|
||||
So(func() {
|
||||
cm.StartServer()
|
||||
}, ShouldPanic)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIKeysGeneratorErrors(t *testing.T) {
|
||||
Convey("Test API keys - unable to generate API keys and API Key IDs", t, func() {
|
||||
log := log.NewLogger("debug", "")
|
||||
|
||||
apiKey, apiKeyID, err := api.GenerateAPIKey(guuid.DefaultGenerator, log)
|
||||
So(err, ShouldBeNil)
|
||||
So(apiKey, ShouldNotEqual, "")
|
||||
So(apiKeyID, ShouldNotEqual, "")
|
||||
|
||||
generator := &mockUUIDGenerator{
|
||||
guuid.DefaultGenerator, 0, 0,
|
||||
}
|
||||
|
||||
apiKey, apiKeyID, err = api.GenerateAPIKey(generator, log)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(apiKey, ShouldEqual, "")
|
||||
So(apiKeyID, ShouldEqual, "")
|
||||
|
||||
generator = &mockUUIDGenerator{
|
||||
guuid.DefaultGenerator, 1, 0,
|
||||
}
|
||||
|
||||
apiKey, apiKeyID, err = api.GenerateAPIKey(generator, log)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(apiKey, ShouldEqual, "")
|
||||
So(apiKeyID, ShouldEqual, "")
|
||||
})
|
||||
}
|
||||
|
||||
type mockUUIDGenerator struct {
|
||||
guuid.Generator
|
||||
succeedAttempts int
|
||||
attemptCount int
|
||||
}
|
||||
|
||||
func (gen *mockUUIDGenerator) NewV4() (
|
||||
guuid.UUID, error,
|
||||
) {
|
||||
defer func() {
|
||||
gen.attemptCount += 1
|
||||
}()
|
||||
|
||||
if gen.attemptCount >= gen.succeedAttempts {
|
||||
return guuid.UUID{}, ErrUnexpectedError
|
||||
}
|
||||
|
||||
return guuid.DefaultGenerator.NewV4()
|
||||
}
|
||||
|
||||
type errReader int
|
||||
|
||||
func (errReader) Read(p []byte) (int, error) {
|
||||
return 0, fmt.Errorf("test error") //nolint:goerr113
|
||||
}
|
||||
Reference in New Issue
Block a user