Add license resource type with grant/revoke support#26
Conversation
|
Claude finished @c1-dev-bot[bot]'s task in 1m 58s —— View job
Review: Add license resource type with grant/revoke supportOverall this is a clean implementation that follows the established patterns in the codebase well. The pagination in Issues1. Missing
If intentional, consider adding a comment. If not: var licenseTypes = []licenseInfo{
{ID: "basic", DisplayName: "Basic", UserType: int(zoom.BasicUser)},
{ID: "licensed", DisplayName: "Licensed", UserType: int(zoom.LicensedUser)},
{ID: "unassigned", DisplayName: "Unassigned", UserType: int(zoom.UnnasignedUser)},
{ID: "none", DisplayName: "None", UserType: int(zoom.NoneUser)},
}2. Revoking the "None" license upgrades user to Basic — semantically surprising
Consider guarding against revoking the "basic" or "none" licenses, or at least the "none" type: if targetType == int(zoom.BasicUser) {
return nil, fmt.Errorf("baton-zoom: cannot revoke basic license (already lowest tier)")
}3. No inactive user check in Grant/Revoke The role builder ( 4. All other non-user resource types define a trait — Minor / Nits5. 6. What looks good
SummaryThe implementation is solid and consistent with the codebase. The main concern is the missing type 3 (Unassigned) license — users with that type will have no license grant in sync output. The revoke-None-upgrades-to-Basic semantic and the missing inactive user guards are worth addressing before merge. The rest is minor. |
|
Claude finished @c1-dev-bot[bot]'s task in 1m 20s —— View job
Review: Add license resource type with grant/revoke supportThe implementation is well-structured and follows existing codebase patterns closely. The pagination in Issues1. Missing
var licenseTypes = []licenseInfo{
{ID: "basic", DisplayName: "Basic", UserType: int(zoom.BasicUser)},
{ID: "licensed", DisplayName: "Licensed", UserType: int(zoom.LicensedUser)},
{ID: "unassigned", DisplayName: "Unassigned", UserType: int(zoom.UnnasignedUser)},
{ID: "none", DisplayName: "None", UserType: int(zoom.NoneUser)},
}2. Revoking "None" (type 99) or "Basic" (type 1) license upgrades user to Basic — semantically wrong
Consider guarding against revoking non-downgradeable licenses: if targetType == int(zoom.NoneUser) {
return nil, fmt.Errorf("baton-zoom: cannot revoke 'none' license — revoking would upgrade the user")
}3. No inactive user check in Grant/Revoke The role builder ( Add the same guard used in if user.Status == userStatusInactive {
return nil, nil, fmt.Errorf("baton-zoom: cannot grant license to inactive user %s", principal.Id.Resource)
}4. All other non-user resource types in Minor / Nits5. 6. Pagination 7. What looks good
SummaryThe implementation is solid and consistent with the codebase. Three issues should be addressed before merge: the missing type 3 (Unassigned) license (users silently dropped from sync), the revoke-None-upgrades-to-Basic semantic surprise, and the missing inactive user guards (inconsistent with |
Sync Zoom license types (Basic, Licensed, None) as first-class
resources with entitlements linking users to their current license.
Grant action updates a user's license type via PATCH /v2/users/{userId}.
Revoke action downgrades a user to Basic (type 1).
Fixes: CXH-1446
Add the license resource type entry to baton_capabilities.json and add a Licenses row to the capabilities table in connector.mdx.
3de40e1 to
6a88d73
Compare
| }, | ||
| "capabilities": [ | ||
| "CAPABILITY_SYNC", | ||
| "CAPABILITY_PROVISION" |
There was a problem hiding this comment.
🟠 Bug: CAPABILITY_PROVISION is declared for the license resource type, but the licenseResourceType builder in license.go does not implement Grant/Revoke methods. ConductorOne will surface provisioning actions that fail at runtime. Either remove CAPABILITY_PROVISION from this file, or add the provisioning implementation.
| } | ||
|
|
||
| if zoom.UserType(userTypeVal.GetNumberValue()) != zoom.LicensedUser { | ||
| return nil, &resource.SyncOpResults{}, nil |
There was a problem hiding this comment.
🟡 Suggestion: Only LicensedUser (type 2) receives a plan_base grant. Users with Basic (1), Unassigned (3), or None (99) types will have no license relationship visible in C1. If the intent is specifically to track Licensed-tier consumption of plan_base seats, a brief comment explaining why other types are excluded would help future readers. If full license visibility is desired, consider mapping each user type to its corresponding license resource.
| Id: &v2.ResourceId{ | ||
| ResourceType: resourceTypeLicense.Id, | ||
| Resource: "plan_base", | ||
| }, |
There was a problem hiding this comment.
🟡 Suggestion: The plan_base resource ID is hardcoded. If the Zoom account's API response has a nil PlanBase field, license.go:List() will skip creating this resource, but user grants will still reference it — creating orphaned grants pointing to a non-existent resource. Consider either guarding this grant behind a check that plan_base exists, or documenting the assumption that plan_base is always present for accounts with licensed users.
Connector PR Review: Add license resource type with grant/revoke supportBlocking Issues: 0 | Suggestions: 2 | Threads Resolved: 0 Review SummaryThis PR adds a Security IssuesNone found. Correctness IssuesNone found. Suggestions
Prompt for AI agents |
6a88d73 to
92ccbe1
Compare
| "permission": "user:read:list_users:admin" | ||
| }, | ||
| { |
There was a problem hiding this comment.
🟡 Suggestion: The license resource declares user:write:user:admin permission, but the sync-only implementation only performs a GET to /accounts/me/plans/usage. This write scope appears to be a leftover from removed provisioning support and may cause ConductorOne to request unnecessary API scopes from users.
92ccbe1 to
7d7ecdc
Compare
Summary
licenseresource type that syncs Zoom license tiers (Basic, Licensed, None) as first-class resourcesPATCH /v2/users/{userId}This enables license visibility in C1, SSAR flows for license requests, automated license reclamation during offboarding, and inclusion of license assignments in access reviews.
Fixes: CXH-1446
Changes
pkg/connector/license.go(new)licenseResourceTypeimplementingResourceSyncerV2+ResourceProvisionerV2List()returns three static license resources: basic, licensed, noneEntitlements()creates an "assigned" entitlement per license, grantable to usersGrants()paginates through users, creating grants for those matching the license typeGrant()updates user type via API, with idempotency (GrantAlreadyExists)Revoke()downgrades user to Basic, with idempotency (GrantAlreadyRevoked)pkg/zoom/client.goUpdateUserType()method:PATCH /v2/users/{userId}with{"type": N}pkg/connector/connector.goresourceTypeLicensedefinition with required API scopeslicenseBuilderinResourceSyncers()Test plan
licensedlicense updates user type to 2licensedlicense downgrades user type to 1 (Basic)GrantAlreadyExistsGrantAlreadyRevokedAutomated PR Notice
This PR was automatically created by c1-dev-bot as a potential implementation.
This code requires: