Skip to content

feat: Implement azdo security permission delete command #84

@tmeckel

Description

@tmeckel

This issue tracks the implementation of the azdo security permission delete command.

Command Description

Delete every explicit allow/deny permission that a user or group has on a securable resource (identified by a security token). This removes the access control entry altogether so that only inherited permissions remain. The Azure CLI az devops security permission reset-all command issues the same deletion via RemoveAccessControlEntries (source); azdo should expose that behavior with an explicit delete verb to reflect its destructive effect.

azdo Command Signature

azdo security permission delete <TARGET>

Accepted <TARGET> formats (consistent with other security permission commands):

  • ORGANIZATION/SUBJECT – operate at the organization scope.
  • ORGANIZATION/PROJECT/SUBJECT – scope the subject to a specific project.

Rules:

  • The organization segment is mandatory. If a project segment is present, resolve it via util.ResolveScopeDescriptor before invoking API calls.
  • SUBJECT must resolve to an Azure DevOps identity descriptor (UPN, descriptor string, group descriptor, etc.).

Flags:

  • --namespace-id, -n (required): GUID of the security namespace that owns the token.
  • --token (required): Security token string to delete. Preserve the token verbatim apart from trimming surrounding whitespace.
  • --yes, -y: Skip the confirmation prompt.

Behavior

  • Parse <TARGET> using shared.ParseSubjectTarget; fail if the subject segment is missing.
  • Resolve IO streams, start the progress indicator immediately, and stop it before emitting output.
  • Validate --namespace-id; parse into a uuid.UUID or return a flag error.
  • When a project is specified, call util.ResolveScopeDescriptor to ensure it exists and prime the cache.
  • Resolve the subject via extensionsClient.ResolveIdentity; raise a wrapped error if the descriptor is empty or the lookup fails.
  • Unless --yes is set, prompt via ctx.Prompter() (e.g., Delete permissions for <subject> on <token>?). Return util.ErrCancel when the user declines.
  • Call securityClient.RemoveAccessControlEntries with:
    • SecurityNamespaceId: pointer to the namespace UUID.
    • Token: pointer to the trimmed token string.
    • Descriptors: pointer to the resolved identity descriptor.
  • Treat a nil or false response as a failure and surface a descriptive error message.
  • Afterwards, call securityClient.QueryAccessControlLists with IncludeExtendedInfo=true, Descriptors=<descriptor>, Token=<token> to confirm the entry is gone. Use types.GetValue helpers for pointer safety and to detect any remaining inherited data.
  • Stop the progress indicator.
  • Output: print Permissions deleted. to ios.Out. Or an error otherwise.
  • Add debug logging with zap.L().Debug statements covering parsed scope, descriptor resolution, API inputs, and deletion outcomes.

Wiring & Docs

  • Implement the command in internal/cmd/security/permission/delete/delete.go using the standard NewCmd + runCommand pattern.
  • Register it inside internal/cmd/security/permission/permission.go via cmd.AddCommand(deletecmd.NewCmd(ctx)) (choose an appropriate package alias such as deletecmd).
  • Update help text and Example strings to demonstrate confirmation, --yes, and project-scoped usage.
  • Run make docs after implementation to regenerate CLI documentation.

Testing

  • Add table-driven unit tests in internal/cmd/security/permission/delete/delete_test.go using mocks from internal/mocks. Cover at minimum:
    1. Successful deletion (assert RemoveAccessControlEntries receives the descriptor/token pair and the follow-up ACL query is invoked).
    2. Input validation failures (missing subject, empty namespace ID, invalid GUID, empty token).
    3. Project-scoped targets ensuring util.ResolveScopeDescriptor is called.
    4. Confirmation flow (--yes bypass vs prompt decline returning util.ErrCancel).
    5. ResolveIdentity returning an empty descriptor (should error).
    6. JSON export path ensuring serialized output matches expectations.
  • Add an acceptance-style integration test (similar to update_acc_test.go) if possible to verify end-to-end behavior against the mock harness.

References

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions