diff --git a/api/apikey/apikey.go b/api/apikey/apikey.go index 41b38f17a..de70b3262 100644 --- a/api/apikey/apikey.go +++ b/api/apikey/apikey.go @@ -6,11 +6,11 @@ import ( // APIKeyService represents a service for managing API keys. type APIKeyService interface { - HashRaw(rawKey string) []byte + HashRaw(rawKey string) string GenerateApiKey(user portainer.User, description string) (string, *portainer.APIKey, error) GetAPIKey(apiKeyID portainer.APIKeyID) (*portainer.APIKey, error) GetAPIKeys(userID portainer.UserID) ([]portainer.APIKey, error) - GetDigestUserAndKey(digest []byte) (portainer.User, portainer.APIKey, error) + GetDigestUserAndKey(digest string) (portainer.User, portainer.APIKey, error) UpdateAPIKey(apiKey *portainer.APIKey) error DeleteAPIKey(apiKeyID portainer.APIKeyID) error InvalidateUserKeyCache(userId portainer.UserID) bool diff --git a/api/apikey/cache.go b/api/apikey/cache.go index 520f7274a..e36a05a17 100644 --- a/api/apikey/cache.go +++ b/api/apikey/cache.go @@ -33,8 +33,8 @@ func NewAPIKeyCache(cacheSize int) *apiKeyCache { // Get returns the user/key associated to an api-key's digest // This is required because HTTP requests will contain the digest of the API key in header, // the digest value must be mapped to a portainer user. -func (c *apiKeyCache) Get(digest []byte) (portainer.User, portainer.APIKey, bool) { - val, ok := c.cache.Get(string(digest)) +func (c *apiKeyCache) Get(digest string) (portainer.User, portainer.APIKey, bool) { + val, ok := c.cache.Get(digest) if !ok { return portainer.User{}, portainer.APIKey{}, false } @@ -44,23 +44,23 @@ func (c *apiKeyCache) Get(digest []byte) (portainer.User, portainer.APIKey, bool } // Set persists a user/key entry to the cache -func (c *apiKeyCache) Set(digest []byte, user portainer.User, apiKey portainer.APIKey) { - c.cache.Add(string(digest), entry{ +func (c *apiKeyCache) Set(digest string, user portainer.User, apiKey portainer.APIKey) { + c.cache.Add(digest, entry{ user: user, apiKey: apiKey, }) } // Delete evicts a digest's user/key entry key from the cache -func (c *apiKeyCache) Delete(digest []byte) { - c.cache.Remove(string(digest)) +func (c *apiKeyCache) Delete(digest string) { + c.cache.Remove(digest) } // InvalidateUserKeyCache loops through all the api-keys associated to a user and removes them from the cache func (c *apiKeyCache) InvalidateUserKeyCache(userId portainer.UserID) bool { present := false for _, k := range c.cache.Keys() { - user, _, _ := c.Get([]byte(k.(string))) + user, _, _ := c.Get(k.(string)) if user.ID == userId { present = c.cache.Remove(k) } diff --git a/api/apikey/cache_test.go b/api/apikey/cache_test.go index 71e1695d1..d34f260a0 100644 --- a/api/apikey/cache_test.go +++ b/api/apikey/cache_test.go @@ -17,19 +17,19 @@ func Test_apiKeyCacheGet(t *testing.T) { keyCache.cache.Add(string(""), entry{user: portainer.User{}, apiKey: portainer.APIKey{}}) tests := []struct { - digest []byte + digest string found bool }{ { - digest: []byte("foo"), + digest: "foo", found: true, }, { - digest: []byte(""), + digest: "", found: true, }, { - digest: []byte("bar"), + digest: "bar", found: false, }, } @@ -48,11 +48,11 @@ func Test_apiKeyCacheSet(t *testing.T) { keyCache := NewAPIKeyCache(10) // pre-populate cache - keyCache.Set([]byte("bar"), portainer.User{ID: 2}, portainer.APIKey{}) - keyCache.Set([]byte("foo"), portainer.User{ID: 1}, portainer.APIKey{}) + keyCache.Set("bar", portainer.User{ID: 2}, portainer.APIKey{}) + keyCache.Set("foo", portainer.User{ID: 1}, portainer.APIKey{}) // overwrite existing entry - keyCache.Set([]byte("foo"), portainer.User{ID: 3}, portainer.APIKey{}) + keyCache.Set("foo", portainer.User{ID: 3}, portainer.APIKey{}) val, ok := keyCache.cache.Get(string("bar")) is.True(ok) @@ -74,14 +74,14 @@ func Test_apiKeyCacheDelete(t *testing.T) { t.Run("Delete an existing entry", func(t *testing.T) { keyCache.cache.Add(string("foo"), entry{user: portainer.User{ID: 1}, apiKey: portainer.APIKey{}}) - keyCache.Delete([]byte("foo")) + keyCache.Delete("foo") _, ok := keyCache.cache.Get(string("foo")) is.False(ok) }) t.Run("Delete a non-existing entry", func(t *testing.T) { - nonPanicFunc := func() { keyCache.Delete([]byte("non-existent-key")) } + nonPanicFunc := func() { keyCache.Delete("non-existent-key") } is.NotPanics(nonPanicFunc) }) } @@ -131,16 +131,16 @@ func Test_apiKeyCacheLRU(t *testing.T) { keyCache := NewAPIKeyCache(test.cacheLen) for _, key := range test.key { - keyCache.Set([]byte(key), portainer.User{ID: 1}, portainer.APIKey{}) + keyCache.Set(key, portainer.User{ID: 1}, portainer.APIKey{}) } for _, key := range test.foundKeys { - _, _, found := keyCache.Get([]byte(key)) + _, _, found := keyCache.Get(key) is.True(found, "Key %s not found", key) } for _, key := range test.evictedKeys { - _, _, found := keyCache.Get([]byte(key)) + _, _, found := keyCache.Get(key) is.False(found, "key %s should have been evicted", key) } }) diff --git a/api/apikey/service.go b/api/apikey/service.go index b60efaf9c..fc3c7f739 100644 --- a/api/apikey/service.go +++ b/api/apikey/service.go @@ -32,9 +32,9 @@ func NewAPIKeyService(apiKeyRepository dataservices.APIKeyRepository, userReposi } // HashRaw computes a hash digest of provided raw API key. -func (a *apiKeyService) HashRaw(rawKey string) []byte { +func (a *apiKeyService) HashRaw(rawKey string) string { hashDigest := sha256.Sum256([]byte(rawKey)) - return hashDigest[:] + return base64.StdEncoding.EncodeToString(hashDigest[:]) } // GenerateApiKey generates a raw API key for a user (for one-time display). @@ -77,7 +77,7 @@ func (a *apiKeyService) GetAPIKeys(userID portainer.UserID) ([]portainer.APIKey, // GetDigestUserAndKey returns the user and api-key associated to a specified hash digest. // A cache lookup is performed first; if the user/api-key is not found in the cache, respective database lookups are performed. -func (a *apiKeyService) GetDigestUserAndKey(digest []byte) (portainer.User, portainer.APIKey, error) { +func (a *apiKeyService) GetDigestUserAndKey(digest string) (portainer.User, portainer.APIKey, error) { // get api key from cache if possible cachedUser, cachedKey, ok := a.cache.Get(digest) if ok { diff --git a/api/apikey/service_test.go b/api/apikey/service_test.go index a9a82d43d..17d95a213 100644 --- a/api/apikey/service_test.go +++ b/api/apikey/service_test.go @@ -2,6 +2,7 @@ package apikey import ( "crypto/sha256" + "encoding/base64" "fmt" "strings" "testing" @@ -68,7 +69,7 @@ func Test_GenerateApiKey(t *testing.T) { generatedDigest := sha256.Sum256([]byte(rawKey)) - is.Equal(apiKey.Digest, generatedDigest[:]) + is.Equal(apiKey.Digest, base64.StdEncoding.EncodeToString(generatedDigest[:])) }) } diff --git a/api/dataservices/apikeyrepository/apikeyrepository.go b/api/dataservices/apikeyrepository/apikeyrepository.go index bbe0d3f0b..ce022c663 100644 --- a/api/dataservices/apikeyrepository/apikeyrepository.go +++ b/api/dataservices/apikeyrepository/apikeyrepository.go @@ -1,7 +1,6 @@ package apikeyrepository import ( - "bytes" "errors" "fmt" @@ -37,7 +36,7 @@ func NewService(connection portainer.Connection) (*Service, error) { // GetAPIKeysByUserID returns a slice containing all the APIKeys a user has access to. func (service *Service) GetAPIKeysByUserID(userID portainer.UserID) ([]portainer.APIKey, error) { - var result = make([]portainer.APIKey, 0) + result := make([]portainer.APIKey, 0) err := service.Connection.GetAll( BucketName, @@ -61,7 +60,7 @@ func (service *Service) GetAPIKeysByUserID(userID portainer.UserID) ([]portainer // GetAPIKeyByDigest returns the API key for the associated digest. // Note: there is a 1-to-1 mapping of api-key and digest -func (service *Service) GetAPIKeyByDigest(digest []byte) (*portainer.APIKey, error) { +func (service *Service) GetAPIKeyByDigest(digest string) (*portainer.APIKey, error) { var k *portainer.APIKey stop := fmt.Errorf("ok") err := service.Connection.GetAll( @@ -73,7 +72,7 @@ func (service *Service) GetAPIKeyByDigest(digest []byte) (*portainer.APIKey, err log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to APIKey object") return nil, fmt.Errorf("failed to convert to APIKey object: %s", obj) } - if bytes.Equal(key.Digest, digest) { + if key.Digest == digest { k = key return nil, stop } diff --git a/api/dataservices/interface.go b/api/dataservices/interface.go index 2ca76dd81..d2e81053b 100644 --- a/api/dataservices/interface.go +++ b/api/dataservices/interface.go @@ -152,7 +152,7 @@ type ( APIKeyRepository interface { BaseCRUD[portainer.APIKey, portainer.APIKeyID] GetAPIKeysByUserID(userID portainer.UserID) ([]portainer.APIKey, error) - GetAPIKeyByDigest(digest []byte) (*portainer.APIKey, error) + GetAPIKeyByDigest(digest string) (*portainer.APIKey, error) } // SettingsService represents a service for managing application settings diff --git a/api/http/handler/users/user_get_access_tokens.go b/api/http/handler/users/user_get_access_tokens.go index dbfa0c0d9..45a26a641 100644 --- a/api/http/handler/users/user_get_access_tokens.go +++ b/api/http/handler/users/user_get_access_tokens.go @@ -64,5 +64,5 @@ func (handler *Handler) userGetAccessTokens(w http.ResponseWriter, r *http.Reque // hideAPIKeyFields remove the digest from the API key (it is not needed in the response) func hideAPIKeyFields(apiKey *portainer.APIKey) { - apiKey.Digest = nil + apiKey.Digest = "" } diff --git a/api/http/handler/users/user_get_access_tokens_test.go b/api/http/handler/users/user_get_access_tokens_test.go index 91c4ce0a8..fc20b7f4c 100644 --- a/api/http/handler/users/user_get_access_tokens_test.go +++ b/api/http/handler/users/user_get_access_tokens_test.go @@ -68,7 +68,7 @@ func Test_userGetAccessTokens(t *testing.T) { is.Len(resp, 1) if len(resp) == 1 { - is.Nil(resp[0].Digest) + is.Equal(resp[0].Digest, "") is.Equal(apiKey.ID, resp[0].ID) is.Equal(apiKey.UserID, resp[0].UserID) is.Equal(apiKey.Prefix, resp[0].Prefix) @@ -129,10 +129,10 @@ func Test_hideAPIKeyFields(t *testing.T) { UserID: 2, Prefix: "abc", Description: "test", - Digest: nil, + Digest: "", } hideAPIKeyFields(apiKey) - is.Nil(apiKey.Digest, "digest should be cleared when hiding api key fields") + is.Equal(apiKey.Digest, "", "digest should be cleared when hiding api key fields") } diff --git a/api/portainer.go b/api/portainer.go index 4d2ad19ca..48cc5f9da 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -32,7 +32,7 @@ type ( // Authorizations represents a set of authorizations associated to a role Authorizations map[Authorization]bool - //AutoUpdateSettings represents the git auto sync config for stack deployment + // AutoUpdateSettings represents the git auto sync config for stack deployment AutoUpdateSettings struct { // Auto update interval Interval string `example:"1m30s"` @@ -311,7 +311,7 @@ type ( ConfigHash string `json:"ConfigHash"` } - //EdgeStack represents an edge stack + // EdgeStack represents an edge stack EdgeStack struct { // EdgeStack Identifier ID EdgeStackID `json:"Id" example:"1"` @@ -335,7 +335,7 @@ type ( EdgeStackDeploymentType int - //EdgeStackID represents an edge stack id + // EdgeStackID represents an edge stack id EdgeStackID int EdgeStackStatusDetails struct { @@ -348,7 +348,7 @@ type ( ImagesPulled bool } - //EdgeStackStatus represents an edge stack status + // EdgeStackStatus represents an edge stack status EdgeStackStatus struct { Status []EdgeStackDeploymentStatus EndpointID EndpointID @@ -372,7 +372,7 @@ type ( RollbackTo *int } - //EdgeStackStatusType represents an edge stack status type + // EdgeStackStatusType represents an edge stack status type EdgeStackStatusType int PendingActionsID int @@ -905,7 +905,7 @@ type ( Prefix string `json:"prefix"` // API key identifier (7 char prefix) DateCreated int64 `json:"dateCreated"` // Unix timestamp (UTC) when the API key was created LastUsed int64 `json:"lastUsed"` // Unix timestamp (UTC) when the API key was last used - Digest []byte `json:"digest,omitempty"` // Digest represents SHA256 hash of the raw API key + Digest string `json:"digest,omitempty"` // Digest represents SHA256 hash of the raw API key } // Schedule represents a scheduled job. @@ -1657,7 +1657,7 @@ const ( AuthenticationInternal // AuthenticationLDAP represents the LDAP authentication method (authentication against a LDAP server) AuthenticationLDAP - //AuthenticationOAuth represents the OAuth authentication method (authentication against a authorization server) + // AuthenticationOAuth represents the OAuth authentication method (authentication against a authorization server) AuthenticationOAuth ) @@ -1697,13 +1697,13 @@ const ( const ( // EdgeStackStatusPending represents a pending edge stack EdgeStackStatusPending EdgeStackStatusType = iota - //EdgeStackStatusDeploymentReceived represents an edge environment which received the edge stack deployment + // EdgeStackStatusDeploymentReceived represents an edge environment which received the edge stack deployment EdgeStackStatusDeploymentReceived - //EdgeStackStatusError represents an edge environment which failed to deploy its edge stack + // EdgeStackStatusError represents an edge environment which failed to deploy its edge stack EdgeStackStatusError - //EdgeStackStatusAcknowledged represents an acknowledged edge stack + // EdgeStackStatusAcknowledged represents an acknowledged edge stack EdgeStackStatusAcknowledged - //EdgeStackStatusRemoved represents a removed edge stack + // EdgeStackStatusRemoved represents a removed edge stack EdgeStackStatusRemoved // StatusRemoteUpdateSuccess represents a successfully updated edge stack EdgeStackStatusRemoteUpdateSuccess