Skip to content

Commit

Permalink
add account resource
Browse files Browse the repository at this point in the history
  • Loading branch information
jakcinmarina committed Mar 22, 2023
1 parent a21f7e6 commit c2ba191
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 75 deletions.
137 changes: 137 additions & 0 deletions pkg/connector/account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package connector

import (
"context"
"fmt"

"github.com/ConductorOne/baton-duo/pkg/duo"
v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
"github.com/conductorone/baton-sdk/pkg/annotations"
"github.com/conductorone/baton-sdk/pkg/pagination"
ent "github.com/conductorone/baton-sdk/pkg/types/entitlement"
grant "github.com/conductorone/baton-sdk/pkg/types/grant"
rs "github.com/conductorone/baton-sdk/pkg/types/resource"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
"go.uber.org/zap"
)

var roles = map[string]string{
"Owner": "owner",
"Administrator": "administrator",
"Application Manager": "application manager",
"User Manager": "user manager",
"Help Desk": "help desk",
"Billing": "billing",
"Phishing Manager": "phishing manager",
"Read-only": "readonly",
}

type accountResourceType struct {
resourceType *v2.ResourceType
client *duo.Client
integrationKey string
}

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

// Create a new connector resource for a Duo account.
func accountResource(ctx context.Context, account duo.Account, integrationKey string) (*v2.Resource, error) {
accountOptions := []rs.ResourceOption{
rs.WithAnnotation(
&v2.ChildResourceType{ResourceTypeId: resourceTypeUser.Id},
&v2.ChildResourceType{ResourceTypeId: resourceTypeGroup.Id},
&v2.ChildResourceType{ResourceTypeId: resourceTypeAdmin.Id},
),
}
ret, err := rs.NewResource(
account.Name,
resourceTypeAccount,
integrationKey,
accountOptions...,
)
if err != nil {
return nil, err
}

return ret, nil
}

func (o *accountResourceType) List(ctx context.Context, _ *v2.ResourceId, _ *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) {
account, err := o.client.GetAccount(ctx)
if err != nil {
return nil, "", nil, fmt.Errorf("duo-connector: failed to list an account: %w", err)
}

var rv []*v2.Resource
ar, err := accountResource(ctx, account, o.integrationKey)
if err != nil {
return nil, "", nil, err
}
rv = append(rv, ar)

return rv, "", nil, nil
}

func (o *accountResourceType) Entitlements(_ context.Context, resource *v2.Resource, _ *pagination.Token) ([]*v2.Entitlement, string, annotations.Annotations, error) {
var rv []*v2.Entitlement
for _, role := range roles {
permissionOptions := []ent.EntitlementOption{
ent.WithGrantableTo(resourceTypeAdmin),
ent.WithDescription(fmt.Sprintf("Role in %s Duo account", resource.DisplayName)),
ent.WithDisplayName(fmt.Sprintf("%s Account %s", resource.DisplayName, role)),
}

permissionEn := ent.NewPermissionEntitlement(resource, role, permissionOptions...)
rv = append(rv, permissionEn)
}
return rv, "", nil, nil
}

func (o *accountResourceType) Grants(ctx context.Context, resource *v2.Resource, token *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) {
var pageToken string
bag, err := parsePageToken(token.Token, &v2.ResourceId{ResourceType: resourceTypeAccount.Id})
if err != nil {
return nil, "", nil, err
}
admins, offset, err := o.client.GetAdmins(ctx, bag.PageToken())
if err != nil {
return nil, "", nil, err
}
if offset != "" {
pageToken, err = bag.NextToken(offset)
if err != nil {
return nil, "", nil, err
}
}

var rv []*v2.Grant
for _, admin := range admins {
roleName, ok := roles[admin.Role]
if !ok {
ctxzap.Extract(ctx).Warn("Unknown Duo Role Name, skipping",
zap.String("role_name", admin.Role),
zap.String("user", admin.Name),
)
continue
}
adminCopy := admin
ar, err := adminResource(ctx, &adminCopy, resource.Id)
if err != nil {
return nil, "", nil, err
}

permissionGrant := grant.NewGrant(resource, roleName, ar.Id)
rv = append(rv, permissionGrant)
}
return rv, pageToken, nil, nil
}

func accountBuilder(client *duo.Client, integrationKey string) *accountResourceType {
return &accountResourceType{
resourceType: resourceTypeAccount,
client: client,
integrationKey: integrationKey,
}
}
72 changes: 8 additions & 64 deletions pkg/connector/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,9 @@ 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"
ent "github.com/conductorone/baton-sdk/pkg/types/entitlement"
grant "github.com/conductorone/baton-sdk/pkg/types/grant"
rs "github.com/conductorone/baton-sdk/pkg/types/resource"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
"go.uber.org/zap"
)

var roles = map[string]string{
"Owner": "owner",
"Administrator": "administrator",
"Application Manager": "application manager",
"User Manager": "user manager",
"Help Desk": "help desk",
"Billing": "billing",
"Phishing Manager": "phishing manager",
"Read-only": "readonly",
}

type adminResourceType struct {
resourceType *v2.ResourceType
client *duo.Client
Expand All @@ -37,7 +22,7 @@ func (o *adminResourceType) ResourceType(_ context.Context) *v2.ResourceType {
}

// Create a new connector resource for a Duo admin.
func adminResource(ctx context.Context, admin *duo.Admin, _ *v2.ResourceId) (*v2.Resource, error) {
func adminResource(ctx context.Context, admin *duo.Admin, parentResourceID *v2.ResourceId) (*v2.Resource, error) {
names := strings.SplitN(admin.Name, " ", 2)
var firstName, lastName string
switch len(names) {
Expand Down Expand Up @@ -65,6 +50,7 @@ func adminResource(ctx context.Context, admin *duo.Admin, _ *v2.ResourceId) (*v2
resourceTypeAdmin,
admin.AdminID,
adminTraitOptions,
rs.WithParentResourceID(parentResourceID),
)
if err != nil {
return nil, err
Expand All @@ -74,6 +60,10 @@ func adminResource(ctx context.Context, admin *duo.Admin, _ *v2.ResourceId) (*v2
}

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

var pageToken string
bag, err := parsePageToken(token.Token, &v2.ResourceId{ResourceType: resourceTypeAdmin.Id})
if err != nil {
Expand Down Expand Up @@ -105,57 +95,11 @@ func (o *adminResourceType) List(ctx context.Context, parentId *v2.ResourceId, t
}

func (o *adminResourceType) Entitlements(_ context.Context, resource *v2.Resource, _ *pagination.Token) ([]*v2.Entitlement, string, annotations.Annotations, error) {
var rv []*v2.Entitlement
for _, role := range roles {
permissionOptions := []ent.EntitlementOption{
ent.WithGrantableTo(resourceTypeAdmin),
ent.WithDescription(fmt.Sprintln("Admin role")),
ent.WithDisplayName(fmt.Sprintf("role %s", role)),
}

permissionEn := ent.NewPermissionEntitlement(resource, role, permissionOptions...)
rv = append(rv, permissionEn)
}
return rv, "", nil, nil
return nil, "", nil, nil
}

func (o *adminResourceType) Grants(ctx context.Context, resource *v2.Resource, token *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) {
var pageToken string
bag, err := parsePageToken(token.Token, &v2.ResourceId{ResourceType: resourceTypeAdmin.Id})
if err != nil {
return nil, "", nil, err
}
admins, offset, err := o.client.GetAdmins(ctx, bag.PageToken())
if err != nil {
return nil, "", nil, err
}
if offset != "" {
pageToken, err = bag.NextToken(offset)
if err != nil {
return nil, "", nil, err
}
}

var rv []*v2.Grant
for _, admin := range admins {
roleName, ok := roles[admin.Role]
if !ok {
ctxzap.Extract(ctx).Warn("Unknown Duo Role Name, skipping",
zap.String("role_name", admin.Role),
zap.String("user", admin.Name),
)
continue
}
adminCopy := admin
ur, err := adminResource(ctx, &adminCopy, resource.Id)
if err != nil {
return nil, "", nil, err
}

permissionGrant := grant.NewGrant(resource, roleName, ur.Id)
rv = append(rv, permissionGrant)
}
return rv, pageToken, nil, nil
return nil, "", nil, nil
}

func adminBuilder(client *duo.Client) *adminResourceType {
Expand Down
11 changes: 9 additions & 2 deletions pkg/connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,23 @@ var (
v2.ResourceType_TRAIT_USER,
},
}
resourceTypeAccount = &v2.ResourceType{
Id: "account",
DisplayName: "Account",
}
)

type Duo struct {
client *duo.Client
client *duo.Client
integrationKey string
}

func (d *Duo) ResourceSyncers(ctx context.Context) []connectorbuilder.ResourceSyncer {
return []connectorbuilder.ResourceSyncer{
userBuilder(d.client),
groupBuilder(d.client),
adminBuilder(d.client),
accountBuilder(d.client, d.integrationKey),
}
}

Expand Down Expand Up @@ -72,6 +78,7 @@ func New(ctx context.Context, integrationKey string, secretKey string, apiHostna
}

return &Duo{
client: duo.NewClient(integrationKey, secretKey, apiHostname, httpClient),
client: duo.NewClient(integrationKey, secretKey, apiHostname, httpClient),
integrationKey: integrationKey,
}, nil
}
24 changes: 15 additions & 9 deletions pkg/connector/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,29 +33,35 @@ func groupBuilder(client *duo.Client) *groupResourceType {
}

// Create a new connector resource for a Duo group.
func groupResource(ctx context.Context, group duo.Group) (*v2.Resource, error) {
func groupResource(ctx context.Context, group duo.Group, parentResourceID *v2.ResourceId) (*v2.Resource, error) {
profile := make(map[string]interface{})
profile["group_id"] = group.GroupID
profile["group_name"] = group.Name

groupTrait := []rs.GroupTraitOption{
rs.WithGroupProfile(profile),
}
groupOptions := []rs.ResourceOption{
rs.WithAnnotation(
&v2.ChildResourceType{ResourceTypeId: resourceTypeUser.Id},
),
}

ret, err := rs.NewGroupResource(group.Name, resourceTypeGroup, group.GroupID, groupTrait, groupOptions...)
ret, err := rs.NewGroupResource(
group.Name,
resourceTypeGroup,
group.GroupID,
groupTrait,
rs.WithParentResourceID(parentResourceID),
)

if err != nil {
return nil, err
}

return ret, nil
}

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

var pageToken string
bag, err := parsePageToken(token.Token, &v2.ResourceId{ResourceType: resourceTypeGroup.Id})
if err != nil {
Expand All @@ -76,7 +82,7 @@ func (o *groupResourceType) List(ctx context.Context, resourceId *v2.ResourceId,

var rv []*v2.Resource
for _, group := range groups {
gr, err := groupResource(ctx, group)
gr, err := groupResource(ctx, group, parentId)
if err != nil {
return nil, "", nil, err
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/connector/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ func userResource(ctx context.Context, user *duo.User, parentResourceID *v2.Reso
}

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

var pageToken string
bag, err := parsePageToken(token.Token, &v2.ResourceId{ResourceType: resourceTypeUser.Id})
if err != nil {
Expand Down
27 changes: 27 additions & 0 deletions pkg/duo/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ type UserResponse struct {
Response User `json:"response"`
}

type AccountResponse struct {
ErrorResponse
Stat string `json:"stat"`
Response Account `json:"response"`
}

type IntegrationResponse struct {
ErrorResponse
Stat string `json:"stat"`
Expand Down Expand Up @@ -308,6 +314,27 @@ func (c *Client) GetIntegration(ctx context.Context) (IntegrationResponse, error
return res, nil
}

// GetAccount returns account info.
func (c *Client) GetAccount(ctx context.Context) (Account, error) {
uri := "/admin/v1/settings"
accountUrl := fmt.Sprint(c.baseUrl, uri)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, accountUrl, nil)
if err != nil {
return Account{}, err
}

var res AccountResponse
if err := c.doRequest(uri, req, &res, nil); err != nil {
return Account{}, err
}

if res.Stat == requestFailedStat {
return Account{}, fmt.Errorf("error fetching account: %s", res.Message)
}

return res.Response, nil
}

func (c *Client) doRequest(uri string, req *http.Request, resType interface{}, params url.Values) error {
now := time.Now().UTC().Format(time.RFC1123Z)
signature, err := sign(c.integrationKey, c.secretKey, "GET", c.host, uri, now, params)
Expand Down
4 changes: 4 additions & 0 deletions pkg/duo/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,7 @@ type Admin struct {
Role string `json:"role"`
Status string `json:"status"`
}

type Account struct {
Name string `json:"name"`
}

0 comments on commit c2ba191

Please sign in to comment.