package mysql import ( "context" "database/sql" "errors" "git.yuntuo.hk/k2safe/orbit/internal/consoleauth" "git.yuntuo.hk/k2safe/orbit/internal/fault" core "git.yuntuo.hk/k2safe/orbit/model" "git.yuntuo.hk/k2safe/orbit/platform/auth" ) // consoleScopeActor 在业务读写的同一事务校验原会话、部署指定管理员及目标应用。 // 仅持共享身份锁,之后沿用业务既有的额度→队列→资源锁序。 func consoleScopeActor(ctx context.Context, tx *sql.Tx, t auth.Token) (core.ID, error) { if t.Console == nil || t.Console.Username == "" || t.Console.SessionNamespaceID.Validate() != nil || t.NamespaceID.Validate() != nil { return "", fault.Forbidden } var user core.ID err := tx.QueryRowContext(ctx, `SELECT u.id FROM orbit_console_sessions s JOIN orbit_console_users u ON u.id=s.user_id AND u.namespace_id=s.namespace_id AND u.credential_id=s.credential_id JOIN orbit_credentials c ON c.namespace_id=s.namespace_id AND c.id=s.credential_id JOIN orbit_namespaces n ON n.id=s.namespace_id JOIN orbit_applications a ON a.id=n.application_id WHERE s.namespace_id=? AND s.credential_id=? AND s.token_digest=? AND s.revoked_at IS NULL AND s.expires_at>UTC_TIMESTAMP(6) AND u.enabled=TRUE AND u.username=? AND c.revoked_at IS NULL AND (c.expires_at IS NULL OR c.expires_at>UTC_TIMESTAMP(6)) AND n.enabled=TRUE AND a.enabled=TRUE FOR SHARE`, t.Console.SessionNamespaceID, t.CredentialID, t.Digest, t.Console.Username).Scan(&user) if errors.Is(err, sql.ErrNoRows) { return "", fault.Forbidden } if err != nil { return "", err } if t.Console.UserID != "" && user != t.Console.UserID { return "", fault.Forbidden } var id core.ID err = tx.QueryRowContext(ctx, `SELECT n.id FROM orbit_namespaces n JOIN orbit_applications a ON a.id=n.application_id WHERE n.id=? AND n.enabled=TRUE AND a.enabled=TRUE FOR SHARE`, t.NamespaceID).Scan(&id) if errors.Is(err, sql.ErrNoRows) { return "", fault.NotFound } return user, err } func consoleScopePermissions() auth.Permissions { return auth.Permissions{Actions: []string{"tasks:read", "tasks:cancel", "tasks:review", "tasks:retry", "tasks:audit:read", "queues:read", "queues:manage", "schedules:read", "schedules:manage", "events:read", "events:manage", "workflows:read", "workflows:control", "workflows:recover", "workflows:audit:read"}, Queues: []string{"*"}} } func (s *ConsoleApplicationStore) ResolveScope(ctx context.Context, t auth.Token, target core.ID) (auth.Token, error) { if target.Validate() != nil { return auth.Token{}, fault.Invalid("invalid console namespace") } out := t out.NamespaceID = target out.Console = &auth.ConsoleIdentity{SessionNamespaceID: t.NamespaceID, Username: s.username} err := WithTx(ctx, s.db, func(tx *sql.Tx) error { user, err := consoleScopeActor(ctx, tx, out) out.Console.UserID = user return err }) return out, err } func (s *ConsoleApplicationStore) GetScope(ctx context.Context, t auth.Token, after core.ID) (consoleauth.Scope, error) { out := consoleauth.Scope{Queues: []consoleauth.AccessQueue{}} if after != "" && after.Validate() != nil { return out, fault.Invalid("invalid queue cursor") } err := WithTx(ctx, s.db, func(tx *sql.Tx) error { p, err := authenticate(ctx, tx, t) if err != nil { return err } if t.Console != nil { out.CanSwitch = true } else { // 元数据接口也必须核验真实控制台会话,SDK 不能读取此入口。 var user core.ID err = tx.QueryRowContext(ctx, `SELECT u.id,(u.username=?) FROM orbit_console_sessions s JOIN orbit_console_users u ON u.id=s.user_id AND u.namespace_id=s.namespace_id AND u.credential_id=s.credential_id WHERE s.namespace_id=? AND s.credential_id=? AND s.token_digest=? AND s.revoked_at IS NULL AND s.expires_at>UTC_TIMESTAMP(6) AND u.enabled=TRUE FOR SHARE`, s.username, t.NamespaceID, t.CredentialID, t.Digest).Scan(&user, &out.CanSwitch) if errors.Is(err, sql.ErrNoRows) { return fault.Unauthorized } if err != nil { return err } } e := &out.Entry err = tx.QueryRowContext(ctx, `SELECT a.id,a.name,n.id,n.name,n.environment,(a.enabled AND n.enabled) FROM orbit_namespaces n JOIN orbit_applications a ON a.id=n.application_id WHERE n.id=?`, t.NamespaceID).Scan(&e.ApplicationID, &e.Application, &e.NamespaceID, &e.Namespace, &e.Environment, &e.Enabled) if err != nil { return err } query := `SELECT id,name,paused,retired_at FROM pulse_queues WHERE namespace_id=?` args := []any{t.NamespaceID} if after != "" { query += ` AND id>?` args = append(args, after) } all := false for _, q := range p.Queues { if q == "*" { all = true } } if !all { query += " AND id IN (" for i, q := range p.Queues { if i > 0 { query += "," } query += "?" args = append(args, core.ID(q)) } query += ")" } rows, err := tx.QueryContext(ctx, query+` ORDER BY id LIMIT 101`, args...) if err != nil { return err } defer rows.Close() for rows.Next() { var q consoleauth.AccessQueue if err := rows.Scan(&q.ID, &q.Name, &q.Paused, &q.RetiredAt); err != nil { return err } out.Queues = append(out.Queues, q) } if len(out.Queues) > 100 { out.Queues = out.Queues[:100] out.Next = out.Queues[99].ID } return rows.Err() }) return out, err }