Truncate access review roles with badge list
Long role strings in the access review table broke row layout when drivers joined many roles into one comma-separated value. Expose roles as a string array in GraphQL by splitting the stored role at the API layer, and render the first three roles as badges with a "+X more" popover for the rest. Closes ENG-459. Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
@@ -71,7 +71,7 @@ func (d *AnthropicDriver) ListAccounts(ctx context.Context) ([]AccountRecord, er
|
||||
record := AccountRecord{
|
||||
Email: u.Email,
|
||||
FullName: u.Name,
|
||||
Role: anthropicRole(u.Role),
|
||||
Roles: anthropicRoles(u.Role),
|
||||
IsAdmin: u.Role == "admin",
|
||||
ExternalID: u.ID,
|
||||
MFAStatus: coredata.MFAStatusUnknown,
|
||||
@@ -141,19 +141,23 @@ func (d *AnthropicDriver) fetchUsers(ctx context.Context, afterID string) (*anth
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func anthropicRole(role string) string {
|
||||
func anthropicRoles(role string) []string {
|
||||
if role == "" {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
switch role {
|
||||
case "admin":
|
||||
return "Admin"
|
||||
return []string{"Admin"}
|
||||
case "billing":
|
||||
return "Billing"
|
||||
return []string{"Billing"}
|
||||
case "developer":
|
||||
return "Developer"
|
||||
return []string{"Developer"}
|
||||
case "claude_code_user":
|
||||
return "Claude Code User"
|
||||
return []string{"Claude Code User"}
|
||||
case "user":
|
||||
return "User"
|
||||
return []string{"User"}
|
||||
default:
|
||||
return role
|
||||
return []string{role}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,38 +39,39 @@ func TestAnthropicDriver(t *testing.T) {
|
||||
assert.NotEmpty(t, first.Email)
|
||||
assert.NotEmpty(t, first.FullName)
|
||||
assert.NotEmpty(t, first.ExternalID)
|
||||
assert.Equal(t, "User", first.Role)
|
||||
assert.Equal(t, []string{"User"}, first.Roles)
|
||||
assert.False(t, first.IsAdmin)
|
||||
assert.NotNil(t, first.CreatedAt)
|
||||
|
||||
assert.Equal(t, "Developer", records[1].Role)
|
||||
assert.Equal(t, []string{"Developer"}, records[1].Roles)
|
||||
assert.False(t, records[1].IsAdmin)
|
||||
|
||||
admin := records[2]
|
||||
assert.Equal(t, "Admin", admin.Role)
|
||||
assert.Equal(t, []string{"Admin"}, admin.Roles)
|
||||
assert.True(t, admin.IsAdmin)
|
||||
}
|
||||
|
||||
func TestAnthropicRole(t *testing.T) {
|
||||
func TestAnthropicRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
in string
|
||||
want string
|
||||
want []string
|
||||
}{
|
||||
{"admin", "Admin"},
|
||||
{"billing", "Billing"},
|
||||
{"developer", "Developer"},
|
||||
{"claude_code_user", "Claude Code User"},
|
||||
{"user", "User"},
|
||||
{"unknown_future_role", "unknown_future_role"},
|
||||
{"admin", []string{"Admin"}},
|
||||
{"billing", []string{"Billing"}},
|
||||
{"developer", []string{"Developer"}},
|
||||
{"claude_code_user", []string{"Claude Code User"}},
|
||||
{"user", []string{"User"}},
|
||||
{"unknown_future_role", []string{"unknown_future_role"}},
|
||||
{"", []string{}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.in, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, c.want, anthropicRole(c.in))
|
||||
assert.Equal(t, c.want, anthropicRoles(c.in))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ func (d *BetterStackDriver) ListAccounts(ctx context.Context) ([]AccountRecord,
|
||||
record := AccountRecord{
|
||||
Email: member.Attributes.Email,
|
||||
FullName: strings.TrimSpace(member.Attributes.FirstName + " " + member.Attributes.LastName),
|
||||
Role: betterStackRole(member.Attributes.Role),
|
||||
Roles: betterStackRoles(member.Attributes.Role),
|
||||
Active: betterStackActive(member.Type),
|
||||
IsAdmin: betterStackIsAdmin(member.Attributes.Role),
|
||||
MFAStatus: coredata.MFAStatusUnknown,
|
||||
@@ -168,23 +168,27 @@ func (d *BetterStackDriver) fetchTeamMembersPage(
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// betterStackRole maps a Better Stack role token to a human-readable label.
|
||||
// betterStackRoles maps a Better Stack role token to a human-readable label.
|
||||
// Unknown roles (including the Enterprise "custom" roles) are passed through
|
||||
// unchanged so the reviewer still sees the source value.
|
||||
func betterStackRole(role string) string {
|
||||
func betterStackRoles(role string) []string {
|
||||
if role == "" {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
switch role {
|
||||
case "admin":
|
||||
return "Admin"
|
||||
return []string{"Admin"}
|
||||
case "billing_admin":
|
||||
return "Billing admin"
|
||||
return []string{"Billing admin"}
|
||||
case "team_lead":
|
||||
return "Team lead"
|
||||
return []string{"Team lead"}
|
||||
case "responder":
|
||||
return "Responder"
|
||||
return []string{"Responder"}
|
||||
case "member":
|
||||
return "Member"
|
||||
return []string{"Member"}
|
||||
default:
|
||||
return role
|
||||
return []string{role}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ func TestBetterStackDriver(t *testing.T) {
|
||||
r := records[0]
|
||||
assert.Equal(t, "alice@example.com", r.Email)
|
||||
assert.Equal(t, "Alice Smith", r.FullName)
|
||||
assert.Equal(t, "Admin", r.Role)
|
||||
assert.Equal(t, []string{"Admin"}, r.Roles)
|
||||
assert.True(t, r.IsAdmin)
|
||||
assert.Equal(t, "101", r.ExternalID)
|
||||
require.NotNil(t, r.Active)
|
||||
@@ -107,28 +107,29 @@ func TestBetterStackDriverListAccountsError(t *testing.T) {
|
||||
assert.Contains(t, err.Error(), "unexpected status 401")
|
||||
}
|
||||
|
||||
func TestBetterStackRole(t *testing.T) {
|
||||
func TestBetterStackRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
in string
|
||||
want string
|
||||
want []string
|
||||
isAdmin bool
|
||||
}{
|
||||
{"admin", "Admin", true},
|
||||
{"billing_admin", "Billing admin", false},
|
||||
{"team_lead", "Team lead", true},
|
||||
{"responder", "Responder", false},
|
||||
{"member", "Member", false},
|
||||
{"custom", "custom", false},
|
||||
{"future_role", "future_role", false},
|
||||
{"admin", []string{"Admin"}, true},
|
||||
{"billing_admin", []string{"Billing admin"}, false},
|
||||
{"team_lead", []string{"Team lead"}, true},
|
||||
{"responder", []string{"Responder"}, false},
|
||||
{"member", []string{"Member"}, false},
|
||||
{"custom", []string{"custom"}, false},
|
||||
{"future_role", []string{"future_role"}, false},
|
||||
{"", []string{}, false},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.in, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, c.want, betterStackRole(c.in))
|
||||
assert.Equal(t, c.want, betterStackRoles(c.in))
|
||||
assert.Equal(t, c.isAdmin, betterStackIsAdmin(c.in))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
@@ -64,10 +65,17 @@ func (d *BrexDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error)
|
||||
}
|
||||
|
||||
for _, u := range resp.Items {
|
||||
role := strings.TrimSpace(u.Role)
|
||||
|
||||
roles := []string{}
|
||||
if role != "" {
|
||||
roles = []string{role}
|
||||
}
|
||||
|
||||
record := AccountRecord{
|
||||
Email: u.Email,
|
||||
FullName: u.FirstName + " " + u.LastName,
|
||||
Role: u.Role,
|
||||
Roles: roles,
|
||||
Active: new(u.Status == "ACTIVE"),
|
||||
IsAdmin: false,
|
||||
ExternalID: u.ID,
|
||||
|
||||
@@ -101,13 +101,13 @@ func (d *ClickUpDriver) ListAccounts(ctx context.Context) ([]AccountRecord, erro
|
||||
|
||||
records := make([]AccountRecord, 0, len(resp.Team.Members))
|
||||
for _, m := range resp.Team.Members {
|
||||
role := clickupRoleLabel(m.User.Role)
|
||||
roles := clickupRoles(m.User.Role)
|
||||
isAdmin := m.User.Role == 1 || m.User.Role == 2
|
||||
|
||||
record := AccountRecord{
|
||||
Email: m.User.Email,
|
||||
FullName: m.User.Username,
|
||||
Role: role,
|
||||
Roles: roles,
|
||||
IsAdmin: isAdmin,
|
||||
ExternalID: m.User.ID.String(),
|
||||
MFAStatus: coredata.MFAStatusUnknown,
|
||||
@@ -134,20 +134,20 @@ func (d *ClickUpDriver) ListAccounts(ctx context.Context) ([]AccountRecord, erro
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// clickupRoleLabel maps ClickUp numeric role codes to human-readable
|
||||
// clickupRoles maps ClickUp numeric role codes to human-readable
|
||||
// labels. Source: https://clickup.com/api (Team Members endpoint).
|
||||
func clickupRoleLabel(role int) string {
|
||||
func clickupRoles(role int) []string {
|
||||
switch role {
|
||||
case 1:
|
||||
return "owner"
|
||||
return []string{"owner"}
|
||||
case 2:
|
||||
return "admin"
|
||||
return []string{"admin"}
|
||||
case 3:
|
||||
return "member"
|
||||
return []string{"member"}
|
||||
case 4:
|
||||
return "guest"
|
||||
return []string{"guest"}
|
||||
default:
|
||||
return ""
|
||||
return []string{}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,5 +46,5 @@ func TestClickUpDriver(t *testing.T) {
|
||||
assert.NotEmpty(t, r.ExternalID)
|
||||
assert.NotEmpty(t, r.Email)
|
||||
assert.NotEmpty(t, r.FullName)
|
||||
assert.NotEmpty(t, r.Role)
|
||||
assert.NotEmpty(t, r.Roles)
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
@@ -170,9 +169,8 @@ func (d *CloudflareDriver) queryAllMembers(ctx context.Context, accountID string
|
||||
roles = append(roles, r.Name)
|
||||
}
|
||||
|
||||
role := "Member"
|
||||
if len(roles) > 0 {
|
||||
role = strings.Join(roles, ", ")
|
||||
if len(roles) == 0 {
|
||||
roles = []string{"Member"}
|
||||
}
|
||||
|
||||
isAdmin := false
|
||||
@@ -192,7 +190,7 @@ func (d *CloudflareDriver) queryAllMembers(ctx context.Context, accountID string
|
||||
record := AccountRecord{
|
||||
Email: m.User.Email,
|
||||
FullName: m.User.FirstName + " " + m.User.LastName,
|
||||
Role: role,
|
||||
Roles: roles,
|
||||
Active: new(m.Status == "accepted"),
|
||||
IsAdmin: isAdmin,
|
||||
ExternalID: m.ID,
|
||||
|
||||
@@ -38,5 +38,5 @@ func TestCloudflareDriver(t *testing.T) {
|
||||
assert.NotEmpty(t, r.Email)
|
||||
assert.NotEmpty(t, r.FullName)
|
||||
assert.NotEmpty(t, r.ExternalID)
|
||||
assert.NotEmpty(t, r.Role)
|
||||
assert.NotEmpty(t, r.Roles)
|
||||
}
|
||||
|
||||
@@ -83,7 +83,14 @@ func (d *CSVDriver) ListAccounts(_ context.Context) ([]AccountRecord, error) {
|
||||
}
|
||||
|
||||
if idx, ok := colIndex["role"]; ok && idx < len(row) {
|
||||
record.Role = strings.TrimSpace(row[idx])
|
||||
role := strings.TrimSpace(row[idx])
|
||||
|
||||
roles := []string{}
|
||||
if role != "" {
|
||||
roles = []string{role}
|
||||
}
|
||||
|
||||
record.Roles = roles
|
||||
}
|
||||
|
||||
if idx, ok := colIndex["job_title"]; ok && idx < len(row) {
|
||||
|
||||
@@ -96,7 +96,7 @@ func (d *CursorDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
|
||||
records = append(records, AccountRecord{
|
||||
Email: m.Email,
|
||||
FullName: m.Name,
|
||||
Role: cursorRole(m.Role),
|
||||
Roles: cursorRoles(m.Role),
|
||||
Active: &active,
|
||||
IsAdmin: cursorIsAdmin(m.Role),
|
||||
MFAStatus: coredata.MFAStatusUnknown,
|
||||
@@ -116,15 +116,19 @@ func cursorIsAdmin(role string) bool {
|
||||
return role == "owner" || role == "free-owner"
|
||||
}
|
||||
|
||||
func cursorRole(role string) string {
|
||||
func cursorRoles(role string) []string {
|
||||
if role == "" {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
switch role {
|
||||
case "owner", "free-owner":
|
||||
return "Owner"
|
||||
return []string{"Owner"}
|
||||
case "member":
|
||||
return "Member"
|
||||
return []string{"Member"}
|
||||
case "removed":
|
||||
return "Removed"
|
||||
return []string{"Removed"}
|
||||
default:
|
||||
return role
|
||||
return []string{role}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestCursorDriver(t *testing.T) {
|
||||
member := records[0]
|
||||
assert.Equal(t, "jane@example.com", member.Email)
|
||||
assert.Equal(t, "Jane Doe", member.FullName)
|
||||
assert.Equal(t, "Member", member.Role)
|
||||
assert.Equal(t, []string{"Member"}, member.Roles)
|
||||
assert.False(t, member.IsAdmin)
|
||||
// The Cursor Admin API returns the member id as a string; it is used
|
||||
// verbatim as the stable ExternalID.
|
||||
@@ -49,7 +49,7 @@ func TestCursorDriver(t *testing.T) {
|
||||
assert.True(t, *member.Active)
|
||||
|
||||
owner := records[1]
|
||||
assert.Equal(t, "Owner", owner.Role)
|
||||
assert.Equal(t, []string{"Owner"}, owner.Roles)
|
||||
assert.True(t, owner.IsAdmin)
|
||||
require.NotNil(t, owner.Active)
|
||||
assert.True(t, *owner.Active)
|
||||
@@ -57,7 +57,7 @@ func TestCursorDriver(t *testing.T) {
|
||||
// A removed member (role "removed", isRemoved true) is still returned,
|
||||
// flagged inactive rather than dropped, per the AccountRecord contract.
|
||||
removed := records[2]
|
||||
assert.Equal(t, "Removed", removed.Role)
|
||||
assert.Equal(t, []string{"Removed"}, removed.Roles)
|
||||
assert.False(t, removed.IsAdmin)
|
||||
require.NotNil(t, removed.Active)
|
||||
assert.False(t, *removed.Active)
|
||||
@@ -66,32 +66,33 @@ func TestCursorDriver(t *testing.T) {
|
||||
// carry role "removed" while isRemoved is still false. The role alone
|
||||
// must mark the account inactive.
|
||||
removedByRole := records[3]
|
||||
assert.Equal(t, "Removed", removedByRole.Role)
|
||||
assert.Equal(t, []string{"Removed"}, removedByRole.Roles)
|
||||
assert.False(t, removedByRole.IsAdmin)
|
||||
require.NotNil(t, removedByRole.Active)
|
||||
assert.False(t, *removedByRole.Active)
|
||||
}
|
||||
|
||||
func TestCursorRole(t *testing.T) {
|
||||
func TestCursorRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
in string
|
||||
want string
|
||||
want []string
|
||||
isAdmin bool
|
||||
}{
|
||||
{"owner", "Owner", true},
|
||||
{"free-owner", "Owner", true},
|
||||
{"member", "Member", false},
|
||||
{"removed", "Removed", false},
|
||||
{"unknown_future_role", "unknown_future_role", false},
|
||||
{"owner", []string{"Owner"}, true},
|
||||
{"free-owner", []string{"Owner"}, true},
|
||||
{"member", []string{"Member"}, false},
|
||||
{"removed", []string{"Removed"}, false},
|
||||
{"unknown_future_role", []string{"unknown_future_role"}, false},
|
||||
{"", []string{}, false},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.in, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, c.want, cursorRole(c.in))
|
||||
assert.Equal(t, c.want, cursorRoles(c.in))
|
||||
assert.Equal(t, c.isAdmin, cursorIsAdmin(c.in))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -107,19 +107,20 @@ func (d *DatadogDriver) ListAccounts(ctx context.Context) ([]AccountRecord, erro
|
||||
active := !u.Attributes.Disabled
|
||||
|
||||
var (
|
||||
role string
|
||||
roles []string
|
||||
isAdmin bool
|
||||
)
|
||||
|
||||
for _, r := range u.Relationships.Roles.Data {
|
||||
name := roleNames[r.ID]
|
||||
if role == "" {
|
||||
role = name
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
roles = append(roles, name)
|
||||
|
||||
if strings.Contains(strings.ToLower(name), "admin") {
|
||||
isAdmin = true
|
||||
role = name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +137,7 @@ func (d *DatadogDriver) ListAccounts(ctx context.Context) ([]AccountRecord, erro
|
||||
records = append(records, AccountRecord{
|
||||
Email: u.Attributes.Email,
|
||||
FullName: u.Attributes.Name,
|
||||
Role: role,
|
||||
Roles: roles,
|
||||
JobTitle: u.Attributes.Title,
|
||||
Active: &active,
|
||||
IsAdmin: isAdmin,
|
||||
|
||||
@@ -42,7 +42,7 @@ func TestDatadogDriver(t *testing.T) {
|
||||
require.NotNil(t, r.Active)
|
||||
assert.True(t, *r.Active)
|
||||
assert.True(t, r.IsAdmin)
|
||||
assert.Equal(t, "Datadog Admin Role", r.Role)
|
||||
assert.Equal(t, []string{"Datadog Admin Role"}, r.Roles)
|
||||
assert.Equal(t, "Security Engineer", r.JobTitle)
|
||||
assert.Equal(t, coredata.AccessReviewEntryAccountTypeUser, r.AccountType)
|
||||
assert.Equal(t, coredata.MFAStatusEnabled, r.MFAStatus)
|
||||
@@ -56,7 +56,7 @@ func TestDatadogDriver(t *testing.T) {
|
||||
require.NotNil(t, r2.Active)
|
||||
assert.False(t, *r2.Active)
|
||||
assert.False(t, r2.IsAdmin)
|
||||
assert.Equal(t, "Datadog Standard Role", r2.Role)
|
||||
assert.Equal(t, []string{"Datadog Standard Role"}, r2.Roles)
|
||||
assert.Equal(t, coredata.AccessReviewEntryAccountTypeServiceAccount, r2.AccountType)
|
||||
assert.Equal(t, coredata.MFAStatusDisabled, r2.MFAStatus)
|
||||
}
|
||||
|
||||
@@ -90,10 +90,17 @@ func (d *DocuSignDriver) ListAccounts(ctx context.Context) ([]AccountRecord, err
|
||||
}
|
||||
|
||||
for _, u := range resp.Users {
|
||||
role := strings.TrimSpace(u.PermissionProfileName)
|
||||
|
||||
roles := []string{}
|
||||
if role != "" {
|
||||
roles = []string{role}
|
||||
}
|
||||
|
||||
record := AccountRecord{
|
||||
Email: u.Email,
|
||||
FullName: u.UserName,
|
||||
Role: u.PermissionProfileName,
|
||||
Roles: roles,
|
||||
JobTitle: u.JobTitle,
|
||||
Active: new(strings.EqualFold(u.UserStatus, "active")),
|
||||
IsAdmin: strings.EqualFold(u.IsAdmin, "True"),
|
||||
|
||||
@@ -38,5 +38,5 @@ func TestDocuSignDriver(t *testing.T) {
|
||||
assert.NotEmpty(t, r.Email)
|
||||
assert.NotEmpty(t, r.FullName)
|
||||
assert.NotEmpty(t, r.ExternalID)
|
||||
assert.NotEmpty(t, r.Role)
|
||||
assert.NotEmpty(t, r.Roles)
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ import (
|
||||
type AccountRecord struct {
|
||||
Email string
|
||||
FullName string
|
||||
Role string // system role/permission (e.g. "Admin", "Viewer")
|
||||
JobTitle string // HR job title / department (e.g. "Software Engineer")
|
||||
Roles []string // system roles/permissions (e.g. "Admin", "Viewer")
|
||||
JobTitle string // HR job title / department (e.g. "Software Engineer")
|
||||
Active *bool
|
||||
IsAdmin bool
|
||||
MFAStatus coredata.MFAStatus
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
@@ -123,10 +124,17 @@ func (d *GitHubDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
|
||||
}
|
||||
}
|
||||
|
||||
role := strings.TrimSpace(membership.Role)
|
||||
|
||||
roles := []string{}
|
||||
if role != "" {
|
||||
roles = []string{role}
|
||||
}
|
||||
|
||||
record := AccountRecord{
|
||||
Email: profile.Email,
|
||||
FullName: fullName,
|
||||
Role: membership.Role,
|
||||
Roles: roles,
|
||||
Active: new(membership.State == "active"),
|
||||
IsAdmin: membership.Role == "admin",
|
||||
MFAStatus: mfaStatus,
|
||||
|
||||
@@ -43,5 +43,5 @@ func TestGitHubDriver(t *testing.T) {
|
||||
r := records[0]
|
||||
assert.NotEmpty(t, r.FullName)
|
||||
assert.NotEmpty(t, r.ExternalID)
|
||||
assert.NotEmpty(t, r.Role)
|
||||
assert.NotEmpty(t, r.Roles)
|
||||
}
|
||||
|
||||
@@ -96,12 +96,12 @@ func (d *GitLabDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
|
||||
|
||||
active := m.State == "active"
|
||||
|
||||
role := gitlabAccessLevelLabel(m.AccessLevel)
|
||||
roles := gitlabRoles(m.AccessLevel)
|
||||
|
||||
record := AccountRecord{
|
||||
Email: m.Email,
|
||||
FullName: fullName,
|
||||
Role: role,
|
||||
Roles: roles,
|
||||
Active: &active,
|
||||
IsAdmin: m.AccessLevel >= 50, // 50 = Owner
|
||||
MFAStatus: coredata.MFAStatusUnknown,
|
||||
@@ -149,25 +149,25 @@ func (d *GitLabDriver) queryMembers(ctx context.Context, endpoint string) ([]git
|
||||
return members, httpResp.Header.Get("Link"), nil
|
||||
}
|
||||
|
||||
// gitlabAccessLevelLabel maps GitLab numeric access levels to human
|
||||
// gitlabRoles maps GitLab numeric access levels to human
|
||||
// labels. Source: https://docs.gitlab.com/api/members/#roles
|
||||
func gitlabAccessLevelLabel(level int) string {
|
||||
func gitlabRoles(level int) []string {
|
||||
switch level {
|
||||
case 5:
|
||||
return "Minimal Access"
|
||||
return []string{"Minimal Access"}
|
||||
case 10:
|
||||
return "Guest"
|
||||
return []string{"Guest"}
|
||||
case 15:
|
||||
return "Planner"
|
||||
return []string{"Planner"}
|
||||
case 20:
|
||||
return "Reporter"
|
||||
return []string{"Reporter"}
|
||||
case 30:
|
||||
return "Developer"
|
||||
return []string{"Developer"}
|
||||
case 40:
|
||||
return "Maintainer"
|
||||
return []string{"Maintainer"}
|
||||
case 50:
|
||||
return "Owner"
|
||||
return []string{"Owner"}
|
||||
default:
|
||||
return ""
|
||||
return []string{}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestGitLabDriver(t *testing.T) {
|
||||
r := records[0]
|
||||
assert.NotEmpty(t, r.ExternalID)
|
||||
assert.NotEmpty(t, r.FullName)
|
||||
assert.NotEmpty(t, r.Role)
|
||||
assert.NotEmpty(t, r.Roles)
|
||||
assert.Equal(t, coredata.MFAStatusUnknown, r.MFAStatus)
|
||||
require.NotNil(t, r.Active)
|
||||
assert.True(t, *r.Active)
|
||||
|
||||
@@ -144,11 +144,11 @@ func (d *GoogleWorkspaceDriver) ListAccounts(ctx context.Context) ([]AccountReco
|
||||
|
||||
switch {
|
||||
case u.IsAdmin:
|
||||
rec.Role = "Super Admin"
|
||||
rec.Roles = []string{"Super Admin"}
|
||||
case u.IsDelegatedAdmin:
|
||||
rec.Role = "Delegated Admin"
|
||||
rec.Roles = []string{"Delegated Admin"}
|
||||
default:
|
||||
rec.Role = "User"
|
||||
rec.Roles = []string{"User"}
|
||||
}
|
||||
|
||||
records = append(records, rec)
|
||||
|
||||
@@ -38,5 +38,5 @@ func TestGoogleWorkspaceDriver(t *testing.T) {
|
||||
assert.NotEmpty(t, r.Email)
|
||||
assert.NotEmpty(t, r.FullName)
|
||||
assert.NotEmpty(t, r.ExternalID)
|
||||
assert.NotEmpty(t, r.Role)
|
||||
assert.NotEmpty(t, r.Roles)
|
||||
}
|
||||
|
||||
@@ -75,11 +75,18 @@ func (d *GrafanaDriver) ListAccounts(ctx context.Context) ([]AccountRecord, erro
|
||||
continue
|
||||
}
|
||||
|
||||
role := strings.TrimSpace(u.Role)
|
||||
|
||||
roles := []string{}
|
||||
if role != "" {
|
||||
roles = []string{role}
|
||||
}
|
||||
|
||||
record := AccountRecord{
|
||||
Email: email,
|
||||
FullName: strings.TrimSpace(u.Name),
|
||||
Role: strings.TrimSpace(u.Role),
|
||||
IsAdmin: strings.EqualFold(strings.TrimSpace(u.Role), "Admin"),
|
||||
Roles: roles,
|
||||
IsAdmin: strings.EqualFold(role, "Admin"),
|
||||
MFAStatus: coredata.MFAStatusUnknown,
|
||||
AuthMethod: coredata.AccessReviewEntryAuthMethodUnknown,
|
||||
AccountType: coredata.AccessReviewEntryAccountTypeUser,
|
||||
|
||||
@@ -42,7 +42,7 @@ func TestGrafanaDriver(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "admin@example.com", records[0].Email)
|
||||
assert.Equal(t, "Admin User", records[0].FullName)
|
||||
assert.Equal(t, "Admin", records[0].Role)
|
||||
assert.Equal(t, []string{"Admin"}, records[0].Roles)
|
||||
assert.True(t, records[0].IsAdmin)
|
||||
assert.Equal(t, strconv.Itoa(1), records[0].ExternalID)
|
||||
require.NotNil(t, records[0].Active)
|
||||
@@ -51,7 +51,7 @@ func TestGrafanaDriver(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "viewer@example.com", records[1].Email)
|
||||
assert.Equal(t, "Viewer User", records[1].FullName)
|
||||
assert.Equal(t, "Viewer", records[1].Role)
|
||||
assert.Equal(t, []string{"Viewer"}, records[1].Roles)
|
||||
assert.False(t, records[1].IsAdmin)
|
||||
assert.Equal(t, strconv.Itoa(2), records[1].ExternalID)
|
||||
require.NotNil(t, records[1].Active)
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
@@ -153,10 +154,17 @@ func (d *HerokuDriver) listTeamMembers(ctx context.Context) ([]AccountRecord, er
|
||||
externalID = m.ID
|
||||
}
|
||||
|
||||
role := strings.TrimSpace(m.Role)
|
||||
|
||||
roles := []string{}
|
||||
if role != "" {
|
||||
roles = []string{role}
|
||||
}
|
||||
|
||||
record := AccountRecord{
|
||||
Email: email,
|
||||
FullName: fullName,
|
||||
Role: m.Role,
|
||||
Roles: roles,
|
||||
IsAdmin: isAdmin,
|
||||
MFAStatus: mfaStatus,
|
||||
AuthMethod: coredata.AccessReviewEntryAuthMethodUnknown,
|
||||
@@ -262,10 +270,17 @@ func (d *HerokuDriver) listPersonalAccounts(ctx context.Context) ([]AccountRecor
|
||||
// personal Heroku app. These endpoints expose no display name or MFA signal,
|
||||
// so the email doubles as the full name and MFA is left unknown.
|
||||
func herokuPersonalRecord(externalID, email, role string, isAdmin bool) AccountRecord {
|
||||
role = strings.TrimSpace(role)
|
||||
|
||||
roles := []string{}
|
||||
if role != "" {
|
||||
roles = []string{role}
|
||||
}
|
||||
|
||||
return AccountRecord{
|
||||
Email: email,
|
||||
FullName: email,
|
||||
Role: role,
|
||||
Roles: roles,
|
||||
IsAdmin: isAdmin,
|
||||
MFAStatus: coredata.MFAStatusUnknown,
|
||||
AuthMethod: coredata.AccessReviewEntryAuthMethodUnknown,
|
||||
|
||||
@@ -46,7 +46,7 @@ func TestHerokuDriver(t *testing.T) {
|
||||
assert.NotEmpty(t, r.Email)
|
||||
assert.NotEmpty(t, r.ExternalID)
|
||||
assert.NotEmpty(t, r.FullName)
|
||||
assert.NotEmpty(t, r.Role)
|
||||
assert.NotEmpty(t, r.Roles)
|
||||
assert.Equal(t, coredata.MFAStatusEnabled, r.MFAStatus)
|
||||
assert.True(t, r.IsAdmin)
|
||||
require.NotNil(t, r.CreatedAt)
|
||||
@@ -106,7 +106,7 @@ func TestHerokuDriverPersonalAccount(t *testing.T) {
|
||||
assert.Contains(t, byEmail, "carol@example.com")
|
||||
|
||||
assert.True(t, byEmail["alice@example.com"].IsAdmin)
|
||||
assert.Equal(t, "owner", byEmail["alice@example.com"].Role)
|
||||
assert.Equal(t, []string{"owner"}, byEmail["alice@example.com"].Roles)
|
||||
assert.False(t, byEmail["bob@example.com"].IsAdmin)
|
||||
}
|
||||
|
||||
|
||||
@@ -92,25 +92,12 @@ func (d *HubSpotDriver) ListAccounts(ctx context.Context) ([]AccountRecord, erro
|
||||
}
|
||||
|
||||
for _, u := range resp.Results {
|
||||
role := "User"
|
||||
roleID := hubspotRoleID(u)
|
||||
|
||||
if roleMap != nil && roleID != "" {
|
||||
if name, ok := roleMap[roleID]; ok {
|
||||
role = name
|
||||
} else if u.SuperAdmin {
|
||||
role = "Super Admin"
|
||||
}
|
||||
} else if u.SuperAdmin {
|
||||
role = "Super Admin"
|
||||
}
|
||||
|
||||
fullName := strings.TrimSpace(u.FirstName + " " + u.LastName)
|
||||
|
||||
record := AccountRecord{
|
||||
Email: u.Email,
|
||||
FullName: fullName,
|
||||
Role: role,
|
||||
Roles: hubspotRoles(u, roleMap),
|
||||
Active: hubspotUserActive(u),
|
||||
IsAdmin: u.SuperAdmin,
|
||||
ExternalID: u.ID,
|
||||
@@ -206,16 +193,65 @@ func (d *HubSpotDriver) fetchRoles(ctx context.Context) (map[string]string, erro
|
||||
return roleMap, nil
|
||||
}
|
||||
|
||||
func hubspotRoleID(user hubspotUser) string {
|
||||
if user.RoleID != "" {
|
||||
return user.RoleID
|
||||
func hubspotRoles(user hubspotUser, roleMap map[string]string) []string {
|
||||
roleIDs := hubspotRoleIDs(user)
|
||||
|
||||
seen := make(map[string]struct{}, len(roleIDs)+1)
|
||||
roles := make([]string, 0, len(roleIDs)+1)
|
||||
|
||||
if roleMap != nil {
|
||||
for _, id := range roleIDs {
|
||||
name, ok := roleMap[id]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, dup := seen[name]; dup {
|
||||
continue
|
||||
}
|
||||
|
||||
seen[name] = struct{}{}
|
||||
roles = append(roles, name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(user.RoleIDs) > 0 {
|
||||
return user.RoleIDs[0]
|
||||
if user.SuperAdmin {
|
||||
if _, dup := seen["Super Admin"]; !dup {
|
||||
roles = append(roles, "Super Admin")
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
if len(roles) > 0 {
|
||||
return roles
|
||||
}
|
||||
|
||||
return []string{"User"}
|
||||
}
|
||||
|
||||
func hubspotRoleIDs(user hubspotUser) []string {
|
||||
seen := make(map[string]struct{}, 1+len(user.RoleIDs))
|
||||
ids := make([]string, 0, 1+len(user.RoleIDs))
|
||||
|
||||
add := func(id string) {
|
||||
if id == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := seen[id]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
seen[id] = struct{}{}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
add(user.RoleID)
|
||||
|
||||
for _, id := range user.RoleIDs {
|
||||
add(id)
|
||||
}
|
||||
|
||||
return ids
|
||||
}
|
||||
|
||||
func hubspotUserActive(user hubspotUser) *bool {
|
||||
|
||||
@@ -73,7 +73,7 @@ func TestHubSpotDriverArchivedUsers(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, records, 2)
|
||||
|
||||
assert.Equal(t, "Sales Admin", records[0].Role)
|
||||
assert.Equal(t, []string{"Sales Admin"}, records[0].Roles)
|
||||
require.NotNil(t, records[0].Active)
|
||||
assert.True(t, *records[0].Active)
|
||||
|
||||
@@ -83,6 +83,65 @@ func TestHubSpotDriverArchivedUsers(t *testing.T) {
|
||||
assert.False(t, *records[1].Active)
|
||||
}
|
||||
|
||||
func TestHubSpotRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
roleMap := map[string]string{
|
||||
"role-1": "Sales Admin",
|
||||
"role-2": "Marketing Admin",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
user hubspotUser
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "multiple role IDs",
|
||||
user: hubspotUser{RoleIDs: []string{"role-1", "role-2"}},
|
||||
want: []string{"Sales Admin", "Marketing Admin"},
|
||||
},
|
||||
{
|
||||
name: "roleId and roleIds merged without duplicates",
|
||||
user: hubspotUser{RoleID: "role-1", RoleIDs: []string{"role-1", "role-2"}},
|
||||
want: []string{"Sales Admin", "Marketing Admin"},
|
||||
},
|
||||
{
|
||||
name: "unknown role falls back to user",
|
||||
user: hubspotUser{RoleIDs: []string{"missing"}},
|
||||
want: []string{"User"},
|
||||
},
|
||||
{
|
||||
name: "unknown role with super admin",
|
||||
user: hubspotUser{RoleIDs: []string{"missing"}, SuperAdmin: true},
|
||||
want: []string{"Super Admin"},
|
||||
},
|
||||
{
|
||||
name: "known role merged with super admin",
|
||||
user: hubspotUser{RoleIDs: []string{"role-1"}, SuperAdmin: true},
|
||||
want: []string{"Sales Admin", "Super Admin"},
|
||||
},
|
||||
{
|
||||
name: "multiple roles merged with super admin",
|
||||
user: hubspotUser{RoleIDs: []string{"role-1", "role-2"}, SuperAdmin: true},
|
||||
want: []string{"Sales Admin", "Marketing Admin", "Super Admin"},
|
||||
},
|
||||
{
|
||||
name: "no roles defaults to user",
|
||||
user: hubspotUser{},
|
||||
want: []string{"User"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, tt.want, hubspotRoles(tt.user, roleMap))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type roundTripFunc func(req *http.Request) (*http.Response, error)
|
||||
|
||||
func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
|
||||
@@ -66,7 +66,7 @@ func (d *IntercomDriver) ListAccounts(ctx context.Context) ([]AccountRecord, err
|
||||
record := AccountRecord{
|
||||
Email: a.Email,
|
||||
FullName: a.Name,
|
||||
Role: intercomRole(a.HasInboxSeat),
|
||||
Roles: intercomRoles(a.HasInboxSeat),
|
||||
JobTitle: a.JobTitle,
|
||||
IsAdmin: false, // Intercom API does not expose admin role information
|
||||
ExternalID: a.ID,
|
||||
@@ -117,10 +117,10 @@ func (d *IntercomDriver) fetchAdmins(ctx context.Context) (*intercomAdminsRespon
|
||||
// seat. The Intercom API does not expose a proper role field, so this is the
|
||||
// best approximation available: users with inbox seats are active agents,
|
||||
// those without are limited/viewer users.
|
||||
func intercomRole(hasInboxSeat bool) string {
|
||||
func intercomRoles(hasInboxSeat bool) []string {
|
||||
if hasInboxSeat {
|
||||
return "Agent"
|
||||
return []string{"Agent"}
|
||||
}
|
||||
|
||||
return "Viewer"
|
||||
return []string{"Viewer"}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func (d *LinearDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
|
||||
record := AccountRecord{
|
||||
Email: u.Email,
|
||||
FullName: u.Name,
|
||||
Role: linearRole(u.Admin, u.Guest),
|
||||
Roles: linearRoles(u.Admin, u.Guest),
|
||||
Active: new(u.Active),
|
||||
IsAdmin: u.Admin,
|
||||
ExternalID: u.ID,
|
||||
@@ -200,13 +200,13 @@ query AccessReviewLinearUsers($after: String) {
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func linearRole(admin, guest bool) string {
|
||||
func linearRoles(admin, guest bool) []string {
|
||||
switch {
|
||||
case admin:
|
||||
return "Admin"
|
||||
return []string{"Admin"}
|
||||
case guest:
|
||||
return "Guest"
|
||||
return []string{"Guest"}
|
||||
default:
|
||||
return "Member"
|
||||
return []string{"Member"}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,5 +38,5 @@ func TestLinearDriver(t *testing.T) {
|
||||
assert.NotEmpty(t, r.Email)
|
||||
assert.NotEmpty(t, r.FullName)
|
||||
assert.NotEmpty(t, r.ExternalID)
|
||||
assert.NotEmpty(t, r.Role)
|
||||
assert.NotEmpty(t, r.Roles)
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ func (d *MetabaseDriver) ListAccounts(ctx context.Context) ([]AccountRecord, err
|
||||
record := AccountRecord{
|
||||
Email: u.Email,
|
||||
FullName: metabaseFullName(u),
|
||||
Role: metabaseRole(u.IsSuperuser),
|
||||
Roles: metabaseRoles(u.IsSuperuser),
|
||||
Active: new(u.IsActive),
|
||||
IsAdmin: u.IsSuperuser,
|
||||
ExternalID: strconv.Itoa(u.ID),
|
||||
@@ -176,12 +176,12 @@ func metabaseFullName(u metabaseUser) string {
|
||||
return strings.TrimSpace(strings.Join([]string{u.FirstName, u.LastName}, " "))
|
||||
}
|
||||
|
||||
func metabaseRole(isSuperuser bool) string {
|
||||
func metabaseRoles(isSuperuser bool) []string {
|
||||
if isSuperuser {
|
||||
return "Admin"
|
||||
return []string{"Admin"}
|
||||
}
|
||||
|
||||
return "User"
|
||||
return []string{"User"}
|
||||
}
|
||||
|
||||
// metabaseNameResolver resolves the Metabase site name by querying
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestMetabaseDriver(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "alice@example.com", records[0].Email)
|
||||
assert.Equal(t, "Alice A.", records[0].FullName)
|
||||
assert.Equal(t, "Admin", records[0].Role)
|
||||
assert.Equal(t, []string{"Admin"}, records[0].Roles)
|
||||
assert.True(t, records[0].IsAdmin)
|
||||
require.NotNil(t, records[0].Active)
|
||||
assert.True(t, *records[0].Active)
|
||||
@@ -51,7 +51,7 @@ func TestMetabaseDriver(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "bob@example.com", records[1].Email)
|
||||
assert.Equal(t, "Bob Builder", records[1].FullName)
|
||||
assert.Equal(t, "User", records[1].Role)
|
||||
assert.Equal(t, []string{"User"}, records[1].Roles)
|
||||
assert.False(t, records[1].IsAdmin)
|
||||
require.NotNil(t, records[1].Active)
|
||||
assert.False(t, *records[1].Active)
|
||||
|
||||
@@ -164,16 +164,16 @@ func (d *Microsoft365Driver) ListAccounts(ctx context.Context) ([]AccountRecord,
|
||||
}
|
||||
}
|
||||
|
||||
role := pickHighestRole(userRoles)
|
||||
if role == "" {
|
||||
role = "User"
|
||||
roles := userRoles
|
||||
if len(roles) == 0 {
|
||||
roles = []string{"User"}
|
||||
}
|
||||
|
||||
active := u.AccountEnabled
|
||||
rec := AccountRecord{
|
||||
Email: email,
|
||||
FullName: u.DisplayName,
|
||||
Role: role,
|
||||
Roles: roles,
|
||||
JobTitle: u.JobTitle,
|
||||
Active: &active,
|
||||
IsAdmin: isAdmin,
|
||||
@@ -195,39 +195,6 @@ func (d *Microsoft365Driver) ListAccounts(ctx context.Context) ([]AccountRecord,
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// pickHighestRole returns the most privileged admin role from the list,
|
||||
// falling back to the first non-admin role when no admin role is present.
|
||||
// Privilege order is hard-coded to Microsoft's well-known directory roles.
|
||||
func pickHighestRole(roles []string) string {
|
||||
priority := []string{
|
||||
"Global Administrator",
|
||||
"Company Administrator",
|
||||
"Privileged Role Administrator",
|
||||
"Privileged Authentication Administrator",
|
||||
"Security Administrator",
|
||||
"Application Administrator",
|
||||
"Cloud Application Administrator",
|
||||
"User Administrator",
|
||||
"Conditional Access Administrator",
|
||||
"Compliance Administrator",
|
||||
"Authentication Administrator",
|
||||
}
|
||||
|
||||
for _, p := range priority {
|
||||
for _, r := range roles {
|
||||
if r == p {
|
||||
return r
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(roles) > 0 {
|
||||
return roles[0]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (d *Microsoft365Driver) listUsers(ctx context.Context) ([]microsoft365User, error) {
|
||||
pageURL, err := buildMicrosoft365UsersURL()
|
||||
if err != nil {
|
||||
|
||||
@@ -90,7 +90,7 @@ func (d *NeonDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error)
|
||||
// The members endpoint exposes no display name;
|
||||
// fall back to the email.
|
||||
FullName: m.User.Email,
|
||||
Role: neonRole(m.Member.Role),
|
||||
Roles: neonRoles(m.Member.Role),
|
||||
// deactivated_at is absent for active accounts.
|
||||
Active: new(m.User.DeactivatedAt == ""),
|
||||
IsAdmin: neonIsAdmin(m.Member.Role),
|
||||
@@ -160,22 +160,26 @@ func (d *NeonDriver) queryMembers(ctx context.Context, cursor string) (*neonMemb
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// neonRole maps Neon's lowercase member roles to their display form,
|
||||
// neonRoles maps Neon's lowercase member roles to their display form,
|
||||
// passing unknown values through unchanged.
|
||||
func neonRole(role string) string {
|
||||
func neonRoles(role string) []string {
|
||||
if role == "" {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
switch strings.ToLower(role) {
|
||||
case "admin":
|
||||
return "Admin"
|
||||
return []string{"Admin"}
|
||||
case "member":
|
||||
return "Member"
|
||||
return []string{"Member"}
|
||||
case "editor":
|
||||
return "Editor"
|
||||
return []string{"Editor"}
|
||||
case "viewer":
|
||||
return "Viewer"
|
||||
return []string{"Viewer"}
|
||||
case "collaborator":
|
||||
return "Collaborator"
|
||||
return []string{"Collaborator"}
|
||||
default:
|
||||
return role
|
||||
return []string{role}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ func TestNeonDriverListAccounts(t *testing.T) {
|
||||
// ExternalID is the stable account UUID (member.user_id).
|
||||
assert.Equal(t, "jane.doe@example.com", records[0].Email)
|
||||
assert.Equal(t, "jane.doe@example.com", records[0].FullName)
|
||||
assert.Equal(t, "Admin", records[0].Role)
|
||||
assert.Equal(t, []string{"Admin"}, records[0].Roles)
|
||||
assert.True(t, records[0].IsAdmin)
|
||||
assert.Equal(t, coredata.MFAStatusEnabled, records[0].MFAStatus)
|
||||
assert.Equal(t, "bbbbbbbb-1111-2222-3333-000000000001", records[0].ExternalID)
|
||||
@@ -62,7 +62,7 @@ func TestNeonDriverListAccounts(t *testing.T) {
|
||||
|
||||
// Deactivated member with MFA disabled.
|
||||
assert.Equal(t, "john.smith@example.com", records[1].Email)
|
||||
assert.Equal(t, "Member", records[1].Role)
|
||||
assert.Equal(t, []string{"Member"}, records[1].Roles)
|
||||
assert.False(t, records[1].IsAdmin)
|
||||
assert.Equal(t, coredata.MFAStatusDisabled, records[1].MFAStatus)
|
||||
assert.Equal(t, "bbbbbbbb-1111-2222-3333-000000000002", records[1].ExternalID)
|
||||
@@ -72,7 +72,7 @@ func TestNeonDriverListAccounts(t *testing.T) {
|
||||
// Second page: editor with has_mfa omitted (Unknown) and an empty
|
||||
// user_id falling back to the membership ID.
|
||||
assert.Equal(t, "erin.lee@example.com", records[2].Email)
|
||||
assert.Equal(t, "Editor", records[2].Role)
|
||||
assert.Equal(t, []string{"Editor"}, records[2].Roles)
|
||||
assert.False(t, records[2].IsAdmin)
|
||||
assert.Equal(t, coredata.MFAStatusUnknown, records[2].MFAStatus)
|
||||
assert.Equal(t, "aaaaaaaa-1111-2222-3333-000000000003", records[2].ExternalID)
|
||||
@@ -101,27 +101,28 @@ func TestNeonDriverListAccountsError(t *testing.T) {
|
||||
assert.Contains(t, err.Error(), "unexpected status 401")
|
||||
}
|
||||
|
||||
func TestNeonRole(t *testing.T) {
|
||||
func TestNeonRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
in string
|
||||
want string
|
||||
want []string
|
||||
isAdmin bool
|
||||
}{
|
||||
{in: "admin", want: "Admin", isAdmin: true},
|
||||
{in: "member", want: "Member", isAdmin: false},
|
||||
{in: "editor", want: "Editor", isAdmin: false},
|
||||
{in: "viewer", want: "Viewer", isAdmin: false},
|
||||
{in: "collaborator", want: "Collaborator", isAdmin: false},
|
||||
{in: "future_role", want: "future_role", isAdmin: false},
|
||||
{in: "admin", want: []string{"Admin"}, isAdmin: true},
|
||||
{in: "member", want: []string{"Member"}, isAdmin: false},
|
||||
{in: "editor", want: []string{"Editor"}, isAdmin: false},
|
||||
{in: "viewer", want: []string{"Viewer"}, isAdmin: false},
|
||||
{in: "collaborator", want: []string{"Collaborator"}, isAdmin: false},
|
||||
{in: "future_role", want: []string{"future_role"}, isAdmin: false},
|
||||
{in: "", want: []string{}, isAdmin: false},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.in, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, c.want, neonRole(c.in))
|
||||
assert.Equal(t, c.want, neonRoles(c.in))
|
||||
assert.Equal(t, c.isAdmin, neonIsAdmin(c.in))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/rfc5988"
|
||||
@@ -78,10 +79,17 @@ func (d *NetlifyDriver) ListAccounts(ctx context.Context) ([]AccountRecord, erro
|
||||
}
|
||||
|
||||
for _, m := range members {
|
||||
role := strings.TrimSpace(m.Role)
|
||||
|
||||
roles := []string{}
|
||||
if role != "" {
|
||||
roles = []string{role}
|
||||
}
|
||||
|
||||
record := AccountRecord{
|
||||
Email: m.Email,
|
||||
FullName: m.FullName,
|
||||
Role: m.Role,
|
||||
Roles: roles,
|
||||
ExternalID: m.ID,
|
||||
MFAStatus: coredata.MFAStatusUnknown,
|
||||
AuthMethod: coredata.AccessReviewEntryAuthMethodUnknown,
|
||||
|
||||
@@ -43,5 +43,5 @@ func TestNetlifyDriver(t *testing.T) {
|
||||
assert.NotEmpty(t, r.ExternalID)
|
||||
assert.NotEmpty(t, r.Email)
|
||||
assert.NotEmpty(t, r.FullName)
|
||||
assert.NotEmpty(t, r.Role)
|
||||
assert.NotEmpty(t, r.Roles)
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func (d *NotionDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
|
||||
record := AccountRecord{
|
||||
Email: email,
|
||||
FullName: u.Name,
|
||||
Role: "Member",
|
||||
Roles: []string{"Member"},
|
||||
IsAdmin: false,
|
||||
ExternalID: u.ID,
|
||||
MFAStatus: coredata.MFAStatusUnknown,
|
||||
|
||||
@@ -67,7 +67,7 @@ func (d *OpenAIDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
|
||||
record := AccountRecord{
|
||||
Email: u.Email,
|
||||
FullName: u.Name,
|
||||
Role: openaiRole(u.Role),
|
||||
Roles: openaiRoles(u.Role),
|
||||
Active: new(!u.Disabled),
|
||||
IsAdmin: u.Role == "owner",
|
||||
ExternalID: u.ID,
|
||||
@@ -134,13 +134,17 @@ func (d *OpenAIDriver) fetchUsers(ctx context.Context, after string) (*openaiUse
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func openaiRole(role string) string {
|
||||
func openaiRoles(role string) []string {
|
||||
if role == "" {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
switch role {
|
||||
case "owner":
|
||||
return "Owner"
|
||||
return []string{"Owner"}
|
||||
case "reader":
|
||||
return "Reader"
|
||||
return []string{"Reader"}
|
||||
default:
|
||||
return "Member"
|
||||
return []string{"Member"}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,5 +38,5 @@ func TestOpenAIDriver(t *testing.T) {
|
||||
assert.NotEmpty(t, r.Email)
|
||||
assert.NotEmpty(t, r.FullName)
|
||||
assert.NotEmpty(t, r.ExternalID)
|
||||
assert.NotEmpty(t, r.Role)
|
||||
assert.NotEmpty(t, r.Roles)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
@@ -77,10 +78,17 @@ func (d *PagerDutyDriver) ListAccounts(ctx context.Context) ([]AccountRecord, er
|
||||
for _, u := range page.Users {
|
||||
isAdmin := u.Role == "admin" || u.Role == "owner"
|
||||
|
||||
role := strings.TrimSpace(u.Role)
|
||||
|
||||
roles := []string{}
|
||||
if role != "" {
|
||||
roles = []string{role}
|
||||
}
|
||||
|
||||
record := AccountRecord{
|
||||
Email: u.Email,
|
||||
FullName: u.Name,
|
||||
Role: u.Role,
|
||||
Roles: roles,
|
||||
IsAdmin: isAdmin,
|
||||
MFAStatus: coredata.MFAStatusUnknown,
|
||||
AuthMethod: coredata.AccessReviewEntryAuthMethodUnknown,
|
||||
|
||||
@@ -38,6 +38,6 @@ func TestPagerDutyDriver(t *testing.T) {
|
||||
assert.NotEmpty(t, r.Email)
|
||||
assert.NotEmpty(t, r.ExternalID)
|
||||
assert.NotEmpty(t, r.FullName)
|
||||
assert.NotEmpty(t, r.Role)
|
||||
assert.NotEmpty(t, r.Roles)
|
||||
assert.True(t, r.IsAdmin)
|
||||
}
|
||||
|
||||
@@ -292,7 +292,7 @@ func posthogAccountRecord(member posthogMember) AccountRecord {
|
||||
record := AccountRecord{
|
||||
Email: member.User.Email,
|
||||
FullName: posthogFullName(member.User),
|
||||
Role: posthogRole(member.Level, member.User.RoleAtOrganization),
|
||||
Roles: posthogRoles(member.Level, member.User.RoleAtOrganization),
|
||||
IsAdmin: posthogIsAdmin(member.Level),
|
||||
ExternalID: member.User.UUID,
|
||||
MFAStatus: posthogMFAStatus(member.Is2FAEnabled),
|
||||
@@ -319,18 +319,18 @@ func posthogFullName(user posthogMemberUser) string {
|
||||
return strings.TrimSpace(strings.Join([]string{user.FirstName, user.LastName}, " "))
|
||||
}
|
||||
|
||||
func posthogRole(level int, fallback string) string {
|
||||
func posthogRoles(level int, fallback string) []string {
|
||||
switch {
|
||||
case level >= posthogMembershipLevelOwner:
|
||||
return "Owner"
|
||||
return []string{"Owner"}
|
||||
case level >= posthogMembershipLevelAdmin:
|
||||
return "Admin"
|
||||
return []string{"Admin"}
|
||||
case level >= posthogMembershipLevelMember:
|
||||
return "Member"
|
||||
return []string{"Member"}
|
||||
case fallback != "":
|
||||
return fallback
|
||||
return []string{fallback}
|
||||
default:
|
||||
return "Member"
|
||||
return []string{"Member"}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestPostHogDriverListAccounts(t *testing.T) {
|
||||
owner := records[0]
|
||||
assert.Equal(t, "owner@example.com", owner.Email)
|
||||
assert.Equal(t, "Olivia Owner", owner.FullName)
|
||||
assert.Equal(t, "Owner", owner.Role)
|
||||
assert.Equal(t, []string{"Owner"}, owner.Roles)
|
||||
assert.True(t, owner.IsAdmin)
|
||||
assert.Equal(t, coredata.MFAStatusEnabled, owner.MFAStatus)
|
||||
assert.Equal(t, "user-1", owner.ExternalID)
|
||||
@@ -50,7 +50,7 @@ func TestPostHogDriverListAccounts(t *testing.T) {
|
||||
|
||||
member := records[1]
|
||||
assert.Equal(t, "member@example.com", member.Email)
|
||||
assert.Equal(t, "Member", member.Role)
|
||||
assert.Equal(t, []string{"Member"}, member.Roles)
|
||||
assert.False(t, member.IsAdmin)
|
||||
assert.Equal(t, coredata.MFAStatusDisabled, member.MFAStatus)
|
||||
require.NotNil(t, member.CreatedAt)
|
||||
@@ -58,7 +58,7 @@ func TestPostHogDriverListAccounts(t *testing.T) {
|
||||
|
||||
admin := records[2]
|
||||
assert.Equal(t, "admin@example.com", admin.Email)
|
||||
assert.Equal(t, "Admin", admin.Role)
|
||||
assert.Equal(t, []string{"Admin"}, admin.Roles)
|
||||
assert.True(t, admin.IsAdmin)
|
||||
assert.Equal(t, coredata.MFAStatusUnknown, admin.MFAStatus)
|
||||
assert.Equal(t, "membership-3", admin.ExternalID)
|
||||
@@ -177,12 +177,12 @@ func TestPostHogNameResolver(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostHogRoleFallback(t *testing.T) {
|
||||
func TestPostHogRolesFallback(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, "Owner", posthogRole(15, ""))
|
||||
assert.Equal(t, "Admin", posthogRole(8, ""))
|
||||
assert.Equal(t, "Member", posthogRole(1, ""))
|
||||
assert.Equal(t, "engineering", posthogRole(0, "engineering"))
|
||||
assert.Equal(t, "Member", posthogRole(0, ""))
|
||||
assert.Equal(t, []string{"Owner"}, posthogRoles(15, ""))
|
||||
assert.Equal(t, []string{"Admin"}, posthogRoles(8, ""))
|
||||
assert.Equal(t, []string{"Member"}, posthogRoles(1, ""))
|
||||
assert.Equal(t, []string{"engineering"}, posthogRoles(0, "engineering"))
|
||||
assert.Equal(t, []string{"Member"}, posthogRoles(0, ""))
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package drivers
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
@@ -61,7 +62,13 @@ func (d *ProboMembershipsDriver) ListAccounts(ctx context.Context) ([]AccountRec
|
||||
}
|
||||
|
||||
for _, account := range accounts {
|
||||
role := account.Role
|
||||
role := strings.TrimSpace(account.Role)
|
||||
|
||||
roles := []string{}
|
||||
if role != "" {
|
||||
roles = []string{role}
|
||||
}
|
||||
|
||||
isAdmin := role == string(coredata.MembershipRoleOwner) || role == string(coredata.MembershipRoleAdmin)
|
||||
createdAt := account.CreatedAt
|
||||
|
||||
@@ -70,7 +77,7 @@ func (d *ProboMembershipsDriver) ListAccounts(ctx context.Context) ([]AccountRec
|
||||
AccountRecord{
|
||||
Email: account.Email,
|
||||
FullName: account.FullName,
|
||||
Role: role,
|
||||
Roles: roles,
|
||||
Active: new(account.State == string(coredata.ProfileStateActive)),
|
||||
IsAdmin: isAdmin,
|
||||
ExternalID: account.ID.String(),
|
||||
|
||||
@@ -101,7 +101,7 @@ func (d *QoveryDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
|
||||
record := AccountRecord{
|
||||
Email: member.Email,
|
||||
FullName: qoveryFullName(member),
|
||||
Role: qoveryRole(member.Role),
|
||||
Roles: qoveryRoles(member.Role),
|
||||
IsAdmin: qoveryIsAdmin(member.Role),
|
||||
MFAStatus: coredata.MFAStatusUnknown,
|
||||
AuthMethod: coredata.AccessReviewEntryAuthMethodUnknown,
|
||||
@@ -139,18 +139,22 @@ func qoveryFullName(member qoveryMember) string {
|
||||
return member.Email
|
||||
}
|
||||
|
||||
func qoveryRole(role string) string {
|
||||
func qoveryRoles(role string) []string {
|
||||
if role == "" {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
switch strings.ToUpper(role) {
|
||||
case "OWNER":
|
||||
return "Owner"
|
||||
return []string{"Owner"}
|
||||
case "ADMIN":
|
||||
return "Admin"
|
||||
return []string{"Admin"}
|
||||
case "DEVELOPER":
|
||||
return "Developer"
|
||||
return []string{"Developer"}
|
||||
case "VIEWER":
|
||||
return "Viewer"
|
||||
return []string{"Viewer"}
|
||||
default:
|
||||
return role
|
||||
return []string{role}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ func TestQoveryDriverListAccounts(t *testing.T) {
|
||||
// Qovery member IDs are the IdP subject (e.g. "google-oauth2|<sub>").
|
||||
assert.Equal(t, "jane.doe@example.com", records[0].Email)
|
||||
assert.Equal(t, "Jane Doe", records[0].FullName)
|
||||
assert.Equal(t, "Owner", records[0].Role)
|
||||
assert.Equal(t, []string{"Owner"}, records[0].Roles)
|
||||
assert.True(t, records[0].IsAdmin)
|
||||
assert.Equal(t, "google-oauth2|100000000000000000001", records[0].ExternalID)
|
||||
require.NotNil(t, records[0].LastLogin)
|
||||
@@ -65,7 +65,7 @@ func TestQoveryDriverListAccounts(t *testing.T) {
|
||||
// leaves LastLogin nil.
|
||||
assert.Equal(t, "john.smith@example.com", records[1].Email)
|
||||
assert.Equal(t, "john", records[1].FullName)
|
||||
assert.Equal(t, "Developer", records[1].Role)
|
||||
assert.Equal(t, []string{"Developer"}, records[1].Roles)
|
||||
assert.False(t, records[1].IsAdmin)
|
||||
assert.Equal(t, "google-oauth2|100000000000000000002", records[1].ExternalID)
|
||||
assert.Nil(t, records[1].LastLogin)
|
||||
@@ -93,26 +93,27 @@ func TestQoveryDriverListAccountsError(t *testing.T) {
|
||||
assert.Contains(t, err.Error(), "unexpected status 401")
|
||||
}
|
||||
|
||||
func TestQoveryRole(t *testing.T) {
|
||||
func TestQoveryRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
in string
|
||||
want string
|
||||
want []string
|
||||
isAdmin bool
|
||||
}{
|
||||
{in: "OWNER", want: "Owner", isAdmin: true},
|
||||
{in: "ADMIN", want: "Admin", isAdmin: true},
|
||||
{in: "DEVELOPER", want: "Developer", isAdmin: false},
|
||||
{in: "VIEWER", want: "Viewer", isAdmin: false},
|
||||
{in: "future_role", want: "future_role", isAdmin: false},
|
||||
{in: "OWNER", want: []string{"Owner"}, isAdmin: true},
|
||||
{in: "ADMIN", want: []string{"Admin"}, isAdmin: true},
|
||||
{in: "DEVELOPER", want: []string{"Developer"}, isAdmin: false},
|
||||
{in: "VIEWER", want: []string{"Viewer"}, isAdmin: false},
|
||||
{in: "future_role", want: []string{"future_role"}, isAdmin: false},
|
||||
{in: "", want: []string{}, isAdmin: false},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.in, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, c.want, qoveryRole(c.in))
|
||||
assert.Equal(t, c.want, qoveryRoles(c.in))
|
||||
assert.Equal(t, c.isAdmin, qoveryIsAdmin(c.in))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ func (d *RenderDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
|
||||
records = append(records, AccountRecord{
|
||||
Email: member.Email,
|
||||
FullName: renderFullName(member),
|
||||
Role: renderRole(member.Role),
|
||||
Roles: renderRoles(member.Role),
|
||||
Active: renderActive(member.Status),
|
||||
IsAdmin: renderIsAdmin(member.Role),
|
||||
MFAStatus: renderMFAStatus(member.MFAEnabled),
|
||||
@@ -120,24 +120,28 @@ func renderFullName(member renderMember) string {
|
||||
return member.Email
|
||||
}
|
||||
|
||||
// renderRole maps Render's uppercase role enum to a human-readable label.
|
||||
// renderRoles maps Render's uppercase role enum to a human-readable label.
|
||||
// Render documents ADMIN, DEVELOPER, WORKSPACE_CONTRIBUTOR,
|
||||
// WORKSPACE_BILLING, and WORKSPACE_VIEWER; unknown future roles fall through
|
||||
// to the raw value.
|
||||
func renderRole(role string) string {
|
||||
func renderRoles(role string) []string {
|
||||
if role == "" {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
switch strings.ToUpper(role) {
|
||||
case "ADMIN":
|
||||
return "Admin"
|
||||
return []string{"Admin"}
|
||||
case "DEVELOPER":
|
||||
return "Developer"
|
||||
return []string{"Developer"}
|
||||
case "WORKSPACE_CONTRIBUTOR":
|
||||
return "Contributor"
|
||||
return []string{"Contributor"}
|
||||
case "WORKSPACE_BILLING":
|
||||
return "Billing"
|
||||
return []string{"Billing"}
|
||||
case "WORKSPACE_VIEWER":
|
||||
return "Viewer"
|
||||
return []string{"Viewer"}
|
||||
default:
|
||||
return role
|
||||
return []string{role}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ func TestRenderDriverListAccounts(t *testing.T) {
|
||||
// never the email.
|
||||
assert.Equal(t, "jane.doe@example.com", records[0].Email)
|
||||
assert.Equal(t, "Jane Doe", records[0].FullName)
|
||||
assert.Equal(t, "Admin", records[0].Role)
|
||||
assert.Equal(t, []string{"Admin"}, records[0].Roles)
|
||||
assert.True(t, records[0].IsAdmin)
|
||||
assert.Equal(t, coredata.MFAStatusEnabled, records[0].MFAStatus)
|
||||
assert.Equal(t, coredata.AccessReviewEntryAccountTypeUser, records[0].AccountType)
|
||||
@@ -63,7 +63,7 @@ func TestRenderDriverListAccounts(t *testing.T) {
|
||||
// Developer: active, MFA disabled, not an admin.
|
||||
assert.Equal(t, "john.smith@example.com", records[1].Email)
|
||||
assert.Equal(t, "John Smith", records[1].FullName)
|
||||
assert.Equal(t, "Developer", records[1].Role)
|
||||
assert.Equal(t, []string{"Developer"}, records[1].Roles)
|
||||
assert.False(t, records[1].IsAdmin)
|
||||
assert.Equal(t, coredata.MFAStatusDisabled, records[1].MFAStatus)
|
||||
assert.Equal(t, "usr-000000000000000000b2", records[1].ExternalID)
|
||||
@@ -74,7 +74,7 @@ func TestRenderDriverListAccounts(t *testing.T) {
|
||||
// email so the row is never nameless.
|
||||
assert.Equal(t, "sam.viewer@example.com", records[2].Email)
|
||||
assert.Equal(t, "sam.viewer@example.com", records[2].FullName)
|
||||
assert.Equal(t, "Viewer", records[2].Role)
|
||||
assert.Equal(t, []string{"Viewer"}, records[2].Roles)
|
||||
assert.False(t, records[2].IsAdmin)
|
||||
assert.Equal(t, coredata.MFAStatusDisabled, records[2].MFAStatus)
|
||||
assert.Equal(t, "usr-000000000000000000c3", records[2].ExternalID)
|
||||
@@ -105,27 +105,28 @@ func TestRenderDriverListAccountsError(t *testing.T) {
|
||||
assert.NotContains(t, err.Error(), "unauthorized")
|
||||
}
|
||||
|
||||
func TestRenderRole(t *testing.T) {
|
||||
func TestRenderRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
in string
|
||||
want string
|
||||
want []string
|
||||
isAdmin bool
|
||||
}{
|
||||
{in: "ADMIN", want: "Admin", isAdmin: true},
|
||||
{in: "DEVELOPER", want: "Developer", isAdmin: false},
|
||||
{in: "WORKSPACE_CONTRIBUTOR", want: "Contributor", isAdmin: false},
|
||||
{in: "WORKSPACE_BILLING", want: "Billing", isAdmin: false},
|
||||
{in: "WORKSPACE_VIEWER", want: "Viewer", isAdmin: false},
|
||||
{in: "future_role", want: "future_role", isAdmin: false},
|
||||
{in: "ADMIN", want: []string{"Admin"}, isAdmin: true},
|
||||
{in: "DEVELOPER", want: []string{"Developer"}, isAdmin: false},
|
||||
{in: "WORKSPACE_CONTRIBUTOR", want: []string{"Contributor"}, isAdmin: false},
|
||||
{in: "WORKSPACE_BILLING", want: []string{"Billing"}, isAdmin: false},
|
||||
{in: "WORKSPACE_VIEWER", want: []string{"Viewer"}, isAdmin: false},
|
||||
{in: "future_role", want: []string{"future_role"}, isAdmin: false},
|
||||
{in: "", want: []string{}, isAdmin: false},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.in, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, c.want, renderRole(c.in))
|
||||
assert.Equal(t, c.want, renderRoles(c.in))
|
||||
assert.Equal(t, c.isAdmin, renderIsAdmin(c.in))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ func (d *SendGridDriver) ListAccounts(ctx context.Context) ([]AccountRecord, err
|
||||
records = append(records, AccountRecord{
|
||||
Email: teammate.Email,
|
||||
FullName: sendGridFullName(teammate.FirstName, teammate.LastName),
|
||||
Role: sendGridRole(teammate.UserType, teammate.IsAdmin),
|
||||
Roles: sendGridRoles(teammate.UserType, teammate.IsAdmin),
|
||||
IsAdmin: teammate.IsAdmin,
|
||||
// SendGrid exposes no UUID for teammates; the username is the
|
||||
// only stable handle. For unified accounts it equals the email.
|
||||
@@ -209,22 +209,22 @@ func sendGridFullName(firstName, lastName string) string {
|
||||
return strings.TrimSpace(strings.Join([]string{firstName, lastName}, " "))
|
||||
}
|
||||
|
||||
func sendGridRole(userType string, isAdmin bool) string {
|
||||
func sendGridRoles(userType string, isAdmin bool) []string {
|
||||
switch userType {
|
||||
case "owner":
|
||||
return "Owner"
|
||||
return []string{"Owner"}
|
||||
case "admin":
|
||||
return "Admin"
|
||||
return []string{"Admin"}
|
||||
case "teammate":
|
||||
return "Teammate"
|
||||
return []string{"Teammate"}
|
||||
case "":
|
||||
if isAdmin {
|
||||
return "Admin"
|
||||
return []string{"Admin"}
|
||||
}
|
||||
|
||||
return "Teammate"
|
||||
return []string{"Teammate"}
|
||||
default:
|
||||
return userType
|
||||
return []string{userType}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestSendGridDriver(t *testing.T) {
|
||||
owner := records[0]
|
||||
assert.Equal(t, "owner@example.com", owner.Email)
|
||||
assert.Empty(t, owner.FullName)
|
||||
assert.Equal(t, "Owner", owner.Role)
|
||||
assert.Equal(t, []string{"Owner"}, owner.Roles)
|
||||
assert.True(t, owner.IsAdmin)
|
||||
assert.Equal(t, "owner@example.com", owner.ExternalID)
|
||||
assert.Equal(t, coredata.AccessReviewEntryAccountTypeUser, owner.AccountType)
|
||||
@@ -62,7 +62,7 @@ func TestSendGridDriver(t *testing.T) {
|
||||
teammate := records[1]
|
||||
assert.Equal(t, "taylor@example.com", teammate.Email)
|
||||
assert.Equal(t, "Taylor Teammate", teammate.FullName)
|
||||
assert.Equal(t, "Teammate", teammate.Role)
|
||||
assert.Equal(t, []string{"Teammate"}, teammate.Roles)
|
||||
assert.False(t, teammate.IsAdmin)
|
||||
// Non-unified teammate: username is a handle distinct from the email.
|
||||
assert.Equal(t, "taylor-teammate", teammate.ExternalID)
|
||||
@@ -70,27 +70,27 @@ func TestSendGridDriver(t *testing.T) {
|
||||
assert.Equal(t, coredata.MFAStatusEnabled, teammate.MFAStatus)
|
||||
}
|
||||
|
||||
func TestSendGridRole(t *testing.T) {
|
||||
func TestSendGridRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
userType string
|
||||
isAdmin bool
|
||||
want string
|
||||
want []string
|
||||
}{
|
||||
{name: "owner", userType: "owner", isAdmin: true, want: "Owner"},
|
||||
{name: "admin", userType: "admin", isAdmin: true, want: "Admin"},
|
||||
{name: "teammate", userType: "teammate", isAdmin: false, want: "Teammate"},
|
||||
{name: "empty admin", userType: "", isAdmin: true, want: "Admin"},
|
||||
{name: "empty teammate", userType: "", isAdmin: false, want: "Teammate"},
|
||||
{name: "unknown", userType: "custom-role", isAdmin: false, want: "custom-role"},
|
||||
{name: "owner", userType: "owner", isAdmin: true, want: []string{"Owner"}},
|
||||
{name: "admin", userType: "admin", isAdmin: true, want: []string{"Admin"}},
|
||||
{name: "teammate", userType: "teammate", isAdmin: false, want: []string{"Teammate"}},
|
||||
{name: "empty admin", userType: "", isAdmin: true, want: []string{"Admin"}},
|
||||
{name: "empty teammate", userType: "", isAdmin: false, want: []string{"Teammate"}},
|
||||
{name: "unknown", userType: "custom-role", isAdmin: false, want: []string{"custom-role"}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert.Equal(t, tt.want, sendGridRole(tt.userType, tt.isAdmin))
|
||||
assert.Equal(t, tt.want, sendGridRoles(tt.userType, tt.isAdmin))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
@@ -120,7 +121,8 @@ func (d *SentryDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
|
||||
active = active && m.User.IsActive
|
||||
}
|
||||
|
||||
isAdmin := m.OrgRole == "admin" || m.OrgRole == "owner"
|
||||
role := strings.TrimSpace(m.OrgRole)
|
||||
isAdmin := role == "admin" || role == "owner"
|
||||
|
||||
mfaStatus := coredata.MFAStatusUnknown
|
||||
|
||||
@@ -134,10 +136,15 @@ func (d *SentryDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
|
||||
|
||||
authMethod := sentryAuthMethod(m.Flags, m.User)
|
||||
|
||||
roles := []string{}
|
||||
if role != "" {
|
||||
roles = []string{role}
|
||||
}
|
||||
|
||||
record := AccountRecord{
|
||||
Email: m.Email,
|
||||
FullName: fullName,
|
||||
Role: m.OrgRole,
|
||||
Roles: roles,
|
||||
Active: new(active),
|
||||
IsAdmin: isAdmin,
|
||||
ExternalID: m.ID,
|
||||
|
||||
@@ -45,7 +45,7 @@ func TestSentryDriver(t *testing.T) {
|
||||
assert.NotEmpty(t, r.Email)
|
||||
assert.NotEmpty(t, r.FullName)
|
||||
assert.NotEmpty(t, r.ExternalID)
|
||||
assert.NotEmpty(t, r.Role)
|
||||
assert.NotEmpty(t, r.Roles)
|
||||
}
|
||||
|
||||
func TestSentryDriverListAccountsStaleSlug(t *testing.T) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -77,14 +78,14 @@ func (d *SigNozDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
|
||||
continue
|
||||
}
|
||||
|
||||
role := sigNozRole(u.Role)
|
||||
roles := sigNozRoles(u.Role)
|
||||
|
||||
record := AccountRecord{
|
||||
Email: email,
|
||||
FullName: strings.TrimSpace(u.DisplayName),
|
||||
Role: role,
|
||||
Roles: roles,
|
||||
Active: sigNozActiveStatus(u.Status),
|
||||
IsAdmin: u.IsRoot || strings.EqualFold(role, "Admin"),
|
||||
IsAdmin: u.IsRoot || slices.Contains(roles, "Admin"),
|
||||
MFAStatus: coredata.MFAStatusUnknown,
|
||||
AuthMethod: coredata.AccessReviewEntryAuthMethodUnknown,
|
||||
AccountType: coredata.AccessReviewEntryAccountTypeUser,
|
||||
@@ -146,26 +147,26 @@ func (d *SigNozDriver) queryUsers(ctx context.Context) ([]sigNozUser, error) {
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// sigNozRole normalizes a SigNoz role string (ADMIN / EDITOR / VIEWER, or the
|
||||
// sigNozRoles normalizes a SigNoz role string (ADMIN / EDITOR / VIEWER, or the
|
||||
// managed-role display names signoz-admin / signoz-editor / signoz-viewer)
|
||||
// into a stable label, preserving unknown custom roles verbatim. Matching is
|
||||
// exact (not substring) so a custom role merely containing "admin" is not
|
||||
// silently promoted to Admin.
|
||||
func sigNozRole(raw string) string {
|
||||
func sigNozRoles(raw string) []string {
|
||||
role := strings.TrimSpace(raw)
|
||||
if role == "" {
|
||||
return "User"
|
||||
return []string{}
|
||||
}
|
||||
|
||||
switch strings.ToLower(role) {
|
||||
case "admin", "signoz-admin":
|
||||
return "Admin"
|
||||
return []string{"Admin"}
|
||||
case "editor", "signoz-editor":
|
||||
return "Editor"
|
||||
return []string{"Editor"}
|
||||
case "viewer", "signoz-viewer":
|
||||
return "Viewer"
|
||||
return []string{"Viewer"}
|
||||
default:
|
||||
return role
|
||||
return []string{role}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestSigNozDriver(t *testing.T) {
|
||||
// ADMIN role -> admin.
|
||||
assert.Equal(t, "admin@example.com", records[0].Email)
|
||||
assert.Equal(t, "Admin User", records[0].FullName)
|
||||
assert.Equal(t, "Admin", records[0].Role)
|
||||
assert.Equal(t, []string{"Admin"}, records[0].Roles)
|
||||
assert.True(t, records[0].IsAdmin)
|
||||
assert.Equal(t, "00000000-0000-4000-8000-000000000001", records[0].ExternalID)
|
||||
assert.Equal(t, coredata.MFAStatusUnknown, records[0].MFAStatus)
|
||||
@@ -57,25 +57,25 @@ func TestSigNozDriver(t *testing.T) {
|
||||
|
||||
// isRoot -> admin even with a non-admin role.
|
||||
assert.Equal(t, "owner@example.com", records[1].Email)
|
||||
assert.Equal(t, "Viewer", records[1].Role)
|
||||
assert.Equal(t, []string{"Viewer"}, records[1].Roles)
|
||||
assert.True(t, records[1].IsAdmin)
|
||||
|
||||
// Managed-role display name -> Editor; not admin.
|
||||
assert.Equal(t, "editor@example.com", records[2].Email)
|
||||
assert.Equal(t, "Editor", records[2].Role)
|
||||
assert.Equal(t, []string{"Editor"}, records[2].Roles)
|
||||
assert.False(t, records[2].IsAdmin)
|
||||
require.NotNil(t, records[2].Active)
|
||||
assert.True(t, *records[2].Active)
|
||||
|
||||
// pending_invite -> inactive.
|
||||
assert.Equal(t, "invited@example.com", records[3].Email)
|
||||
assert.Equal(t, "Viewer", records[3].Role)
|
||||
assert.Equal(t, []string{"Viewer"}, records[3].Roles)
|
||||
require.NotNil(t, records[3].Active)
|
||||
assert.False(t, *records[3].Active)
|
||||
|
||||
// deleted -> inactive.
|
||||
assert.Equal(t, "removed@example.com", records[4].Email)
|
||||
assert.Equal(t, "Editor", records[4].Role)
|
||||
assert.Equal(t, []string{"Editor"}, records[4].Roles)
|
||||
require.NotNil(t, records[4].Active)
|
||||
assert.False(t, *records[4].Active)
|
||||
|
||||
@@ -121,22 +121,22 @@ func TestSigNozDriverListAccountsErrorStatus(t *testing.T) {
|
||||
assert.Contains(t, err.Error(), "unexpected status 403")
|
||||
}
|
||||
|
||||
func TestSigNozRole(t *testing.T) {
|
||||
func TestSigNozRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for in, want := range map[string]string{
|
||||
"ADMIN": "Admin",
|
||||
"signoz-admin": "Admin",
|
||||
"EDITOR": "Editor",
|
||||
"signoz-editor": "Editor",
|
||||
"VIEWER": "Viewer",
|
||||
"signoz-viewer": "Viewer",
|
||||
"": "User",
|
||||
" ": "User",
|
||||
"custom-role": "custom-role", // unknown role preserved verbatim
|
||||
"superadmin": "superadmin", // contains "admin" but must NOT be promoted
|
||||
for in, want := range map[string][]string{
|
||||
"ADMIN": {"Admin"},
|
||||
"signoz-admin": {"Admin"},
|
||||
"EDITOR": {"Editor"},
|
||||
"signoz-editor": {"Editor"},
|
||||
"VIEWER": {"Viewer"},
|
||||
"signoz-viewer": {"Viewer"},
|
||||
"": {},
|
||||
" ": {},
|
||||
"custom-role": {"custom-role"}, // unknown role preserved verbatim
|
||||
"superadmin": {"superadmin"}, // contains "admin" but must NOT be promoted
|
||||
} {
|
||||
assert.Equalf(t, want, sigNozRole(in), "role %q", in)
|
||||
assert.Equalf(t, want, sigNozRoles(in), "role %q", in)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ func (d *SlackDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error)
|
||||
Email: m.Profile.Email,
|
||||
FullName: m.RealName,
|
||||
JobTitle: m.Profile.Title,
|
||||
Role: slackRole(m),
|
||||
Roles: slackRoles(m),
|
||||
Active: new(!m.Deleted),
|
||||
IsAdmin: m.IsAdmin || m.IsOwner || m.IsPrimaryOwner,
|
||||
ExternalID: m.ID,
|
||||
@@ -163,20 +163,20 @@ func (d *SlackDriver) queryUsers(ctx context.Context, cursor string) (*slackUser
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func slackRole(m slackMember) string {
|
||||
func slackRoles(m slackMember) []string {
|
||||
switch {
|
||||
case m.IsPrimaryOwner:
|
||||
return "Primary Owner"
|
||||
return []string{"Primary Owner"}
|
||||
case m.IsOwner:
|
||||
return "Owner"
|
||||
return []string{"Owner"}
|
||||
case m.IsAdmin:
|
||||
return "Admin"
|
||||
return []string{"Admin"}
|
||||
case m.IsUltraRestricted:
|
||||
return "Ultra Restricted"
|
||||
return []string{"Ultra Restricted"}
|
||||
case m.IsRestricted:
|
||||
return "Restricted"
|
||||
return []string{"Restricted"}
|
||||
default:
|
||||
return "Member"
|
||||
return []string{"Member"}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,5 +46,5 @@ func TestSlackDriver(t *testing.T) {
|
||||
|
||||
require.NotEmpty(t, r.Email, "expected at least one record with an email")
|
||||
assert.NotEmpty(t, r.ExternalID)
|
||||
assert.NotEmpty(t, r.Role)
|
||||
assert.NotEmpty(t, r.Roles)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
@@ -62,10 +63,17 @@ func (d *SupabaseDriver) ListAccounts(ctx context.Context) ([]AccountRecord, err
|
||||
|
||||
isAdmin := m.RoleName == "Owner" || m.RoleName == "Administrator"
|
||||
|
||||
role := strings.TrimSpace(m.RoleName)
|
||||
|
||||
roles := []string{}
|
||||
if role != "" {
|
||||
roles = []string{role}
|
||||
}
|
||||
|
||||
record := AccountRecord{
|
||||
Email: m.Email,
|
||||
FullName: m.UserName,
|
||||
Role: m.RoleName,
|
||||
Roles: roles,
|
||||
IsAdmin: isAdmin,
|
||||
ExternalID: m.UserID,
|
||||
MFAStatus: mfaStatus,
|
||||
|
||||
@@ -42,5 +42,5 @@ func TestSupabaseDriver(t *testing.T) {
|
||||
r := records[0]
|
||||
assert.NotEmpty(t, r.FullName)
|
||||
assert.NotEmpty(t, r.ExternalID)
|
||||
assert.NotEmpty(t, r.Role)
|
||||
assert.NotEmpty(t, r.Roles)
|
||||
}
|
||||
|
||||
@@ -75,10 +75,17 @@ func (d *TailscaleDriver) ListAccounts(ctx context.Context) ([]AccountRecord, er
|
||||
continue
|
||||
}
|
||||
|
||||
role := strings.TrimSpace(u.Role)
|
||||
|
||||
roles := []string{}
|
||||
if role != "" {
|
||||
roles = []string{role}
|
||||
}
|
||||
|
||||
record := AccountRecord{
|
||||
Email: email,
|
||||
FullName: u.DisplayName,
|
||||
Role: u.Role,
|
||||
Roles: roles,
|
||||
Active: tailscaleUserActive(u.Status),
|
||||
IsAdmin: tailscaleUserIsAdmin(u.Role),
|
||||
ExternalID: u.ID,
|
||||
|
||||
@@ -37,6 +37,6 @@ func TestTailscaleDriver(t *testing.T) {
|
||||
r := records[0]
|
||||
assert.NotEmpty(t, r.Email)
|
||||
assert.NotEmpty(t, r.ExternalID)
|
||||
assert.NotEmpty(t, r.Role)
|
||||
assert.NotEmpty(t, r.Roles)
|
||||
assert.NotNil(t, r.Active)
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ func (d *TallyDriver) listInvites(ctx context.Context) ([]AccountRecord, error)
|
||||
MFAStatus: coredata.MFAStatusUnknown,
|
||||
AuthMethod: coredata.AccessReviewEntryAuthMethodUnknown,
|
||||
AccountType: coredata.AccessReviewEntryAccountTypeUser,
|
||||
Role: "Invited",
|
||||
Roles: tallyRoles(),
|
||||
}
|
||||
|
||||
if record.Email != "" {
|
||||
@@ -188,3 +188,7 @@ func (d *TallyDriver) listInvites(ctx context.Context) ([]AccountRecord, error)
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func tallyRoles() []string {
|
||||
return []string{"Invited"}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
@@ -92,11 +93,18 @@ func (d *VercelDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
|
||||
fullName = m.Username
|
||||
}
|
||||
|
||||
role := strings.TrimSpace(m.Role)
|
||||
|
||||
roles := []string{}
|
||||
if role != "" {
|
||||
roles = []string{role}
|
||||
}
|
||||
|
||||
confirmed := m.Confirmed
|
||||
record := AccountRecord{
|
||||
Email: m.Email,
|
||||
FullName: fullName,
|
||||
Role: m.Role,
|
||||
Roles: roles,
|
||||
Active: &confirmed,
|
||||
IsAdmin: m.Role == "OWNER" || m.Role == "owner",
|
||||
MFAStatus: coredata.MFAStatusUnknown,
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestVercelDriver(t *testing.T) {
|
||||
assert.NotEmpty(t, r.Email)
|
||||
assert.NotEmpty(t, r.ExternalID)
|
||||
assert.NotEmpty(t, r.FullName)
|
||||
assert.NotEmpty(t, r.Role)
|
||||
assert.NotEmpty(t, r.Roles)
|
||||
assert.True(t, r.IsAdmin)
|
||||
require.NotNil(t, r.Active)
|
||||
assert.True(t, *r.Active)
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
@@ -129,10 +130,17 @@ func zendeskRecord(u zendeskUser) AccountRecord {
|
||||
lastLogin = *u.LastLoginAt
|
||||
}
|
||||
|
||||
role := strings.TrimSpace(u.Role)
|
||||
|
||||
roles := []string{}
|
||||
if role != "" {
|
||||
roles = []string{role}
|
||||
}
|
||||
|
||||
return AccountRecord{
|
||||
Email: u.Email,
|
||||
FullName: u.Name,
|
||||
Role: u.Role,
|
||||
Roles: roles,
|
||||
Active: &active,
|
||||
IsAdmin: isAdmin,
|
||||
MFAStatus: mfaStatus,
|
||||
|
||||
@@ -44,7 +44,7 @@ func TestZendeskDriver(t *testing.T) {
|
||||
require.NotNil(t, r.Active)
|
||||
assert.True(t, *r.Active)
|
||||
assert.True(t, r.IsAdmin)
|
||||
assert.Equal(t, "admin", r.Role)
|
||||
assert.Equal(t, []string{"admin"}, r.Roles)
|
||||
assert.Equal(t, coredata.AccessReviewEntryAccountTypeUser, r.AccountType)
|
||||
assert.Equal(t, coredata.MFAStatusEnabled, r.MFAStatus)
|
||||
assert.Equal(t, coredata.AccessReviewEntryAuthMethodUnknown, r.AuthMethod)
|
||||
@@ -59,7 +59,7 @@ func TestZendeskDriver(t *testing.T) {
|
||||
require.NotNil(t, r2.Active)
|
||||
assert.True(t, *r2.Active)
|
||||
assert.False(t, r2.IsAdmin)
|
||||
assert.Equal(t, "agent", r2.Role)
|
||||
assert.Equal(t, []string{"agent"}, r2.Roles)
|
||||
assert.Equal(t, coredata.MFAStatusDisabled, r2.MFAStatus)
|
||||
assert.Nil(t, r2.LastLogin)
|
||||
}
|
||||
@@ -85,7 +85,7 @@ func TestZendeskRecord_FieldMapping(t *testing.T) {
|
||||
require.NotNil(t, rec.Active)
|
||||
assert.False(t, *rec.Active, "a suspended user must be inactive")
|
||||
assert.Equal(t, coredata.MFAStatusUnknown, rec.MFAStatus, "null 2FA must stay unknown")
|
||||
assert.Equal(t, "Light agent", rec.Role, "custom role names pass through")
|
||||
assert.Equal(t, []string{"Light agent"}, rec.Roles, "custom role names pass through")
|
||||
assert.False(t, rec.IsAdmin)
|
||||
assert.Equal(t, "42", rec.ExternalID)
|
||||
assert.Nil(t, rec.LastLogin)
|
||||
|
||||
@@ -131,7 +131,7 @@ func (s *Service) FetchSource(
|
||||
AccessReviewCampaignSourceID: campaignSource.ID,
|
||||
Email: account.Email,
|
||||
FullName: account.FullName,
|
||||
Role: account.Role,
|
||||
Roles: account.Roles,
|
||||
JobTitle: account.JobTitle,
|
||||
IsAdmin: account.IsAdmin,
|
||||
MFAStatus: account.MFAStatus,
|
||||
|
||||
Reference in New Issue
Block a user