mirror of
https://github.com/project-zot/zot.git
synced 2024-12-16 21:56:37 -05:00
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:
parent
28858f695f
commit
6926bddd3a
15 changed files with 1132 additions and 153 deletions
|
@ -331,13 +331,15 @@ Create an API key for the current user using the REST API
|
|||
|
||||
```
|
||||
POST /auth/apikey
|
||||
Body: {"label": "git", "scopes": ["repo1", "repo2"]}'
|
||||
Body: {"label": "git", "scopes": ["repo1", "repo2"], "expirationDate": "2023-08-28T17:10:05+03:00"}'
|
||||
```
|
||||
|
||||
**Example cURL**
|
||||
The time format of expirationDate is RFC1123Z.
|
||||
|
||||
**Example cURL without expiration date**
|
||||
|
||||
```bash
|
||||
curl -u user:password -X POST http://localhost:8080/auth/apikey -d '{"label": "myLabel"}'
|
||||
curl -u user:password -X POST http://localhost:8080/auth/apikey -d '{"label": "git", "scopes": ["repo1", "repo2"]}'
|
||||
```
|
||||
|
||||
**Sample output**:
|
||||
|
@ -345,16 +347,93 @@ curl -u user:password -X POST http://localhost:8080/auth/apikey -d '{"label": "m
|
|||
```json
|
||||
{
|
||||
"createdAt": "2023-05-05T15:39:28.420926+03:00",
|
||||
"expirationDate": "0001-01-01T00:00:00Z",
|
||||
"isExpired": false,
|
||||
"creatorUa": "curl/7.68.0",
|
||||
"generatedBy": "manual",
|
||||
"lastUsed": "2023-05-05T15:39:28.4209282+03:00",
|
||||
"lastUsed": "0001-01-01T00:00:00Z",
|
||||
"label": "git",
|
||||
"scopes": null,
|
||||
"scopes": [
|
||||
"repo1",
|
||||
"repo2"
|
||||
],
|
||||
"uuid": "46a45ce7-5d92-498a-a9cb-9654b1da3da1",
|
||||
"apiKey": "zak_e77bcb9e9f634f1581756abbf9ecd269"
|
||||
}
|
||||
```
|
||||
|
||||
**Example cURL with expiration date**
|
||||
|
||||
```bash
|
||||
curl -u user:password -X POST http://localhost:8080/auth/apikey -d '{"label": "myAPIKEY", "expirationDate": "2023-08-28T17:10:05+03:00"}'
|
||||
```
|
||||
|
||||
**Sample output**:
|
||||
|
||||
```json
|
||||
{
|
||||
"createdAt":"2023-08-28T17:09:59.2603515+03:00",
|
||||
"expirationDate":"2023-08-28T17:10:05+03:00",
|
||||
"isExpired":false,
|
||||
"creatorUa":"curl/7.68.0",
|
||||
"generatedBy":"manual",
|
||||
"lastUsed":"0001-01-01T00:00:00Z",
|
||||
"label":"myAPIKEY",
|
||||
"scopes":null,
|
||||
"uuid":"c931e635-a80d-4b52-b035-6b57be5f6e74",
|
||||
"apiKey":"zak_ac55a8693d6b4370a2003fa9e10b3682"
|
||||
}
|
||||
```
|
||||
|
||||
##### How to get list of API Keys
|
||||
|
||||
Get list of API keys for the current user using the REST API
|
||||
|
||||
**Usage**: GET /auth/apikey
|
||||
|
||||
**Produces**: application/json
|
||||
|
||||
**Example cURL**
|
||||
|
||||
```bash
|
||||
curl -u user:password -X GET http://localhost:8080/auth/apikey
|
||||
```
|
||||
|
||||
**Sample output**:
|
||||
|
||||
```json
|
||||
{
|
||||
"apiKeys": [
|
||||
{
|
||||
"createdAt": "2023-05-05T15:39:28.420926+03:00",
|
||||
"expirationDate": "0001-01-01T00:00:00Z",
|
||||
"isExpired": true,
|
||||
"creatorUa": "curl/7.68.0",
|
||||
"generatedBy": "manual",
|
||||
"lastUsed": "0001-01-01T00:00:00Z",
|
||||
"label": "git",
|
||||
"scopes": [
|
||||
"repo1",
|
||||
"repo2"
|
||||
],
|
||||
"uuid": "46a45ce7-5d92-498a-a9cb-9654b1da3da1"
|
||||
},
|
||||
{
|
||||
"createdAt": "2023-08-11T14:43:00.6459729+03:00",
|
||||
"expirationDate": "2023-08-17T18:24:05+03:00",
|
||||
"isExpired": false,
|
||||
"creatorUa": "curl/7.68.0",
|
||||
"generatedBy": "manual",
|
||||
"lastUsed": "2023-08-11T14:43:47.5559998+03:00",
|
||||
"label": "myAPIKEY",
|
||||
"scopes": null,
|
||||
"uuid": "294abf69-b62f-4e58-b214-dad2aec0bc52"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
##### How to use API Keys
|
||||
|
||||
**Using API keys with cURL**
|
||||
|
|
|
@ -5,7 +5,14 @@
|
|||
},
|
||||
"http": {
|
||||
"address": "127.0.0.1",
|
||||
"port": "8080"
|
||||
"port": "8080",
|
||||
"auth": {
|
||||
"apikey": true,
|
||||
"htpasswd": {
|
||||
"path": "/home/peusebiu/htpasswd"
|
||||
},
|
||||
"failDelay": 5
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": "debug"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -21,6 +21,36 @@ const docTemplate = `{
|
|||
"basePath": "{{.BasePath}}",
|
||||
"paths": {
|
||||
"/auth/apikey": {
|
||||
"get": {
|
||||
"description": "Get list of all API keys for a logged in user",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"summary": "Get list of API keys for the current user",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "ok",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "unauthorized",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Can create an api key for a logged in user, based on the provided label and scopes.",
|
||||
"consumes": [
|
||||
|
@ -1139,6 +1169,9 @@ const docTemplate = `{
|
|||
"api.APIKeyPayload": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"expirationDate": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
@ -12,6 +12,36 @@
|
|||
},
|
||||
"paths": {
|
||||
"/auth/apikey": {
|
||||
"get": {
|
||||
"description": "Get list of all API keys for a logged in user",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"summary": "Get list of API keys for the current user",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "ok",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "unauthorized",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Can create an api key for a logged in user, based on the provided label and scopes.",
|
||||
"consumes": [
|
||||
|
@ -1130,6 +1160,9 @@
|
|||
"api.APIKeyPayload": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"expirationDate": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
definitions:
|
||||
api.APIKeyPayload:
|
||||
properties:
|
||||
expirationDate:
|
||||
type: string
|
||||
label:
|
||||
type: string
|
||||
scopes:
|
||||
|
@ -270,6 +272,26 @@ paths:
|
|||
schema:
|
||||
type: string
|
||||
summary: Revokes one current user API key
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get list of all API keys for a logged in user
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: ok
|
||||
schema:
|
||||
type: string
|
||||
"401":
|
||||
description: unauthorized
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: internal server error
|
||||
schema:
|
||||
type: string
|
||||
summary: Get list of API keys for the current user
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
|
|
Loading…
Reference in a new issue