Files
orbit/internal/storage/mysql/console_scope.go

136 lines
5.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 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); 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
}