2023-08-02 13:58:34 -05:00
|
|
|
//go:build mgmt
|
|
|
|
// +build mgmt
|
2023-07-07 11:27:10 -05:00
|
|
|
|
2023-08-02 13:58:34 -05:00
|
|
|
package api_test
|
2023-07-07 11:27:10 -05:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2023-08-02 13:58:34 -05:00
|
|
|
"fmt"
|
2023-07-07 11:27:10 -05:00
|
|
|
"net/http"
|
2023-08-02 13:58:34 -05:00
|
|
|
"net/http/httptest"
|
2023-07-07 11:27:10 -05:00
|
|
|
"os"
|
|
|
|
"testing"
|
|
|
|
|
2023-08-02 13:58:34 -05:00
|
|
|
guuid "github.com/gofrs/uuid"
|
2023-07-07 11:27:10 -05:00
|
|
|
"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"
|
2023-08-02 13:58:34 -05:00
|
|
|
"zotregistry.io/zot/pkg/log"
|
2023-07-18 12:27:26 -05:00
|
|
|
mTypes "zotregistry.io/zot/pkg/meta/types"
|
2023-07-07 11:27:10 -05:00
|
|
|
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
|
|
|
"zotregistry.io/zot/pkg/test"
|
|
|
|
"zotregistry.io/zot/pkg/test/mocks"
|
|
|
|
)
|
|
|
|
|
2023-08-02 13:58:34 -05:00
|
|
|
var ErrUnexpectedError = errors.New("error: unexpected error")
|
|
|
|
|
2023-07-07 11:27:10 -05:00
|
|
|
type (
|
|
|
|
apiKeyResponse struct {
|
2023-07-18 12:27:26 -05:00
|
|
|
mTypes.APIKeyDetails
|
2023-07-07 11:27:10 -05:00
|
|
|
APIKey string `json:"apiKey"`
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2023-08-02 13:58:34 -05:00
|
|
|
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)
|
|
|
|
})
|
|
|
|
}
|
2023-07-07 11:27:10 -05:00
|
|
|
|
|
|
|
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()
|
2023-08-02 13:58:34 -05:00
|
|
|
defaultVal := true
|
|
|
|
|
2023-07-07 11:27:10 -05:00
|
|
|
conf.HTTP.Auth = &config.AuthConfig{
|
|
|
|
HTPasswd: config.AuthHTPasswd{
|
|
|
|
Path: htpasswdPath,
|
|
|
|
},
|
|
|
|
OpenID: &config.OpenIDConfig{
|
|
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
2023-08-24 04:33:35 -05:00
|
|
|
"oidc": {
|
2023-07-07 11:27:10 -05:00
|
|
|
ClientID: mockOIDCConfig.ClientID,
|
|
|
|
ClientSecret: mockOIDCConfig.ClientSecret,
|
|
|
|
KeyPath: "",
|
|
|
|
Issuer: mockOIDCConfig.Issuer,
|
|
|
|
Scopes: []string{"openid", "email", "groups"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2023-08-02 13:58:34 -05:00
|
|
|
APIKey: defaultVal,
|
2023-07-07 11:27:10 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
conf.HTTP.AccessControl = &config.AccessControlConfig{}
|
|
|
|
|
2023-08-02 13:58:34 -05:00
|
|
|
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
|
2023-07-07 11:27:10 -05:00
|
|
|
|
|
|
|
ctlr := api.NewController(conf)
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
|
|
|
ctlr.Config.Storage.RootDirectory = dir
|
|
|
|
|
|
|
|
cm := test.NewControllerManager(ctlr)
|
|
|
|
|
|
|
|
cm.StartServer()
|
|
|
|
defer cm.StopServer()
|
|
|
|
test.WaitTillServerReady(baseURL)
|
|
|
|
|
2023-08-02 13:58:34 -05:00
|
|
|
payload := api.APIKeyPayload{
|
2023-07-07 11:27:10 -05:00
|
|
|
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").
|
2023-08-02 13:58:34 -05:00
|
|
|
Post(baseURL + constants.APIKeyPath)
|
2023-07-07 11:27:10 -05:00
|
|
|
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").
|
2023-08-02 13:58:34 -05:00
|
|
|
Post(baseURL + constants.APIKeyPath)
|
2023-07-07 11:27:10 -05:00
|
|
|
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).
|
2023-08-24 04:33:35 -05:00
|
|
|
SetQueryParam("provider", "oidc").
|
2023-07-07 11:27:10 -05:00
|
|
|
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).
|
2023-08-02 13:58:34 -05:00
|
|
|
Post(baseURL + constants.APIKeyPath)
|
2023-07-07 11:27:10 -05:00
|
|
|
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).
|
2023-08-02 13:58:34 -05:00
|
|
|
Post(baseURL + constants.APIKeyPath)
|
2023-07-07 11:27:10 -05:00
|
|
|
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
|
2023-07-18 12:27:26 -05:00
|
|
|
ctlr.MetaDB = mocks.MetaDBMock{
|
2023-07-07 11:27:10 -05:00
|
|
|
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)
|
|
|
|
|
2023-07-18 12:27:26 -05:00
|
|
|
ctlr.MetaDB = mocks.MetaDBMock{
|
2023-07-07 11:27:10 -05:00
|
|
|
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)
|
|
|
|
|
2023-07-18 12:27:26 -05:00
|
|
|
ctlr.MetaDB = mocks.MetaDBMock{
|
2023-07-07 11:27:10 -05:00
|
|
|
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).
|
2023-08-02 13:58:34 -05:00
|
|
|
Post(baseURL + constants.APIKeyPath)
|
2023-07-07 11:27:10 -05:00
|
|
|
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
|
2023-08-02 13:58:34 -05:00
|
|
|
resp, err := client.R().Get(baseURL + constants.FullMgmt)
|
2023-07-07 11:27:10 -05:00
|
|
|
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).
|
2023-08-24 04:33:35 -05:00
|
|
|
SetQueryParam("provider", "oidc").
|
2023-07-07 11:27:10 -05:00
|
|
|
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).
|
2023-08-02 13:58:34 -05:00
|
|
|
Post(baseURL + constants.APIKeyPath)
|
2023-07-07 11:27:10 -05:00
|
|
|
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).
|
2023-08-02 13:58:34 -05:00
|
|
|
Get(baseURL + constants.FullMgmt)
|
2023-07-07 11:27:10 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp, ShouldNotBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
|
|
|
|
|
|
|
// invalid api keys
|
|
|
|
resp, err = client.R().
|
|
|
|
SetBasicAuth("invalidEmail", apiKeyResponse.APIKey).
|
2023-08-02 13:58:34 -05:00
|
|
|
Get(baseURL + constants.FullMgmt)
|
2023-07-07 11:27:10 -05:00
|
|
|
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)
|
|
|
|
|
2023-07-18 12:27:26 -05:00
|
|
|
err = ctlr.MetaDB.DeleteUserData(ctx)
|
2023-07-07 11:27:10 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
resp, err = client.R().
|
|
|
|
SetBasicAuth(email, apiKeyResponse.APIKey).
|
2023-08-02 13:58:34 -05:00
|
|
|
Get(baseURL + constants.FullMgmt)
|
2023-07-07 11:27:10 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp, ShouldNotBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError)
|
|
|
|
|
|
|
|
client = resty.New()
|
|
|
|
client.SetRedirectPolicy(test.CustomRedirectPolicy(20))
|
|
|
|
|
|
|
|
// without creds should work
|
2023-08-02 13:58:34 -05:00
|
|
|
resp, err = client.R().Get(baseURL + constants.FullMgmt)
|
2023-07-07 11:27:10 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp, ShouldNotBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
|
|
|
|
|
|
|
// login again
|
|
|
|
resp, err = client.R().
|
|
|
|
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
|
2023-08-24 04:33:35 -05:00
|
|
|
SetQueryParam("provider", "oidc").
|
2023-07-07 11:27:10 -05:00
|
|
|
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).
|
2023-08-02 13:58:34 -05:00
|
|
|
Post(baseURL + constants.APIKeyPath)
|
2023-07-07 11:27:10 -05:00
|
|
|
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).
|
2023-08-02 13:58:34 -05:00
|
|
|
Get(baseURL + constants.FullMgmt)
|
2023-07-07 11:27:10 -05:00
|
|
|
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).
|
2023-08-02 13:58:34 -05:00
|
|
|
Get(baseURL + constants.FullMgmt)
|
2023-07-07 11:27:10 -05:00
|
|
|
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).
|
2023-08-02 13:58:34 -05:00
|
|
|
Delete(baseURL + constants.APIKeyPath)
|
2023-07-07 11:27:10 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp, ShouldNotBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
|
|
|
|
|
|
|
resp, err = client.R().
|
|
|
|
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
|
2023-08-02 13:58:34 -05:00
|
|
|
Delete(baseURL + constants.APIKeyPath)
|
2023-07-07 11:27:10 -05:00
|
|
|
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).
|
2023-08-02 13:58:34 -05:00
|
|
|
Delete(baseURL + constants.APIKeyPath)
|
2023-07-07 11:27:10 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp, ShouldNotBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
|
|
|
|
|
|
|
// unsupported method
|
|
|
|
resp, err = client.R().
|
2023-08-02 13:58:34 -05:00
|
|
|
Put(baseURL + constants.APIKeyPath)
|
2023-07-07 11:27:10 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp, ShouldNotBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, http.StatusMethodNotAllowed)
|
|
|
|
})
|
2023-08-02 13:58:34 -05:00
|
|
|
|
|
|
|
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)
|
|
|
|
})
|
2023-07-07 11:27:10 -05:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
2023-08-02 13:58:34 -05:00
|
|
|
defaultVal := true
|
|
|
|
|
2023-07-07 11:27:10 -05:00
|
|
|
conf.HTTP.Auth = &config.AuthConfig{
|
|
|
|
HTPasswd: config.AuthHTPasswd{
|
|
|
|
Path: htpasswdPath,
|
|
|
|
},
|
|
|
|
|
|
|
|
OpenID: &config.OpenIDConfig{
|
|
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
2023-08-24 04:33:35 -05:00
|
|
|
"oidc": {
|
2023-07-07 11:27:10 -05:00
|
|
|
ClientID: mockOIDCConfig.ClientID,
|
|
|
|
ClientSecret: mockOIDCConfig.ClientSecret,
|
|
|
|
KeyPath: "",
|
|
|
|
Issuer: mockOIDCConfig.Issuer,
|
|
|
|
Scopes: []string{"openid", "email"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2023-08-02 13:58:34 -05:00
|
|
|
APIKey: defaultVal,
|
2023-07-07 11:27:10 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
})
|
|
|
|
}
|
2023-08-02 13:58:34 -05:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|