Files
sm-user-plugin/classes/frontend_permission_catalog.go
Jakub Zych 0f545f5c81 feat: user MCP guard provider, plugin frontend permissions and the user_me tool
- classes.FrontendPermissionCatalog: the seeded rows, then codes activated
  plugins declare through pact.HasFrontendPermissions (seeded row wins);
  both permission editors offer it
- classes.MCPUserGuard (guard "user"): reads user.api_token, user.scope:*
  and user.permission:*, allows with the route's checks (session scopes,
  group grants, every code), dispatches with an in-memory token credential,
  resolves usr_ personal tokens; registered in *pact.MCPGuards and published
  as pact.MCPPersonalTokenResolver
- GET /_user/api/v1/token/me (user.api_token, user.scope:read) and the MCP
  tool me (user_me)
- pact.MCPSubjectChanged on token revoke, Users saves and User Groups saves
  and deletes
2026-10-23 12:08:38 +02:00

60 lines
1.9 KiB
Go

package classes
import (
"context"
"strings"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/cabana"
"git.golem15.com/golem15/summercms/modules/pact"
"git.golem15.com/golem15/summercms/modules/party"
"gorm.io/gorm"
)
// FrontendPermissionCatalog is what the user and user group permission
// editors offer (D-09): the rows of golem15_user_frontend_permissions first,
// in FrontendPermissionOptions order and unchanged, then the codes activated
// plugins declare through pact.HasFrontendPermissions, in activation order.
// A declared code that a seeded row already has is skipped (the seeded row
// wins), and so is a code an earlier plugin declared; an empty code is
// skipped. The table, its rows and the stored permissions are never
// written.
func FrontendPermissionCatalog(ctx context.Context, app *backpack.App, db *gorm.DB) ([]cabana.PermissionOption, error) {
rows, err := FrontendPermissionOptions(ctx, db)
if err != nil {
return nil, err
}
options := make([]cabana.PermissionOption, 0, len(rows))
seen := make(map[string]bool, len(rows))
for _, row := range rows {
option := cabana.PermissionOption{Code: row.Code, Label: row.Label, Tab: row.Tab}
if row.Comment != nil {
option.Comment = *row.Comment
}
options = append(options, option)
seen[row.Code] = true
}
if app == nil {
return options, nil
}
activated, ok := app.Lookup[party.Activated]()
if !ok {
return options, nil
}
for _, plugin := range activated {
declares, ok := plugin.(pact.HasFrontendPermissions)
if !ok || declares == nil {
continue
}
for _, perm := range declares.FrontendPermissions() {
code := strings.TrimSpace(perm.Code)
if code == "" || seen[code] {
continue
}
seen[code] = true
options = append(options, cabana.PermissionOption{Code: code, Label: perm.Label, Tab: perm.Tab, Comment: perm.Comment})
}
}
return options, nil
}