Add granular role grant/revoke endpoints to REST and gRPC APIs
- Add POST /v1/accounts/{id}/roles and DELETE /v1/accounts/{id}/roles/{role} REST endpoints
- Add GrantRole and RevokeRole RPCs to AccountService in gRPC API
- Update OpenAPI specification with new endpoints
- Add grant and revoke subcommands to mciasctl
- Add grant and revoke subcommands to mciasgrpcctl
- Regenerate proto files with new message types and RPCs
- Implement gRPC server methods for granular role management
- All existing tests pass; build verified with goimports
Security: Role changes are audited via EventRoleGranted and EventRoleRevoked events,
consistent with existing SetRoles implementation.
This commit is contained in:
@@ -28,6 +28,8 @@
|
||||
//
|
||||
// role list -id UUID
|
||||
// role set -id UUID -roles role1,role2,...
|
||||
// role grant -id UUID -role ROLE
|
||||
// role revoke -id UUID -role ROLE
|
||||
//
|
||||
// token issue -id UUID
|
||||
// token revoke -jti JTI
|
||||
@@ -386,13 +388,17 @@ func (c *controller) accountSetPassword(args []string) {
|
||||
|
||||
func (c *controller) runRole(args []string) {
|
||||
if len(args) == 0 {
|
||||
fatalf("role requires a subcommand: list, set")
|
||||
fatalf("role requires a subcommand: list, set, grant, revoke")
|
||||
}
|
||||
switch args[0] {
|
||||
case "list":
|
||||
c.roleList(args[1:])
|
||||
case "set":
|
||||
c.roleSet(args[1:])
|
||||
case "grant":
|
||||
c.roleGrant(args[1:])
|
||||
case "revoke":
|
||||
c.roleRevoke(args[1:])
|
||||
default:
|
||||
fatalf("unknown role subcommand %q", args[0])
|
||||
}
|
||||
@@ -437,6 +443,41 @@ func (c *controller) roleSet(args []string) {
|
||||
fmt.Printf("roles set: %v\n", roles)
|
||||
}
|
||||
|
||||
func (c *controller) roleGrant(args []string) {
|
||||
fs := flag.NewFlagSet("role grant", flag.ExitOnError)
|
||||
id := fs.String("id", "", "account UUID (required)")
|
||||
role := fs.String("role", "", "role name (required)")
|
||||
_ = fs.Parse(args)
|
||||
|
||||
if *id == "" {
|
||||
fatalf("role grant: -id is required")
|
||||
}
|
||||
if *role == "" {
|
||||
fatalf("role grant: -role is required")
|
||||
}
|
||||
|
||||
body := map[string]string{"role": *role}
|
||||
c.doRequest("POST", "/v1/accounts/"+*id+"/roles", body, nil)
|
||||
fmt.Printf("role granted: %s\n", *role)
|
||||
}
|
||||
|
||||
func (c *controller) roleRevoke(args []string) {
|
||||
fs := flag.NewFlagSet("role revoke", flag.ExitOnError)
|
||||
id := fs.String("id", "", "account UUID (required)")
|
||||
role := fs.String("role", "", "role name (required)")
|
||||
_ = fs.Parse(args)
|
||||
|
||||
if *id == "" {
|
||||
fatalf("role revoke: -id is required")
|
||||
}
|
||||
if *role == "" {
|
||||
fatalf("role revoke: -role is required")
|
||||
}
|
||||
|
||||
c.doRequest("DELETE", "/v1/accounts/"+*id+"/roles/"+*role, nil, nil)
|
||||
fmt.Printf("role revoked: %s\n", *role)
|
||||
}
|
||||
|
||||
// ---- token subcommands ----
|
||||
|
||||
func (c *controller) runToken(args []string) {
|
||||
|
||||
@@ -28,8 +28,10 @@
|
||||
// account update -id UUID -status active|inactive
|
||||
// account delete -id UUID
|
||||
//
|
||||
// role list -id UUID
|
||||
// role set -id UUID -roles role1,role2,...
|
||||
// role list -id UUID
|
||||
// role set -id UUID -roles role1,role2,...
|
||||
// role grant -id UUID -role ROLE
|
||||
// role revoke -id UUID -role ROLE
|
||||
//
|
||||
// token validate -token TOKEN
|
||||
// token issue -id UUID
|
||||
@@ -392,13 +394,17 @@ func (c *controller) accountDelete(args []string) {
|
||||
|
||||
func (c *controller) runRole(args []string) {
|
||||
if len(args) == 0 {
|
||||
fatalf("role requires a subcommand: list, set")
|
||||
fatalf("role requires a subcommand: list, set, grant, revoke")
|
||||
}
|
||||
switch args[0] {
|
||||
case "list":
|
||||
c.roleList(args[1:])
|
||||
case "set":
|
||||
c.roleSet(args[1:])
|
||||
case "grant":
|
||||
c.roleGrant(args[1:])
|
||||
case "revoke":
|
||||
c.roleRevoke(args[1:])
|
||||
default:
|
||||
fatalf("unknown role subcommand %q", args[0])
|
||||
}
|
||||
@@ -455,6 +461,54 @@ func (c *controller) roleSet(args []string) {
|
||||
fmt.Printf("roles set: %v\n", roles)
|
||||
}
|
||||
|
||||
func (c *controller) roleGrant(args []string) {
|
||||
fs := flag.NewFlagSet("role grant", flag.ExitOnError)
|
||||
id := fs.String("id", "", "account UUID (required)")
|
||||
role := fs.String("role", "", "role name (required)")
|
||||
_ = fs.Parse(args)
|
||||
|
||||
if *id == "" {
|
||||
fatalf("role grant: -id is required")
|
||||
}
|
||||
if *role == "" {
|
||||
fatalf("role grant: -role is required")
|
||||
}
|
||||
|
||||
cl := mciasv1.NewAccountServiceClient(c.conn)
|
||||
ctx, cancel := c.callCtx()
|
||||
defer cancel()
|
||||
|
||||
_, err := cl.GrantRole(ctx, &mciasv1.GrantRoleRequest{Id: *id, Role: *role})
|
||||
if err != nil {
|
||||
fatalf("role grant: %v", err)
|
||||
}
|
||||
fmt.Printf("role granted: %s\n", *role)
|
||||
}
|
||||
|
||||
func (c *controller) roleRevoke(args []string) {
|
||||
fs := flag.NewFlagSet("role revoke", flag.ExitOnError)
|
||||
id := fs.String("id", "", "account UUID (required)")
|
||||
role := fs.String("role", "", "role name (required)")
|
||||
_ = fs.Parse(args)
|
||||
|
||||
if *id == "" {
|
||||
fatalf("role revoke: -id is required")
|
||||
}
|
||||
if *role == "" {
|
||||
fatalf("role revoke: -role is required")
|
||||
}
|
||||
|
||||
cl := mciasv1.NewAccountServiceClient(c.conn)
|
||||
ctx, cancel := c.callCtx()
|
||||
defer cancel()
|
||||
|
||||
_, err := cl.RevokeRole(ctx, &mciasv1.RevokeRoleRequest{Id: *id, Role: *role})
|
||||
if err != nil {
|
||||
fatalf("role revoke: %v", err)
|
||||
}
|
||||
fmt.Printf("role revoked: %s\n", *role)
|
||||
}
|
||||
|
||||
// ---- token subcommands ----
|
||||
|
||||
func (c *controller) runToken(args []string) {
|
||||
|
||||
Reference in New Issue
Block a user