138 lines
5.7 KiB
Go
138 lines
5.7 KiB
Go
package mysql
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.yuntuo.hk/k2safe/orbit/internal/fault"
|
|
"git.yuntuo.hk/k2safe/orbit/internal/retention"
|
|
"git.yuntuo.hk/k2safe/orbit/platform/auth"
|
|
)
|
|
|
|
func TestIntegrationConsoleScope(t *testing.T) {
|
|
dsn := os.Getenv("ORBIT_TEST_MYSQL_DSN")
|
|
if dsn == "" {
|
|
t.Skip("requires disposable MySQL")
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
db, err := Open(ctx, dsn, 8, 2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
if err = Migrate(ctx, db); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
username := "scope-" + string(testID(t))
|
|
password := "test-password-for-scopes-123"
|
|
var source, target BootstrapResult
|
|
for i, dest := range []*BootstrapResult{&source, &target} {
|
|
in := BootstrapInput{Application: string(testID(t)), Namespace: "default", Environment: "test", Queue: "scope-queue"}
|
|
if i == 0 {
|
|
in.ConsoleUsername = username
|
|
in.ConsolePassword = password
|
|
}
|
|
if err = Bootstrap(ctx, db, in, func(v BootstrapResult) error { *dest = v; return nil }); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
accounts := NewConsoleAuthStore(db)
|
|
login, err := accounts.Login(ctx, username, password, "scope-test", "127.0.0.1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
session, _ := auth.Parse(login.Token)
|
|
sdk, _ := auth.Parse(source.Token)
|
|
scopes := NewConsoleApplicationStore(db, username)
|
|
initial, err := scopes.GetScope(ctx, session, "")
|
|
if err != nil || initial.Entry.NamespaceID != source.NamespaceID || !initial.CanSwitch {
|
|
t.Fatalf("initial scope: %+v %v", initial, err)
|
|
}
|
|
if _, err = scopes.GetScope(ctx, sdk, ""); fault.Code(err) != "UNAUTHENTICATED" {
|
|
t.Fatalf("SDK scope metadata: %v", err)
|
|
}
|
|
// 普通账号的队列白名单使用 BINARY ID 查询,不能以字符串错误比较成空列表。
|
|
if _, err = db.ExecContext(ctx, `UPDATE orbit_credentials SET permissions=JSON_OBJECT('actions',JSON_ARRAY('queues:read'),'queues',JSON_ARRAY(?)) WHERE namespace_id=? AND id=?`, string(source.QueueID), source.NamespaceID, source.CredentialID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
limited, err := NewConsoleApplicationStore(db, "another-admin").GetScope(ctx, session, "")
|
|
if err != nil || limited.CanSwitch || len(limited.Queues) != 1 || limited.Queues[0].ID != source.QueueID {
|
|
t.Fatalf("limited scope: %+v %v", limited, err)
|
|
}
|
|
last, err := scopes.GetScope(ctx, session, source.QueueID)
|
|
if err != nil || len(last.Queues) != 0 || last.Next != "" {
|
|
t.Fatalf("queue cursor: %+v %v", last, err)
|
|
}
|
|
if _, err = scopes.ResolveScope(ctx, sdk, target.NamespaceID); fault.Code(err) != "PERMISSION_DENIED" {
|
|
t.Fatalf("SDK override: %v", err)
|
|
}
|
|
for _, name := range []string{"", "another-admin"} {
|
|
if _, err = NewConsoleApplicationStore(db, name).ResolveScope(ctx, session, target.NamespaceID); fault.Code(err) != "PERMISSION_DENIED" {
|
|
t.Fatalf("unconfigured admin: %v", err)
|
|
}
|
|
}
|
|
scoped, err := scopes.ResolveScope(ctx, session, target.NamespaceID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tasks := NewTaskStore(db)
|
|
if _, err = tasks.GetQueue(ctx, scoped, target.QueueID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err = tasks.GetQueue(ctx, scoped, source.QueueID); fault.Code(err) != "NOT_FOUND" {
|
|
t.Fatalf("foreign queue: %v", err)
|
|
}
|
|
if err = tasks.AuthorizeSubmit(ctx, scoped); fault.Code(err) != "PERMISSION_DENIED" {
|
|
t.Fatalf("scope must not create work: %v", err)
|
|
}
|
|
got, err := scopes.GetScope(ctx, scoped, "")
|
|
if err != nil || got.Entry.ApplicationID != target.ApplicationID || len(got.Queues) != 1 || got.Queues[0].ID != target.QueueID {
|
|
t.Fatalf("target metadata: %+v %v", got, err)
|
|
}
|
|
// 完整应用/队列名称从数据库读取,包含特殊字符也不改变授权身份。
|
|
policy := NewRetentionPolicyStore(db)
|
|
in := retention.UpdateInput{ExpectedRevision: 0, Enabled: false, IdempotencyDays: 120, TaskHistoryDays: 400, WorkflowHistoryDays: 400, EventLogDays: 180, DeliveryHistoryDays: 120, InboxHistoryDays: 120, AuditLogDays: 2555, Reason: "scope policy test"}
|
|
saved, err := policy.UpdateRetentionPolicy(ctx, scoped, "scope-policy", in)
|
|
if err != nil || saved.NamespaceID != target.NamespaceID {
|
|
t.Fatalf("policy: %+v %v", saved, err)
|
|
}
|
|
if _, err = policy.UpdateRetentionPolicy(ctx, scoped, "scope-policy", in); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
original, err := policy.GetRetentionPolicy(ctx, source.NamespaceID)
|
|
if err != nil || original.Revision != 0 {
|
|
t.Fatalf("changed original scope: %+v %v", original, err)
|
|
}
|
|
var actor string
|
|
if err = db.QueryRowContext(ctx, `SELECT actor_id FROM orbit_audit_events WHERE namespace_id=? AND action='retention.policy.update'`, target.NamespaceID).Scan(&actor); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if actor != scoped.ActorID() {
|
|
t.Fatalf("audit actor: %s", actor)
|
|
}
|
|
// 解析后停用目标应用/退出会话,旧的内存范围不能继续授权。
|
|
if _, err = db.ExecContext(ctx, `UPDATE orbit_applications SET enabled=FALSE WHERE id=?`, target.ApplicationID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err = tasks.GetQueue(ctx, scoped, target.QueueID); fault.Code(err) != "NOT_FOUND" {
|
|
t.Fatalf("disabled target: %v", err)
|
|
}
|
|
if _, err = db.ExecContext(ctx, `UPDATE orbit_applications SET enabled=TRUE WHERE id=?`, target.ApplicationID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = accounts.RevokeSession(ctx, session); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
in.ExpectedRevision = 1
|
|
if _, err = policy.UpdateRetentionPolicy(ctx, scoped, "after-logout", in); fault.Code(err) != "PERMISSION_DENIED" {
|
|
t.Fatalf("stale scope write: %v", err)
|
|
}
|
|
var foreignKeys int
|
|
if err = db.QueryRowContext(ctx, `SELECT COUNT(*) FROM information_schema.REFERENTIAL_CONSTRAINTS WHERE CONSTRAINT_SCHEMA=DATABASE()`).Scan(&foreignKeys); err != nil || foreignKeys != 0 {
|
|
t.Fatalf("foreign keys %d %v", foreignKeys, err)
|
|
}
|
|
}
|