feat(apikey): added route to list user api keys (#1708)

adding api key expiration date

Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>
This commit is contained in:
peusebiu
2023-08-29 19:38:38 +03:00
committed by GitHub
parent 28858f695f
commit 6926bddd3a
15 changed files with 1132 additions and 153 deletions
+15 -1
View File
@@ -203,7 +203,19 @@ func (amw *AuthnMiddleware) basicAuthn(ctlr *Controller, response http.ResponseW
if storedIdentity == identity {
ctx := getReqContextWithAuthorization(identity, []string{}, request)
err := ctlr.MetaDB.UpdateUserAPIKeyLastUsed(ctx, hashedKey)
// check if api key expired
isExpired, err := ctlr.MetaDB.IsAPIKeyExpired(ctx, hashedKey)
if err != nil {
ctlr.Log.Err(err).Str("identity", identity).Msg("can not verify if api key expired")
return false, err
}
if isExpired {
return false, nil
}
err = ctlr.MetaDB.UpdateUserAPIKeyLastUsed(ctx, hashedKey)
if err != nil {
ctlr.Log.Err(err).Str("identity", identity).Msg("can not update user profile in DB")
@@ -514,6 +526,8 @@ func (rh *RouteHandler) AuthURLHandler() http.HandlerFunc {
http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
response.WriteHeader(http.StatusBadRequest)
})(w, r)
return
}
/* save cookie containing state to later verify it and
+332 -5
View File
@@ -12,6 +12,7 @@ import (
"net/http/httptest"
"os"
"testing"
"time"
guuid "github.com/gofrs/uuid"
"github.com/project-zot/mockoidc"
@@ -38,6 +39,12 @@ type (
}
)
type (
apiKeyListResponse struct {
APIKeys []mTypes.APIKeyDetails `json:"apiKeys"`
}
)
func TestAllowedMethodsHeaderAPIKey(t *testing.T) {
defaultVal := true
@@ -58,7 +65,7 @@ func TestAllowedMethodsHeaderAPIKey(t *testing.T) {
resp, _ := resty.R().Options(baseURL + constants.APIKeyPath)
So(resp, ShouldNotBeNil)
So(resp.Header().Get("Access-Control-Allow-Methods"), ShouldResemble, "POST,DELETE,OPTIONS")
So(resp.Header().Get("Access-Control-Allow-Methods"), ShouldResemble, "GET,POST,DELETE,OPTIONS")
So(resp.StatusCode(), ShouldEqual, http.StatusNoContent)
})
}
@@ -135,7 +142,6 @@ func TestAPIKeys(t *testing.T) {
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").
@@ -161,6 +167,24 @@ func TestAPIKeys(t *testing.T) {
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
// get API key list with basic auth
resp, err = resty.R().
SetBasicAuth("test", "test").
Get(baseURL + constants.APIKeyPath)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
var apiKeyListResponse apiKeyListResponse
err = json.Unmarshal(resp.Body(), &apiKeyListResponse)
So(err, ShouldBeNil)
So(len(apiKeyListResponse.APIKeys), ShouldEqual, 1)
So(apiKeyListResponse.APIKeys[0].CreatedAt, ShouldEqual, apiKeyResponse.APIKeyDetails.CreatedAt)
So(apiKeyListResponse.APIKeys[0].CreatorUA, ShouldEqual, apiKeyResponse.APIKeyDetails.CreatorUA)
So(apiKeyListResponse.APIKeys[0].Label, ShouldEqual, apiKeyResponse.APIKeyDetails.Label)
So(apiKeyListResponse.APIKeys[0].Scopes, ShouldEqual, apiKeyResponse.APIKeyDetails.Scopes)
So(apiKeyListResponse.APIKeys[0].UUID, ShouldEqual, apiKeyResponse.APIKeyDetails.UUID)
// add another one
resp, err = resty.R().
SetBody(reqBody).
@@ -179,9 +203,21 @@ func TestAPIKeys(t *testing.T) {
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
// get API key list with api key auth
resp, err = resty.R().
SetBasicAuth("test", apiKeyResponse.APIKey).
Get(baseURL + constants.APIKeyPath)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
err = json.Unmarshal(resp.Body(), &apiKeyListResponse)
So(err, ShouldBeNil)
So(len(apiKeyListResponse.APIKeys), ShouldEqual, 2)
})
Convey("API key retrieved with openID", func() {
Convey("API key retrieved with openID and with no expire", func() {
client := resty.New()
client.SetRedirectPolicy(test.CustomRedirectPolicy(20))
@@ -197,7 +233,6 @@ func TestAPIKeys(t *testing.T) {
// call endpoint without session
resp, err = client.R().
SetBody(reqBody).
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
Post(baseURL + constants.APIKeyPath)
So(err, ShouldBeNil)
@@ -225,6 +260,25 @@ func TestAPIKeys(t *testing.T) {
email := user.Email
So(email, ShouldNotBeEmpty)
// get API key list
resp, err = client.R().
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
Get(baseURL + constants.APIKeyPath)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
var apiKeyListResponse apiKeyListResponse
err = json.Unmarshal(resp.Body(), &apiKeyListResponse)
So(err, ShouldBeNil)
So(len(apiKeyListResponse.APIKeys), ShouldEqual, 1)
So(apiKeyListResponse.APIKeys[0].CreatedAt, ShouldEqual, apiKeyResponse.APIKeyDetails.CreatedAt)
So(apiKeyListResponse.APIKeys[0].CreatorUA, ShouldEqual, apiKeyResponse.APIKeyDetails.CreatorUA)
So(apiKeyListResponse.APIKeys[0].Label, ShouldEqual, apiKeyResponse.APIKeyDetails.Label)
So(apiKeyListResponse.APIKeys[0].Scopes, ShouldEqual, apiKeyResponse.APIKeyDetails.Scopes)
So(apiKeyListResponse.APIKeys[0].UUID, ShouldEqual, apiKeyResponse.APIKeyDetails.UUID)
resp, err = client.R().
SetBasicAuth(email, apiKeyResponse.APIKey).
Get(baseURL + "/v2/_catalog")
@@ -290,7 +344,16 @@ func TestAPIKeys(t *testing.T) {
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
})
Convey("Login with openid and create API key", func() {
Convey("API key retrieved with openID and with long expire", func() {
payload := api.APIKeyPayload{
Label: "test",
Scopes: []string{"test"},
ExpirationDate: time.Now().Add(time.Hour).Local().Format(constants.APIKeyTimeFormat),
}
reqBody, err := json.Marshal(payload)
So(err, ShouldBeNil)
client := resty.New()
// mgmt should work both unauthenticated and authenticated
@@ -324,6 +387,25 @@ func TestAPIKeys(t *testing.T) {
err = json.Unmarshal(resp.Body(), &apiKeyResponse)
So(err, ShouldBeNil)
// get API key list
resp, err = client.R().
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
Get(baseURL + constants.APIKeyPath)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
var apiKeyListResponse apiKeyListResponse
err = json.Unmarshal(resp.Body(), &apiKeyListResponse)
So(err, ShouldBeNil)
So(len(apiKeyListResponse.APIKeys), ShouldEqual, 1)
So(apiKeyListResponse.APIKeys[0].CreatedAt, ShouldEqual, apiKeyResponse.APIKeyDetails.CreatedAt)
So(apiKeyListResponse.APIKeys[0].CreatorUA, ShouldEqual, apiKeyResponse.APIKeyDetails.CreatorUA)
So(apiKeyListResponse.APIKeys[0].Label, ShouldEqual, apiKeyResponse.APIKeyDetails.Label)
So(apiKeyListResponse.APIKeys[0].Scopes, ShouldEqual, apiKeyResponse.APIKeyDetails.Scopes)
So(apiKeyListResponse.APIKeys[0].UUID, ShouldEqual, apiKeyResponse.APIKeyDetails.UUID)
user := mockoidc.DefaultUser()
email := user.Email
So(email, ShouldNotBeEmpty)
@@ -354,6 +436,18 @@ func TestAPIKeys(t *testing.T) {
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
// get API key list
resp, err = resty.R().
SetBasicAuth(email, apiKeyResponse.APIKey).
Get(baseURL + constants.APIKeyPath)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
err = json.Unmarshal(resp.Body(), &apiKeyListResponse)
So(err, ShouldBeNil)
So(len(apiKeyListResponse.APIKeys), ShouldEqual, 1)
// invalid api keys
resp, err = client.R().
SetBasicAuth("invalidEmail", apiKeyResponse.APIKey).
@@ -433,6 +527,13 @@ func TestAPIKeys(t *testing.T) {
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
resp, err = client.R().
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
Get(baseURL + "/v2/_catalog")
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).
@@ -460,6 +561,14 @@ func TestAPIKeys(t *testing.T) {
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
// apiKey removed, should get 401
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().
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
Delete(baseURL + constants.APIKeyPath)
@@ -474,6 +583,25 @@ func TestAPIKeys(t *testing.T) {
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
resp, err = client.R().
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
Get(baseURL + "/v2/_catalog")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
// get API key list
resp, err = client.R().
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
Get(baseURL + constants.APIKeyPath)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
err = json.Unmarshal(resp.Body(), &apiKeyListResponse)
So(err, ShouldBeNil)
So(len(apiKeyListResponse.APIKeys), ShouldEqual, 0)
resp, err = client.R().
SetBasicAuth("test", "test").
SetQueryParam("id", apiKeyResponse.UUID).
@@ -490,6 +618,205 @@ func TestAPIKeys(t *testing.T) {
So(resp.StatusCode(), ShouldEqual, http.StatusMethodNotAllowed)
})
Convey("API key retrieved with openID and with short expire", func() {
expirationDate := time.Now().Add(1 * time.Second).Local().Round(time.Second)
payload := api.APIKeyPayload{
Label: "test",
Scopes: []string{"test"},
ExpirationDate: expirationDate.Format(constants.APIKeyTimeFormat),
}
reqBody, err := json.Marshal(payload)
So(err, ShouldBeNil)
client := resty.New()
client.SetRedirectPolicy(test.CustomRedirectPolicy(20))
// first login user
resp, err := client.R().
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
SetQueryParam("provider", "oidc").
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)
// get API key list
resp, err = client.R().
SetBasicAuth(email, apiKeyResponse.APIKey).
Get(baseURL + constants.APIKeyPath)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
var apiKeyListResponse apiKeyListResponse
err = json.Unmarshal(resp.Body(), &apiKeyListResponse)
So(err, ShouldBeNil)
So(len(apiKeyListResponse.APIKeys), ShouldEqual, 1)
So(apiKeyListResponse.APIKeys[0].CreatedAt, ShouldEqual, apiKeyResponse.APIKeyDetails.CreatedAt)
So(apiKeyListResponse.APIKeys[0].CreatorUA, ShouldEqual, apiKeyResponse.APIKeyDetails.CreatorUA)
So(apiKeyListResponse.APIKeys[0].Label, ShouldEqual, apiKeyResponse.APIKeyDetails.Label)
So(apiKeyListResponse.APIKeys[0].Scopes, ShouldEqual, apiKeyResponse.APIKeyDetails.Scopes)
So(apiKeyListResponse.APIKeys[0].UUID, ShouldEqual, apiKeyResponse.APIKeyDetails.UUID)
So(apiKeyListResponse.APIKeys[0].IsExpired, ShouldEqual, false)
So(apiKeyListResponse.APIKeys[0].ExpirationDate.Equal(expirationDate), ShouldBeTrue)
resp, err = client.R().
SetBasicAuth(email, apiKeyResponse.APIKey).
Get(baseURL + "/v2/_catalog")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
// sleep past expire time
time.Sleep(1500 * time.Millisecond)
resp, err = client.R().
SetBasicAuth(email, apiKeyResponse.APIKey).
Get(baseURL + "/v2/_catalog")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
// again for coverage
resp, err = client.R().
SetBasicAuth(email, apiKeyResponse.APIKey).
Get(baseURL + "/v2/_catalog")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
// get API key list with session authn
resp, err = client.R().
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
Get(baseURL + constants.APIKeyPath)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
err = json.Unmarshal(resp.Body(), &apiKeyListResponse)
So(err, ShouldBeNil)
So(len(apiKeyListResponse.APIKeys), ShouldEqual, 1)
So(apiKeyListResponse.APIKeys[0].CreatedAt, ShouldEqual, apiKeyResponse.APIKeyDetails.CreatedAt)
So(apiKeyListResponse.APIKeys[0].CreatorUA, ShouldEqual, apiKeyResponse.APIKeyDetails.CreatorUA)
So(apiKeyListResponse.APIKeys[0].Label, ShouldEqual, apiKeyResponse.APIKeyDetails.Label)
So(apiKeyListResponse.APIKeys[0].Scopes, ShouldEqual, apiKeyResponse.APIKeyDetails.Scopes)
So(apiKeyListResponse.APIKeys[0].UUID, ShouldEqual, apiKeyResponse.APIKeyDetails.UUID)
So(apiKeyListResponse.APIKeys[0].IsExpired, ShouldEqual, true)
So(apiKeyListResponse.APIKeys[0].ExpirationDate.Equal(expirationDate), ShouldBeTrue)
// delete expired 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)
// get API key list with session authn
resp, err = client.R().
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
Get(baseURL + constants.APIKeyPath)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
err = json.Unmarshal(resp.Body(), &apiKeyListResponse)
So(err, ShouldBeNil)
So(len(apiKeyListResponse.APIKeys), ShouldEqual, 0)
})
Convey("Create API key with expirationDate before actual date", func() {
expirationDate := time.Now().Add(-5 * time.Second).Local().Round(time.Second)
payload := api.APIKeyPayload{
Label: "test",
Scopes: []string{"test"},
ExpirationDate: expirationDate.Format(constants.APIKeyTimeFormat),
}
reqBody, err := json.Marshal(payload)
So(err, ShouldBeNil)
client := resty.New()
client.SetRedirectPolicy(test.CustomRedirectPolicy(20))
// first login user
resp, err := client.R().
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
SetQueryParam("provider", "oidc").
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.StatusBadRequest)
})
Convey("Create API key with unparsable expirationDate", func() {
expirationDate := time.Now().Add(-5 * time.Second).Local().Round(time.Second)
payload := api.APIKeyPayload{
Label: "test",
Scopes: []string{"test"},
ExpirationDate: expirationDate.Format(time.RFC1123Z),
}
reqBody, err := json.Marshal(payload)
So(err, ShouldBeNil)
client := resty.New()
client.SetRedirectPolicy(test.CustomRedirectPolicy(20))
// first login user
resp, err := client.R().
SetHeader(constants.SessionClientHeaderName, constants.SessionClientHeaderValue).
SetQueryParam("provider", "oidc").
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.StatusBadRequest)
})
Convey("Test error handling when API Key handler reads the request body", func() {
request, _ := http.NewRequestWithContext(context.TODO(),
http.MethodPost, "baseURL", errReader(0))
+3
View File
@@ -1,5 +1,7 @@
package constants
import "time"
const (
ArtifactSpecRoutePrefix = "/oras/artifacts/v1"
RoutePrefix = "/v2"
@@ -20,4 +22,5 @@ const (
SessionClientHeaderValue = "zot-ui"
APIKeysPrefix = "zak_"
CallbackUIQueryParam = "callback_ui"
APIKeyTimeFormat = time.RFC3339
)
+73 -10
View File
@@ -90,10 +90,11 @@ func (rh *RouteHandler) SetupRoutes() {
apiKeyRouter.Use(authHandler)
apiKeyRouter.Use(BaseAuthzHandler(rh.c))
apiKeyRouter.Use(zcommon.ACHeadersMiddleware(rh.c.Config,
http.MethodPost, http.MethodDelete, http.MethodOptions))
http.MethodGet, http.MethodPost, http.MethodDelete, http.MethodOptions))
apiKeyRouter.Use(zcommon.CORSHeadersMiddleware(rh.c.Config.HTTP.AllowOrigin))
apiKeyRouter.Methods(http.MethodPost, http.MethodOptions).HandlerFunc(rh.CreateAPIKey)
apiKeyRouter.Methods(http.MethodGet).HandlerFunc(rh.GetAPIKeys)
apiKeyRouter.Methods(http.MethodDelete).HandlerFunc(rh.RevokeAPIKey)
}
@@ -2029,8 +2030,49 @@ func (rh *RouteHandler) GetOrasReferrers(response http.ResponseWriter, request *
}
type APIKeyPayload struct { //nolint:revive
Label string `json:"label"`
Scopes []string `json:"scopes"`
Label string `json:"label"`
Scopes []string `json:"scopes"`
ExpirationDate string `json:"expirationDate"`
}
// GetAPIKeys godoc
// @Summary Get list of API keys for the current user
// @Description Get list of all API keys for a logged in user
// @Accept json
// @Produce json
// @Success 200 {string} string "ok"
// @Failure 401 {string} string "unauthorized"
// @Failure 500 {string} string "internal server error"
// @Router /auth/apikey [get].
func (rh *RouteHandler) GetAPIKeys(resp http.ResponseWriter, req *http.Request) {
apiKeys, err := rh.c.MetaDB.GetUserAPIKeys(req.Context())
if err != nil {
rh.c.Log.Error().Err(err).Msg("error getting list of API keys for user")
resp.WriteHeader(http.StatusInternalServerError)
return
}
apiKeyResponse := struct {
APIKeys []mTypes.APIKeyDetails `json:"apiKeys"`
}{
APIKeys: apiKeys,
}
json := jsoniter.ConfigCompatibleWithStandardLibrary
data, err := json.Marshal(apiKeyResponse)
if err != nil {
rh.c.Log.Error().Err(err).Msg("unable to marshal api key response")
resp.WriteHeader(http.StatusInternalServerError)
return
}
resp.Header().Set("Content-Type", constants.DefaultMediaType)
resp.WriteHeader(http.StatusOK)
_, _ = resp.Write(data)
}
// CreateAPIKey godoc
@@ -2071,14 +2113,35 @@ func (rh *RouteHandler) CreateAPIKey(resp http.ResponseWriter, req *http.Request
hashedAPIKey := hashUUID(apiKey)
createdAt := time.Now()
// won't expire if no value provided
expirationDate := time.Time{}
if payload.ExpirationDate != "" {
expirationDate, err = time.ParseInLocation(constants.APIKeyTimeFormat, payload.ExpirationDate, time.Local)
if err != nil {
resp.WriteHeader(http.StatusBadRequest)
return
}
if createdAt.After(expirationDate) {
resp.WriteHeader(http.StatusBadRequest)
return
}
}
apiKeyDetails := &mTypes.APIKeyDetails{
CreatedAt: time.Now(),
LastUsed: time.Now(),
CreatorUA: req.UserAgent(),
GeneratedBy: "manual",
Label: payload.Label,
Scopes: payload.Scopes,
UUID: apiKeyID,
CreatedAt: createdAt,
ExpirationDate: expirationDate,
IsExpired: false,
CreatorUA: req.UserAgent(),
GeneratedBy: "manual",
Label: payload.Label,
Scopes: payload.Scopes,
UUID: apiKeyID,
}
err = rh.c.MetaDB.AddUserAPIKey(req.Context(), hashedAPIKey, apiKeyDetails)
+86 -53
View File
@@ -1416,79 +1416,112 @@ func TestRoutes(t *testing.T) {
})
Convey("Test API keys", func() {
var invalid struct{}
Convey("CreateAPIKey invalid access control context", func() {
var invalid struct{}
ctx := context.TODO()
key := localCtx.GetContextKey()
ctx = context.WithValue(ctx, key, invalid)
ctx := context.TODO()
key := localCtx.GetContextKey()
ctx = context.WithValue(ctx, key, invalid)
request, _ := http.NewRequestWithContext(ctx, http.MethodPost, baseURL, bytes.NewReader([]byte{}))
response := httptest.NewRecorder()
request, _ := http.NewRequestWithContext(ctx, http.MethodPost, baseURL, bytes.NewReader([]byte{}))
response := httptest.NewRecorder()
rthdlr.CreateAPIKey(response, request)
rthdlr.CreateAPIKey(response, request)
resp := response.Result()
defer resp.Body.Close()
So(resp.StatusCode, ShouldEqual, http.StatusBadRequest)
resp := response.Result()
defer resp.Body.Close()
So(resp.StatusCode, ShouldEqual, http.StatusBadRequest)
acCtx := localCtx.AccessControlContext{
Username: "test",
}
request, _ = http.NewRequestWithContext(ctx, http.MethodGet, baseURL, nil)
response = httptest.NewRecorder()
ctx = context.TODO()
key = localCtx.GetContextKey()
ctx = context.WithValue(ctx, key, acCtx)
rthdlr.GetAPIKeys(response, request)
request, _ = http.NewRequestWithContext(ctx, http.MethodPost, baseURL, bytes.NewReader([]byte{}))
response = httptest.NewRecorder()
resp = response.Result()
defer resp.Body.Close()
So(resp.StatusCode, ShouldEqual, http.StatusInternalServerError)
})
rthdlr.CreateAPIKey(response, request)
Convey("CreateAPIKey bad request body", func() {
acCtx := localCtx.AccessControlContext{
Username: "test",
}
resp = response.Result()
defer resp.Body.Close()
ctx := context.TODO()
key := localCtx.GetContextKey()
ctx = context.WithValue(ctx, key, acCtx)
So(resp.StatusCode, ShouldEqual, http.StatusBadRequest)
request, _ := http.NewRequestWithContext(ctx, http.MethodPost, baseURL, bytes.NewReader([]byte{}))
response := httptest.NewRecorder()
payload := api.APIKeyPayload{
Label: "test",
Scopes: []string{"test"},
}
reqBody, err := json.Marshal(payload)
So(err, ShouldBeNil)
rthdlr.CreateAPIKey(response, request)
request, _ = http.NewRequestWithContext(ctx, http.MethodPost, baseURL, bytes.NewReader(reqBody))
response = httptest.NewRecorder()
resp := response.Result()
defer resp.Body.Close()
So(resp.StatusCode, ShouldEqual, http.StatusBadRequest)
})
ctlr.MetaDB = mocks.MetaDBMock{
AddUserAPIKeyFn: func(ctx context.Context, hashedKey string, apiKeyDetails *mTypes.APIKeyDetails) error {
return ErrUnexpectedError
},
}
rthdlr.CreateAPIKey(response, request)
Convey("CreateAPIKey error on AddUserAPIKey", func() {
acCtx := localCtx.AccessControlContext{
Username: "test",
}
resp = response.Result()
defer resp.Body.Close()
ctx := context.TODO()
key := localCtx.GetContextKey()
ctx = context.WithValue(ctx, key, acCtx)
So(resp.StatusCode, ShouldEqual, http.StatusInternalServerError)
payload := api.APIKeyPayload{
Label: "test",
Scopes: []string{"test"},
}
reqBody, err := json.Marshal(payload)
So(err, ShouldBeNil)
request, _ = http.NewRequestWithContext(ctx, http.MethodDelete, baseURL, bytes.NewReader([]byte{}))
response = httptest.NewRecorder()
request, _ := http.NewRequestWithContext(ctx, http.MethodPost, baseURL, bytes.NewReader(reqBody))
response := httptest.NewRecorder()
q := request.URL.Query()
q.Add("id", "apikeyid")
request.URL.RawQuery = q.Encode()
ctlr.MetaDB = mocks.MetaDBMock{
AddUserAPIKeyFn: func(ctx context.Context, hashedKey string, apiKeyDetails *mTypes.APIKeyDetails) error {
return ErrUnexpectedError
},
}
ctlr.MetaDB = mocks.MetaDBMock{
DeleteUserAPIKeyFn: func(ctx context.Context, id string) error {
return ErrUnexpectedError
},
}
rthdlr.RevokeAPIKey(response, request)
rthdlr.CreateAPIKey(response, request)
resp = response.Result()
defer resp.Body.Close()
resp := response.Result()
defer resp.Body.Close()
So(resp.StatusCode, ShouldEqual, http.StatusInternalServerError)
})
So(resp.StatusCode, ShouldEqual, http.StatusInternalServerError)
Convey("Revoke error on DeleteUserAPIKeyFn", func() {
acCtx := localCtx.AccessControlContext{
Username: "test",
}
ctx := context.TODO()
key := localCtx.GetContextKey()
ctx = context.WithValue(ctx, key, acCtx)
request, _ := http.NewRequestWithContext(ctx, http.MethodDelete, baseURL, bytes.NewReader([]byte{}))
response := httptest.NewRecorder()
q := request.URL.Query()
q.Add("id", "apikeyid")
request.URL.RawQuery = q.Encode()
ctlr.MetaDB = mocks.MetaDBMock{
DeleteUserAPIKeyFn: func(ctx context.Context, id string) error {
return ErrUnexpectedError
},
}
rthdlr.RevokeAPIKey(response, request)
resp := response.Result()
defer resp.Body.Close()
So(resp.StatusCode, ShouldEqual, http.StatusInternalServerError)
})
})
Convey("Helper functions", func() {
+90
View File
@@ -1696,6 +1696,96 @@ func (bdw *BoltDB) UpdateUserAPIKeyLastUsed(ctx context.Context, hashedKey strin
return err
}
func (bdw *BoltDB) IsAPIKeyExpired(ctx context.Context, hashedKey string) (bool, error) {
acCtx, err := localCtx.GetAccessControlContext(ctx)
if err != nil {
return false, err
}
userid := localCtx.GetUsernameFromContext(acCtx)
if userid == "" {
// empty user is anonymous
return false, zerr.ErrUserDataNotAllowed
}
var isExpired bool
err = bdw.DB.Update(func(tx *bbolt.Tx) error { //nolint:varnamelen
var userData mTypes.UserData
err := bdw.getUserData(userid, tx, &userData)
if err != nil {
return err
}
apiKeyDetails := userData.APIKeys[hashedKey]
if apiKeyDetails.IsExpired {
isExpired = true
return nil
}
// if expiresAt is not nil value
if !apiKeyDetails.ExpirationDate.Equal(time.Time{}) && time.Now().After(apiKeyDetails.ExpirationDate) {
isExpired = true
apiKeyDetails.IsExpired = true
}
userData.APIKeys[hashedKey] = apiKeyDetails
err = bdw.setUserData(userid, tx, userData)
return err
})
return isExpired, err
}
func (bdw *BoltDB) GetUserAPIKeys(ctx context.Context) ([]mTypes.APIKeyDetails, error) {
apiKeys := make([]mTypes.APIKeyDetails, 0)
acCtx, err := localCtx.GetAccessControlContext(ctx)
if err != nil {
return nil, err
}
userid := localCtx.GetUsernameFromContext(acCtx)
if userid == "" {
// empty user is anonymous
return nil, zerr.ErrUserDataNotAllowed
}
err = bdw.DB.Update(func(transaction *bbolt.Tx) error {
var userData mTypes.UserData
err = bdw.getUserData(userid, transaction, &userData)
if err != nil && !errors.Is(err, zerr.ErrUserDataNotFound) {
return err
}
for hashedKey, apiKeyDetails := range userData.APIKeys {
// if expiresAt is not nil value
if !apiKeyDetails.ExpirationDate.Equal(time.Time{}) && time.Now().After(apiKeyDetails.ExpirationDate) {
apiKeyDetails.IsExpired = true
}
userData.APIKeys[hashedKey] = apiKeyDetails
err = bdw.setUserData(userid, transaction, userData)
if err != nil {
return err
}
apiKeys = append(apiKeys, apiKeyDetails)
}
return nil
})
return apiKeys, err
}
func (bdw *BoltDB) AddUserAPIKey(ctx context.Context, hashedKey string, apiKeyDetails *mTypes.APIKeyDetails) error {
acCtx, err := localCtx.GetAccessControlContext(ctx)
if err != nil {
+66
View File
@@ -1781,6 +1781,34 @@ func (dwr DynamoDB) GetUserGroups(ctx context.Context) ([]string, error) {
return userData.Groups, err
}
func (dwr *DynamoDB) IsAPIKeyExpired(ctx context.Context, hashedKey string) (bool, error) {
userData, err := dwr.GetUserData(ctx)
if err != nil {
return false, err
}
var isExpired bool
apiKeyDetails := userData.APIKeys[hashedKey]
if apiKeyDetails.IsExpired {
isExpired = true
return isExpired, nil
}
// if expiresAt is not nil value
if !apiKeyDetails.ExpirationDate.Equal(time.Time{}) && time.Now().After(apiKeyDetails.ExpirationDate) {
isExpired = true
apiKeyDetails.IsExpired = true
}
userData.APIKeys[hashedKey] = apiKeyDetails
err = dwr.SetUserData(ctx, userData)
return isExpired, err
}
func (dwr DynamoDB) UpdateUserAPIKeyLastUsed(ctx context.Context, hashedKey string) error {
userData, err := dwr.GetUserData(ctx)
if err != nil {
@@ -1797,6 +1825,44 @@ func (dwr DynamoDB) UpdateUserAPIKeyLastUsed(ctx context.Context, hashedKey stri
return err
}
func (dwr DynamoDB) GetUserAPIKeys(ctx context.Context) ([]mTypes.APIKeyDetails, error) {
apiKeys := make([]mTypes.APIKeyDetails, 0)
acCtx, err := localCtx.GetAccessControlContext(ctx)
if err != nil {
return nil, err
}
userid := localCtx.GetUsernameFromContext(acCtx)
if userid == "" {
// empty user is anonymous
return nil, zerr.ErrUserDataNotAllowed
}
userData, err := dwr.GetUserData(ctx)
if err != nil && !errors.Is(err, zerr.ErrUserDataNotFound) {
return nil, fmt.Errorf("metaDB: error while getting userData for identity %s %w", userid, err)
}
for hashedKey, apiKeyDetails := range userData.APIKeys {
// if expiresAt is not nil value
if !apiKeyDetails.ExpirationDate.Equal(time.Time{}) && time.Now().After(apiKeyDetails.ExpirationDate) {
apiKeyDetails.IsExpired = true
}
userData.APIKeys[hashedKey] = apiKeyDetails
err = dwr.SetUserData(ctx, userData)
if err != nil {
return nil, err
}
apiKeys = append(apiKeys, apiKeyDetails)
}
return apiKeys, nil
}
func (dwr DynamoDB) AddUserAPIKey(ctx context.Context, hashedKey string, apiKeyDetails *mTypes.APIKeyDetails) error {
acCtx, err := localCtx.GetAccessControlContext(ctx)
if err != nil {
+254 -71
View File
@@ -143,10 +143,11 @@ func RunMetaDBTests(t *testing.T, metaDB mTypes.MetaDB, preparationFuncs ...func
Convey("Test CRUD operations on UserData and API keys", func() {
hashKey1 := "id"
hashKey2 := "key"
label1 := "apiKey1"
apiKeys := make(map[string]mTypes.APIKeyDetails)
apiKeyDetails := mTypes.APIKeyDetails{
Label: "apiKey",
Label: label1,
Scopes: []string{"repo"},
UUID: hashKey1,
}
@@ -158,99 +159,281 @@ func RunMetaDBTests(t *testing.T, metaDB mTypes.MetaDB, preparationFuncs ...func
APIKeys: apiKeys,
}
authzCtxKey := localCtx.GetContextKey()
Convey("Test basic operations on API keys", func() {
hashKey2 := "key"
label2 := "apiKey2"
acCtx := localCtx.AccessControlContext{
Username: "test",
}
authzCtxKey := localCtx.GetContextKey()
acCtx := localCtx.AccessControlContext{
Username: "test",
}
ctx := context.WithValue(context.Background(), authzCtxKey, acCtx)
ctx := context.WithValue(context.Background(), authzCtxKey, acCtx)
err := metaDB.AddUserAPIKey(ctx, hashKey1, &apiKeyDetails)
So(err, ShouldBeNil)
err := metaDB.AddUserAPIKey(ctx, hashKey1, &apiKeyDetails)
So(err, ShouldBeNil)
isExpired, err := metaDB.IsAPIKeyExpired(ctx, hashKey1)
So(isExpired, ShouldBeFalse)
So(err, ShouldBeNil)
err = metaDB.SetUserData(ctx, userProfileSrc)
So(err, ShouldBeNil)
storedAPIKeys, err := metaDB.GetUserAPIKeys(ctx)
So(err, ShouldBeNil)
So(len(storedAPIKeys), ShouldEqual, 1)
So(storedAPIKeys[0], ShouldResemble, apiKeyDetails)
userProfile, err := metaDB.GetUserData(ctx)
So(err, ShouldBeNil)
So(userProfile.Groups, ShouldResemble, userProfileSrc.Groups)
So(userProfile.APIKeys, ShouldContainKey, hashKey1)
So(userProfile.APIKeys[hashKey1].Label, ShouldEqual, apiKeyDetails.Label)
So(userProfile.APIKeys[hashKey1].Scopes, ShouldResemble, apiKeyDetails.Scopes)
userProfile, err := metaDB.GetUserData(ctx)
So(err, ShouldBeNil)
So(userProfile.APIKeys, ShouldContainKey, hashKey1)
So(userProfile.APIKeys[hashKey1].Label, ShouldEqual, apiKeyDetails.Label)
So(userProfile.APIKeys[hashKey1].Scopes, ShouldResemble, apiKeyDetails.Scopes)
lastUsed := userProfile.APIKeys[hashKey1].LastUsed
err = metaDB.SetUserData(ctx, userProfileSrc)
So(err, ShouldBeNil)
err = metaDB.UpdateUserAPIKeyLastUsed(ctx, hashKey1)
So(err, ShouldBeNil)
userProfile, err = metaDB.GetUserData(ctx)
So(err, ShouldBeNil)
So(userProfile.Groups, ShouldResemble, userProfileSrc.Groups)
So(userProfile.APIKeys, ShouldContainKey, hashKey1)
So(userProfile.APIKeys[hashKey1].Label, ShouldEqual, apiKeyDetails.Label)
So(userProfile.APIKeys[hashKey1].Scopes, ShouldResemble, apiKeyDetails.Scopes)
userProfile, err = metaDB.GetUserData(ctx)
So(err, ShouldBeNil)
So(userProfile.APIKeys[hashKey1].LastUsed, ShouldHappenAfter, lastUsed)
storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx)
So(err, ShouldBeNil)
So(len(storedAPIKeys), ShouldEqual, 1)
So(storedAPIKeys[0], ShouldResemble, apiKeyDetails)
userGroups, err := metaDB.GetUserGroups(ctx)
So(err, ShouldBeNil)
So(userGroups, ShouldResemble, userProfileSrc.Groups)
lastUsed := userProfile.APIKeys[hashKey1].LastUsed
apiKeyDetails.UUID = hashKey2
err = metaDB.AddUserAPIKey(ctx, hashKey2, &apiKeyDetails)
So(err, ShouldBeNil)
err = metaDB.UpdateUserAPIKeyLastUsed(ctx, hashKey1)
So(err, ShouldBeNil)
userProfile, err = metaDB.GetUserData(ctx)
So(err, ShouldBeNil)
So(userProfile.Groups, ShouldResemble, userProfileSrc.Groups)
So(userProfile.APIKeys, ShouldContainKey, hashKey2)
So(userProfile.APIKeys[hashKey2].Label, ShouldEqual, apiKeyDetails.Label)
So(userProfile.APIKeys[hashKey2].Scopes, ShouldResemble, apiKeyDetails.Scopes)
userProfile, err = metaDB.GetUserData(ctx)
So(err, ShouldBeNil)
So(userProfile.APIKeys[hashKey1].LastUsed, ShouldHappenAfter, lastUsed)
email, err := metaDB.GetUserAPIKeyInfo(hashKey2)
So(err, ShouldBeNil)
So(email, ShouldEqual, "test")
storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx)
So(err, ShouldBeNil)
So(len(storedAPIKeys), ShouldEqual, 1)
So(storedAPIKeys[0].LastUsed, ShouldHappenAfter, lastUsed)
err = metaDB.DeleteUserAPIKey(ctx, hashKey1)
So(err, ShouldBeNil)
userGroups, err := metaDB.GetUserGroups(ctx)
So(err, ShouldBeNil)
So(userGroups, ShouldResemble, userProfileSrc.Groups)
userProfile, err = metaDB.GetUserData(ctx)
So(err, ShouldBeNil)
So(len(userProfile.APIKeys), ShouldEqual, 1)
So(userProfile.APIKeys, ShouldNotContainKey, hashKey1)
apiKeyDetails.UUID = hashKey2
apiKeyDetails.Label = label2
err = metaDB.AddUserAPIKey(ctx, hashKey2, &apiKeyDetails)
So(err, ShouldBeNil)
err = metaDB.DeleteUserAPIKey(ctx, hashKey2)
So(err, ShouldBeNil)
userProfile, err = metaDB.GetUserData(ctx)
So(err, ShouldBeNil)
So(userProfile.Groups, ShouldResemble, userProfileSrc.Groups)
So(userProfile.APIKeys, ShouldContainKey, hashKey2)
So(userProfile.APIKeys[hashKey2].Label, ShouldEqual, apiKeyDetails.Label)
So(userProfile.APIKeys[hashKey2].Scopes, ShouldResemble, apiKeyDetails.Scopes)
userProfile, err = metaDB.GetUserData(ctx)
So(err, ShouldBeNil)
So(len(userProfile.APIKeys), ShouldEqual, 0)
So(userProfile.APIKeys, ShouldNotContainKey, hashKey2)
storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx)
So(err, ShouldBeNil)
So(len(storedAPIKeys), ShouldEqual, 2)
So(storedAPIKeys[0].Scopes, ShouldResemble, apiKeyDetails.Scopes)
So(storedAPIKeys[1].Scopes, ShouldResemble, apiKeyDetails.Scopes)
scopes := []string{storedAPIKeys[0].Label, storedAPIKeys[1].Label}
// order is not preserved when getting api keys from db
So(scopes, ShouldContain, label1)
So(scopes, ShouldContain, label2)
// delete non existent api key
err = metaDB.DeleteUserAPIKey(ctx, hashKey2)
So(err, ShouldBeNil)
email, err := metaDB.GetUserAPIKeyInfo(hashKey2)
So(err, ShouldBeNil)
So(email, ShouldEqual, "test")
err = metaDB.DeleteUserData(ctx)
So(err, ShouldBeNil)
email, err = metaDB.GetUserAPIKeyInfo(hashKey1)
So(err, ShouldBeNil)
So(email, ShouldEqual, "test")
email, err = metaDB.GetUserAPIKeyInfo(hashKey2)
So(err, ShouldNotBeNil)
So(email, ShouldBeEmpty)
err = metaDB.DeleteUserAPIKey(ctx, hashKey1)
So(err, ShouldBeNil)
email, err = metaDB.GetUserAPIKeyInfo(hashKey1)
So(err, ShouldNotBeNil)
So(email, ShouldBeEmpty)
storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx)
So(err, ShouldBeNil)
So(len(storedAPIKeys), ShouldEqual, 1)
So(storedAPIKeys[0].Label, ShouldEqual, label2)
_, err = metaDB.GetUserData(ctx)
So(err, ShouldNotBeNil)
userProfile, err = metaDB.GetUserData(ctx)
So(err, ShouldBeNil)
So(len(userProfile.APIKeys), ShouldEqual, 1)
userGroups, err = metaDB.GetUserGroups(ctx)
So(err, ShouldNotBeNil)
So(userGroups, ShouldBeEmpty)
err = metaDB.DeleteUserAPIKey(ctx, hashKey2)
So(err, ShouldBeNil)
err = metaDB.SetUserGroups(ctx, userProfileSrc.Groups)
So(err, ShouldBeNil)
storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx)
So(err, ShouldBeNil)
So(len(storedAPIKeys), ShouldEqual, 0)
userGroups, err = metaDB.GetUserGroups(ctx)
So(err, ShouldBeNil)
So(userGroups, ShouldResemble, userProfileSrc.Groups)
userProfile, err = metaDB.GetUserData(ctx)
So(err, ShouldBeNil)
So(len(userProfile.APIKeys), ShouldEqual, 0)
So(userProfile.APIKeys, ShouldNotContainKey, hashKey2)
// delete non existent api key
err = metaDB.DeleteUserAPIKey(ctx, hashKey2)
So(err, ShouldBeNil)
storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx)
So(err, ShouldBeNil)
So(len(storedAPIKeys), ShouldEqual, 0)
err = metaDB.DeleteUserData(ctx)
So(err, ShouldBeNil)
storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx)
So(err, ShouldBeNil)
So(len(storedAPIKeys), ShouldEqual, 0)
email, err = metaDB.GetUserAPIKeyInfo(hashKey2)
So(err, ShouldNotBeNil)
So(email, ShouldBeEmpty)
email, err = metaDB.GetUserAPIKeyInfo(hashKey1)
So(err, ShouldNotBeNil)
So(email, ShouldBeEmpty)
_, err = metaDB.GetUserData(ctx)
So(err, ShouldNotBeNil)
userGroups, err = metaDB.GetUserGroups(ctx)
So(err, ShouldNotBeNil)
So(userGroups, ShouldBeEmpty)
err = metaDB.SetUserGroups(ctx, userProfileSrc.Groups)
So(err, ShouldBeNil)
userGroups, err = metaDB.GetUserGroups(ctx)
So(err, ShouldBeNil)
So(userGroups, ShouldResemble, userProfileSrc.Groups)
})
Convey("Test API keys operations with invalid access control context", func() {
var invalid struct{}
ctx := context.TODO()
key := localCtx.GetContextKey()
ctx = context.WithValue(ctx, key, invalid)
_, err := metaDB.GetUserAPIKeys(ctx)
So(err, ShouldNotBeNil)
err = metaDB.AddUserAPIKey(ctx, hashKey1, &apiKeyDetails)
So(err, ShouldNotBeNil)
isExpired, err := metaDB.IsAPIKeyExpired(ctx, hashKey1)
So(isExpired, ShouldBeFalse)
So(err, ShouldNotBeNil)
err = metaDB.DeleteUserAPIKey(ctx, hashKey1)
So(err, ShouldNotBeNil)
_, err = metaDB.GetUserData(ctx)
So(err, ShouldNotBeNil)
_, err = metaDB.GetUserGroups(ctx)
So(err, ShouldNotBeNil)
_, err = metaDB.GetUserAPIKeyInfo(hashKey1)
So(err, ShouldNotBeNil)
err = metaDB.UpdateUserAPIKeyLastUsed(ctx, hashKey1)
So(err, ShouldNotBeNil)
err = metaDB.SetUserData(ctx, userProfileSrc)
So(err, ShouldNotBeNil)
})
Convey("Test API keys operations with empty userid", func() {
acCtx := localCtx.AccessControlContext{
Username: "",
}
ctx := context.TODO()
key := localCtx.GetContextKey()
ctx = context.WithValue(ctx, key, acCtx)
_, err := metaDB.GetUserAPIKeys(ctx)
So(err, ShouldNotBeNil)
isExpired, err := metaDB.IsAPIKeyExpired(ctx, hashKey1)
So(isExpired, ShouldBeFalse)
So(err, ShouldNotBeNil)
err = metaDB.AddUserAPIKey(ctx, hashKey1, &apiKeyDetails)
So(err, ShouldNotBeNil)
err = metaDB.DeleteUserAPIKey(ctx, hashKey1)
So(err, ShouldNotBeNil)
_, err = metaDB.GetUserData(ctx)
So(err, ShouldNotBeNil)
_, err = metaDB.GetUserGroups(ctx)
So(err, ShouldNotBeNil)
_, err = metaDB.GetUserAPIKeyInfo(hashKey1)
So(err, ShouldNotBeNil)
err = metaDB.UpdateUserAPIKeyLastUsed(ctx, hashKey1)
So(err, ShouldNotBeNil)
err = metaDB.SetUserData(ctx, userProfileSrc)
So(err, ShouldNotBeNil)
})
Convey("Test API keys with short expiration date", func() {
expirationDate := time.Now().Add(500 * time.Millisecond).Local().Round(time.Millisecond)
apiKeyDetails.ExpirationDate = expirationDate
authzCtxKey := localCtx.GetContextKey()
acCtx := localCtx.AccessControlContext{
Username: "test",
}
ctx := context.WithValue(context.Background(), authzCtxKey, acCtx)
err := metaDB.AddUserAPIKey(ctx, hashKey1, &apiKeyDetails)
So(err, ShouldBeNil)
storedAPIKeys, err := metaDB.GetUserAPIKeys(ctx)
So(err, ShouldBeNil)
So(len(storedAPIKeys), ShouldEqual, 1)
So(storedAPIKeys[0].ExpirationDate, ShouldResemble, expirationDate)
So(storedAPIKeys[0].Label, ShouldEqual, apiKeyDetails.Label)
So(storedAPIKeys[0].Scopes, ShouldResemble, apiKeyDetails.Scopes)
isExpired, err := metaDB.IsAPIKeyExpired(ctx, hashKey1)
So(isExpired, ShouldBeFalse)
So(err, ShouldBeNil)
time.Sleep(600 * time.Millisecond)
Convey("GetUserAPIKeys detects api key expired", func() {
storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx)
So(err, ShouldBeNil)
So(len(storedAPIKeys), ShouldEqual, 1)
So(storedAPIKeys[0].IsExpired, ShouldBeTrue)
isExpired, err = metaDB.IsAPIKeyExpired(ctx, hashKey1)
So(isExpired, ShouldBeTrue)
So(err, ShouldBeNil)
})
Convey("IsAPIKeyExpired detects api key expired", func() {
isExpired, err = metaDB.IsAPIKeyExpired(ctx, hashKey1)
So(isExpired, ShouldBeTrue)
So(err, ShouldBeNil)
storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx)
So(err, ShouldBeNil)
So(len(storedAPIKeys), ShouldEqual, 1)
So(storedAPIKeys[0].IsExpired, ShouldBeTrue)
})
})
})
Convey("Test SetManifestData and GetManifestData", func() {
+13 -7
View File
@@ -149,8 +149,12 @@ type UserDB interface { //nolint:interfacebloat
GetUserAPIKeyInfo(hashedKey string) (identity string, err error)
GetUserAPIKeys(ctx context.Context) ([]APIKeyDetails, error)
AddUserAPIKey(ctx context.Context, hashedKey string, apiKeyDetails *APIKeyDetails) error
IsAPIKeyExpired(ctx context.Context, hashedKey string) (bool, error)
UpdateUserAPIKeyLastUsed(ctx context.Context, hashedKey string) error
DeleteUserAPIKey(ctx context.Context, id string) error
@@ -252,11 +256,13 @@ type FilterData struct {
}
type APIKeyDetails struct {
CreatedAt time.Time `json:"createdAt"`
CreatorUA string `json:"creatorUa"`
GeneratedBy string `json:"generatedBy"`
LastUsed time.Time `json:"lastUsed"`
Label string `json:"label"`
Scopes []string `json:"scopes"`
UUID string `json:"uuid"`
CreatedAt time.Time `json:"createdAt"`
ExpirationDate time.Time `json:"expirationDate"`
IsExpired bool `json:"isExpired"`
CreatorUA string `json:"creatorUa"`
GeneratedBy string `json:"generatedBy"`
LastUsed time.Time `json:"lastUsed"`
Label string `json:"label"`
Scopes []string `json:"scopes"`
UUID string `json:"uuid"`
}
+20
View File
@@ -95,6 +95,10 @@ type MetaDBMock struct {
GetUserAPIKeyInfoFn func(hashedKey string) (string, error)
IsAPIKeyExpiredFn func(ctx context.Context, hashedKey string) (bool, error)
GetUserAPIKeysFn func(ctx context.Context) ([]mTypes.APIKeyDetails, error)
AddUserAPIKeyFn func(ctx context.Context, hashedKey string, apiKeyDetails *mTypes.APIKeyDetails) error
UpdateUserAPIKeyLastUsedFn func(ctx context.Context, hashedKey string) error
@@ -427,6 +431,22 @@ func (sdm MetaDBMock) GetUserAPIKeyInfo(hashedKey string) (string, error) {
return "", nil
}
func (sdm MetaDBMock) IsAPIKeyExpired(ctx context.Context, hashedKey string) (bool, error) {
if sdm.IsAPIKeyExpiredFn != nil {
return sdm.IsAPIKeyExpiredFn(ctx, hashedKey)
}
return false, nil
}
func (sdm MetaDBMock) GetUserAPIKeys(ctx context.Context) ([]mTypes.APIKeyDetails, error) {
if sdm.GetUserAPIKeysFn != nil {
return sdm.GetUserAPIKeysFn(ctx)
}
return nil, nil
}
func (sdm MetaDBMock) AddUserAPIKey(ctx context.Context, hashedKey string, apiKeyDetails *mTypes.APIKeyDetails) error {
if sdm.AddUserAPIKeyFn != nil {
return sdm.AddUserAPIKeyFn(ctx, hashedKey, apiKeyDetails)