Files
codit/backend/tests/store_api_keys_test.go

309 lines
8.1 KiB
Go

package tests
import "database/sql"
import "path/filepath"
import "testing"
import "time"
import "codit/internal/auth"
import "codit/internal/db"
import "codit/internal/models"
import "codit/internal/util"
import _ "modernc.org/sqlite"
func openTestStore(t *testing.T) *db.Store {
var dir string
var dsn string
var store *db.Store
var err error
dir = t.TempDir()
dsn = "file:" + filepath.Join(dir, "test.db") + "?_pragma=foreign_keys(1)"
store, err = db.Open("sqlite", dsn)
if err != nil {
t.Fatalf("open db: %v", err)
}
err = store.ApplyMigrations(filepath.Join("..", "migrations"))
if err != nil {
t.Fatalf("migrate db: %v", err)
}
return store
}
func createTestUser(t *testing.T, store *db.Store, username string) models.User {
var passwordHash string
var err error
var user models.User
passwordHash, err = auth.HashPassword("pass-123")
if err != nil {
t.Fatalf("hash password: %v", err)
}
user = models.User{
Username: username,
DisplayName: username,
Email: username + "@local",
IsAdmin: false,
Disabled: false,
AuthSource: "db",
}
user, err = store.CreateUser(user, passwordHash)
if err != nil {
t.Fatalf("create user: %v", err)
}
return user
}
func createAPIKey(t *testing.T, store *db.Store, userID string, name string, expiresAt int64) string {
var token string
var prefix string
var hash string
var key models.APIKey
var err error
token = "tok-" + name + "-" + time.Now().UTC().Format(time.RFC3339Nano)
if len(token) > 8 {
prefix = token[:8]
} else {
prefix = token
}
hash = util.HashToken(token)
key, err = store.CreateAPIKey(userID, name, hash, prefix, expiresAt)
if err != nil {
t.Fatalf("create api key: %v", err)
}
if key.ID == "" {
t.Fatalf("create api key: empty id")
}
return token
}
func createTestServicePrincipal(t *testing.T, store *db.Store, name string) models.ServicePrincipal {
var principal models.ServicePrincipal
var err error
principal = models.ServicePrincipal{
Name: name,
Description: name,
IsAdmin: false,
Disabled: false,
}
principal, err = store.CreateServicePrincipal(principal)
if err != nil {
t.Fatalf("create service principal: %v", err)
}
return principal
}
func createPrincipalAPIKey(t *testing.T, store *db.Store, principalID string, name string, expiresAt int64) string {
var token string
var prefix string
var hash string
var key models.PrincipalAPIKey
var err error
token = "ptok-" + name + "-" + time.Now().UTC().Format(time.RFC3339Nano)
if len(token) > 8 {
prefix = token[:8]
} else {
prefix = token
}
hash = util.HashToken(token)
key, err = store.CreatePrincipalAPIKey(principalID, name, hash, prefix, expiresAt)
if err != nil {
t.Fatalf("create principal api key: %v", err)
}
if key.ID == "" {
t.Fatalf("create principal api key: empty id")
}
return token
}
func TestAPIKeyAuthSuccess(t *testing.T) {
var store *db.Store
var user models.User
var token string
var got models.User
var err error
store = openTestStore(t)
defer store.Close()
user = createTestUser(t, store, "alice")
token = createAPIKey(t, store, user.ID, "default", 0)
got, err = store.GetUserByAPIKeyHash(util.HashToken(token))
if err != nil {
t.Fatalf("lookup by api key: %v", err)
}
if got.ID != user.ID {
t.Fatalf("unexpected user id: got=%s want=%s", got.ID, user.ID)
}
}
func TestAPIKeyAuthExpiredKeyFails(t *testing.T) {
var store *db.Store
var user models.User
var token string
var err error
store = openTestStore(t)
defer store.Close()
user = createTestUser(t, store, "bob")
token = createAPIKey(t, store, user.ID, "expired", time.Now().UTC().Unix()-60)
_, err = store.GetUserByAPIKeyHash(util.HashToken(token))
if err == nil {
t.Fatalf("expected error for expired key")
}
if err != sql.ErrNoRows {
t.Fatalf("unexpected error for expired key: %v", err)
}
}
func TestAPIKeyAuthDisabledKeyFails(t *testing.T) {
var store *db.Store
var user models.User
var token string
var keys []models.APIKey
var keyID string
var err error
store = openTestStore(t)
defer store.Close()
user = createTestUser(t, store, "carol")
token = createAPIKey(t, store, user.ID, "disabled", 0)
keys, err = store.ListAPIKeys(user.ID)
if err != nil {
t.Fatalf("list api keys: %v", err)
}
if len(keys) != 1 {
t.Fatalf("expected one key, got %d", len(keys))
}
keyID = keys[0].ID
err = store.SetAPIKeyDisabled(user.ID, keyID, true)
if err != nil {
t.Fatalf("disable key: %v", err)
}
_, err = store.GetUserByAPIKeyHash(util.HashToken(token))
if err == nil {
t.Fatalf("expected error for disabled key")
}
if err != sql.ErrNoRows {
t.Fatalf("unexpected error for disabled key: %v", err)
}
}
func TestAPIKeyAuthDisabledUserFails(t *testing.T) {
var store *db.Store
var user models.User
var token string
var err error
store = openTestStore(t)
defer store.Close()
user = createTestUser(t, store, "dave")
token = createAPIKey(t, store, user.ID, "user-disabled", 0)
err = store.SetUserDisabled(user.ID, true)
if err != nil {
t.Fatalf("disable user: %v", err)
}
_, err = store.GetUserByAPIKeyHash(util.HashToken(token))
if err == nil {
t.Fatalf("expected error for disabled user")
}
if err != sql.ErrNoRows {
t.Fatalf("unexpected error for disabled user: %v", err)
}
}
func TestDeleteUserCascadesAPIKeys(t *testing.T) {
var store *db.Store
var user models.User
var keys []models.APIKey
var err error
store = openTestStore(t)
defer store.Close()
user = createTestUser(t, store, "erin")
_ = createAPIKey(t, store, user.ID, "one", 0)
_ = createAPIKey(t, store, user.ID, "two", 0)
keys, err = store.ListAPIKeys(user.ID)
if err != nil {
t.Fatalf("list api keys before delete: %v", err)
}
if len(keys) != 2 {
t.Fatalf("expected two keys before delete, got %d", len(keys))
}
err = store.DeleteUser(user.ID)
if err != nil {
t.Fatalf("delete user: %v", err)
}
keys, err = store.ListAPIKeys(user.ID)
if err != nil {
t.Fatalf("list api keys after delete: %v", err)
}
if len(keys) != 0 {
t.Fatalf("expected zero keys after delete, got %d", len(keys))
}
}
func TestPrincipalAPIKeyAuthSuccess(t *testing.T) {
var store *db.Store
var principal models.ServicePrincipal
var token string
var got models.ServicePrincipal
var err error
store = openTestStore(t)
defer store.Close()
principal = createTestServicePrincipal(t, store, "robot")
token = createPrincipalAPIKey(t, store, principal.ID, "default", 0)
got, err = store.GetPrincipalByAPIKeyHash(util.HashToken(token))
if err != nil {
t.Fatalf("lookup principal by api key: %v", err)
}
if got.ID != principal.ID {
t.Fatalf("unexpected principal id: got=%s want=%s", got.ID, principal.ID)
}
}
func TestPrincipalAPIKeyAuthDisabledPrincipalFails(t *testing.T) {
var store *db.Store
var principal models.ServicePrincipal
var token string
var err error
store = openTestStore(t)
defer store.Close()
principal = createTestServicePrincipal(t, store, "robot-disabled")
token = createPrincipalAPIKey(t, store, principal.ID, "default", 0)
principal.Disabled = true
err = store.UpdateServicePrincipal(principal)
if err != nil {
t.Fatalf("disable principal: %v", err)
}
_, err = store.GetPrincipalByAPIKeyHash(util.HashToken(token))
if err == nil {
t.Fatalf("expected error for disabled principal")
}
if err != sql.ErrNoRows {
t.Fatalf("unexpected error for disabled principal: %v", err)
}
}
func TestListAPIKeysAdminIncludesPrincipalKeys(t *testing.T) {
var store *db.Store
var user models.User
var principal models.ServicePrincipal
var items []models.AdminAPIKey
var err error
store = openTestStore(t)
defer store.Close()
user = createTestUser(t, store, "frank")
principal = createTestServicePrincipal(t, store, "robot-admin-list")
_ = createAPIKey(t, store, user.ID, "user-key", 0)
_ = createPrincipalAPIKey(t, store, principal.ID, "principal-key", 0)
items, err = store.ListAPIKeysAdmin("", "")
if err != nil {
t.Fatalf("list admin api keys: %v", err)
}
if len(items) != 2 {
t.Fatalf("expected two admin api keys, got %d", len(items))
}
if items[0].SubjectType == items[1].SubjectType {
t.Fatalf("expected mixed subject types, got %q and %q", items[0].SubjectType, items[1].SubjectType)
}
}