309 lines
8.1 KiB
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)
|
|
}
|
|
}
|