pkg/connector/actions.go
110 linesgo
package connector

import (
	"context"
	"fmt"

	"github.com/conductorone/baton-sdk/pkg/actions"
	"github.com/conductorone/baton-sdk/pkg/annotations"
	"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/structpb"
)

// attrsLookup maps ConductorOne standard attribute names to the target app's
// API field names. Used by updateUserProfile to translate attribute update masks.
var attrsLookup = map[string]string{
	"first_name": "first_name",
	"last_name":  "last_name",
	"email":      "email",
	"department": "department",
}

func (m *Connector) enableUser(ctx context.Context, args *structpb.Struct) (*structpb.Struct, annotations.Annotations, error) {
	return m.setUserStatus(ctx, args, "active", "enable-user")
}

func (m *Connector) disableUser(ctx context.Context, args *structpb.Struct) (*structpb.Struct, annotations.Annotations, error) {
	return m.setUserStatus(ctx, args, "inactive", "disable-user")
}

func (m *Connector) setUserStatus(
	ctx context.Context,
	args *structpb.Struct,
	status string,
	actionName string,
) (*structpb.Struct, annotations.Annotations, error) {
	l := ctxzap.Extract(ctx)

	userID, ok := actions.GetStringArg(args, "resource_id")
	if !ok || userID == "" {
		return nil, nil, fmt.Errorf("baton-junction: %s: missing resource_id", actionName)
	}

	updated, err := m.client.UpdateUser(ctx, userID, map[string]string{"status": status})
	if err != nil {
		l.Error(fmt.Sprintf("baton-junction: %s: failed to update user status", actionName),
			zap.Error(err), zap.String("user_id", userID))
		return nil, nil, fmt.Errorf("baton-junction: %s: %w", actionName, err)
	}

	l.Info(fmt.Sprintf("baton-junction: %s: success", actionName),
		zap.String("user_id", userID), zap.String("new_status", updated.Status))

	result := &structpb.Struct{
		Fields: map[string]*structpb.Value{
			"success": {Kind: &structpb.Value_BoolValue{BoolValue: true}},
		},
	}

	return result, nil, nil
}

func (o *userBuilder) updateUserProfile(ctx context.Context, args *structpb.Struct) (*structpb.Struct, annotations.Annotations, error) {
	l := ctxzap.Extract(ctx)

	userResourceID, ok := actions.GetResourceIDArg(args, "user_id")
	if !ok {
		return nil, nil, fmt.Errorf("baton-junction: update-user-profile: missing required argument user_id")
	}
	userID := userResourceID.GetResource()
	if userID == "" {
		return nil, nil, fmt.Errorf("baton-junction: update-user-profile: empty user_id provided")
	}

	attrs := make(map[string]string)
	for c1Name, apiName := range attrsLookup {
		val, found := actions.GetStringArg(args, c1Name)
		if found && val != "" {
			attrs[apiName] = val
		}
	}

	if len(attrs) == 0 {
		return nil, nil, fmt.Errorf("baton-junction: update-user-profile: no attributes provided to update")
	}

	updated, err := o.client.UpdateUser(ctx, userID, attrs)
	if err != nil {
		l.Error("baton-junction: update-user-profile: failed to update user",
			zap.Error(err), zap.String("user_id", userID))
		return nil, nil, fmt.Errorf("baton-junction: update-user-profile: %w", err)
	}

	l.Info("baton-junction: update-user-profile: success",
		zap.String("user_id", userID), zap.Int("attrs_updated", len(attrs)))

	result := &structpb.Struct{
		Fields: map[string]*structpb.Value{
			"success":    {Kind: &structpb.Value_BoolValue{BoolValue: true}},
			"user_id":    {Kind: &structpb.Value_StringValue{StringValue: updated.ID}},
			"first_name": {Kind: &structpb.Value_StringValue{StringValue: updated.FirstName}},
			"last_name":  {Kind: &structpb.Value_StringValue{StringValue: updated.LastName}},
			"email":      {Kind: &structpb.Value_StringValue{StringValue: updated.Email}},
			"department": {Kind: &structpb.Value_StringValue{StringValue: updated.Department}},
		},
	}

	return result, nil, nil
}