2019-01-24 16:26:36 +00:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
2018-12-26 14:00:53 +00:00
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package satellitedb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2019-09-12 15:19:30 +01:00
|
|
|
"strings"
|
2018-12-26 14:00:53 +00:00
|
|
|
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
|
2020-03-30 10:08:50 +01:00
|
|
|
"storj.io/common/uuid"
|
2020-01-10 01:12:27 +00:00
|
|
|
"storj.io/storj/pkg/cache"
|
2019-01-15 13:03:24 +00:00
|
|
|
"storj.io/storj/satellite/console"
|
2020-01-15 02:29:51 +00:00
|
|
|
"storj.io/storj/satellite/satellitedb/dbx"
|
2018-12-26 14:00:53 +00:00
|
|
|
)
|
|
|
|
|
2019-11-04 14:37:39 +00:00
|
|
|
// ensures that apikeys implements console.APIKeys.
|
|
|
|
var _ console.APIKeys = (*apikeys)(nil)
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// apikeys is an implementation of satellite.APIKeys.
|
2018-12-26 14:00:53 +00:00
|
|
|
type apikeys struct {
|
2019-09-12 15:19:30 +01:00
|
|
|
methods dbx.Methods
|
2020-01-10 01:12:27 +00:00
|
|
|
lru *cache.ExpiringLRU
|
2019-12-14 02:29:54 +00:00
|
|
|
db *satelliteDB
|
2018-12-26 14:00:53 +00:00
|
|
|
}
|
|
|
|
|
2019-09-12 15:19:30 +01:00
|
|
|
func (keys *apikeys) GetPagedByProjectID(ctx context.Context, projectID uuid.UUID, cursor console.APIKeyCursor) (akp *console.APIKeyPage, err error) {
|
2019-06-04 12:55:38 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-09-12 15:19:30 +01:00
|
|
|
|
|
|
|
search := "%" + strings.Replace(cursor.Search, " ", "%", -1) + "%"
|
|
|
|
|
|
|
|
if cursor.Limit > 50 {
|
|
|
|
cursor.Limit = 50
|
|
|
|
}
|
|
|
|
|
|
|
|
if cursor.Page == 0 {
|
|
|
|
return nil, errs.New("page cannot be 0")
|
|
|
|
}
|
|
|
|
|
|
|
|
page := &console.APIKeyPage{
|
|
|
|
Search: cursor.Search,
|
|
|
|
Limit: cursor.Limit,
|
|
|
|
Offset: uint64((cursor.Page - 1) * cursor.Limit),
|
|
|
|
Order: cursor.Order,
|
|
|
|
OrderDirection: cursor.OrderDirection,
|
|
|
|
}
|
|
|
|
|
|
|
|
countQuery := keys.db.Rebind(`
|
|
|
|
SELECT COUNT(*)
|
|
|
|
FROM api_keys ak
|
|
|
|
WHERE ak.project_id = ?
|
2019-11-20 22:56:55 +00:00
|
|
|
AND lower(ak.name) LIKE ?
|
2019-09-12 15:19:30 +01:00
|
|
|
`)
|
|
|
|
|
|
|
|
countRow := keys.db.QueryRowContext(ctx,
|
|
|
|
countQuery,
|
|
|
|
projectID[:],
|
2019-11-20 22:56:55 +00:00
|
|
|
strings.ToLower(search))
|
2019-09-12 15:19:30 +01:00
|
|
|
|
|
|
|
err = countRow.Scan(&page.TotalCount)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if page.TotalCount == 0 {
|
|
|
|
return page, nil
|
|
|
|
}
|
|
|
|
if page.Offset > page.TotalCount-1 {
|
|
|
|
return nil, errs.New("page is out of range")
|
|
|
|
}
|
|
|
|
|
|
|
|
repoundQuery := keys.db.Rebind(`
|
2020-01-10 01:12:27 +00:00
|
|
|
SELECT ak.id, ak.project_id, ak.name, ak.partner_id, ak.created_at
|
2019-09-12 15:19:30 +01:00
|
|
|
FROM api_keys ak
|
|
|
|
WHERE ak.project_id = ?
|
2019-11-20 22:56:55 +00:00
|
|
|
AND lower(ak.name) LIKE ?
|
2019-09-12 15:19:30 +01:00
|
|
|
ORDER BY ` + sanitizedAPIKeyOrderColumnName(cursor.Order) + `
|
|
|
|
` + sanitizeOrderDirectionName(page.OrderDirection) + `
|
|
|
|
LIMIT ? OFFSET ?`)
|
|
|
|
|
|
|
|
rows, err := keys.db.QueryContext(ctx,
|
|
|
|
repoundQuery,
|
|
|
|
projectID[:],
|
2019-11-20 22:56:55 +00:00
|
|
|
strings.ToLower(search),
|
2019-09-12 15:19:30 +01:00
|
|
|
page.Limit,
|
|
|
|
page.Offset)
|
|
|
|
|
2018-12-26 14:00:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-01-16 14:27:24 +00:00
|
|
|
defer func() { err = errs.Combine(err, rows.Close()) }()
|
2018-12-26 14:00:53 +00:00
|
|
|
|
2019-01-15 13:03:24 +00:00
|
|
|
var apiKeys []console.APIKeyInfo
|
2019-09-12 15:19:30 +01:00
|
|
|
for rows.Next() {
|
|
|
|
ak := console.APIKeyInfo{}
|
2020-03-31 17:49:16 +01:00
|
|
|
var partnerID uuid.NullUUID
|
2018-12-26 14:00:53 +00:00
|
|
|
|
2020-03-31 17:49:16 +01:00
|
|
|
err = rows.Scan(&ak.ID, &ak.ProjectID, &ak.Name, &partnerID, &ak.CreatedAt)
|
2018-12-26 14:00:53 +00:00
|
|
|
if err != nil {
|
2019-09-12 15:19:30 +01:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-03-31 17:49:16 +01:00
|
|
|
ak.PartnerID = partnerID.UUID
|
2019-09-12 15:19:30 +01:00
|
|
|
apiKeys = append(apiKeys, ak)
|
2018-12-26 14:00:53 +00:00
|
|
|
}
|
|
|
|
|
2019-09-12 15:19:30 +01:00
|
|
|
page.APIKeys = apiKeys
|
|
|
|
page.Order = cursor.Order
|
|
|
|
|
|
|
|
page.PageCount = uint(page.TotalCount / uint64(cursor.Limit))
|
|
|
|
if page.TotalCount%uint64(cursor.Limit) != 0 {
|
|
|
|
page.PageCount++
|
|
|
|
}
|
|
|
|
|
|
|
|
page.CurrentPage = cursor.Page
|
|
|
|
|
|
|
|
err = rows.Err()
|
|
|
|
if err != nil {
|
2018-12-26 14:00:53 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-09-12 15:19:30 +01:00
|
|
|
return page, err
|
2018-12-26 14:00:53 +00:00
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// Get implements satellite.APIKeys.
|
2019-06-04 12:55:38 +01:00
|
|
|
func (keys *apikeys) Get(ctx context.Context, id uuid.UUID) (_ *console.APIKeyInfo, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-09-12 15:19:30 +01:00
|
|
|
dbKey, err := keys.methods.Get_ApiKey_By_Id(ctx, dbx.ApiKey_Id(id[:]))
|
2018-12-26 14:00:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-06-04 12:55:38 +01:00
|
|
|
return fromDBXAPIKey(ctx, dbKey)
|
2018-12-26 14:00:53 +00:00
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// GetByHead implements satellite.APIKeys.
|
2019-06-04 12:55:38 +01:00
|
|
|
func (keys *apikeys) GetByHead(ctx context.Context, head []byte) (_ *console.APIKeyInfo, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2020-01-10 01:12:27 +00:00
|
|
|
|
|
|
|
dbKeyI, err := keys.lru.Get(string(head), func() (interface{}, error) {
|
|
|
|
return keys.methods.Get_ApiKey_By_Head(ctx, dbx.ApiKey_Head(head))
|
|
|
|
})
|
2019-01-16 20:23:28 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-01-10 01:12:27 +00:00
|
|
|
dbKey, ok := dbKeyI.(*dbx.ApiKey)
|
|
|
|
if !ok {
|
|
|
|
return nil, Error.New("invalid key type: %T", dbKeyI)
|
|
|
|
}
|
2019-06-04 12:55:38 +01:00
|
|
|
return fromDBXAPIKey(ctx, dbKey)
|
2019-01-16 20:23:28 +00:00
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// GetByNameAndProjectID implements satellite.APIKeys.
|
2019-10-10 14:28:35 +01:00
|
|
|
func (keys *apikeys) GetByNameAndProjectID(ctx context.Context, name string, projectID uuid.UUID) (_ *console.APIKeyInfo, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
dbKey, err := keys.methods.Get_ApiKey_By_Name_And_ProjectId(ctx,
|
|
|
|
dbx.ApiKey_Name(name),
|
|
|
|
dbx.ApiKey_ProjectId(projectID[:]))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return fromDBXAPIKey(ctx, dbKey)
|
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// Create implements satellite.APIKeys.
|
2019-06-04 12:55:38 +01:00
|
|
|
func (keys *apikeys) Create(ctx context.Context, head []byte, info console.APIKeyInfo) (_ *console.APIKeyInfo, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-12-26 14:00:53 +00:00
|
|
|
id, err := uuid.New()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-07-17 21:53:14 +01:00
|
|
|
optional := dbx.ApiKey_Create_Fields{}
|
|
|
|
if !info.PartnerID.IsZero() {
|
|
|
|
optional.PartnerId = dbx.ApiKey_PartnerId(info.PartnerID[:])
|
|
|
|
}
|
|
|
|
|
2019-09-12 15:19:30 +01:00
|
|
|
dbKey, err := keys.methods.Create_ApiKey(
|
2018-12-26 14:00:53 +00:00
|
|
|
ctx,
|
|
|
|
dbx.ApiKey_Id(id[:]),
|
2018-12-27 15:30:15 +00:00
|
|
|
dbx.ApiKey_ProjectId(info.ProjectID[:]),
|
2019-05-24 17:51:27 +01:00
|
|
|
dbx.ApiKey_Head(head),
|
2018-12-27 15:30:15 +00:00
|
|
|
dbx.ApiKey_Name(info.Name),
|
2019-05-24 17:51:27 +01:00
|
|
|
dbx.ApiKey_Secret(info.Secret),
|
2019-07-17 21:53:14 +01:00
|
|
|
optional,
|
2018-12-26 14:00:53 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-06-04 12:55:38 +01:00
|
|
|
return fromDBXAPIKey(ctx, dbKey)
|
2018-12-26 14:00:53 +00:00
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// Update implements satellite.APIKeys.
|
2019-06-04 12:55:38 +01:00
|
|
|
func (keys *apikeys) Update(ctx context.Context, key console.APIKeyInfo) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-09-12 18:31:50 +01:00
|
|
|
return keys.methods.UpdateNoReturn_ApiKey_By_Id(
|
2018-12-26 14:00:53 +00:00
|
|
|
ctx,
|
|
|
|
dbx.ApiKey_Id(key.ID[:]),
|
|
|
|
dbx.ApiKey_Update_Fields{
|
|
|
|
Name: dbx.ApiKey_Name(key.Name),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// Delete implements satellite.APIKeys.
|
2019-06-04 12:55:38 +01:00
|
|
|
func (keys *apikeys) Delete(ctx context.Context, id uuid.UUID) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-09-12 15:19:30 +01:00
|
|
|
_, err = keys.methods.Delete_ApiKey_By_Id(ctx, dbx.ApiKey_Id(id[:]))
|
2018-12-26 14:00:53 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// fromDBXAPIKey converts dbx.ApiKey to satellite.APIKeyInfo.
|
2019-06-04 12:55:38 +01:00
|
|
|
func fromDBXAPIKey(ctx context.Context, key *dbx.ApiKey) (_ *console.APIKeyInfo, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2020-03-31 17:49:16 +01:00
|
|
|
id, err := uuid.FromBytes(key.Id)
|
2018-12-26 14:00:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-03-31 17:49:16 +01:00
|
|
|
projectID, err := uuid.FromBytes(key.ProjectId)
|
2018-12-26 14:00:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-07-17 21:53:14 +01:00
|
|
|
result := &console.APIKeyInfo{
|
2018-12-26 14:00:53 +00:00
|
|
|
ID: id,
|
|
|
|
ProjectID: projectID,
|
|
|
|
Name: key.Name,
|
|
|
|
CreatedAt: key.CreatedAt,
|
2019-05-24 17:51:27 +01:00
|
|
|
Secret: key.Secret,
|
2019-07-17 21:53:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if key.PartnerId != nil {
|
2020-03-31 17:49:16 +01:00
|
|
|
result.PartnerID, err = uuid.FromBytes(key.PartnerId)
|
2019-07-17 21:53:14 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
2018-12-26 14:00:53 +00:00
|
|
|
}
|
2019-09-12 15:19:30 +01:00
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// sanitizedAPIKeyOrderColumnName return valid order by column.
|
2019-09-12 15:19:30 +01:00
|
|
|
func sanitizedAPIKeyOrderColumnName(pmo console.APIKeyOrder) string {
|
|
|
|
if pmo == 2 {
|
|
|
|
return "ak.created_at"
|
|
|
|
}
|
|
|
|
|
|
|
|
return "ak.name"
|
|
|
|
}
|