mirror of
https://github.com/portainer/portainer.git
synced 2025-07-18 21:09:40 +02:00
fix(users): optimize the /users/me API endpoint BE-11688 (#515)
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com> Co-authored-by: LP B <xAt0mZ@users.noreply.github.com> Co-authored-by: JamesPlayer <james.player@portainer.io>
This commit is contained in:
parent
a67b917bdd
commit
0296998fae
8 changed files with 96 additions and 19 deletions
|
@ -6,8 +6,10 @@ import (
|
||||||
|
|
||||||
type ReadTransaction interface {
|
type ReadTransaction interface {
|
||||||
GetObject(bucketName string, key []byte, object any) error
|
GetObject(bucketName string, key []byte, object any) error
|
||||||
|
GetRawBytes(bucketName string, key []byte) ([]byte, error)
|
||||||
GetAll(bucketName string, obj any, append func(o any) (any, error)) error
|
GetAll(bucketName string, obj any, append func(o any) (any, error)) error
|
||||||
GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj any, append func(o any) (any, error)) error
|
GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj any, append func(o any) (any, error)) error
|
||||||
|
KeyExists(bucketName string, key []byte) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Transaction interface {
|
type Transaction interface {
|
||||||
|
|
|
@ -244,6 +244,32 @@ func (connection *DbConnection) GetObject(bucketName string, key []byte, object
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (connection *DbConnection) GetRawBytes(bucketName string, key []byte) ([]byte, error) {
|
||||||
|
var value []byte
|
||||||
|
|
||||||
|
err := connection.ViewTx(func(tx portainer.Transaction) error {
|
||||||
|
var err error
|
||||||
|
value, err = tx.GetRawBytes(bucketName, key)
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (connection *DbConnection) KeyExists(bucketName string, key []byte) (bool, error) {
|
||||||
|
var exists bool
|
||||||
|
|
||||||
|
err := connection.ViewTx(func(tx portainer.Transaction) error {
|
||||||
|
var err error
|
||||||
|
exists, err = tx.KeyExists(bucketName, key)
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
return exists, err
|
||||||
|
}
|
||||||
|
|
||||||
func (connection *DbConnection) getEncryptionKey() []byte {
|
func (connection *DbConnection) getEncryptionKey() []byte {
|
||||||
if !connection.isEncrypted {
|
if !connection.isEncrypted {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
dserrors "github.com/portainer/portainer/api/dataservices/errors"
|
dserrors "github.com/portainer/portainer/api/dataservices/errors"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
bolt "go.etcd.io/bbolt"
|
bolt "go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
|
@ -31,6 +32,33 @@ func (tx *DbTransaction) GetObject(bucketName string, key []byte, object any) er
|
||||||
return tx.conn.UnmarshalObject(value, object)
|
return tx.conn.UnmarshalObject(value, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tx *DbTransaction) GetRawBytes(bucketName string, key []byte) ([]byte, error) {
|
||||||
|
bucket := tx.tx.Bucket([]byte(bucketName))
|
||||||
|
|
||||||
|
value := bucket.Get(key)
|
||||||
|
if value == nil {
|
||||||
|
return nil, fmt.Errorf("%w (bucket=%s, key=%s)", dserrors.ErrObjectNotFound, bucketName, keyToString(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
if tx.conn.getEncryptionKey() != nil {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if value, err = decrypt(value, tx.conn.getEncryptionKey()); err != nil {
|
||||||
|
return value, errors.Wrap(err, "Failed decrypting object")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *DbTransaction) KeyExists(bucketName string, key []byte) (bool, error) {
|
||||||
|
bucket := tx.tx.Bucket([]byte(bucketName))
|
||||||
|
|
||||||
|
value := bucket.Get(key)
|
||||||
|
|
||||||
|
return value != nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (tx *DbTransaction) UpdateObject(bucketName string, key []byte, object any) error {
|
func (tx *DbTransaction) UpdateObject(bucketName string, key []byte, object any) error {
|
||||||
data, err := tx.conn.MarshalObject(object)
|
data, err := tx.conn.MarshalObject(object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
type BaseCRUD[T any, I constraints.Integer] interface {
|
type BaseCRUD[T any, I constraints.Integer] interface {
|
||||||
Create(element *T) error
|
Create(element *T) error
|
||||||
Read(ID I) (*T, error)
|
Read(ID I) (*T, error)
|
||||||
|
Exists(ID I) (bool, error)
|
||||||
ReadAll() ([]T, error)
|
ReadAll() ([]T, error)
|
||||||
Update(ID I, element *T) error
|
Update(ID I, element *T) error
|
||||||
Delete(ID I) error
|
Delete(ID I) error
|
||||||
|
@ -42,6 +43,19 @@ func (service BaseDataService[T, I]) Read(ID I) (*T, error) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (service BaseDataService[T, I]) Exists(ID I) (bool, error) {
|
||||||
|
var exists bool
|
||||||
|
|
||||||
|
err := service.Connection.ViewTx(func(tx portainer.Transaction) error {
|
||||||
|
var err error
|
||||||
|
exists, err = service.Tx(tx).Exists(ID)
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
return exists, err
|
||||||
|
}
|
||||||
|
|
||||||
func (service BaseDataService[T, I]) ReadAll() ([]T, error) {
|
func (service BaseDataService[T, I]) ReadAll() ([]T, error) {
|
||||||
var collection = make([]T, 0)
|
var collection = make([]T, 0)
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,12 @@ func (service BaseDataServiceTx[T, I]) Read(ID I) (*T, error) {
|
||||||
return &element, nil
|
return &element, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (service BaseDataServiceTx[T, I]) Exists(ID I) (bool, error) {
|
||||||
|
identifier := service.Connection.ConvertToKey(int(ID))
|
||||||
|
|
||||||
|
return service.Tx.KeyExists(service.Bucket, identifier)
|
||||||
|
}
|
||||||
|
|
||||||
func (service BaseDataServiceTx[T, I]) ReadAll() ([]T, error) {
|
func (service BaseDataServiceTx[T, I]) ReadAll() ([]T, error) {
|
||||||
var collection = make([]T, 0)
|
var collection = make([]T, 0)
|
||||||
|
|
||||||
|
|
|
@ -105,14 +105,16 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht
|
||||||
|
|
||||||
for idx := range paginatedEndpoints {
|
for idx := range paginatedEndpoints {
|
||||||
hideFields(&paginatedEndpoints[idx])
|
hideFields(&paginatedEndpoints[idx])
|
||||||
|
|
||||||
paginatedEndpoints[idx].ComposeSyntaxMaxVersion = handler.ComposeStackManager.ComposeSyntaxMaxVersion()
|
paginatedEndpoints[idx].ComposeSyntaxMaxVersion = handler.ComposeStackManager.ComposeSyntaxMaxVersion()
|
||||||
if paginatedEndpoints[idx].EdgeCheckinInterval == 0 {
|
if paginatedEndpoints[idx].EdgeCheckinInterval == 0 {
|
||||||
paginatedEndpoints[idx].EdgeCheckinInterval = settings.EdgeAgentCheckinInterval
|
paginatedEndpoints[idx].EdgeCheckinInterval = settings.EdgeAgentCheckinInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
endpointutils.UpdateEdgeEndpointHeartbeat(&paginatedEndpoints[idx], settings)
|
endpointutils.UpdateEdgeEndpointHeartbeat(&paginatedEndpoints[idx], settings)
|
||||||
|
|
||||||
if !query.excludeSnapshots {
|
if !query.excludeSnapshots {
|
||||||
err = handler.SnapshotService.FillSnapshotData(&paginatedEndpoints[idx])
|
if err := handler.SnapshotService.FillSnapshotData(&paginatedEndpoints[idx]); err != nil {
|
||||||
if err != nil {
|
|
||||||
return httperror.InternalServerError("Unable to add snapshot data", err)
|
return httperror.InternalServerError("Unable to add snapshot data", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,6 +122,7 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht
|
||||||
|
|
||||||
w.Header().Set("X-Total-Count", strconv.Itoa(filteredEndpointCount))
|
w.Header().Set("X-Total-Count", strconv.Itoa(filteredEndpointCount))
|
||||||
w.Header().Set("X-Total-Available", strconv.Itoa(totalAvailableEndpoints))
|
w.Header().Set("X-Total-Available", strconv.Itoa(totalAvailableEndpoints))
|
||||||
|
|
||||||
return response.JSON(w, paginatedEndpoints)
|
return response.JSON(w, paginatedEndpoints)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,18 +133,8 @@ func paginateEndpoints(endpoints []portainer.Endpoint, start, limit int) []porta
|
||||||
|
|
||||||
endpointCount := len(endpoints)
|
endpointCount := len(endpoints)
|
||||||
|
|
||||||
if start < 0 {
|
start = min(max(start, 0), endpointCount)
|
||||||
start = 0
|
end := min(start+limit, endpointCount)
|
||||||
}
|
|
||||||
|
|
||||||
if start > endpointCount {
|
|
||||||
start = endpointCount
|
|
||||||
}
|
|
||||||
|
|
||||||
end := start + limit
|
|
||||||
if end > endpointCount {
|
|
||||||
end = endpointCount
|
|
||||||
}
|
|
||||||
|
|
||||||
return endpoints[start:end]
|
return endpoints[start:end]
|
||||||
}
|
}
|
||||||
|
@ -151,8 +144,10 @@ func getEndpointGroup(groupID portainer.EndpointGroupID, groups []portainer.Endp
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
if group.ID == groupID {
|
if group.ID == groupID {
|
||||||
endpointGroup = group
|
endpointGroup = group
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpointGroup
|
return endpointGroup
|
||||||
}
|
}
|
||||||
|
|
|
@ -243,8 +243,7 @@ func (bouncer *RequestBouncer) mwCheckPortainerAuthorizations(next http.Handler,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = bouncer.dataStore.User().Read(tokenData.ID)
|
if ok, err := bouncer.dataStore.User().Exists(tokenData.ID); !ok {
|
||||||
if bouncer.dataStore.IsErrObjectNotFound(err) {
|
|
||||||
httperror.WriteError(w, http.StatusUnauthorized, "Unauthorized", httperrors.ErrUnauthorized)
|
httperror.WriteError(w, http.StatusUnauthorized, "Unauthorized", httperrors.ErrUnauthorized)
|
||||||
return
|
return
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
@ -322,9 +321,8 @@ func (bouncer *RequestBouncer) mwAuthenticateFirst(tokenLookups []tokenLookup, n
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, _ := bouncer.dataStore.User().Read(token.ID)
|
if ok, _ := bouncer.dataStore.User().Exists(token.ID); !ok {
|
||||||
if user == nil {
|
httperror.WriteError(w, http.StatusUnauthorized, "The authorization token is invalid", httperrors.ErrUnauthorized)
|
||||||
httperror.WriteError(w, http.StatusUnauthorized, "An authorization token is invalid", httperrors.ErrUnauthorized)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,6 +153,7 @@ func (s *stubUserService) UsersByRole(role portainer.UserRole) ([]portainer.User
|
||||||
func (s *stubUserService) Create(user *portainer.User) error { return nil }
|
func (s *stubUserService) Create(user *portainer.User) error { return nil }
|
||||||
func (s *stubUserService) Update(ID portainer.UserID, user *portainer.User) error { return nil }
|
func (s *stubUserService) Update(ID portainer.UserID, user *portainer.User) error { return nil }
|
||||||
func (s *stubUserService) Delete(ID portainer.UserID) error { return nil }
|
func (s *stubUserService) Delete(ID portainer.UserID) error { return nil }
|
||||||
|
func (s *stubUserService) Exists(ID portainer.UserID) (bool, error) { return false, nil }
|
||||||
|
|
||||||
// WithUsers testDatastore option that will instruct testDatastore to return provided users
|
// WithUsers testDatastore option that will instruct testDatastore to return provided users
|
||||||
func WithUsers(us []portainer.User) datastoreOption {
|
func WithUsers(us []portainer.User) datastoreOption {
|
||||||
|
@ -188,6 +189,9 @@ func (s *stubEdgeJobService) UpdateEdgeJobFunc(ID portainer.EdgeJobID, updateFun
|
||||||
}
|
}
|
||||||
func (s *stubEdgeJobService) Delete(ID portainer.EdgeJobID) error { return nil }
|
func (s *stubEdgeJobService) Delete(ID portainer.EdgeJobID) error { return nil }
|
||||||
func (s *stubEdgeJobService) GetNextIdentifier() int { return 0 }
|
func (s *stubEdgeJobService) GetNextIdentifier() int { return 0 }
|
||||||
|
func (s *stubEdgeJobService) Exists(ID portainer.EdgeJobID) (bool, error) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
// WithEdgeJobs option will instruct testDatastore to return provided jobs
|
// WithEdgeJobs option will instruct testDatastore to return provided jobs
|
||||||
func WithEdgeJobs(js []portainer.EdgeJob) datastoreOption {
|
func WithEdgeJobs(js []portainer.EdgeJob) datastoreOption {
|
||||||
|
@ -452,6 +456,10 @@ func (s *stubStacksService) GetNextIdentifier() int {
|
||||||
return len(s.stacks)
|
return len(s.stacks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *stubStacksService) Exists(ID portainer.StackID) (bool, error) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
// WithStacks option will instruct testDatastore to return provided stacks
|
// WithStacks option will instruct testDatastore to return provided stacks
|
||||||
func WithStacks(stacks []portainer.Stack) datastoreOption {
|
func WithStacks(stacks []portainer.Stack) datastoreOption {
|
||||||
return func(d *testDatastore) {
|
return func(d *testDatastore) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue