136 lines
5.2 KiB
Go
136 lines
5.2 KiB
Go
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
|
||
}
|