Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provisioning & roles refactor #13

Merged
merged 4 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Additional scopes for User Token are:
- admin.users:read

Other difference is in the way the application is installed, on enterprise grid app should be installed on the Organization level and on all the Workspaces from which you want to sync the resources. The installation has to be done by Admin or Owner of an Enterprise Grid organization. More info with an example [here](https://api.slack.com/methods/admin.teams.list#markdown).
To work with Enterprise Grid APIs use User Oath Token passed as `--enterprise-token` along with the Bot User OAuth Token passed via `--token` flag.
To work with Enterprise Grid APIs use User OAuth Token passed as `--enterprise-token` along with the Bot User OAuth Token passed via `--token` flag.


## brew
Expand Down Expand Up @@ -93,13 +93,13 @@ Available Commands:
Flags:
--client-id string The client ID used to authenticate with ConductorOne ($BATON_CLIENT_ID)
--client-secret string The client secret used to authenticate with ConductorOne ($BATON_CLIENT_SECRET)
--enterprise-token string The Slack user oath token used to connect to the Slack Enterprise Grid Admin API. ($BATON_ENTERPRISE_TOKEN)
--enterprise-token string The Slack user oauth token used to connect to the Slack Enterprise Grid Admin API. ($BATON_ENTERPRISE_TOKEN)
-f, --file string The path to the c1z file to sync with ($BATON_FILE) (default "sync.c1z")
-h, --help help for baton-slack
--log-format string The output format for logs: json, console ($BATON_LOG_FORMAT) (default "json")
--log-level string The log level: debug, info, warn, error ($BATON_LOG_LEVEL) (default "info")
-p, --provisioning This must be set in order for provisioning actions to be enabled. ($BATON_PROVISIONING)
--token string The Slack bot user oath token used to connect to the Slack API. ($BATON_TOKEN)
--token string The Slack bot user oauth token used to connect to the Slack API. ($BATON_TOKEN)
-v, --version version for baton-slack

Use "baton-slack [command] --help" for more information about a command.
Expand Down
4 changes: 2 additions & 2 deletions cmd/baton-slack/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ func validateConfig(ctx context.Context, cfg *config) error {

// cmdFlags sets the cmdFlags required for the connector.
func cmdFlags(cmd *cobra.Command) {
cmd.PersistentFlags().String("token", "", "The Slack bot user oath token used to connect to the Slack API. ($BATON_TOKEN)")
cmd.PersistentFlags().String("enterprise-token", "", "The Slack user oath token used to connect to the Slack Enterprise Grid Admin API. ($BATON_ENTERPRISE_TOKEN)")
cmd.PersistentFlags().String("token", "", "The Slack bot user oauth token used to connect to the Slack API. ($BATON_TOKEN)")
cmd.PersistentFlags().String("enterprise-token", "", "The Slack user oauth token used to connect to the Slack Enterprise Grid Admin API. ($BATON_ENTERPRISE_TOKEN)")
}
2 changes: 1 addition & 1 deletion pkg/connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func (s *Slack) ResourceSyncers(ctx context.Context) []connectorbuilder.Resource
userBuilder(s.client, s.enterpriseID, s.enterpriseClient),
workspaceBuilder(s.client, s.enterpriseID, s.enterpriseClient),
userGroupBuilder(s.client, s.enterpriseID, s.enterpriseClient),
workspaceRoleBuilder(s.client),
workspaceRoleBuilder(s.client, s.enterpriseClient),
enterpriseRoleBuilder(s.enterpriseID, s.enterpriseClient),
}
}
89 changes: 75 additions & 14 deletions pkg/connector/roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import (
"github.com/conductorone/baton-sdk/pkg/pagination"
ent "github.com/conductorone/baton-sdk/pkg/types/entitlement"
resources "github.com/conductorone/baton-sdk/pkg/types/resource"
enterprise "github.com/conductorone/baton-slack/pkg/slack"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
"github.com/slack-go/slack"
"go.uber.org/zap"
)

const (
Expand All @@ -35,18 +38,20 @@ var roles = map[string]string{
}

type workspaceRoleType struct {
resourceType *v2.ResourceType
client *slack.Client
resourceType *v2.ResourceType
client *slack.Client
enterpriseClient *enterprise.Client
}

func (o *workspaceRoleType) ResourceType(_ context.Context) *v2.ResourceType {
return o.resourceType
}

func workspaceRoleBuilder(client *slack.Client) *workspaceRoleType {
func workspaceRoleBuilder(client *slack.Client, enterpriseClient *enterprise.Client) *workspaceRoleType {
return &workspaceRoleType{
resourceType: resourceTypeWorkspaceRole,
client: client,
resourceType: resourceTypeWorkspaceRole,
client: client,
enterpriseClient: enterpriseClient,
}
}

Expand All @@ -56,10 +61,12 @@ func roleResource(roleID string, parentResourceID *v2.ResourceId) (*v2.Resource,
return nil, fmt.Errorf("invalid roleID: %s", roleID)
}

roleId := fmt.Sprintf("%s:%s", parentResourceID.Resource, roleID)

r, err := resources.NewRoleResource(
roleName,
resourceTypeWorkspaceRole,
roleID,
roleId,
nil,
resources.WithParentResourceID(parentResourceID))
if err != nil {
Expand All @@ -70,6 +77,10 @@ func roleResource(roleID string, parentResourceID *v2.ResourceId) (*v2.Resource,
}

func (o *workspaceRoleType) List(ctx context.Context, parentResourceID *v2.ResourceId, pt *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) {
if parentResourceID == nil {
return nil, "", nil, nil
}

var ret []*v2.Resource

for roleID := range roles {
Expand All @@ -85,16 +96,22 @@ func (o *workspaceRoleType) List(ctx context.Context, parentResourceID *v2.Resou
}

func (o *workspaceRoleType) Entitlements(ctx context.Context, resource *v2.Resource, _ *pagination.Token) ([]*v2.Entitlement, string, annotations.Annotations, error) {
rv := []*v2.Entitlement{
ent.NewAssignmentEntitlement(
resource,
RoleAssignmentEntitlement,
ent.WithGrantableTo(resourceTypeUser),
ent.WithDescription(fmt.Sprintf("Has the %s role in the Slack workspace", resource.DisplayName)),
ent.WithDisplayName(fmt.Sprintf("%s Workspace Role", resource.DisplayName)),
),
var rv []*v2.Entitlement

workspaceName, ok := workspacesMap[resource.ParentResourceId.Resource]
if !ok {
return nil, "", nil, fmt.Errorf("invalid workspace: %s", resource.ParentResourceId.Resource)
}

options := []ent.EntitlementOption{
ent.WithGrantableTo(resourceTypeUser),
ent.WithDescription(fmt.Sprintf("Has the %s role in the Slack %s workspace", resource.DisplayName, workspaceName)),
ent.WithDisplayName(fmt.Sprintf("%s workspace %s role", workspaceName, resource.DisplayName)),
}

roleEntitlement := ent.NewAssignmentEntitlement(resource, RoleAssignmentEntitlement, options...)
rv = append(rv, roleEntitlement)

return rv, "", nil, nil
}

Expand All @@ -103,3 +120,47 @@ func (o *workspaceRoleType) Entitlements(ctx context.Context, resource *v2.Resou
func (o *workspaceRoleType) Grants(ctx context.Context, resource *v2.Resource, pt *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) {
return nil, "", nil, nil
}

func (o *workspaceRoleType) Grant(ctx context.Context, principal *v2.Resource, entitlement *v2.Entitlement) (annotations.Annotations, error) {
l := ctxzap.Extract(ctx)

if principal.Id.ResourceType != resourceTypeUser.Id {
l.Warn(
"baton-slack: only users can be assigned a role",
zap.String("principal_type", principal.Id.ResourceType),
zap.String("principal_id", principal.Id.Resource),
)
return nil, fmt.Errorf("baton-slack: only users can be assigned a role")
}

err := o.enterpriseClient.SetWorkspaceRole(ctx, principal.ParentResourceId.Resource, principal.Id.Resource, entitlement.Resource.Id.Resource)
if err != nil {
return nil, fmt.Errorf("baton-slack: failed to assign user role: %w", err)
}

return nil, nil
}

func (o *workspaceRoleType) Revoke(ctx context.Context, grant *v2.Grant) (annotations.Annotations, error) {
l := ctxzap.Extract(ctx)

principal := grant.Principal

if principal.Id.ResourceType != resourceTypeUser.Id {
l.Warn(
"baton-slack: only users can have role revoked",
zap.String("principal_type", principal.Id.ResourceType),
zap.String("principal_id", principal.Id.Resource),
)
return nil, fmt.Errorf("baton-slack: only users can have role revoked")
}

// empty role type means regular user
err := o.enterpriseClient.SetWorkspaceRole(ctx, principal.ParentResourceId.Resource, principal.Id.Resource, "")

if err != nil {
return nil, fmt.Errorf("baton-slack: failed to revoke user role: %w", err)
}

return nil, nil
}
99 changes: 1 addition & 98 deletions pkg/connector/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
"github.com/conductorone/baton-sdk/pkg/annotations"
"github.com/conductorone/baton-sdk/pkg/pagination"
"github.com/conductorone/baton-sdk/pkg/types/grant"
"github.com/conductorone/baton-sdk/pkg/types/resource"
enterprise "github.com/conductorone/baton-slack/pkg/slack"
"github.com/slack-go/slack"
Expand Down Expand Up @@ -55,103 +54,7 @@ func (o *userResourceType) Entitlements(_ context.Context, _ *v2.Resource, _ *pa
}

func (o *userResourceType) Grants(ctx context.Context, resource *v2.Resource, pt *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) {
var rv []*v2.Grant

user, err := o.enterpriseClient.GetUserInfo(ctx, resource.Id.Resource)
if err != nil {
annos, err := annotationsForError(err)
return nil, "", annos, err
}

var userRoles []*v2.Resource

if user.IsPrimaryOwner {
rr, err := roleResource(PrimaryOwnerRoleID, resource.ParentResourceId)
if err != nil {
return nil, "", nil, err
}
userRoles = append(userRoles, rr)
}

if user.IsOwner {
rr, err := roleResource(OwnerRoleID, resource.ParentResourceId)
if err != nil {
return nil, "", nil, err
}
userRoles = append(userRoles, rr)
}

if user.IsAdmin {
rr, err := roleResource(AdminRoleID, resource.ParentResourceId)
if err != nil {
return nil, "", nil, err
}
userRoles = append(userRoles, rr)
}

if user.IsRestricted {
if user.IsUltraRestricted {
rr, err := roleResource(SingleChannelGuestRoleID, resource.ParentResourceId)
if err != nil {
return nil, "", nil, err
}
userRoles = append(userRoles, rr)
} else {
rr, err := roleResource(MultiChannelGuestRoleID, resource.ParentResourceId)
if err != nil {
return nil, "", nil, err
}
userRoles = append(userRoles, rr)
}
}

if user.IsInvitedUser {
rr, err := roleResource(InvitedMemberRoleID, resource.ParentResourceId)
if err != nil {
return nil, "", nil, err
}
userRoles = append(userRoles, rr)
}

if user.IsBot {
rr, err := roleResource(BotRoleID, resource.ParentResourceId)
if err != nil {
return nil, "", nil, err
}
userRoles = append(userRoles, rr)
}

if o.enterpriseID != "" {
if user.Enterprise.IsAdmin {
rr, err := enterpriseRoleResource(OrganizationAdminID, resource.ParentResourceId)
if err != nil {
return nil, "", nil, err
}
userRoles = append(userRoles, rr)
}

if user.Enterprise.IsOwner {
rr, err := enterpriseRoleResource(OrganizationOwnerID, resource.ParentResourceId)
if err != nil {
return nil, "", nil, err
}
userRoles = append(userRoles, rr)
}

if user.Enterprise.IsPrimaryOwner {
rr, err := enterpriseRoleResource(OrganizationPrimaryOwnerID, resource.ParentResourceId)
if err != nil {
return nil, "", nil, err
}
userRoles = append(userRoles, rr)
}
}

for _, ur := range userRoles {
rv = append(rv, grant.NewGrant(ur, RoleAssignmentEntitlement, resource.Id))
}

return rv, "", nil, nil
return nil, "", nil, nil
}

func (o *userResourceType) List(ctx context.Context, parentResourceID *v2.ResourceId, pt *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/connector/user_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (o *userGroupResourceType) List(ctx context.Context, parentResourceID *v2.R

var userGroups []slack.UserGroup
var err error
// different method here becuase we need to pass a teamID, but it's not supported by the slack-go library
// different method here because we need to pass a teamID, but it's not supported by the slack-go library
if o.enterpriseID != "" {
userGroups, err = o.enterpriseClient.GetUserGroups(ctx, parentResourceID.Resource)
if err != nil {
Expand Down
Loading
Loading