feat: add flag to see all tokens if owner (#6227)

* added query for tokens by user id

* updated query args

* adding owner col

* fix request params

* update-golden-files

* added owners col to ls table output

* added ttoken translations

* prettier

* format table according to arg

* using slice.Contains

* refactored token state

* cleanup
This commit is contained in:
Kira Pilot
2023-02-23 07:00:27 -08:00
committed by GitHub
parent 7a52a9cfc8
commit a32169ccb5
22 changed files with 348 additions and 214 deletions
+29 -8
View File
@@ -8,6 +8,7 @@ import (
"fmt"
"net"
"net/http"
"strconv"
"time"
"github.com/go-chi/chi/v5"
@@ -175,15 +176,35 @@ func (api *API) apiKey(rw http.ResponseWriter, r *http.Request) {
// @Success 200 {array} codersdk.APIKey
// @Router /users/{user}/keys/tokens [get]
func (api *API) tokens(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var (
ctx = r.Context()
user = httpmw.UserParam(r)
keys []database.APIKey
err error
queryStr = r.URL.Query().Get("include_all")
includeAll, _ = strconv.ParseBool(queryStr)
)
keys, err := api.Database.GetAPIKeysByLoginType(ctx, database.LoginTypeToken)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching API keys.",
Detail: err.Error(),
})
return
if includeAll {
// get tokens for all users
keys, err = api.Database.GetAPIKeysByLoginType(ctx, database.LoginTypeToken)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching API keys.",
Detail: err.Error(),
})
return
}
} else {
// get user's tokens only
keys, err = api.Database.GetAPIKeysByUserID(ctx, database.GetAPIKeysByUserIDParams{LoginType: database.LoginTypeToken, UserID: user.ID})
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching API keys.",
Detail: err.Error(),
})
return
}
}
keys, err = AuthorizeFilter(api.HTTPAuth, r, rbac.ActionRead, keys)
+5 -5
View File
@@ -24,7 +24,7 @@ func TestTokenCRUD(t *testing.T) {
defer cancel()
client := coderdtest.New(t, nil)
_ = coderdtest.CreateFirstUser(t, client)
keys, err := client.Tokens(ctx, codersdk.Me)
keys, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
require.NoError(t, err)
require.Empty(t, keys)
@@ -32,7 +32,7 @@ func TestTokenCRUD(t *testing.T) {
require.NoError(t, err)
require.Greater(t, len(res.Key), 2)
keys, err = client.Tokens(ctx, codersdk.Me)
keys, err = client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
require.NoError(t, err)
require.EqualValues(t, len(keys), 1)
require.Contains(t, res.Key, keys[0].ID)
@@ -45,7 +45,7 @@ func TestTokenCRUD(t *testing.T) {
err = client.DeleteAPIKey(ctx, codersdk.Me, keys[0].ID)
require.NoError(t, err)
keys, err = client.Tokens(ctx, codersdk.Me)
keys, err = client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
require.NoError(t, err)
require.Empty(t, keys)
}
@@ -64,7 +64,7 @@ func TestTokenScoped(t *testing.T) {
require.NoError(t, err)
require.Greater(t, len(res.Key), 2)
keys, err := client.Tokens(ctx, codersdk.Me)
keys, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
require.NoError(t, err)
require.EqualValues(t, len(keys), 1)
require.Contains(t, res.Key, keys[0].ID)
@@ -83,7 +83,7 @@ func TestTokenDuration(t *testing.T) {
Lifetime: time.Hour * 24 * 7,
})
require.NoError(t, err)
keys, err := client.Tokens(ctx, codersdk.Me)
keys, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
require.NoError(t, err)
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*6*24))
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*8*24))
+3 -1
View File
@@ -347,7 +347,9 @@ func NewAuthTester(ctx context.Context, t *testing.T, client *codersdk.Client, a
})
require.NoError(t, err, "create token")
apiKeys, err := client.Tokens(ctx, admin.UserID.String())
apiKeys, err := client.Tokens(ctx, admin.UserID.String(), codersdk.TokensFilter{
IncludeAll: true,
})
require.NoError(t, err, "get tokens")
apiKey := apiKeys[0]
+4
View File
@@ -40,6 +40,10 @@ func (q *querier) GetAPIKeysByLoginType(ctx context.Context, loginType database.
return fetchWithPostFilter(q.auth, q.db.GetAPIKeysByLoginType)(ctx, loginType)
}
func (q *querier) GetAPIKeysByUserID(ctx context.Context, params database.GetAPIKeysByUserIDParams) ([]database.APIKey, error) {
return fetchWithPostFilter(q.auth, q.db.GetAPIKeysByUserID)(ctx, database.GetAPIKeysByUserIDParams{LoginType: params.LoginType, UserID: params.UserID})
}
func (q *querier) GetAPIKeysLastUsedAfter(ctx context.Context, lastUsed time.Time) ([]database.APIKey, error) {
return fetchWithPostFilter(q.auth, q.db.GetAPIKeysLastUsedAfter)(ctx, lastUsed)
}
+12
View File
@@ -31,6 +31,18 @@ func (s *MethodTestSuite) TestAPIKey() {
Asserts(a, rbac.ActionRead, b, rbac.ActionRead).
Returns(slice.New(a, b))
}))
s.Run("GetAPIKeysByUserID", s.Subtest(func(db database.Store, check *expects) {
idAB := uuid.New()
idC := uuid.New()
keyA, _ := dbgen.APIKey(s.T(), db, database.APIKey{UserID: idAB, LoginType: database.LoginTypeToken})
keyB, _ := dbgen.APIKey(s.T(), db, database.APIKey{UserID: idAB, LoginType: database.LoginTypeToken})
_, _ = dbgen.APIKey(s.T(), db, database.APIKey{UserID: idC, LoginType: database.LoginTypeToken})
check.Args(database.GetAPIKeysByUserIDParams{LoginType: database.LoginTypeToken, UserID: idAB}).
Asserts(keyA, rbac.ActionRead, keyB, rbac.ActionRead).
Returns(slice.New(keyA, keyB))
}))
s.Run("GetAPIKeysLastUsedAfter", s.Subtest(func(db database.Store, check *expects) {
a, _ := dbgen.APIKey(s.T(), db, database.APIKey{LastUsed: time.Now().Add(time.Hour)})
b, _ := dbgen.APIKey(s.T(), db, database.APIKey{LastUsed: time.Now().Add(time.Hour)})
+13
View File
@@ -484,6 +484,19 @@ func (q *fakeQuerier) GetAPIKeysByLoginType(_ context.Context, t database.LoginT
return apiKeys, nil
}
func (q *fakeQuerier) GetAPIKeysByUserID(_ context.Context, params database.GetAPIKeysByUserIDParams) ([]database.APIKey, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
apiKeys := make([]database.APIKey, 0)
for _, key := range q.apiKeys {
if key.UserID == params.UserID && key.LoginType == params.LoginType {
apiKeys = append(apiKeys, key)
}
}
return apiKeys, nil
}
func (q *fakeQuerier) DeleteAPIKeyByID(_ context.Context, id string) error {
q.mutex.Lock()
defer q.mutex.Unlock()
+1
View File
@@ -31,6 +31,7 @@ type sqlcQuerier interface {
DeleteReplicasUpdatedBefore(ctx context.Context, updatedAt time.Time) error
GetAPIKeyByID(ctx context.Context, id string) (APIKey, error)
GetAPIKeysByLoginType(ctx context.Context, loginType LoginType) ([]APIKey, error)
GetAPIKeysByUserID(ctx context.Context, arg GetAPIKeysByUserIDParams) ([]APIKey, error)
GetAPIKeysLastUsedAfter(ctx context.Context, lastUsed time.Time) ([]APIKey, error)
GetActiveUserCount(ctx context.Context) (int64, error)
// GetAuditLogsBefore retrieves `row_limit` number of audit logs before the provided
+44
View File
@@ -249,6 +249,50 @@ func (q *sqlQuerier) GetAPIKeysByLoginType(ctx context.Context, loginType LoginT
return items, nil
}
const getAPIKeysByUserID = `-- name: GetAPIKeysByUserID :many
SELECT id, hashed_secret, user_id, last_used, expires_at, created_at, updated_at, login_type, lifetime_seconds, ip_address, scope FROM api_keys WHERE login_type = $1 AND user_id = $2
`
type GetAPIKeysByUserIDParams struct {
LoginType LoginType `db:"login_type" json:"login_type"`
UserID uuid.UUID `db:"user_id" json:"user_id"`
}
func (q *sqlQuerier) GetAPIKeysByUserID(ctx context.Context, arg GetAPIKeysByUserIDParams) ([]APIKey, error) {
rows, err := q.db.QueryContext(ctx, getAPIKeysByUserID, arg.LoginType, arg.UserID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []APIKey
for rows.Next() {
var i APIKey
if err := rows.Scan(
&i.ID,
&i.HashedSecret,
&i.UserID,
&i.LastUsed,
&i.ExpiresAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.LoginType,
&i.LifetimeSeconds,
&i.IPAddress,
&i.Scope,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getAPIKeysLastUsedAfter = `-- name: GetAPIKeysLastUsedAfter :many
SELECT id, hashed_secret, user_id, last_used, expires_at, created_at, updated_at, login_type, lifetime_seconds, ip_address, scope FROM api_keys WHERE last_used > $1
`
+3
View File
@@ -14,6 +14,9 @@ SELECT * FROM api_keys WHERE last_used > $1;
-- name: GetAPIKeysByLoginType :many
SELECT * FROM api_keys WHERE login_type = $1;
-- name: GetAPIKeysByUserID :many
SELECT * FROM api_keys WHERE login_type = $1 AND user_id = $2;
-- name: InsertAPIKey :one
INSERT INTO
api_keys (