mirror of https://github.com/portainer/portainer
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>pull/12608/head
parent
a67b917bdd
commit
0296998fae
|
@ -6,8 +6,10 @@ import (
|
|||
|
||||
type ReadTransaction interface {
|
||||
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
|
||||
GetAllWithKeyPrefix(bucketName string, keyPrefix []byte, obj any, append func(o any) (any, error)) error
|
||||
KeyExists(bucketName string, key []byte) (bool, error)
|
||||
}
|
||||
|
||||
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 {
|
||||
if !connection.isEncrypted {
|
||||
return nil
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
dserrors "github.com/portainer/portainer/api/dataservices/errors"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
data, err := tx.conn.MarshalObject(object)
|
||||
if err != nil {
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
type BaseCRUD[T any, I constraints.Integer] interface {
|
||||
Create(element *T) error
|
||||
Read(ID I) (*T, error)
|
||||
Exists(ID I) (bool, error)
|
||||
ReadAll() ([]T, error)
|
||||
Update(ID I, element *T) 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) {
|
||||
var collection = make([]T, 0)
|
||||
|
||||
|
|
|
@ -28,6 +28,12 @@ func (service BaseDataServiceTx[T, I]) Read(ID I) (*T, error) {
|
|||
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) {
|
||||
var collection = make([]T, 0)
|
||||
|
||||
|
|
|
@ -105,14 +105,16 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht
|
|||
|
||||
for idx := range paginatedEndpoints {
|
||||
hideFields(&paginatedEndpoints[idx])
|
||||
|
||||
paginatedEndpoints[idx].ComposeSyntaxMaxVersion = handler.ComposeStackManager.ComposeSyntaxMaxVersion()
|
||||
if paginatedEndpoints[idx].EdgeCheckinInterval == 0 {
|
||||
paginatedEndpoints[idx].EdgeCheckinInterval = settings.EdgeAgentCheckinInterval
|
||||
}
|
||||
|
||||
endpointutils.UpdateEdgeEndpointHeartbeat(&paginatedEndpoints[idx], settings)
|
||||
|
||||
if !query.excludeSnapshots {
|
||||
err = handler.SnapshotService.FillSnapshotData(&paginatedEndpoints[idx])
|
||||
if err != nil {
|
||||
if err := handler.SnapshotService.FillSnapshotData(&paginatedEndpoints[idx]); err != nil {
|
||||
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-Available", strconv.Itoa(totalAvailableEndpoints))
|
||||
|
||||
return response.JSON(w, paginatedEndpoints)
|
||||
}
|
||||
|
||||
|
@ -130,18 +133,8 @@ func paginateEndpoints(endpoints []portainer.Endpoint, start, limit int) []porta
|
|||
|
||||
endpointCount := len(endpoints)
|
||||
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
|
||||
if start > endpointCount {
|
||||
start = endpointCount
|
||||
}
|
||||
|
||||
end := start + limit
|
||||
if end > endpointCount {
|
||||
end = endpointCount
|
||||
}
|
||||
start = min(max(start, 0), endpointCount)
|
||||
end := min(start+limit, endpointCount)
|
||||
|
||||
return endpoints[start:end]
|
||||
}
|
||||
|
@ -151,8 +144,10 @@ func getEndpointGroup(groupID portainer.EndpointGroupID, groups []portainer.Endp
|
|||
for _, group := range groups {
|
||||
if group.ID == groupID {
|
||||
endpointGroup = group
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return endpointGroup
|
||||
}
|
||||
|
|
|
@ -243,8 +243,7 @@ func (bouncer *RequestBouncer) mwCheckPortainerAuthorizations(next http.Handler,
|
|||
return
|
||||
}
|
||||
|
||||
_, err = bouncer.dataStore.User().Read(tokenData.ID)
|
||||
if bouncer.dataStore.IsErrObjectNotFound(err) {
|
||||
if ok, err := bouncer.dataStore.User().Exists(tokenData.ID); !ok {
|
||||
httperror.WriteError(w, http.StatusUnauthorized, "Unauthorized", httperrors.ErrUnauthorized)
|
||||
return
|
||||
} else if err != nil {
|
||||
|
@ -322,9 +321,8 @@ func (bouncer *RequestBouncer) mwAuthenticateFirst(tokenLookups []tokenLookup, n
|
|||
return
|
||||
}
|
||||
|
||||
user, _ := bouncer.dataStore.User().Read(token.ID)
|
||||
if user == nil {
|
||||
httperror.WriteError(w, http.StatusUnauthorized, "An authorization token is invalid", httperrors.ErrUnauthorized)
|
||||
if ok, _ := bouncer.dataStore.User().Exists(token.ID); !ok {
|
||||
httperror.WriteError(w, http.StatusUnauthorized, "The authorization token is invalid", httperrors.ErrUnauthorized)
|
||||
|
||||
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) Update(ID portainer.UserID, user *portainer.User) 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
|
||||
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) 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
|
||||
func WithEdgeJobs(js []portainer.EdgeJob) datastoreOption {
|
||||
|
@ -452,6 +456,10 @@ func (s *stubStacksService) GetNextIdentifier() int {
|
|||
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
|
||||
func WithStacks(stacks []portainer.Stack) datastoreOption {
|
||||
return func(d *testDatastore) {
|
||||
|
|
Loading…
Reference in New Issue