Introduce access-review source snapshot and normalize naming
Decouple each campaign from the live access-review sources it was started with by introducing a per-campaign source snapshot table (access_review_campaign_sources). The snapshot captures the source name, category, and connector at start time, so a review remains coherent even after the underlying source is edited or deleted. Fetch tracking becomes an append-only log (access_review_campaign_source_fetch_attempts) that preserves every attempt with its own status and error rather than overwriting a single row. Rename the shared access-review tables and enums to use a consistent access_review_ prefix throughout: access_entries → access_review_entries access_sources → access_review_sources access_source_category → access_review_source_category access_entry_* → access_review_entry_* The same rename propagates to every coredata type, service, GraphQL schema, MCP specification, CLI command, frontend component, and e2e test. The accessreview package gains dedicated actions.go and policies.go files for its own IAM policy set, mirroring the agentrun package pattern. Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -40,7 +40,7 @@ type providerOrgConfig struct {
|
||||
}
|
||||
|
||||
// providerOrgConfigs is the single source of truth that the three
|
||||
// AccessSource picker resolvers (ProviderOrganizations,
|
||||
// AccessReviewSource picker resolvers (ProviderOrganizations,
|
||||
// SelectedOrganization, NeedsConfiguration) dispatch through. Adding a
|
||||
// provider takes one entry here, not three switch arms.
|
||||
var providerOrgConfigs = map[coredata.ConnectorProvider]providerOrgConfig{
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview"
|
||||
"go.probo.inc/probo/pkg/agentrun"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
@@ -384,34 +385,34 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
||||
return types.NewAgentRun(run), nil
|
||||
}
|
||||
case coredata.AccessReviewCampaignEntityType:
|
||||
action = probo.ActionAccessReviewCampaignGet
|
||||
action = accessreview.ActionCampaignGet
|
||||
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
|
||||
campaign, err := r.accessReview.Campaigns(scope).Get(ctx, id)
|
||||
campaign, err := r.accessReview.GetCampaign(ctx, scope, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return types.NewAccessReviewCampaign(campaign), nil
|
||||
}
|
||||
case coredata.AccessSourceEntityType:
|
||||
action = probo.ActionAccessSourceGet
|
||||
case coredata.AccessReviewSourceEntityType:
|
||||
action = accessreview.ActionSourceGet
|
||||
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
|
||||
source, err := r.accessReview.Sources(scope).Get(ctx, id)
|
||||
source, err := r.accessReview.GetSource(ctx, scope, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return types.NewAccessSource(source), nil
|
||||
return types.NewAccessReviewSource(source), nil
|
||||
}
|
||||
case coredata.AccessEntryEntityType:
|
||||
action = probo.ActionAccessEntryGet
|
||||
case coredata.AccessReviewEntryEntityType:
|
||||
action = accessreview.ActionEntryGet
|
||||
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
|
||||
entry, err := r.accessReview.Entries(scope).Get(ctx, id)
|
||||
entry, err := r.accessReview.GetEntry(ctx, scope, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return types.NewAccessEntry(entry), nil
|
||||
return types.NewAccessReviewEntry(entry), nil
|
||||
}
|
||||
case coredata.CookieBannerEntityType:
|
||||
action = probo.ActionCookieBannerGet
|
||||
|
||||
@@ -24,25 +24,25 @@ enum AccessReviewCampaignStatus
|
||||
)
|
||||
}
|
||||
|
||||
enum AccessSourceCategory
|
||||
enum AccessReviewSourceCategory
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/coredata.AccessSourceCategory"
|
||||
model: "go.probo.inc/probo/pkg/coredata.AccessReviewSourceCategory"
|
||||
) {
|
||||
SAAS
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessSourceCategorySaaS"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewSourceCategorySaaS"
|
||||
)
|
||||
CLOUD_INFRA
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessSourceCategoryCloudInfra"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewSourceCategoryCloudInfra"
|
||||
)
|
||||
SOURCE_CODE
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessSourceCategorySourceCode"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewSourceCategorySourceCode"
|
||||
)
|
||||
OTHER
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessSourceCategoryOther"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewSourceCategoryOther"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -68,109 +68,109 @@ enum AccessReviewCampaignSourceFetchStatus
|
||||
)
|
||||
}
|
||||
|
||||
enum AccessEntryFlag
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.AccessEntryFlag") {
|
||||
enum AccessReviewEntryFlag
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryFlag") {
|
||||
NONE
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagNone"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryFlagNone"
|
||||
)
|
||||
ORPHANED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagOrphaned"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryFlagOrphaned"
|
||||
)
|
||||
INACTIVE
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagInactive"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryFlagInactive"
|
||||
)
|
||||
EXCESSIVE
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagExcessive"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryFlagExcessive"
|
||||
)
|
||||
ROLE_MISMATCH
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagRoleMismatch"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryFlagRoleMismatch"
|
||||
)
|
||||
NEW
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagNew"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryFlagNew"
|
||||
)
|
||||
DORMANT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagDormant"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryFlagDormant"
|
||||
)
|
||||
TERMINATED_USER
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagTerminatedUser"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryFlagTerminatedUser"
|
||||
)
|
||||
CONTRACTOR_EXPIRED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagContractorExpired"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryFlagContractorExpired"
|
||||
)
|
||||
SOD_CONFLICT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagSoDConflict"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryFlagSoDConflict"
|
||||
)
|
||||
PRIVILEGED_ACCESS
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagPrivilegedAccess"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryFlagPrivilegedAccess"
|
||||
)
|
||||
ROLE_CREEP
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagRoleCreep"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryFlagRoleCreep"
|
||||
)
|
||||
NO_BUSINESS_JUSTIFICATION
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagNoBusinessJustification"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryFlagNoBusinessJustification"
|
||||
)
|
||||
OUT_OF_DEPARTMENT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagOutOfDepartment"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryFlagOutOfDepartment"
|
||||
)
|
||||
SHARED_ACCOUNT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryFlagSharedAccount"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryFlagSharedAccount"
|
||||
)
|
||||
}
|
||||
|
||||
enum AccessEntryDecision
|
||||
enum AccessReviewEntryDecision
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/coredata.AccessEntryDecision"
|
||||
model: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryDecision"
|
||||
) {
|
||||
PENDING
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryDecisionPending"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryDecisionPending"
|
||||
)
|
||||
APPROVED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryDecisionApproved"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryDecisionApproved"
|
||||
)
|
||||
REVOKE
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryDecisionRevoke"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryDecisionRevoke"
|
||||
)
|
||||
DEFER
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryDecisionDefer"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryDecisionDefer"
|
||||
)
|
||||
ESCALATE
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryDecisionEscalate"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryDecisionEscalate"
|
||||
)
|
||||
}
|
||||
|
||||
enum AccessEntryIncrementalTag
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.AccessEntryIncrementalTag") {
|
||||
enum AccessReviewEntryIncrementalTag
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryIncrementalTag") {
|
||||
NEW
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryIncrementalTagNew"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryIncrementalTagNew"
|
||||
)
|
||||
REMOVED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryIncrementalTagRemoved"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryIncrementalTagRemoved"
|
||||
)
|
||||
UNCHANGED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryIncrementalTagUnchanged"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryIncrementalTagUnchanged"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -190,49 +190,49 @@ enum MfaStatus
|
||||
)
|
||||
}
|
||||
|
||||
enum AccessEntryAuthMethod
|
||||
enum AccessReviewEntryAuthMethod
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethod"
|
||||
model: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryAuthMethod"
|
||||
) {
|
||||
SSO
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethodSSO"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryAuthMethodSSO"
|
||||
)
|
||||
PASSWORD
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethodPassword"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryAuthMethodPassword"
|
||||
)
|
||||
API_KEY
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethodAPIKey"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryAuthMethodAPIKey"
|
||||
)
|
||||
SERVICE_ACCOUNT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethodServiceAccount"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryAuthMethodServiceAccount"
|
||||
)
|
||||
UNKNOWN
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethodUnknown"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryAuthMethodUnknown"
|
||||
)
|
||||
}
|
||||
|
||||
enum AccessEntryAccountType
|
||||
enum AccessReviewEntryAccountType
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/coredata.AccessEntryAccountType"
|
||||
model: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryAccountType"
|
||||
) {
|
||||
USER
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryAccountTypeUser"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryAccountTypeUser"
|
||||
)
|
||||
SERVICE_ACCOUNT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessEntryAccountTypeServiceAccount"
|
||||
value: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryAccountTypeServiceAccount"
|
||||
)
|
||||
}
|
||||
|
||||
enum AccessSourceOrderField
|
||||
enum AccessReviewSourceOrderField
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/coredata.AccessSourceOrderField"
|
||||
model: "go.probo.inc/probo/pkg/coredata.AccessReviewSourceOrderField"
|
||||
) {
|
||||
CREATED_AT
|
||||
}
|
||||
@@ -244,22 +244,22 @@ enum AccessReviewCampaignOrderField
|
||||
CREATED_AT
|
||||
}
|
||||
|
||||
enum AccessEntryOrderField
|
||||
enum AccessReviewEntryOrderField
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/coredata.AccessEntryOrderField"
|
||||
model: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryOrderField"
|
||||
) {
|
||||
CREATED_AT
|
||||
}
|
||||
|
||||
enum AccessSourceConnectionStatus {
|
||||
enum AccessReviewSourceConnectionStatus {
|
||||
CONNECTED
|
||||
DISCONNECTED
|
||||
NOT_APPLICABLE
|
||||
}
|
||||
|
||||
input AccessSourceOrder {
|
||||
input AccessReviewSourceOrder {
|
||||
direction: OrderDirection!
|
||||
field: AccessSourceOrderField!
|
||||
field: AccessReviewSourceOrderField!
|
||||
}
|
||||
|
||||
input AccessReviewCampaignOrder {
|
||||
@@ -267,22 +267,22 @@ input AccessReviewCampaignOrder {
|
||||
field: AccessReviewCampaignOrderField!
|
||||
}
|
||||
|
||||
input AccessEntryOrder {
|
||||
input AccessReviewEntryOrder {
|
||||
direction: OrderDirection!
|
||||
field: AccessEntryOrderField!
|
||||
field: AccessReviewEntryOrderField!
|
||||
}
|
||||
|
||||
input AccessEntryFilter
|
||||
input AccessReviewEntryFilter
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/coredata.AccessEntryFilter"
|
||||
model: "go.probo.inc/probo/pkg/coredata.AccessReviewEntryFilter"
|
||||
) {
|
||||
decision: AccessEntryDecision
|
||||
flag: AccessEntryFlag
|
||||
incrementalTag: AccessEntryIncrementalTag
|
||||
decision: AccessReviewEntryDecision
|
||||
flag: AccessReviewEntryFlag
|
||||
incrementalTag: AccessReviewEntryIncrementalTag
|
||||
isAdmin: Boolean
|
||||
active: Boolean
|
||||
authMethod: AccessEntryAuthMethod
|
||||
accountType: AccessEntryAccountType
|
||||
authMethod: AccessReviewEntryAuthMethod
|
||||
accountType: AccessReviewEntryAccountType
|
||||
}
|
||||
|
||||
type ProviderOrganization {
|
||||
@@ -290,7 +290,7 @@ type ProviderOrganization {
|
||||
displayName: String!
|
||||
}
|
||||
|
||||
type AccessSource implements Node {
|
||||
type AccessReviewSource implements Node {
|
||||
id: ID!
|
||||
organization: Organization! @goField(forceResolver: true)
|
||||
connectorId: ID
|
||||
@@ -299,7 +299,7 @@ type AccessSource implements Node {
|
||||
csvData: String
|
||||
providerOrganizations: [ProviderOrganization!]! @goField(forceResolver: true)
|
||||
needsConfiguration: Boolean! @goField(forceResolver: true)
|
||||
connectionStatus: AccessSourceConnectionStatus! @goField(forceResolver: true)
|
||||
connectionStatus: AccessReviewSourceConnectionStatus! @goField(forceResolver: true)
|
||||
selectedOrganization: String @goField(forceResolver: true)
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
@@ -307,30 +307,51 @@ type AccessSource implements Node {
|
||||
permission(action: String!): Boolean! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type AccessReviewCampaignScopeSource
|
||||
type AccessReviewCampaignSourceFetchAttempt
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AccessReviewCampaignScopeSource"
|
||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AccessReviewCampaignSourceFetchAttempt"
|
||||
) {
|
||||
id: ID!
|
||||
source: AccessSource!
|
||||
attemptNumber: Int!
|
||||
status: AccessReviewCampaignSourceFetchStatus!
|
||||
fetchedAccountsCount: Int!
|
||||
error: String
|
||||
startedAt: Datetime
|
||||
completedAt: Datetime
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
|
||||
type AccessReviewCampaignSource
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AccessReviewCampaignSource"
|
||||
) {
|
||||
id: ID!
|
||||
"The live access source this snapshot was taken from. Null once the source is deleted."
|
||||
source: AccessReviewSource @goField(forceResolver: true)
|
||||
name: String!
|
||||
"Current fetch status, derived from the latest fetch attempt."
|
||||
fetchStatus: AccessReviewCampaignSourceFetchStatus!
|
||||
fetchedAccountsCount: Int!
|
||||
attemptCount: Int!
|
||||
"Generic, user-facing error from the latest attempt. Raw errors stay in logs."
|
||||
lastError: String
|
||||
fetchStartedAt: Datetime
|
||||
fetchCompletedAt: Datetime
|
||||
|
||||
"Full append-only history of fetch attempts, most recent first."
|
||||
fetchAttempts: [AccessReviewCampaignSourceFetchAttempt!]! @goField(forceResolver: true)
|
||||
|
||||
entries(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: AccessEntryOrder
|
||||
filter: AccessEntryFilter
|
||||
): AccessEntryConnection! @goField(forceResolver: true)
|
||||
orderBy: AccessReviewEntryOrder
|
||||
filter: AccessReviewEntryFilter
|
||||
): AccessReviewEntryConnection! @goField(forceResolver: true)
|
||||
|
||||
statistics: AccessReviewCampaignStatistics! @goField(forceResolver: true)
|
||||
statistics: AccessReviewStatistics! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type AccessReviewCampaign implements Node {
|
||||
@@ -345,29 +366,30 @@ type AccessReviewCampaign implements Node {
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
|
||||
scopeSources: [AccessReviewCampaignScopeSource!]! @goField(forceResolver: true)
|
||||
sources: [AccessReviewCampaignSource!]! @goField(forceResolver: true)
|
||||
|
||||
entries(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: AccessEntryOrder
|
||||
accessSourceId: ID
|
||||
filter: AccessEntryFilter
|
||||
): AccessEntryConnection! @goField(forceResolver: true)
|
||||
orderBy: AccessReviewEntryOrder
|
||||
campaignSourceId: ID
|
||||
filter: AccessReviewEntryFilter
|
||||
): AccessReviewEntryConnection! @goField(forceResolver: true)
|
||||
|
||||
pendingEntryCount: Int! @goField(forceResolver: true)
|
||||
|
||||
statistics: AccessReviewCampaignStatistics! @goField(forceResolver: true)
|
||||
statistics: AccessReviewStatistics! @goField(forceResolver: true)
|
||||
|
||||
permission(action: String!): Boolean! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type AccessEntry implements Node {
|
||||
type AccessReviewEntry implements Node {
|
||||
id: ID!
|
||||
campaign: AccessReviewCampaign! @goField(forceResolver: true)
|
||||
accessSource: AccessSource! @goField(forceResolver: true)
|
||||
"The live access source this entry came from. Null once the source is deleted."
|
||||
accessReviewSource: AccessReviewSource @goField(forceResolver: true)
|
||||
email: String!
|
||||
fullName: String!
|
||||
role: String!
|
||||
@@ -375,19 +397,19 @@ type AccessEntry implements Node {
|
||||
isAdmin: Boolean!
|
||||
active: Boolean
|
||||
mfaStatus: MfaStatus!
|
||||
authMethod: AccessEntryAuthMethod!
|
||||
accountType: AccessEntryAccountType!
|
||||
authMethod: AccessReviewEntryAuthMethod!
|
||||
accountType: AccessReviewEntryAccountType!
|
||||
lastLogin: Datetime
|
||||
accountCreatedAt: Datetime
|
||||
externalId: String!
|
||||
incrementalTag: AccessEntryIncrementalTag!
|
||||
flags: [AccessEntryFlag!]!
|
||||
incrementalTag: AccessReviewEntryIncrementalTag!
|
||||
flags: [AccessReviewEntryFlag!]!
|
||||
flagReasons: [String!]!
|
||||
decision: AccessEntryDecision!
|
||||
decision: AccessReviewEntryDecision!
|
||||
decisionNote: String
|
||||
decidedBy: ID
|
||||
decidedAt: Datetime
|
||||
decisionHistory: [AccessEntryDecisionHistoryEntry!]!
|
||||
decisionHistory: [AccessReviewEntryDecisionHistoryEntry!]!
|
||||
@goField(forceResolver: true)
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
@@ -395,27 +417,27 @@ type AccessEntry implements Node {
|
||||
permission(action: String!): Boolean! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type AccessEntryDecisionHistoryEntry {
|
||||
type AccessReviewEntryDecisionHistoryEntry {
|
||||
id: ID!
|
||||
decision: AccessEntryDecision!
|
||||
decision: AccessReviewEntryDecision!
|
||||
decisionNote: String
|
||||
decidedBy: ID
|
||||
decidedAt: Datetime!
|
||||
createdAt: Datetime!
|
||||
}
|
||||
|
||||
type AccessSourceConnection
|
||||
type AccessReviewSourceConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AccessSourceConnection"
|
||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AccessReviewSourceConnection"
|
||||
) {
|
||||
totalCount: Int! @goField(forceResolver: true)
|
||||
edges: [AccessSourceEdge!]!
|
||||
edges: [AccessReviewSourceEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type AccessSourceEdge {
|
||||
type AccessReviewSourceEdge {
|
||||
cursor: CursorKey!
|
||||
node: AccessSource!
|
||||
node: AccessReviewSource!
|
||||
}
|
||||
|
||||
type AccessReviewCampaignConnection
|
||||
@@ -432,55 +454,55 @@ type AccessReviewCampaignEdge {
|
||||
node: AccessReviewCampaign!
|
||||
}
|
||||
|
||||
type AccessEntryConnection
|
||||
type AccessReviewEntryConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AccessEntryConnection"
|
||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.AccessReviewEntryConnection"
|
||||
) {
|
||||
totalCount: Int! @goField(forceResolver: true)
|
||||
edges: [AccessEntryEdge!]!
|
||||
edges: [AccessReviewEntryEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type AccessEntryEdge {
|
||||
type AccessReviewEntryEdge {
|
||||
cursor: CursorKey!
|
||||
node: AccessEntry!
|
||||
node: AccessReviewEntry!
|
||||
}
|
||||
|
||||
type AccessReviewCampaignStatistics {
|
||||
type AccessReviewStatistics {
|
||||
totalCount: Int!
|
||||
decisionCounts: [AccessEntryDecisionCount!]!
|
||||
flagCounts: [AccessEntryFlagCount!]!
|
||||
incrementalTagCounts: [AccessEntryIncrementalTagCount!]!
|
||||
decisionCounts: [AccessReviewEntryDecisionCount!]!
|
||||
flagCounts: [AccessReviewEntryFlagCount!]!
|
||||
incrementalTagCounts: [AccessReviewEntryIncrementalTagCount!]!
|
||||
}
|
||||
|
||||
type AccessEntryDecisionCount {
|
||||
decision: AccessEntryDecision!
|
||||
type AccessReviewEntryDecisionCount {
|
||||
decision: AccessReviewEntryDecision!
|
||||
count: Int!
|
||||
}
|
||||
|
||||
type AccessEntryFlagCount {
|
||||
flag: AccessEntryFlag!
|
||||
type AccessReviewEntryFlagCount {
|
||||
flag: AccessReviewEntryFlag!
|
||||
count: Int!
|
||||
}
|
||||
|
||||
type AccessEntryIncrementalTagCount {
|
||||
incrementalTag: AccessEntryIncrementalTag!
|
||||
type AccessReviewEntryIncrementalTagCount {
|
||||
incrementalTag: AccessReviewEntryIncrementalTag!
|
||||
count: Int!
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
createAccessSource(
|
||||
input: CreateAccessSourceInput!
|
||||
): CreateAccessSourcePayload!
|
||||
updateAccessSource(
|
||||
input: UpdateAccessSourceInput!
|
||||
): UpdateAccessSourcePayload!
|
||||
deleteAccessSource(
|
||||
input: DeleteAccessSourceInput!
|
||||
): DeleteAccessSourcePayload!
|
||||
configureAccessSource(
|
||||
input: ConfigureAccessSourceInput!
|
||||
): ConfigureAccessSourcePayload!
|
||||
createAccessReviewSource(
|
||||
input: CreateAccessReviewSourceInput!
|
||||
): CreateAccessReviewSourcePayload!
|
||||
updateAccessReviewSource(
|
||||
input: UpdateAccessReviewSourceInput!
|
||||
): UpdateAccessReviewSourcePayload!
|
||||
deleteAccessReviewSource(
|
||||
input: DeleteAccessReviewSourceInput!
|
||||
): DeleteAccessReviewSourcePayload!
|
||||
configureAccessReviewSource(
|
||||
input: ConfigureAccessReviewSourceInput!
|
||||
): ConfigureAccessReviewSourcePayload!
|
||||
createAccessReviewCampaign(
|
||||
input: CreateAccessReviewCampaignInput!
|
||||
): CreateAccessReviewCampaignPayload!
|
||||
@@ -499,60 +521,60 @@ extend type Mutation {
|
||||
cancelAccessReviewCampaign(
|
||||
input: CancelAccessReviewCampaignInput!
|
||||
): CancelAccessReviewCampaignPayload!
|
||||
addAccessReviewCampaignScopeSource(
|
||||
input: AddAccessReviewCampaignScopeSourceInput!
|
||||
): AddAccessReviewCampaignScopeSourcePayload!
|
||||
removeAccessReviewCampaignScopeSource(
|
||||
input: RemoveAccessReviewCampaignScopeSourceInput!
|
||||
): RemoveAccessReviewCampaignScopeSourcePayload!
|
||||
recordAccessEntryDecision(
|
||||
input: RecordAccessEntryDecisionInput!
|
||||
): RecordAccessEntryDecisionPayload!
|
||||
recordAccessEntryDecisions(
|
||||
input: RecordAccessEntryDecisionsInput!
|
||||
): RecordAccessEntryDecisionsPayload!
|
||||
flagAccessEntry(
|
||||
input: FlagAccessEntryInput!
|
||||
): FlagAccessEntryPayload!
|
||||
addAccessReviewCampaignSource(
|
||||
input: AddAccessReviewCampaignSourceInput!
|
||||
): AddAccessReviewCampaignSourcePayload!
|
||||
removeAccessReviewCampaignSource(
|
||||
input: RemoveAccessReviewCampaignSourceInput!
|
||||
): RemoveAccessReviewCampaignSourcePayload!
|
||||
recordAccessReviewEntryDecision(
|
||||
input: RecordAccessReviewEntryDecisionInput!
|
||||
): RecordAccessReviewEntryDecisionPayload!
|
||||
recordAccessReviewEntryDecisions(
|
||||
input: RecordAccessReviewEntryDecisionsInput!
|
||||
): RecordAccessReviewEntryDecisionsPayload!
|
||||
flagAccessReviewEntry(
|
||||
input: FlagAccessReviewEntryInput!
|
||||
): FlagAccessReviewEntryPayload!
|
||||
}
|
||||
|
||||
input CreateAccessSourceInput {
|
||||
input CreateAccessReviewSourceInput {
|
||||
organizationId: ID!
|
||||
connectorId: ID
|
||||
name: String!
|
||||
csvData: String
|
||||
}
|
||||
|
||||
type CreateAccessSourcePayload {
|
||||
accessSourceEdge: AccessSourceEdge!
|
||||
type CreateAccessReviewSourcePayload {
|
||||
accessReviewSourceEdge: AccessReviewSourceEdge!
|
||||
}
|
||||
|
||||
input UpdateAccessSourceInput {
|
||||
accessSourceId: ID!
|
||||
input UpdateAccessReviewSourceInput {
|
||||
accessReviewSourceId: ID!
|
||||
name: String @goField(omittable: true)
|
||||
connectorId: ID @goField(omittable: true)
|
||||
csvData: String @goField(omittable: true)
|
||||
}
|
||||
|
||||
type UpdateAccessSourcePayload {
|
||||
accessSource: AccessSource!
|
||||
type UpdateAccessReviewSourcePayload {
|
||||
accessReviewSource: AccessReviewSource!
|
||||
}
|
||||
|
||||
input DeleteAccessSourceInput {
|
||||
accessSourceId: ID!
|
||||
input DeleteAccessReviewSourceInput {
|
||||
accessReviewSourceId: ID!
|
||||
}
|
||||
|
||||
type DeleteAccessSourcePayload {
|
||||
deletedAccessSourceId: ID!
|
||||
type DeleteAccessReviewSourcePayload {
|
||||
deletedAccessReviewSourceId: ID!
|
||||
}
|
||||
|
||||
input ConfigureAccessSourceInput {
|
||||
accessSourceId: ID!
|
||||
input ConfigureAccessReviewSourceInput {
|
||||
accessReviewSourceId: ID!
|
||||
organizationSlug: String!
|
||||
}
|
||||
|
||||
type ConfigureAccessSourcePayload {
|
||||
accessSource: AccessSource!
|
||||
type ConfigureAccessReviewSourcePayload {
|
||||
accessReviewSource: AccessReviewSource!
|
||||
}
|
||||
|
||||
input CreateAccessReviewCampaignInput {
|
||||
@@ -560,7 +582,7 @@ input CreateAccessReviewCampaignInput {
|
||||
name: String!
|
||||
description: String
|
||||
frameworkControls: [String!]
|
||||
accessSourceIds: [ID!]
|
||||
accessReviewSourceIds: [ID!]
|
||||
}
|
||||
|
||||
type CreateAccessReviewCampaignPayload {
|
||||
@@ -610,54 +632,54 @@ type CancelAccessReviewCampaignPayload {
|
||||
accessReviewCampaign: AccessReviewCampaign!
|
||||
}
|
||||
|
||||
input AddAccessReviewCampaignScopeSourceInput {
|
||||
input AddAccessReviewCampaignSourceInput {
|
||||
accessReviewCampaignId: ID!
|
||||
accessSourceId: ID!
|
||||
accessReviewSourceId: ID!
|
||||
}
|
||||
|
||||
type AddAccessReviewCampaignScopeSourcePayload {
|
||||
type AddAccessReviewCampaignSourcePayload {
|
||||
accessReviewCampaign: AccessReviewCampaign!
|
||||
}
|
||||
|
||||
input RemoveAccessReviewCampaignScopeSourceInput {
|
||||
input RemoveAccessReviewCampaignSourceInput {
|
||||
accessReviewCampaignId: ID!
|
||||
accessSourceId: ID!
|
||||
accessReviewSourceId: ID!
|
||||
}
|
||||
|
||||
type RemoveAccessReviewCampaignScopeSourcePayload {
|
||||
type RemoveAccessReviewCampaignSourcePayload {
|
||||
accessReviewCampaign: AccessReviewCampaign!
|
||||
}
|
||||
|
||||
input RecordAccessEntryDecisionInput {
|
||||
accessEntryId: ID!
|
||||
decision: AccessEntryDecision!
|
||||
input RecordAccessReviewEntryDecisionInput {
|
||||
accessReviewEntryId: ID!
|
||||
decision: AccessReviewEntryDecision!
|
||||
decisionNote: String
|
||||
}
|
||||
|
||||
type RecordAccessEntryDecisionPayload {
|
||||
accessEntry: AccessEntry!
|
||||
type RecordAccessReviewEntryDecisionPayload {
|
||||
accessReviewEntry: AccessReviewEntry!
|
||||
}
|
||||
|
||||
input RecordAccessEntryDecisionsInput {
|
||||
decisions: [AccessEntryDecisionInput!]!
|
||||
input RecordAccessReviewEntryDecisionsInput {
|
||||
decisions: [AccessReviewEntryDecisionInput!]!
|
||||
}
|
||||
|
||||
input AccessEntryDecisionInput {
|
||||
accessEntryId: ID!
|
||||
decision: AccessEntryDecision!
|
||||
input AccessReviewEntryDecisionInput {
|
||||
accessReviewEntryId: ID!
|
||||
decision: AccessReviewEntryDecision!
|
||||
decisionNote: String
|
||||
}
|
||||
|
||||
type RecordAccessEntryDecisionsPayload {
|
||||
accessEntries: [AccessEntry!]!
|
||||
type RecordAccessReviewEntryDecisionsPayload {
|
||||
accessReviewEntries: [AccessReviewEntry!]!
|
||||
}
|
||||
|
||||
input FlagAccessEntryInput {
|
||||
accessEntryId: ID!
|
||||
flags: [AccessEntryFlag!]!
|
||||
input FlagAccessReviewEntryInput {
|
||||
accessReviewEntryId: ID!
|
||||
flags: [AccessReviewEntryFlag!]!
|
||||
flagReasons: [String!]
|
||||
}
|
||||
|
||||
type FlagAccessEntryPayload {
|
||||
accessEntry: AccessEntry!
|
||||
type FlagAccessReviewEntryPayload {
|
||||
accessReviewEntry: AccessReviewEntry!
|
||||
}
|
||||
|
||||
@@ -110,13 +110,13 @@ type Organization implements Node {
|
||||
|
||||
measureCategories: [String!]! @goField(forceResolver: true)
|
||||
|
||||
accessSources(
|
||||
accessReviewSources(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: AccessSourceOrder
|
||||
): AccessSourceConnection! @goField(forceResolver: true)
|
||||
orderBy: AccessReviewSourceOrder
|
||||
): AccessReviewSourceConnection! @goField(forceResolver: true)
|
||||
|
||||
accessReviewCampaigns(
|
||||
first: Int
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview"
|
||||
"go.probo.inc/probo/pkg/agentrun"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
@@ -157,20 +158,20 @@ func (r *organizationResolver) MeasureCategories(ctx context.Context, obj *types
|
||||
return categories, nil
|
||||
}
|
||||
|
||||
// AccessSources is the resolver for the accessSources field.
|
||||
func (r *organizationResolver) AccessSources(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessSourceOrder) (*types.AccessSourceConnection, error) {
|
||||
scope, err := r.authorize(ctx, obj.ID, probo.ActionAccessSourceList)
|
||||
// AccessReviewSources is the resolver for the accessSources field.
|
||||
func (r *organizationResolver) AccessReviewSources(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessReviewSourceOrder) (*types.AccessReviewSourceConnection, error) {
|
||||
scope, err := r.authorize(ctx, obj.ID, accessreview.ActionSourceList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.AccessSourceOrderField]{
|
||||
Field: coredata.AccessSourceOrderFieldCreatedAt,
|
||||
pageOrderBy := page.OrderBy[coredata.AccessReviewSourceOrderField]{
|
||||
Field: coredata.AccessReviewSourceOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
|
||||
if orderBy != nil {
|
||||
pageOrderBy = page.OrderBy[coredata.AccessSourceOrderField]{
|
||||
pageOrderBy = page.OrderBy[coredata.AccessReviewSourceOrderField]{
|
||||
Field: orderBy.Field,
|
||||
Direction: orderBy.Direction,
|
||||
}
|
||||
@@ -178,17 +179,17 @@ func (r *organizationResolver) AccessSources(ctx context.Context, obj *types.Org
|
||||
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
p, err := r.accessReview.Sources(scope).ListForOrganizationID(ctx, obj.ID, cursor)
|
||||
p, err := r.accessReview.ListSourcesForOrganizationID(ctx, scope, obj.ID, cursor)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list access sources: %w", err))
|
||||
}
|
||||
|
||||
return types.NewAccessSourceConnection(p, r, obj.ID), nil
|
||||
return types.NewAccessReviewSourceConnection(p, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// AccessReviewCampaigns is the resolver for the accessReviewCampaigns field.
|
||||
func (r *organizationResolver) AccessReviewCampaigns(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessReviewCampaignOrder) (*types.AccessReviewCampaignConnection, error) {
|
||||
scope, err := r.authorize(ctx, obj.ID, probo.ActionAccessReviewCampaignList)
|
||||
scope, err := r.authorize(ctx, obj.ID, accessreview.ActionCampaignList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -207,7 +208,7 @@ func (r *organizationResolver) AccessReviewCampaigns(ctx context.Context, obj *t
|
||||
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
p, err := r.accessReview.Campaigns(scope).ListForOrganizationID(ctx, obj.ID, cursor)
|
||||
p, err := r.accessReview.ListCampaignsForOrganizationID(ctx, scope, obj.ID, cursor)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list access review campaigns: %w", err))
|
||||
}
|
||||
|
||||
@@ -23,13 +23,13 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
AccessSourceOrderBy OrderBy[coredata.AccessSourceOrderField]
|
||||
AccessReviewSourceOrderBy OrderBy[coredata.AccessReviewSourceOrderField]
|
||||
AccessReviewCampaignOrderBy OrderBy[coredata.AccessReviewCampaignOrderField]
|
||||
AccessEntryOrderBy OrderBy[coredata.AccessEntryOrderField]
|
||||
AccessReviewEntryOrderBy OrderBy[coredata.AccessReviewEntryOrderField]
|
||||
|
||||
AccessSourceConnection struct {
|
||||
AccessReviewSourceConnection struct {
|
||||
TotalCount int
|
||||
Edges []*AccessSourceEdge
|
||||
Edges []*AccessReviewSourceEdge
|
||||
PageInfo PageInfo
|
||||
|
||||
Resolver any
|
||||
@@ -45,32 +45,32 @@ type (
|
||||
ParentID gid.GID
|
||||
}
|
||||
|
||||
AccessEntryConnection struct {
|
||||
AccessReviewEntryConnection struct {
|
||||
TotalCount int
|
||||
Edges []*AccessEntryEdge
|
||||
Edges []*AccessReviewEntryEdge
|
||||
PageInfo PageInfo
|
||||
|
||||
Resolver any
|
||||
ParentID gid.GID
|
||||
SourceID *gid.GID
|
||||
Filter *coredata.AccessEntryFilter
|
||||
Filter *coredata.AccessReviewEntryFilter
|
||||
}
|
||||
)
|
||||
|
||||
// AccessSource helpers
|
||||
// AccessReviewSource helpers
|
||||
|
||||
func NewAccessSourceConnection(
|
||||
p *page.Page[*coredata.AccessSource, coredata.AccessSourceOrderField],
|
||||
func NewAccessReviewSourceConnection(
|
||||
p *page.Page[*coredata.AccessReviewSource, coredata.AccessReviewSourceOrderField],
|
||||
parentType any,
|
||||
parentID gid.GID,
|
||||
) *AccessSourceConnection {
|
||||
edges := make([]*AccessSourceEdge, len(p.Data))
|
||||
) *AccessReviewSourceConnection {
|
||||
edges := make([]*AccessReviewSourceEdge, len(p.Data))
|
||||
|
||||
for i := range edges {
|
||||
edges[i] = NewAccessSourceEdge(p.Data[i], p.Cursor.OrderBy.Field)
|
||||
edges[i] = NewAccessReviewSourceEdge(p.Data[i], p.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &AccessSourceConnection{
|
||||
return &AccessReviewSourceConnection{
|
||||
Edges: edges,
|
||||
PageInfo: *NewPageInfo(p),
|
||||
|
||||
@@ -79,15 +79,15 @@ func NewAccessSourceConnection(
|
||||
}
|
||||
}
|
||||
|
||||
func NewAccessSourceEdge(s *coredata.AccessSource, orderBy coredata.AccessSourceOrderField) *AccessSourceEdge {
|
||||
return &AccessSourceEdge{
|
||||
func NewAccessReviewSourceEdge(s *coredata.AccessReviewSource, orderBy coredata.AccessReviewSourceOrderField) *AccessReviewSourceEdge {
|
||||
return &AccessReviewSourceEdge{
|
||||
Cursor: s.CursorKey(orderBy),
|
||||
Node: NewAccessSource(s),
|
||||
Node: NewAccessReviewSource(s),
|
||||
}
|
||||
}
|
||||
|
||||
func NewAccessSource(s *coredata.AccessSource) *AccessSource {
|
||||
return &AccessSource{
|
||||
func NewAccessReviewSource(s *coredata.AccessReviewSource) *AccessReviewSource {
|
||||
return &AccessReviewSource{
|
||||
ID: s.ID,
|
||||
Organization: &Organization{
|
||||
ID: s.OrganizationID,
|
||||
@@ -100,11 +100,14 @@ func NewAccessSource(s *coredata.AccessSource) *AccessSource {
|
||||
}
|
||||
}
|
||||
|
||||
func NewAccessReviewCampaignScopeSource(
|
||||
campaignID gid.GID,
|
||||
source *coredata.AccessSource,
|
||||
fetch *coredata.AccessReviewCampaignSourceFetch,
|
||||
) *AccessReviewCampaignScopeSource {
|
||||
// NewAccessReviewCampaignSource builds the GraphQL scope source from a
|
||||
// campaign source snapshot. The current fetch state is derived from the latest
|
||||
// fetch attempt (nil when the source has never been fetched). The live access
|
||||
// source is resolved lazily via the source field resolver from SourceID.
|
||||
func NewAccessReviewCampaignSource(
|
||||
campaignSource *coredata.AccessReviewCampaignSource,
|
||||
latestAttempt *coredata.AccessReviewCampaignSourceFetchAttempt,
|
||||
) *AccessReviewCampaignSource {
|
||||
status := coredata.AccessReviewCampaignSourceFetchStatusQueued
|
||||
fetchedAccountsCount := 0
|
||||
attemptCount := 0
|
||||
@@ -115,20 +118,20 @@ func NewAccessReviewCampaignScopeSource(
|
||||
fetchCompletedAt *time.Time
|
||||
)
|
||||
|
||||
if fetch != nil {
|
||||
status = fetch.Status
|
||||
fetchedAccountsCount = fetch.FetchedAccountsCount
|
||||
attemptCount = fetch.AttemptCount
|
||||
lastError = fetch.LastError
|
||||
fetchStartedAt = fetch.StartedAt
|
||||
fetchCompletedAt = fetch.CompletedAt
|
||||
if latestAttempt != nil {
|
||||
status = latestAttempt.Status
|
||||
fetchedAccountsCount = latestAttempt.FetchedAccountsCount
|
||||
attemptCount = latestAttempt.AttemptNumber
|
||||
lastError = latestAttempt.Error
|
||||
fetchStartedAt = latestAttempt.StartedAt
|
||||
fetchCompletedAt = latestAttempt.CompletedAt
|
||||
}
|
||||
|
||||
return &AccessReviewCampaignScopeSource{
|
||||
ID: source.ID,
|
||||
CampaignID: campaignID,
|
||||
Source: NewAccessSource(source),
|
||||
Name: source.Name,
|
||||
return &AccessReviewCampaignSource{
|
||||
ID: campaignSource.ID,
|
||||
CampaignID: campaignSource.AccessReviewCampaignID,
|
||||
SourceID: campaignSource.AccessReviewSourceID,
|
||||
Name: campaignSource.Name,
|
||||
FetchStatus: status,
|
||||
FetchedAccountsCount: fetchedAccountsCount,
|
||||
AttemptCount: attemptCount,
|
||||
@@ -138,6 +141,22 @@ func NewAccessReviewCampaignScopeSource(
|
||||
}
|
||||
}
|
||||
|
||||
// NewAccessReviewCampaignSourceFetchAttempt builds the GraphQL representation of a
|
||||
// single append-only fetch attempt.
|
||||
func NewAccessReviewCampaignSourceFetchAttempt(a *coredata.AccessReviewCampaignSourceFetchAttempt) *AccessReviewCampaignSourceFetchAttempt {
|
||||
return &AccessReviewCampaignSourceFetchAttempt{
|
||||
ID: a.ID,
|
||||
AttemptNumber: a.AttemptNumber,
|
||||
Status: a.Status,
|
||||
FetchedAccountsCount: a.FetchedAccountsCount,
|
||||
Error: a.Error,
|
||||
StartedAt: a.StartedAt,
|
||||
CompletedAt: a.CompletedAt,
|
||||
CreatedAt: a.CreatedAt,
|
||||
UpdatedAt: a.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// AccessReviewCampaign helpers
|
||||
|
||||
func NewAccessReviewCampaignConnection(
|
||||
@@ -186,8 +205,8 @@ func NewAccessReviewCampaign(c *coredata.AccessReviewCampaign) *AccessReviewCamp
|
||||
return campaign
|
||||
}
|
||||
|
||||
func NewAccessEntryDecisionHistoryEntry(h *coredata.AccessEntryDecisionHistory) *AccessEntryDecisionHistoryEntry {
|
||||
entry := &AccessEntryDecisionHistoryEntry{
|
||||
func NewAccessReviewEntryDecisionHistoryEntry(h *coredata.AccessReviewEntryDecisionHistory) *AccessReviewEntryDecisionHistoryEntry {
|
||||
entry := &AccessReviewEntryDecisionHistoryEntry{
|
||||
ID: h.ID,
|
||||
Decision: h.Decision,
|
||||
DecisionNote: h.DecisionNote,
|
||||
@@ -202,22 +221,22 @@ func NewAccessEntryDecisionHistoryEntry(h *coredata.AccessEntryDecisionHistory)
|
||||
return entry
|
||||
}
|
||||
|
||||
// AccessEntry helpers
|
||||
// AccessReviewEntry helpers
|
||||
|
||||
func NewAccessEntryConnection(
|
||||
p *page.Page[*coredata.AccessEntry, coredata.AccessEntryOrderField],
|
||||
func NewAccessReviewEntryConnection(
|
||||
p *page.Page[*coredata.AccessReviewEntry, coredata.AccessReviewEntryOrderField],
|
||||
parentType any,
|
||||
parentID gid.GID,
|
||||
sourceID *gid.GID,
|
||||
filter *coredata.AccessEntryFilter,
|
||||
) *AccessEntryConnection {
|
||||
edges := make([]*AccessEntryEdge, len(p.Data))
|
||||
filter *coredata.AccessReviewEntryFilter,
|
||||
) *AccessReviewEntryConnection {
|
||||
edges := make([]*AccessReviewEntryEdge, len(p.Data))
|
||||
|
||||
for i := range edges {
|
||||
edges[i] = NewAccessEntryEdge(p.Data[i], p.Cursor.OrderBy.Field)
|
||||
edges[i] = NewAccessReviewEntryEdge(p.Data[i], p.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &AccessEntryConnection{
|
||||
return &AccessReviewEntryConnection{
|
||||
Edges: edges,
|
||||
PageInfo: *NewPageInfo(p),
|
||||
|
||||
@@ -228,22 +247,19 @@ func NewAccessEntryConnection(
|
||||
}
|
||||
}
|
||||
|
||||
func NewAccessEntryEdge(e *coredata.AccessEntry, orderBy coredata.AccessEntryOrderField) *AccessEntryEdge {
|
||||
return &AccessEntryEdge{
|
||||
func NewAccessReviewEntryEdge(e *coredata.AccessReviewEntry, orderBy coredata.AccessReviewEntryOrderField) *AccessReviewEntryEdge {
|
||||
return &AccessReviewEntryEdge{
|
||||
Cursor: e.CursorKey(orderBy),
|
||||
Node: NewAccessEntry(e),
|
||||
Node: NewAccessReviewEntry(e),
|
||||
}
|
||||
}
|
||||
|
||||
func NewAccessEntry(e *coredata.AccessEntry) *AccessEntry {
|
||||
entry := &AccessEntry{
|
||||
func NewAccessReviewEntry(e *coredata.AccessReviewEntry) *AccessReviewEntry {
|
||||
entry := &AccessReviewEntry{
|
||||
ID: e.ID,
|
||||
Campaign: &AccessReviewCampaign{
|
||||
ID: e.AccessReviewCampaignID,
|
||||
},
|
||||
AccessSource: &AccessSource{
|
||||
ID: e.AccessSourceID,
|
||||
},
|
||||
Email: e.Email,
|
||||
FullName: e.FullName,
|
||||
Role: e.Role,
|
||||
@@ -273,32 +289,32 @@ func NewAccessEntry(e *coredata.AccessEntry) *AccessEntry {
|
||||
return entry
|
||||
}
|
||||
|
||||
func NewAccessReviewCampaignStatistics(stats *coredata.AccessEntryStatistics) *AccessReviewCampaignStatistics {
|
||||
decisionCounts := make([]*AccessEntryDecisionCount, 0, len(stats.DecisionCounts))
|
||||
func NewAccessReviewStatistics(stats *coredata.AccessReviewStatistics) *AccessReviewStatistics {
|
||||
decisionCounts := make([]*AccessReviewEntryDecisionCount, 0, len(stats.DecisionCounts))
|
||||
for decision, count := range stats.DecisionCounts {
|
||||
decisionCounts = append(
|
||||
decisionCounts,
|
||||
&AccessEntryDecisionCount{Decision: decision, Count: count},
|
||||
&AccessReviewEntryDecisionCount{Decision: decision, Count: count},
|
||||
)
|
||||
}
|
||||
|
||||
flagCounts := make([]*AccessEntryFlagCount, 0, len(stats.FlagCounts))
|
||||
flagCounts := make([]*AccessReviewEntryFlagCount, 0, len(stats.FlagCounts))
|
||||
for flag, count := range stats.FlagCounts {
|
||||
flagCounts = append(
|
||||
flagCounts,
|
||||
&AccessEntryFlagCount{Flag: flag, Count: count},
|
||||
&AccessReviewEntryFlagCount{Flag: flag, Count: count},
|
||||
)
|
||||
}
|
||||
|
||||
incrementalTagCounts := make([]*AccessEntryIncrementalTagCount, 0, len(stats.IncrementalTagCounts))
|
||||
incrementalTagCounts := make([]*AccessReviewEntryIncrementalTagCount, 0, len(stats.IncrementalTagCounts))
|
||||
for tag, count := range stats.IncrementalTagCounts {
|
||||
incrementalTagCounts = append(
|
||||
incrementalTagCounts,
|
||||
&AccessEntryIncrementalTagCount{IncrementalTag: tag, Count: count},
|
||||
&AccessReviewEntryIncrementalTagCount{IncrementalTag: tag, Count: count},
|
||||
)
|
||||
}
|
||||
|
||||
return &AccessReviewCampaignStatistics{
|
||||
return &AccessReviewStatistics{
|
||||
TotalCount: stats.TotalCount,
|
||||
DecisionCounts: decisionCounts,
|
||||
FlagCounts: flagCounts,
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
func TestNewAccessReviewCampaignScopeSource_DefaultFetchState(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tenantID := gid.NewTenantID()
|
||||
source := &coredata.AccessSource{
|
||||
ID: gid.New(tenantID, coredata.AccessSourceEntityType),
|
||||
OrganizationID: gid.New(tenantID, coredata.OrganizationEntityType),
|
||||
Name: "Google Workspace",
|
||||
}
|
||||
|
||||
campaignID := gid.New(tenantID, coredata.AccessReviewCampaignEntityType)
|
||||
|
||||
got := NewAccessReviewCampaignScopeSource(campaignID, source, nil)
|
||||
if got.FetchStatus != coredata.AccessReviewCampaignSourceFetchStatusQueued {
|
||||
t.Fatalf("fetch status = %q, want QUEUED", got.FetchStatus)
|
||||
}
|
||||
|
||||
if got.FetchedAccountsCount != 0 {
|
||||
t.Fatalf("fetched accounts count = %d, want 0", got.FetchedAccountsCount)
|
||||
}
|
||||
|
||||
if got.AttemptCount != 0 {
|
||||
t.Fatalf("attempt count = %d, want 0", got.AttemptCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAccessReviewCampaignScopeSource_UsesFetchState(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now()
|
||||
errMsg := "connector timeout"
|
||||
tenantID := gid.NewTenantID()
|
||||
source := &coredata.AccessSource{
|
||||
ID: gid.New(tenantID, coredata.AccessSourceEntityType),
|
||||
OrganizationID: gid.New(tenantID, coredata.OrganizationEntityType),
|
||||
Name: "Linear",
|
||||
}
|
||||
fetch := &coredata.AccessReviewCampaignSourceFetch{
|
||||
Status: coredata.AccessReviewCampaignSourceFetchStatusFailed,
|
||||
FetchedAccountsCount: 42,
|
||||
AttemptCount: 3,
|
||||
LastError: &errMsg,
|
||||
StartedAt: &now,
|
||||
CompletedAt: &now,
|
||||
}
|
||||
|
||||
campaignID := gid.New(tenantID, coredata.AccessReviewCampaignEntityType)
|
||||
|
||||
got := NewAccessReviewCampaignScopeSource(campaignID, source, fetch)
|
||||
if got.FetchStatus != coredata.AccessReviewCampaignSourceFetchStatusFailed {
|
||||
t.Fatalf("fetch status = %q, want FAILED", got.FetchStatus)
|
||||
}
|
||||
|
||||
if got.FetchedAccountsCount != 42 {
|
||||
t.Fatalf("fetched accounts count = %d, want 42", got.FetchedAccountsCount)
|
||||
}
|
||||
|
||||
if got.AttemptCount != 3 {
|
||||
t.Fatalf("attempt count = %d, want 3", got.AttemptCount)
|
||||
}
|
||||
|
||||
if got.LastError == nil || *got.LastError != errMsg {
|
||||
t.Fatalf("last error = %v, want %q", got.LastError, errMsg)
|
||||
}
|
||||
}
|
||||
@@ -21,10 +21,12 @@ import (
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
type AccessReviewCampaignScopeSource struct {
|
||||
ID gid.GID `json:"id"`
|
||||
CampaignID gid.GID `json:"-"`
|
||||
Source *AccessSource `json:"source"`
|
||||
type AccessReviewCampaignSource struct {
|
||||
ID gid.GID `json:"id"`
|
||||
CampaignID gid.GID `json:"-"`
|
||||
// SourceID is the live access source this snapshot points at, or nil once
|
||||
// that source has been deleted. The source field is resolved lazily from it.
|
||||
SourceID *gid.GID `json:"-"`
|
||||
Name string `json:"name"`
|
||||
FetchStatus coredata.AccessReviewCampaignSourceFetchStatus `json:"fetchStatus"`
|
||||
FetchedAccountsCount int `json:"fetchedAccountsCount"`
|
||||
@@ -33,3 +35,15 @@ type AccessReviewCampaignScopeSource struct {
|
||||
FetchStartedAt *time.Time `json:"fetchStartedAt,omitempty"`
|
||||
FetchCompletedAt *time.Time `json:"fetchCompletedAt,omitempty"`
|
||||
}
|
||||
|
||||
type AccessReviewCampaignSourceFetchAttempt struct {
|
||||
ID gid.GID `json:"id"`
|
||||
AttemptNumber int `json:"attemptNumber"`
|
||||
Status coredata.AccessReviewCampaignSourceFetchStatus `json:"status"`
|
||||
FetchedAccountsCount int `json:"fetchedAccountsCount"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
StartedAt *time.Time `json:"startedAt,omitempty"`
|
||||
CompletedAt *time.Time `json:"completedAt,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
func newTestCampaignSource(tenantID gid.TenantID, sourceID *gid.GID, name string) *coredata.AccessReviewCampaignSource {
|
||||
return &coredata.AccessReviewCampaignSource{
|
||||
ID: gid.New(tenantID, coredata.AccessReviewCampaignSourceEntityType),
|
||||
TenantID: tenantID,
|
||||
AccessReviewCampaignID: gid.New(tenantID, coredata.AccessReviewCampaignEntityType),
|
||||
AccessReviewSourceID: sourceID,
|
||||
Name: name,
|
||||
Category: coredata.AccessReviewSourceCategorySaaS,
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAccessReviewCampaignSource_DefaultFetchState(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tenantID := gid.NewTenantID()
|
||||
sourceID := gid.New(tenantID, coredata.AccessReviewSourceEntityType)
|
||||
campaignSource := newTestCampaignSource(tenantID, &sourceID, "Google Workspace")
|
||||
|
||||
got := NewAccessReviewCampaignSource(campaignSource, nil)
|
||||
if got.FetchStatus != coredata.AccessReviewCampaignSourceFetchStatusQueued {
|
||||
t.Fatalf("fetch status = %q, want QUEUED", got.FetchStatus)
|
||||
}
|
||||
|
||||
if got.FetchedAccountsCount != 0 {
|
||||
t.Fatalf("fetched accounts count = %d, want 0", got.FetchedAccountsCount)
|
||||
}
|
||||
|
||||
if got.AttemptCount != 0 {
|
||||
t.Fatalf("attempt count = %d, want 0", got.AttemptCount)
|
||||
}
|
||||
|
||||
if got.SourceID == nil || *got.SourceID != sourceID {
|
||||
t.Fatalf("source id = %v, want %v", got.SourceID, sourceID)
|
||||
}
|
||||
|
||||
if got.Name != "Google Workspace" {
|
||||
t.Fatalf("name = %q, want snapshot name", got.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAccessReviewCampaignSource_UsesLatestAttempt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now()
|
||||
errMsg := "We couldn't fetch accounts from this source."
|
||||
tenantID := gid.NewTenantID()
|
||||
sourceID := gid.New(tenantID, coredata.AccessReviewSourceEntityType)
|
||||
campaignSource := newTestCampaignSource(tenantID, &sourceID, "Linear")
|
||||
attempt := &coredata.AccessReviewCampaignSourceFetchAttempt{
|
||||
Status: coredata.AccessReviewCampaignSourceFetchStatusFailed,
|
||||
FetchedAccountsCount: 42,
|
||||
AttemptNumber: 3,
|
||||
Error: &errMsg,
|
||||
StartedAt: &now,
|
||||
CompletedAt: &now,
|
||||
}
|
||||
|
||||
got := NewAccessReviewCampaignSource(campaignSource, attempt)
|
||||
if got.FetchStatus != coredata.AccessReviewCampaignSourceFetchStatusFailed {
|
||||
t.Fatalf("fetch status = %q, want FAILED", got.FetchStatus)
|
||||
}
|
||||
|
||||
if got.FetchedAccountsCount != 42 {
|
||||
t.Fatalf("fetched accounts count = %d, want 42", got.FetchedAccountsCount)
|
||||
}
|
||||
|
||||
if got.AttemptCount != 3 {
|
||||
t.Fatalf("attempt count = %d, want 3", got.AttemptCount)
|
||||
}
|
||||
|
||||
if got.LastError == nil || *got.LastError != errMsg {
|
||||
t.Fatalf("last error = %v, want %q", got.LastError, errMsg)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewAccessReviewCampaignSource_DeletedSource verifies a snapshot whose
|
||||
// live source has been deleted still renders (with a nil source link) so the
|
||||
// historical review data remains visible.
|
||||
func TestNewAccessReviewCampaignSource_DeletedSource(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tenantID := gid.NewTenantID()
|
||||
campaignSource := newTestCampaignSource(tenantID, nil, "Deleted Source")
|
||||
|
||||
got := NewAccessReviewCampaignSource(campaignSource, nil)
|
||||
if got.SourceID != nil {
|
||||
t.Fatalf("source id = %v, want nil for deleted source", got.SourceID)
|
||||
}
|
||||
|
||||
if got.Name != "Deleted Source" {
|
||||
t.Fatalf("name = %q, want snapshot name preserved", got.Name)
|
||||
}
|
||||
}
|
||||
@@ -3382,7 +3382,7 @@ func (r *Resolver) ListFindingAuditsTool(ctx context.Context, req *mcp.CallToolR
|
||||
// ListAccessReviewCampaignsTool handles the listAccessReviewCampaigns tool
|
||||
// List access review campaigns for an organization
|
||||
func (r *Resolver) ListAccessReviewCampaignsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListAccessReviewCampaignsInput) (*mcp.CallToolResult, types.ListAccessReviewCampaignsOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.OrganizationID, probo.ActionAccessReviewCampaignList)
|
||||
scope, err := r.Authorize(ctx, input.OrganizationID, accessreview.ActionCampaignList)
|
||||
if err != nil {
|
||||
return nil, types.ListAccessReviewCampaignsOutput{}, err
|
||||
}
|
||||
@@ -3401,7 +3401,7 @@ func (r *Resolver) ListAccessReviewCampaignsTool(ctx context.Context, req *mcp.C
|
||||
|
||||
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
|
||||
|
||||
p, err := r.accessReview.Campaigns(scope).ListForOrganizationID(ctx, input.OrganizationID, cursor)
|
||||
p, err := r.accessReview.ListCampaignsForOrganizationID(ctx, scope, input.OrganizationID, cursor)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list access review campaigns: %w", err))
|
||||
}
|
||||
@@ -3412,18 +3412,18 @@ func (r *Resolver) ListAccessReviewCampaignsTool(ctx context.Context, req *mcp.C
|
||||
// ListAccessEntriesTool handles the listAccessEntries tool
|
||||
// List access entries for a campaign with optional filters
|
||||
func (r *Resolver) ListAccessEntriesTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListAccessEntriesInput) (*mcp.CallToolResult, types.ListAccessEntriesOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.CampaignID, probo.ActionAccessEntryList)
|
||||
scope, err := r.Authorize(ctx, input.CampaignID, accessreview.ActionEntryList)
|
||||
if err != nil {
|
||||
return nil, types.ListAccessEntriesOutput{}, err
|
||||
}
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.AccessEntryOrderField]{
|
||||
Field: coredata.AccessEntryOrderFieldCreatedAt,
|
||||
pageOrderBy := page.OrderBy[coredata.AccessReviewEntryOrderField]{
|
||||
Field: coredata.AccessReviewEntryOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
|
||||
if input.OrderBy != nil {
|
||||
pageOrderBy = page.OrderBy[coredata.AccessEntryOrderField]{
|
||||
pageOrderBy = page.OrderBy[coredata.AccessReviewEntryOrderField]{
|
||||
Field: input.OrderBy.Field,
|
||||
Direction: input.OrderBy.Direction,
|
||||
}
|
||||
@@ -3431,9 +3431,9 @@ func (r *Resolver) ListAccessEntriesTool(ctx context.Context, req *mcp.CallToolR
|
||||
|
||||
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
|
||||
|
||||
var filter *coredata.AccessEntryFilter
|
||||
var filter *coredata.AccessReviewEntryFilter
|
||||
if input.Filter != nil {
|
||||
filter = &coredata.AccessEntryFilter{
|
||||
filter = &coredata.AccessReviewEntryFilter{
|
||||
Decision: input.Filter.Decision,
|
||||
Flag: input.Filter.Flag,
|
||||
IncrementalTag: input.Filter.IncrementalTag,
|
||||
@@ -3444,15 +3444,16 @@ func (r *Resolver) ListAccessEntriesTool(ctx context.Context, req *mcp.CallToolR
|
||||
}
|
||||
}
|
||||
|
||||
var p *page.Page[*coredata.AccessEntry, coredata.AccessEntryOrderField]
|
||||
var p *page.Page[*coredata.AccessReviewEntry, coredata.AccessReviewEntryOrderField]
|
||||
|
||||
if input.AccessSourceID != nil {
|
||||
if input.AccessReviewCampaignSourceID != nil {
|
||||
var err error
|
||||
|
||||
p, err = r.accessReview.Entries(scope).ListForCampaignIDAndSourceID(
|
||||
p, err = r.accessReview.ListEntriesForCampaignIDAndSourceID(
|
||||
ctx,
|
||||
scope,
|
||||
input.CampaignID,
|
||||
*input.AccessSourceID,
|
||||
*input.AccessReviewCampaignSourceID,
|
||||
cursor,
|
||||
filter,
|
||||
)
|
||||
@@ -3462,7 +3463,7 @@ func (r *Resolver) ListAccessEntriesTool(ctx context.Context, req *mcp.CallToolR
|
||||
} else {
|
||||
var err error
|
||||
|
||||
p, err = r.accessReview.Entries(scope).ListForCampaignID(ctx, input.CampaignID, cursor, filter)
|
||||
p, err = r.accessReview.ListEntriesForCampaignID(ctx, scope, input.CampaignID, cursor, filter)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list access entries: %w", err))
|
||||
}
|
||||
@@ -3471,44 +3472,44 @@ func (r *Resolver) ListAccessEntriesTool(ctx context.Context, req *mcp.CallToolR
|
||||
return nil, types.NewListAccessEntriesOutput(p), nil
|
||||
}
|
||||
|
||||
// GetAccessReviewCampaignStatisticsTool handles the getAccessReviewCampaignStatistics tool
|
||||
// GetAccessReviewStatisticsTool handles the getAccessReviewCampaignStatistics tool
|
||||
// Get statistics for an access review campaign
|
||||
func (r *Resolver) GetAccessReviewCampaignStatisticsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetAccessReviewCampaignStatisticsInput) (*mcp.CallToolResult, types.GetAccessReviewCampaignStatisticsOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.CampaignID, probo.ActionAccessReviewCampaignGet)
|
||||
func (r *Resolver) GetAccessReviewStatisticsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetAccessReviewStatisticsInput) (*mcp.CallToolResult, types.GetAccessReviewStatisticsOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.CampaignID, accessreview.ActionCampaignGet)
|
||||
if err != nil {
|
||||
return nil, types.GetAccessReviewCampaignStatisticsOutput{}, err
|
||||
return nil, types.GetAccessReviewStatisticsOutput{}, err
|
||||
}
|
||||
|
||||
stats, err := r.accessReview.Entries(scope).Statistics(ctx, input.CampaignID)
|
||||
stats, err := r.accessReview.CampaignStatistics(ctx, scope, input.CampaignID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get campaign statistics: %w", err))
|
||||
}
|
||||
|
||||
return nil, types.GetAccessReviewCampaignStatisticsOutput{
|
||||
Statistics: types.NewAccessEntryStatistics(stats),
|
||||
return nil, types.GetAccessReviewStatisticsOutput{
|
||||
Statistics: types.NewAccessReviewStatistics(stats),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RecordAccessEntryDecisionTool handles the recordAccessEntryDecision tool
|
||||
// RecordAccessReviewEntryDecisionTool handles the recordAccessEntryDecision tool
|
||||
// Record a decision on an access entry
|
||||
func (r *Resolver) RecordAccessEntryDecisionTool(ctx context.Context, req *mcp.CallToolRequest, input *types.RecordAccessEntryDecisionInput) (*mcp.CallToolResult, types.RecordAccessEntryDecisionOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.AccessEntryID, probo.ActionAccessEntryDecide)
|
||||
func (r *Resolver) RecordAccessReviewEntryDecisionTool(ctx context.Context, req *mcp.CallToolRequest, input *types.RecordAccessReviewEntryDecisionInput) (*mcp.CallToolResult, types.RecordAccessReviewEntryDecisionOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.AccessReviewEntryID, accessreview.ActionEntryDecide)
|
||||
if err != nil {
|
||||
return nil, types.RecordAccessEntryDecisionOutput{}, err
|
||||
return nil, types.RecordAccessReviewEntryDecisionOutput{}, err
|
||||
}
|
||||
|
||||
identity := authn.IdentityFromContext(ctx)
|
||||
if identity == nil {
|
||||
return nil, types.RecordAccessEntryDecisionOutput{}, fmt.Errorf("no identity in context")
|
||||
return nil, types.RecordAccessReviewEntryDecisionOutput{}, fmt.Errorf("no identity in context")
|
||||
}
|
||||
|
||||
decisionReq := accessreview.RecordAccessEntryDecisionRequest{
|
||||
EntryID: input.AccessEntryID,
|
||||
decisionReq := accessreview.RecordAccessReviewEntryDecisionRequest{
|
||||
EntryID: input.AccessReviewEntryID,
|
||||
Decision: input.Decision,
|
||||
DecisionNote: input.DecisionNote,
|
||||
}
|
||||
|
||||
organizationID, err := r.accessReview.ResolveEntryOrganizationID(ctx, input.AccessEntryID)
|
||||
organizationID, err := r.accessReview.ResolveEntryOrganizationID(ctx, input.AccessReviewEntryID)
|
||||
if err == nil {
|
||||
profile, err := r.iamSvc.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, organizationID)
|
||||
if err == nil {
|
||||
@@ -3516,53 +3517,53 @@ func (r *Resolver) RecordAccessEntryDecisionTool(ctx context.Context, req *mcp.C
|
||||
}
|
||||
}
|
||||
|
||||
entry, err := r.accessReview.Entries(scope).RecordDecision(ctx, decisionReq)
|
||||
entry, err := r.accessReview.RecordDecision(ctx, scope, decisionReq)
|
||||
if err != nil {
|
||||
return nil, types.RecordAccessEntryDecisionOutput{}, fmt.Errorf("cannot record decision: %w", err)
|
||||
return nil, types.RecordAccessReviewEntryDecisionOutput{}, fmt.Errorf("cannot record decision: %w", err)
|
||||
}
|
||||
|
||||
return nil, types.RecordAccessEntryDecisionOutput{
|
||||
AccessEntry: types.NewAccessEntry(entry),
|
||||
return nil, types.RecordAccessReviewEntryDecisionOutput{
|
||||
AccessEntry: types.NewAccessReviewEntry(entry),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RecordAccessEntryDecisionsTool handles the recordAccessEntryDecisions tool
|
||||
// RecordAccessReviewEntryDecisionsTool handles the recordAccessEntryDecisions tool
|
||||
// Record decisions on multiple access entries in a single batch
|
||||
func (r *Resolver) RecordAccessEntryDecisionsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.RecordAccessEntryDecisionsInput) (*mcp.CallToolResult, types.RecordAccessEntryDecisionsOutput, error) {
|
||||
func (r *Resolver) RecordAccessReviewEntryDecisionsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.RecordAccessReviewEntryDecisionsInput) (*mcp.CallToolResult, types.RecordAccessReviewEntryDecisionsOutput, error) {
|
||||
if len(input.Decisions) == 0 {
|
||||
return nil, types.RecordAccessEntryDecisionsOutput{
|
||||
AccessEntries: []*types.AccessEntry{},
|
||||
return nil, types.RecordAccessReviewEntryDecisionsOutput{
|
||||
AccessReviewEntries: []*types.AccessReviewEntry{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
const maxBatchSize = 100
|
||||
if len(input.Decisions) > maxBatchSize {
|
||||
return nil, types.RecordAccessEntryDecisionsOutput{}, fmt.Errorf("cannot record decisions: batch size %d exceeds maximum of %d", len(input.Decisions), maxBatchSize)
|
||||
return nil, types.RecordAccessReviewEntryDecisionsOutput{}, fmt.Errorf("cannot record decisions: batch size %d exceeds maximum of %d", len(input.Decisions), maxBatchSize)
|
||||
}
|
||||
|
||||
// Authorize each entry individually to prevent cross-org bypass.
|
||||
for _, d := range input.Decisions {
|
||||
if _, err := r.Authorize(ctx, d.AccessEntryID, probo.ActionAccessEntryDecide); err != nil {
|
||||
return nil, types.RecordAccessEntryDecisionsOutput{}, err
|
||||
if _, err := r.Authorize(ctx, d.AccessReviewEntryID, accessreview.ActionEntryDecide); err != nil {
|
||||
return nil, types.RecordAccessReviewEntryDecisionsOutput{}, err
|
||||
}
|
||||
}
|
||||
|
||||
scope := coredata.NewScopeFromObjectID(input.Decisions[0].AccessEntryID)
|
||||
scope := coredata.NewScopeFromObjectID(input.Decisions[0].AccessReviewEntryID)
|
||||
|
||||
identity := authn.IdentityFromContext(ctx)
|
||||
if identity == nil {
|
||||
return nil, types.RecordAccessEntryDecisionsOutput{}, fmt.Errorf("no identity in context")
|
||||
return nil, types.RecordAccessReviewEntryDecisionsOutput{}, fmt.Errorf("no identity in context")
|
||||
}
|
||||
|
||||
// Cache profile lookups per organization so we resolve the correct
|
||||
// decidedByID for each entry even when a batch spans multiple orgs.
|
||||
profileCache := make(map[gid.GID]*gid.GID)
|
||||
|
||||
decisions := make([]accessreview.RecordAccessEntryDecisionRequest, len(input.Decisions))
|
||||
decisions := make([]accessreview.RecordAccessReviewEntryDecisionRequest, len(input.Decisions))
|
||||
for i, d := range input.Decisions {
|
||||
var decidedByID *gid.GID
|
||||
|
||||
organizationID, err := r.accessReview.ResolveEntryOrganizationID(ctx, d.AccessEntryID)
|
||||
organizationID, err := r.accessReview.ResolveEntryOrganizationID(ctx, d.AccessReviewEntryID)
|
||||
if err == nil {
|
||||
if cached, ok := profileCache[organizationID]; ok {
|
||||
decidedByID = cached
|
||||
@@ -3576,38 +3577,38 @@ func (r *Resolver) RecordAccessEntryDecisionsTool(ctx context.Context, req *mcp.
|
||||
}
|
||||
}
|
||||
|
||||
decisions[i] = accessreview.RecordAccessEntryDecisionRequest{
|
||||
EntryID: d.AccessEntryID,
|
||||
decisions[i] = accessreview.RecordAccessReviewEntryDecisionRequest{
|
||||
EntryID: d.AccessReviewEntryID,
|
||||
Decision: d.Decision,
|
||||
DecisionNote: d.DecisionNote,
|
||||
DecidedByID: decidedByID,
|
||||
}
|
||||
}
|
||||
|
||||
entries, err := r.accessReview.Entries(scope).RecordDecisions(ctx, decisions)
|
||||
entries, err := r.accessReview.RecordDecisions(ctx, scope, decisions)
|
||||
if err != nil {
|
||||
return nil, types.RecordAccessEntryDecisionsOutput{}, fmt.Errorf("cannot record decisions: %w", err)
|
||||
return nil, types.RecordAccessReviewEntryDecisionsOutput{}, fmt.Errorf("cannot record decisions: %w", err)
|
||||
}
|
||||
|
||||
accessEntries := make([]*types.AccessEntry, len(entries))
|
||||
accessEntries := make([]*types.AccessReviewEntry, len(entries))
|
||||
for i, e := range entries {
|
||||
accessEntries[i] = types.NewAccessEntry(e)
|
||||
accessEntries[i] = types.NewAccessReviewEntry(e)
|
||||
}
|
||||
|
||||
return nil, types.RecordAccessEntryDecisionsOutput{
|
||||
AccessEntries: accessEntries,
|
||||
return nil, types.RecordAccessReviewEntryDecisionsOutput{
|
||||
AccessReviewEntries: accessEntries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CloseAccessReviewCampaignTool handles the closeAccessReviewCampaign tool
|
||||
// Close an access review campaign
|
||||
func (r *Resolver) CloseAccessReviewCampaignTool(ctx context.Context, req *mcp.CallToolRequest, input *types.CloseAccessReviewCampaignInput) (*mcp.CallToolResult, types.CloseAccessReviewCampaignOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.CampaignID, probo.ActionAccessReviewCampaignClose)
|
||||
scope, err := r.Authorize(ctx, input.CampaignID, accessreview.ActionCampaignClose)
|
||||
if err != nil {
|
||||
return nil, types.CloseAccessReviewCampaignOutput{}, err
|
||||
}
|
||||
|
||||
campaign, err := r.accessReview.Campaigns(scope).Close(ctx, input.CampaignID)
|
||||
campaign, err := r.accessReview.CloseCampaign(ctx, scope, input.CampaignID)
|
||||
if err != nil {
|
||||
return nil, types.CloseAccessReviewCampaignOutput{}, fmt.Errorf("cannot close campaign: %w", err)
|
||||
}
|
||||
@@ -3617,21 +3618,21 @@ func (r *Resolver) CloseAccessReviewCampaignTool(ctx context.Context, req *mcp.C
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListAccessSourcesTool handles the listAccessSources tool
|
||||
// ListAccessReviewSourcesTool handles the listAccessSources tool
|
||||
// List access sources for an organization
|
||||
func (r *Resolver) ListAccessSourcesTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListAccessSourcesInput) (*mcp.CallToolResult, types.ListAccessSourcesOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.OrganizationID, probo.ActionAccessSourceList)
|
||||
func (r *Resolver) ListAccessReviewSourcesTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListAccessReviewSourcesInput) (*mcp.CallToolResult, types.ListAccessReviewSourcesOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.OrganizationID, accessreview.ActionSourceList)
|
||||
if err != nil {
|
||||
return nil, types.ListAccessSourcesOutput{}, err
|
||||
return nil, types.ListAccessReviewSourcesOutput{}, err
|
||||
}
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.AccessSourceOrderField]{
|
||||
Field: coredata.AccessSourceOrderFieldCreatedAt,
|
||||
pageOrderBy := page.OrderBy[coredata.AccessReviewSourceOrderField]{
|
||||
Field: coredata.AccessReviewSourceOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
|
||||
if input.OrderBy != nil {
|
||||
pageOrderBy = page.OrderBy[coredata.AccessSourceOrderField]{
|
||||
pageOrderBy = page.OrderBy[coredata.AccessReviewSourceOrderField]{
|
||||
Field: input.OrderBy.Field,
|
||||
Direction: input.OrderBy.Direction,
|
||||
}
|
||||
@@ -3639,56 +3640,56 @@ func (r *Resolver) ListAccessSourcesTool(ctx context.Context, req *mcp.CallToolR
|
||||
|
||||
cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
|
||||
|
||||
p, err := r.accessReview.Sources(scope).ListForOrganizationID(ctx, input.OrganizationID, cursor)
|
||||
p, err := r.accessReview.ListSourcesForOrganizationID(ctx, scope, input.OrganizationID, cursor)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list access sources: %w", err))
|
||||
}
|
||||
|
||||
return nil, types.NewListAccessSourcesOutput(p), nil
|
||||
return nil, types.NewListAccessReviewSourcesOutput(p), nil
|
||||
}
|
||||
|
||||
// CreateAccessSourceTool handles the createAccessSource tool
|
||||
// CreateAccessReviewSourceTool handles the createAccessSource tool
|
||||
// Create a new access source for an organization
|
||||
func (r *Resolver) CreateAccessSourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.CreateAccessSourceInput) (*mcp.CallToolResult, types.CreateAccessSourceOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.OrganizationID, probo.ActionAccessSourceCreate)
|
||||
func (r *Resolver) CreateAccessReviewSourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.CreateAccessReviewSourceInput) (*mcp.CallToolResult, types.CreateAccessReviewSourceOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.OrganizationID, accessreview.ActionSourceCreate)
|
||||
if err != nil {
|
||||
return nil, types.CreateAccessSourceOutput{}, err
|
||||
return nil, types.CreateAccessReviewSourceOutput{}, err
|
||||
}
|
||||
|
||||
source, err := r.accessReview.Sources(scope).Create(ctx, accessreview.CreateAccessSourceRequest{
|
||||
source, err := r.accessReview.CreateSource(ctx, scope, accessreview.CreateAccessReviewSourceRequest{
|
||||
OrganizationID: input.OrganizationID,
|
||||
ConnectorID: input.ConnectorID,
|
||||
Name: input.Name,
|
||||
Category: coredata.AccessSourceCategorySaaS,
|
||||
Category: coredata.AccessReviewSourceCategorySaaS,
|
||||
CsvData: input.CsvData,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, types.CreateAccessSourceOutput{}, fmt.Errorf("cannot create access source: %w", err)
|
||||
return nil, types.CreateAccessReviewSourceOutput{}, fmt.Errorf("cannot create access source: %w", err)
|
||||
}
|
||||
|
||||
return nil, types.CreateAccessSourceOutput{
|
||||
AccessSource: types.NewAccessSource(source),
|
||||
return nil, types.CreateAccessReviewSourceOutput{
|
||||
AccessReviewSource: types.NewAccessReviewSource(source),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateAccessSourceTool handles the updateAccessSource tool
|
||||
// UpdateAccessReviewSourceTool handles the updateAccessSource tool
|
||||
// Update an existing access source
|
||||
func (r *Resolver) UpdateAccessSourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateAccessSourceInput) (*mcp.CallToolResult, types.UpdateAccessSourceOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.AccessSourceID, probo.ActionAccessSourceUpdate)
|
||||
func (r *Resolver) UpdateAccessReviewSourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateAccessReviewSourceInput) (*mcp.CallToolResult, types.UpdateAccessReviewSourceOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.AccessReviewSourceID, accessreview.ActionSourceUpdate)
|
||||
if err != nil {
|
||||
return nil, types.UpdateAccessSourceOutput{}, err
|
||||
return nil, types.UpdateAccessReviewSourceOutput{}, err
|
||||
}
|
||||
|
||||
updateReq := accessreview.UpdateAccessSourceRequest{
|
||||
AccessSourceID: input.AccessSourceID,
|
||||
Name: input.Name,
|
||||
updateReq := accessreview.UpdateAccessReviewSourceRequest{
|
||||
AccessReviewSourceID: input.AccessReviewSourceID,
|
||||
Name: input.Name,
|
||||
}
|
||||
|
||||
if rawConnectorID := UnwrapOmittable(input.ConnectorID); rawConnectorID != nil {
|
||||
if *rawConnectorID != nil {
|
||||
id, err := gid.ParseGID(**rawConnectorID)
|
||||
if err != nil {
|
||||
return nil, types.UpdateAccessSourceOutput{}, fmt.Errorf("cannot parse connector_id: %w", err)
|
||||
return nil, types.UpdateAccessReviewSourceOutput{}, fmt.Errorf("cannot parse connector_id: %w", err)
|
||||
}
|
||||
|
||||
idPtr := &id
|
||||
@@ -3704,37 +3705,37 @@ func (r *Resolver) UpdateAccessSourceTool(ctx context.Context, req *mcp.CallTool
|
||||
updateReq.CsvData = rawCsvData
|
||||
}
|
||||
|
||||
source, err := r.accessReview.Sources(scope).Update(ctx, updateReq)
|
||||
source, err := r.accessReview.UpdateSource(ctx, scope, updateReq)
|
||||
if err != nil {
|
||||
return nil, types.UpdateAccessSourceOutput{}, fmt.Errorf("cannot update access source: %w", err)
|
||||
return nil, types.UpdateAccessReviewSourceOutput{}, fmt.Errorf("cannot update access source: %w", err)
|
||||
}
|
||||
|
||||
return nil, types.UpdateAccessSourceOutput{
|
||||
AccessSource: types.NewAccessSource(source),
|
||||
return nil, types.UpdateAccessReviewSourceOutput{
|
||||
AccessReviewSource: types.NewAccessReviewSource(source),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteAccessSourceTool handles the deleteAccessSource tool
|
||||
// DeleteAccessReviewSourceTool handles the deleteAccessSource tool
|
||||
// Delete an access source
|
||||
func (r *Resolver) DeleteAccessSourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteAccessSourceInput) (*mcp.CallToolResult, types.DeleteAccessSourceOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.AccessSourceID, probo.ActionAccessSourceDelete)
|
||||
func (r *Resolver) DeleteAccessReviewSourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteAccessReviewSourceInput) (*mcp.CallToolResult, types.DeleteAccessReviewSourceOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.AccessReviewSourceID, accessreview.ActionSourceDelete)
|
||||
if err != nil {
|
||||
return nil, types.DeleteAccessSourceOutput{}, err
|
||||
return nil, types.DeleteAccessReviewSourceOutput{}, err
|
||||
}
|
||||
|
||||
if err := r.accessReview.Sources(scope).Delete(ctx, input.AccessSourceID); err != nil {
|
||||
return nil, types.DeleteAccessSourceOutput{}, fmt.Errorf("cannot delete access source: %w", err)
|
||||
if err := r.accessReview.DeleteSource(ctx, scope, input.AccessReviewSourceID); err != nil {
|
||||
return nil, types.DeleteAccessReviewSourceOutput{}, fmt.Errorf("cannot delete access source: %w", err)
|
||||
}
|
||||
|
||||
return nil, types.DeleteAccessSourceOutput{
|
||||
DeletedAccessSourceID: input.AccessSourceID,
|
||||
return nil, types.DeleteAccessReviewSourceOutput{
|
||||
DeletedAccessReviewSourceID: input.AccessReviewSourceID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateAccessReviewCampaignTool handles the createAccessReviewCampaign tool
|
||||
// Create a new access review campaign for an organization
|
||||
func (r *Resolver) CreateAccessReviewCampaignTool(ctx context.Context, req *mcp.CallToolRequest, input *types.CreateAccessReviewCampaignInput) (*mcp.CallToolResult, types.CreateAccessReviewCampaignOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.OrganizationID, probo.ActionAccessReviewCampaignCreate)
|
||||
scope, err := r.Authorize(ctx, input.OrganizationID, accessreview.ActionCampaignCreate)
|
||||
if err != nil {
|
||||
return nil, types.CreateAccessReviewCampaignOutput{}, err
|
||||
}
|
||||
@@ -3744,12 +3745,12 @@ func (r *Resolver) CreateAccessReviewCampaignTool(ctx context.Context, req *mcp.
|
||||
description = *input.Description
|
||||
}
|
||||
|
||||
campaign, err := r.accessReview.Campaigns(scope).Create(ctx, accessreview.CreateAccessReviewCampaignRequest{
|
||||
OrganizationID: input.OrganizationID,
|
||||
Name: input.Name,
|
||||
Description: description,
|
||||
FrameworkControls: input.FrameworkControls,
|
||||
AccessSourceIDs: input.AccessSourceIds,
|
||||
campaign, err := r.accessReview.CreateCampaign(ctx, scope, accessreview.CreateAccessReviewCampaignRequest{
|
||||
OrganizationID: input.OrganizationID,
|
||||
Name: input.Name,
|
||||
Description: description,
|
||||
FrameworkControls: input.FrameworkControls,
|
||||
AccessReviewSourceIDs: input.AccessReviewSourceIds,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, types.CreateAccessReviewCampaignOutput{}, fmt.Errorf("cannot create access review campaign: %w", err)
|
||||
@@ -3763,7 +3764,7 @@ func (r *Resolver) CreateAccessReviewCampaignTool(ctx context.Context, req *mcp.
|
||||
// UpdateAccessReviewCampaignTool handles the updateAccessReviewCampaign tool
|
||||
// Update an existing access review campaign
|
||||
func (r *Resolver) UpdateAccessReviewCampaignTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateAccessReviewCampaignInput) (*mcp.CallToolResult, types.UpdateAccessReviewCampaignOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.CampaignID, probo.ActionAccessReviewCampaignUpdate)
|
||||
scope, err := r.Authorize(ctx, input.CampaignID, accessreview.ActionCampaignUpdate)
|
||||
if err != nil {
|
||||
return nil, types.UpdateAccessReviewCampaignOutput{}, err
|
||||
}
|
||||
@@ -3790,7 +3791,7 @@ func (r *Resolver) UpdateAccessReviewCampaignTool(ctx context.Context, req *mcp.
|
||||
}
|
||||
}
|
||||
|
||||
campaign, err := r.accessReview.Campaigns(scope).Update(ctx, updateReq)
|
||||
campaign, err := r.accessReview.UpdateCampaign(ctx, scope, updateReq)
|
||||
if err != nil {
|
||||
return nil, types.UpdateAccessReviewCampaignOutput{}, fmt.Errorf("cannot update access review campaign: %w", err)
|
||||
}
|
||||
@@ -3803,12 +3804,12 @@ func (r *Resolver) UpdateAccessReviewCampaignTool(ctx context.Context, req *mcp.
|
||||
// DeleteAccessReviewCampaignTool handles the deleteAccessReviewCampaign tool
|
||||
// Delete an access review campaign
|
||||
func (r *Resolver) DeleteAccessReviewCampaignTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteAccessReviewCampaignInput) (*mcp.CallToolResult, types.DeleteAccessReviewCampaignOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.CampaignID, probo.ActionAccessReviewCampaignDelete)
|
||||
scope, err := r.Authorize(ctx, input.CampaignID, accessreview.ActionCampaignDelete)
|
||||
if err != nil {
|
||||
return nil, types.DeleteAccessReviewCampaignOutput{}, err
|
||||
}
|
||||
|
||||
if err := r.accessReview.Campaigns(scope).Delete(ctx, input.CampaignID); err != nil {
|
||||
if err := r.accessReview.DeleteCampaign(ctx, scope, input.CampaignID); err != nil {
|
||||
return nil, types.DeleteAccessReviewCampaignOutput{}, fmt.Errorf("cannot delete access review campaign: %w", err)
|
||||
}
|
||||
|
||||
@@ -3820,12 +3821,12 @@ func (r *Resolver) DeleteAccessReviewCampaignTool(ctx context.Context, req *mcp.
|
||||
// StartAccessReviewCampaignTool handles the startAccessReviewCampaign tool
|
||||
// Start an access review campaign
|
||||
func (r *Resolver) StartAccessReviewCampaignTool(ctx context.Context, req *mcp.CallToolRequest, input *types.StartAccessReviewCampaignInput) (*mcp.CallToolResult, types.StartAccessReviewCampaignOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.CampaignID, probo.ActionAccessReviewCampaignStart)
|
||||
scope, err := r.Authorize(ctx, input.CampaignID, accessreview.ActionCampaignStart)
|
||||
if err != nil {
|
||||
return nil, types.StartAccessReviewCampaignOutput{}, err
|
||||
}
|
||||
|
||||
campaign, err := r.accessReview.Campaigns(scope).Start(ctx, input.CampaignID)
|
||||
campaign, err := r.accessReview.StartCampaign(ctx, scope, input.CampaignID)
|
||||
if err != nil {
|
||||
return nil, types.StartAccessReviewCampaignOutput{}, fmt.Errorf("cannot start access review campaign: %w", err)
|
||||
}
|
||||
@@ -3838,12 +3839,12 @@ func (r *Resolver) StartAccessReviewCampaignTool(ctx context.Context, req *mcp.C
|
||||
// CancelAccessReviewCampaignTool handles the cancelAccessReviewCampaign tool
|
||||
// Cancel an in-progress access review campaign
|
||||
func (r *Resolver) CancelAccessReviewCampaignTool(ctx context.Context, req *mcp.CallToolRequest, input *types.CancelAccessReviewCampaignInput) (*mcp.CallToolResult, types.CancelAccessReviewCampaignOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.CampaignID, probo.ActionAccessReviewCampaignCancel)
|
||||
scope, err := r.Authorize(ctx, input.CampaignID, accessreview.ActionCampaignCancel)
|
||||
if err != nil {
|
||||
return nil, types.CancelAccessReviewCampaignOutput{}, err
|
||||
}
|
||||
|
||||
campaign, err := r.accessReview.Campaigns(scope).Cancel(ctx, input.CampaignID)
|
||||
campaign, err := r.accessReview.CancelCampaign(ctx, scope, input.CampaignID)
|
||||
if err != nil {
|
||||
return nil, types.CancelAccessReviewCampaignOutput{}, fmt.Errorf("cannot cancel access review campaign: %w", err)
|
||||
}
|
||||
@@ -3853,67 +3854,67 @@ func (r *Resolver) CancelAccessReviewCampaignTool(ctx context.Context, req *mcp.
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AddAccessReviewCampaignScopeSourceTool handles the addAccessReviewCampaignScopeSource tool
|
||||
// AddAccessReviewCampaignSourceTool handles the addAccessReviewCampaignScopeSource tool
|
||||
// Add an access source to an access review campaign's scope
|
||||
func (r *Resolver) AddAccessReviewCampaignScopeSourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddAccessReviewCampaignScopeSourceInput) (*mcp.CallToolResult, types.AddAccessReviewCampaignScopeSourceOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.CampaignID, probo.ActionAccessReviewCampaignAddScopeSource)
|
||||
func (r *Resolver) AddAccessReviewCampaignSourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddAccessReviewCampaignSourceInput) (*mcp.CallToolResult, types.AddAccessReviewCampaignSourceOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.CampaignID, accessreview.ActionCampaignAddSource)
|
||||
if err != nil {
|
||||
return nil, types.AddAccessReviewCampaignScopeSourceOutput{}, err
|
||||
return nil, types.AddAccessReviewCampaignSourceOutput{}, err
|
||||
}
|
||||
|
||||
campaign, err := r.accessReview.Campaigns(scope).AddScopeSource(ctx, accessreview.AddCampaignScopeSourceRequest{
|
||||
CampaignID: input.CampaignID,
|
||||
AccessSourceID: input.AccessSourceID,
|
||||
campaign, err := r.accessReview.AddCampaignSource(ctx, scope, accessreview.AddCampaignSourceRequest{
|
||||
CampaignID: input.CampaignID,
|
||||
AccessReviewSourceID: input.AccessReviewSourceID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, types.AddAccessReviewCampaignScopeSourceOutput{}, fmt.Errorf("cannot add scope source to access review campaign: %w", err)
|
||||
return nil, types.AddAccessReviewCampaignSourceOutput{}, fmt.Errorf("cannot add scope source to access review campaign: %w", err)
|
||||
}
|
||||
|
||||
return nil, types.AddAccessReviewCampaignScopeSourceOutput{
|
||||
return nil, types.AddAccessReviewCampaignSourceOutput{
|
||||
Campaign: types.NewAccessReviewCampaign(campaign),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RemoveAccessReviewCampaignScopeSourceTool handles the removeAccessReviewCampaignScopeSource tool
|
||||
// RemoveAccessReviewCampaignSourceTool handles the removeAccessReviewCampaignScopeSource tool
|
||||
// Remove an access source from an access review campaign's scope
|
||||
func (r *Resolver) RemoveAccessReviewCampaignScopeSourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.RemoveAccessReviewCampaignScopeSourceInput) (*mcp.CallToolResult, types.RemoveAccessReviewCampaignScopeSourceOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.CampaignID, probo.ActionAccessReviewCampaignRemoveScopeSource)
|
||||
func (r *Resolver) RemoveAccessReviewCampaignSourceTool(ctx context.Context, req *mcp.CallToolRequest, input *types.RemoveAccessReviewCampaignSourceInput) (*mcp.CallToolResult, types.RemoveAccessReviewCampaignSourceOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.CampaignID, accessreview.ActionCampaignRemoveSource)
|
||||
if err != nil {
|
||||
return nil, types.RemoveAccessReviewCampaignScopeSourceOutput{}, err
|
||||
return nil, types.RemoveAccessReviewCampaignSourceOutput{}, err
|
||||
}
|
||||
|
||||
campaign, err := r.accessReview.Campaigns(scope).RemoveScopeSource(ctx, accessreview.RemoveCampaignScopeSourceRequest{
|
||||
CampaignID: input.CampaignID,
|
||||
AccessSourceID: input.AccessSourceID,
|
||||
campaign, err := r.accessReview.RemoveCampaignSource(ctx, scope, accessreview.RemoveCampaignSourceRequest{
|
||||
CampaignID: input.CampaignID,
|
||||
AccessReviewSourceID: input.AccessReviewSourceID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, types.RemoveAccessReviewCampaignScopeSourceOutput{}, fmt.Errorf("cannot remove scope source from access review campaign: %w", err)
|
||||
return nil, types.RemoveAccessReviewCampaignSourceOutput{}, fmt.Errorf("cannot remove scope source from access review campaign: %w", err)
|
||||
}
|
||||
|
||||
return nil, types.RemoveAccessReviewCampaignScopeSourceOutput{
|
||||
return nil, types.RemoveAccessReviewCampaignSourceOutput{
|
||||
Campaign: types.NewAccessReviewCampaign(campaign),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FlagAccessEntryTool handles the flagAccessEntry tool
|
||||
// FlagAccessReviewEntryTool handles the flagAccessEntry tool
|
||||
// Flag an access entry during review
|
||||
func (r *Resolver) FlagAccessEntryTool(ctx context.Context, req *mcp.CallToolRequest, input *types.FlagAccessEntryInput) (*mcp.CallToolResult, types.FlagAccessEntryOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.AccessEntryID, probo.ActionAccessEntryFlag)
|
||||
func (r *Resolver) FlagAccessReviewEntryTool(ctx context.Context, req *mcp.CallToolRequest, input *types.FlagAccessReviewEntryInput) (*mcp.CallToolResult, types.FlagAccessReviewEntryOutput, error) {
|
||||
scope, err := r.Authorize(ctx, input.AccessReviewEntryID, accessreview.ActionEntryFlag)
|
||||
if err != nil {
|
||||
return nil, types.FlagAccessEntryOutput{}, err
|
||||
return nil, types.FlagAccessReviewEntryOutput{}, err
|
||||
}
|
||||
|
||||
entry, err := r.accessReview.Entries(scope).FlagEntry(ctx, accessreview.FlagAccessEntryRequest{
|
||||
EntryID: input.AccessEntryID,
|
||||
entry, err := r.accessReview.FlagEntry(ctx, scope, accessreview.FlagAccessReviewEntryRequest{
|
||||
EntryID: input.AccessReviewEntryID,
|
||||
Flags: input.Flags,
|
||||
FlagReasons: input.FlagReasons,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, types.FlagAccessEntryOutput{}, fmt.Errorf("cannot flag access entry: %w", err)
|
||||
return nil, types.FlagAccessReviewEntryOutput{}, fmt.Errorf("cannot flag access entry: %w", err)
|
||||
}
|
||||
|
||||
return nil, types.FlagAccessEntryOutput{
|
||||
AccessEntry: types.NewAccessEntry(entry),
|
||||
return nil, types.FlagAccessReviewEntryOutput{
|
||||
AccessEntry: types.NewAccessReviewEntry(entry),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -7524,7 +7524,7 @@ components:
|
||||
- CANCELLED
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessReviewCampaignStatus
|
||||
|
||||
AccessEntryDecision:
|
||||
AccessReviewEntryDecision:
|
||||
type: string
|
||||
enum:
|
||||
- PENDING
|
||||
@@ -7532,9 +7532,9 @@ components:
|
||||
- REVOKE
|
||||
- DEFER
|
||||
- ESCALATE
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessEntryDecision
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessReviewEntryDecision
|
||||
|
||||
AccessEntryFlag:
|
||||
AccessReviewEntryFlag:
|
||||
type: string
|
||||
enum:
|
||||
- NONE
|
||||
@@ -7552,17 +7552,17 @@ components:
|
||||
- NO_BUSINESS_JUSTIFICATION
|
||||
- OUT_OF_DEPARTMENT
|
||||
- SHARED_ACCOUNT
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessEntryFlag
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessReviewEntryFlag
|
||||
|
||||
AccessEntryIncrementalTag:
|
||||
AccessReviewEntryIncrementalTag:
|
||||
type: string
|
||||
enum:
|
||||
- NEW
|
||||
- REMOVED
|
||||
- UNCHANGED
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessEntryIncrementalTag
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessReviewEntryIncrementalTag
|
||||
|
||||
AccessEntryAuthMethod:
|
||||
AccessReviewEntryAuthMethod:
|
||||
type: string
|
||||
enum:
|
||||
- SSO
|
||||
@@ -7570,14 +7570,14 @@ components:
|
||||
- API_KEY
|
||||
- SERVICE_ACCOUNT
|
||||
- UNKNOWN
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessEntryAuthMethod
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessReviewEntryAuthMethod
|
||||
|
||||
AccessEntryAccountType:
|
||||
AccessReviewEntryAccountType:
|
||||
type: string
|
||||
enum:
|
||||
- USER
|
||||
- SERVICE_ACCOUNT
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessEntryAccountType
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessReviewEntryAccountType
|
||||
|
||||
MFAStatus:
|
||||
type: string
|
||||
@@ -7606,20 +7606,20 @@ components:
|
||||
$ref: "#/components/schemas/OrderDirection"
|
||||
description: Order direction
|
||||
|
||||
AccessEntryOrderField:
|
||||
AccessReviewEntryOrderField:
|
||||
type: string
|
||||
enum:
|
||||
- CREATED_AT
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessEntryOrderField
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessReviewEntryOrderField
|
||||
|
||||
AccessEntryOrderBy:
|
||||
AccessReviewEntryOrderBy:
|
||||
type: object
|
||||
required:
|
||||
- field
|
||||
- direction
|
||||
properties:
|
||||
field:
|
||||
$ref: "#/components/schemas/AccessEntryOrderField"
|
||||
$ref: "#/components/schemas/AccessReviewEntryOrderField"
|
||||
description: Order field
|
||||
direction:
|
||||
$ref: "#/components/schemas/OrderDirection"
|
||||
@@ -7676,12 +7676,12 @@ components:
|
||||
format: date-time
|
||||
description: Update timestamp
|
||||
|
||||
AccessEntry:
|
||||
AccessReviewEntry:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
- campaign_id
|
||||
- access_source_id
|
||||
- access_review_campaign_source_id
|
||||
- email
|
||||
- full_name
|
||||
- role
|
||||
@@ -7703,9 +7703,9 @@ components:
|
||||
campaign_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Campaign ID
|
||||
access_source_id:
|
||||
access_review_campaign_source_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Access source ID
|
||||
description: Per-campaign source snapshot ID
|
||||
email:
|
||||
type: string
|
||||
description: User email
|
||||
@@ -7730,10 +7730,10 @@ components:
|
||||
$ref: "#/components/schemas/MFAStatus"
|
||||
description: MFA status
|
||||
auth_method:
|
||||
$ref: "#/components/schemas/AccessEntryAuthMethod"
|
||||
$ref: "#/components/schemas/AccessReviewEntryAuthMethod"
|
||||
description: Authentication method
|
||||
account_type:
|
||||
$ref: "#/components/schemas/AccessEntryAccountType"
|
||||
$ref: "#/components/schemas/AccessReviewEntryAccountType"
|
||||
description: Account type (user or service account)
|
||||
last_login:
|
||||
type:
|
||||
@@ -7751,12 +7751,12 @@ components:
|
||||
type: string
|
||||
description: External ID in the source system
|
||||
incremental_tag:
|
||||
$ref: "#/components/schemas/AccessEntryIncrementalTag"
|
||||
$ref: "#/components/schemas/AccessReviewEntryIncrementalTag"
|
||||
description: Change tag compared to previous campaign
|
||||
flags:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/AccessEntryFlag"
|
||||
$ref: "#/components/schemas/AccessReviewEntryFlag"
|
||||
description: Risk flags
|
||||
flag_reasons:
|
||||
type: array
|
||||
@@ -7764,7 +7764,7 @@ components:
|
||||
type: string
|
||||
description: Reasons for the flags
|
||||
decision:
|
||||
$ref: "#/components/schemas/AccessEntryDecision"
|
||||
$ref: "#/components/schemas/AccessReviewEntryDecision"
|
||||
description: Review decision
|
||||
decision_note:
|
||||
type:
|
||||
@@ -7791,7 +7791,7 @@ components:
|
||||
format: date-time
|
||||
description: Update timestamp
|
||||
|
||||
AccessEntryStatistics:
|
||||
AccessReviewStatistics:
|
||||
type: object
|
||||
required:
|
||||
- total_count
|
||||
@@ -7857,11 +7857,11 @@ components:
|
||||
campaign_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Campaign ID
|
||||
access_source_id:
|
||||
access_review_campaign_source_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Filter by access source ID
|
||||
description: Filter by per-campaign source snapshot ID
|
||||
order_by:
|
||||
$ref: "#/components/schemas/AccessEntryOrderBy"
|
||||
$ref: "#/components/schemas/AccessReviewEntryOrderBy"
|
||||
description: Order by
|
||||
size:
|
||||
type: integer
|
||||
@@ -7873,13 +7873,13 @@ components:
|
||||
type: object
|
||||
properties:
|
||||
decision:
|
||||
$ref: "#/components/schemas/AccessEntryDecision"
|
||||
$ref: "#/components/schemas/AccessReviewEntryDecision"
|
||||
description: Filter by decision status
|
||||
flag:
|
||||
$ref: "#/components/schemas/AccessEntryFlag"
|
||||
$ref: "#/components/schemas/AccessReviewEntryFlag"
|
||||
description: Filter by flag
|
||||
incremental_tag:
|
||||
$ref: "#/components/schemas/AccessEntryIncrementalTag"
|
||||
$ref: "#/components/schemas/AccessReviewEntryIncrementalTag"
|
||||
description: Filter by incremental tag
|
||||
is_admin:
|
||||
type: boolean
|
||||
@@ -7888,10 +7888,10 @@ components:
|
||||
type: boolean
|
||||
description: Filter by active status at the source
|
||||
auth_method:
|
||||
$ref: "#/components/schemas/AccessEntryAuthMethod"
|
||||
$ref: "#/components/schemas/AccessReviewEntryAuthMethod"
|
||||
description: Filter by auth method
|
||||
account_type:
|
||||
$ref: "#/components/schemas/AccessEntryAccountType"
|
||||
$ref: "#/components/schemas/AccessReviewEntryAccountType"
|
||||
description: Filter by account type
|
||||
|
||||
ListAccessEntriesOutput:
|
||||
@@ -7905,9 +7905,9 @@ components:
|
||||
entries:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/AccessEntry"
|
||||
$ref: "#/components/schemas/AccessReviewEntry"
|
||||
|
||||
GetAccessReviewCampaignStatisticsInput:
|
||||
GetAccessReviewStatisticsInput:
|
||||
type: object
|
||||
required:
|
||||
- campaign_id
|
||||
@@ -7916,39 +7916,39 @@ components:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Campaign ID
|
||||
|
||||
GetAccessReviewCampaignStatisticsOutput:
|
||||
GetAccessReviewStatisticsOutput:
|
||||
type: object
|
||||
required:
|
||||
- statistics
|
||||
properties:
|
||||
statistics:
|
||||
$ref: "#/components/schemas/AccessEntryStatistics"
|
||||
$ref: "#/components/schemas/AccessReviewStatistics"
|
||||
|
||||
RecordAccessEntryDecisionMCPInput:
|
||||
RecordAccessReviewEntryDecisionMCPInput:
|
||||
type: object
|
||||
required:
|
||||
- access_entry_id
|
||||
- access_review_entry_id
|
||||
- decision
|
||||
properties:
|
||||
access_entry_id:
|
||||
access_review_entry_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Access entry ID
|
||||
decision:
|
||||
$ref: "#/components/schemas/AccessEntryDecision"
|
||||
$ref: "#/components/schemas/AccessReviewEntryDecision"
|
||||
description: Decision (APPROVED, REVOKE, DEFER, ESCALATE)
|
||||
decision_note:
|
||||
type: string
|
||||
description: Decision justification (required for non-APPROVED decisions)
|
||||
|
||||
RecordAccessEntryDecisionMCPOutput:
|
||||
RecordAccessReviewEntryDecisionMCPOutput:
|
||||
type: object
|
||||
required:
|
||||
- access_entry
|
||||
properties:
|
||||
access_entry:
|
||||
$ref: "#/components/schemas/AccessEntry"
|
||||
$ref: "#/components/schemas/AccessReviewEntry"
|
||||
|
||||
RecordAccessEntryDecisionsMCPInput:
|
||||
RecordAccessReviewEntryDecisionsMCPInput:
|
||||
type: object
|
||||
required:
|
||||
- decisions
|
||||
@@ -7958,42 +7958,42 @@ components:
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- access_entry_id
|
||||
- access_review_entry_id
|
||||
- decision
|
||||
properties:
|
||||
access_entry_id:
|
||||
access_review_entry_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Access entry ID
|
||||
decision:
|
||||
$ref: "#/components/schemas/AccessEntryDecision"
|
||||
$ref: "#/components/schemas/AccessReviewEntryDecision"
|
||||
description: Decision (APPROVED, REVOKE, DEFER, ESCALATE)
|
||||
decision_note:
|
||||
type: string
|
||||
description: Decision justification (required for non-APPROVED decisions)
|
||||
|
||||
RecordAccessEntryDecisionsMCPOutput:
|
||||
RecordAccessReviewEntryDecisionsMCPOutput:
|
||||
type: object
|
||||
required:
|
||||
- access_entries
|
||||
- access_review_entries
|
||||
properties:
|
||||
access_entries:
|
||||
access_review_entries:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/AccessEntry"
|
||||
$ref: "#/components/schemas/AccessReviewEntry"
|
||||
|
||||
FlagAccessEntryMCPInput:
|
||||
FlagAccessReviewEntryMCPInput:
|
||||
type: object
|
||||
required:
|
||||
- access_entry_id
|
||||
- access_review_entry_id
|
||||
- flags
|
||||
properties:
|
||||
access_entry_id:
|
||||
access_review_entry_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Access entry ID
|
||||
flags:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/AccessEntryFlag"
|
||||
$ref: "#/components/schemas/AccessReviewEntryFlag"
|
||||
description: Flags to set (ORPHANED, INACTIVE, EXCESSIVE, ROLE_MISMATCH, NEW, DORMANT, TERMINATED_USER, CONTRACTOR_EXPIRED, SOD_CONFLICT, PRIVILEGED_ACCESS, ROLE_CREEP, NO_BUSINESS_JUSTIFICATION, OUT_OF_DEPARTMENT, SHARED_ACCOUNT)
|
||||
flag_reasons:
|
||||
type: array
|
||||
@@ -8001,13 +8001,13 @@ components:
|
||||
type: string
|
||||
description: Reasons for flagging
|
||||
|
||||
FlagAccessEntryMCPOutput:
|
||||
FlagAccessReviewEntryMCPOutput:
|
||||
type: object
|
||||
required:
|
||||
- access_entry
|
||||
properties:
|
||||
access_entry:
|
||||
$ref: "#/components/schemas/AccessEntry"
|
||||
$ref: "#/components/schemas/AccessReviewEntry"
|
||||
|
||||
CloseAccessReviewCampaignMCPInput:
|
||||
type: object
|
||||
@@ -8026,35 +8026,35 @@ components:
|
||||
campaign:
|
||||
$ref: "#/components/schemas/AccessReviewCampaign"
|
||||
|
||||
AccessSourceCategory:
|
||||
AccessReviewSourceCategory:
|
||||
type: string
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessSourceCategory
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessReviewSourceCategory
|
||||
enum:
|
||||
- SAAS
|
||||
- CLOUD_INFRA
|
||||
- SOURCE_CODE
|
||||
- OTHER
|
||||
|
||||
AccessSourceOrderField:
|
||||
AccessReviewSourceOrderField:
|
||||
type: string
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessSourceOrderField
|
||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.AccessReviewSourceOrderField
|
||||
enum:
|
||||
- CREATED_AT
|
||||
|
||||
AccessSourceOrderBy:
|
||||
AccessReviewSourceOrderBy:
|
||||
type: object
|
||||
required:
|
||||
- field
|
||||
- direction
|
||||
properties:
|
||||
field:
|
||||
$ref: "#/components/schemas/AccessSourceOrderField"
|
||||
$ref: "#/components/schemas/AccessReviewSourceOrderField"
|
||||
description: Order field
|
||||
direction:
|
||||
$ref: "#/components/schemas/OrderDirection"
|
||||
description: Order direction
|
||||
|
||||
AccessSource:
|
||||
AccessReviewSource:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
@@ -8091,7 +8091,7 @@ components:
|
||||
format: date-time
|
||||
description: Update timestamp
|
||||
|
||||
ListAccessSourcesInput:
|
||||
ListAccessReviewSourcesInput:
|
||||
type: object
|
||||
required:
|
||||
- organization_id
|
||||
@@ -8100,7 +8100,7 @@ components:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Organization ID
|
||||
order_by:
|
||||
$ref: "#/components/schemas/AccessSourceOrderBy"
|
||||
$ref: "#/components/schemas/AccessReviewSourceOrderBy"
|
||||
description: Order by
|
||||
size:
|
||||
type: integer
|
||||
@@ -8109,20 +8109,20 @@ components:
|
||||
$ref: "#/components/schemas/CursorKey"
|
||||
description: Page cursor
|
||||
|
||||
ListAccessSourcesOutput:
|
||||
ListAccessReviewSourcesOutput:
|
||||
type: object
|
||||
required:
|
||||
- access_sources
|
||||
- access_review_sources
|
||||
properties:
|
||||
next_cursor:
|
||||
$ref: "#/components/schemas/CursorKey"
|
||||
description: Next cursor
|
||||
access_sources:
|
||||
access_review_sources:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/AccessSource"
|
||||
$ref: "#/components/schemas/AccessReviewSource"
|
||||
|
||||
CreateAccessSourceMCPInput:
|
||||
CreateAccessReviewSourceMCPInput:
|
||||
type: object
|
||||
required:
|
||||
- organization_id
|
||||
@@ -8141,20 +8141,20 @@ components:
|
||||
type: string
|
||||
description: CSV data for manual sources (optional)
|
||||
|
||||
CreateAccessSourceMCPOutput:
|
||||
CreateAccessReviewSourceMCPOutput:
|
||||
type: object
|
||||
required:
|
||||
- access_source
|
||||
properties:
|
||||
access_source:
|
||||
$ref: "#/components/schemas/AccessSource"
|
||||
access_review_source:
|
||||
$ref: "#/components/schemas/AccessReviewSource"
|
||||
|
||||
UpdateAccessSourceMCPInput:
|
||||
UpdateAccessReviewSourceMCPInput:
|
||||
type: object
|
||||
required:
|
||||
- access_source_id
|
||||
- access_review_source_id
|
||||
properties:
|
||||
access_source_id:
|
||||
access_review_source_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Access source ID
|
||||
name:
|
||||
@@ -8173,29 +8173,29 @@ components:
|
||||
go.probo.inc/mcpgen/omittable: true
|
||||
description: CSV data for manual sources (set to null to remove)
|
||||
|
||||
UpdateAccessSourceMCPOutput:
|
||||
UpdateAccessReviewSourceMCPOutput:
|
||||
type: object
|
||||
required:
|
||||
- access_source
|
||||
properties:
|
||||
access_source:
|
||||
$ref: "#/components/schemas/AccessSource"
|
||||
access_review_source:
|
||||
$ref: "#/components/schemas/AccessReviewSource"
|
||||
|
||||
DeleteAccessSourceMCPInput:
|
||||
DeleteAccessReviewSourceMCPInput:
|
||||
type: object
|
||||
required:
|
||||
- access_source_id
|
||||
- access_review_source_id
|
||||
properties:
|
||||
access_source_id:
|
||||
access_review_source_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Access source ID
|
||||
|
||||
DeleteAccessSourceMCPOutput:
|
||||
DeleteAccessReviewSourceMCPOutput:
|
||||
type: object
|
||||
required:
|
||||
- deleted_access_source_id
|
||||
- deleted_access_review_source_id
|
||||
properties:
|
||||
deleted_access_source_id:
|
||||
deleted_access_review_source_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Deleted access source ID
|
||||
|
||||
@@ -8219,7 +8219,7 @@ components:
|
||||
items:
|
||||
type: string
|
||||
description: Framework control references
|
||||
access_source_ids:
|
||||
access_review_source_ids:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/GID"
|
||||
@@ -8316,20 +8316,20 @@ components:
|
||||
campaign:
|
||||
$ref: "#/components/schemas/AccessReviewCampaign"
|
||||
|
||||
AddAccessReviewCampaignScopeSourceMCPInput:
|
||||
AddAccessReviewCampaignSourceMCPInput:
|
||||
type: object
|
||||
required:
|
||||
- campaign_id
|
||||
- access_source_id
|
||||
- access_review_source_id
|
||||
properties:
|
||||
campaign_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Campaign ID
|
||||
access_source_id:
|
||||
access_review_source_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Access source ID to add to scope
|
||||
|
||||
AddAccessReviewCampaignScopeSourceMCPOutput:
|
||||
AddAccessReviewCampaignSourceMCPOutput:
|
||||
type: object
|
||||
required:
|
||||
- campaign
|
||||
@@ -8337,20 +8337,20 @@ components:
|
||||
campaign:
|
||||
$ref: "#/components/schemas/AccessReviewCampaign"
|
||||
|
||||
RemoveAccessReviewCampaignScopeSourceMCPInput:
|
||||
RemoveAccessReviewCampaignSourceMCPInput:
|
||||
type: object
|
||||
required:
|
||||
- campaign_id
|
||||
- access_source_id
|
||||
- access_review_source_id
|
||||
properties:
|
||||
campaign_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Campaign ID
|
||||
access_source_id:
|
||||
access_review_source_id:
|
||||
$ref: "#/components/schemas/GID"
|
||||
description: Access source ID to remove from scope
|
||||
|
||||
RemoveAccessReviewCampaignScopeSourceMCPOutput:
|
||||
RemoveAccessReviewCampaignSourceMCPOutput:
|
||||
type: object
|
||||
required:
|
||||
- campaign
|
||||
@@ -13310,39 +13310,39 @@ tools:
|
||||
$ref: "#/components/schemas/ListAccessEntriesInput"
|
||||
outputSchema:
|
||||
$ref: "#/components/schemas/ListAccessEntriesOutput"
|
||||
- name: getAccessReviewCampaignStatistics
|
||||
- name: getAccessReviewStatistics
|
||||
description: Get statistics for an access review campaign including counts by decision, flag, and incremental tag
|
||||
hints:
|
||||
readonly: true
|
||||
idempotent: true
|
||||
inputSchema:
|
||||
$ref: "#/components/schemas/GetAccessReviewCampaignStatisticsInput"
|
||||
$ref: "#/components/schemas/GetAccessReviewStatisticsInput"
|
||||
outputSchema:
|
||||
$ref: "#/components/schemas/GetAccessReviewCampaignStatisticsOutput"
|
||||
- name: recordAccessEntryDecision
|
||||
$ref: "#/components/schemas/GetAccessReviewStatisticsOutput"
|
||||
- name: recordAccessReviewEntryDecision
|
||||
description: Record a decision on an access entry (APPROVED, REVOKE, DEFER, or ESCALATE). Non-APPROVED decisions require a decision_note.
|
||||
hints:
|
||||
readonly: false
|
||||
inputSchema:
|
||||
$ref: "#/components/schemas/RecordAccessEntryDecisionMCPInput"
|
||||
$ref: "#/components/schemas/RecordAccessReviewEntryDecisionMCPInput"
|
||||
outputSchema:
|
||||
$ref: "#/components/schemas/RecordAccessEntryDecisionMCPOutput"
|
||||
- name: recordAccessEntryDecisions
|
||||
$ref: "#/components/schemas/RecordAccessReviewEntryDecisionMCPOutput"
|
||||
- name: recordAccessReviewEntryDecisions
|
||||
description: Record decisions on multiple access entries in a single batch. Non-APPROVED decisions require a decision_note.
|
||||
hints:
|
||||
readonly: false
|
||||
inputSchema:
|
||||
$ref: "#/components/schemas/RecordAccessEntryDecisionsMCPInput"
|
||||
$ref: "#/components/schemas/RecordAccessReviewEntryDecisionsMCPInput"
|
||||
outputSchema:
|
||||
$ref: "#/components/schemas/RecordAccessEntryDecisionsMCPOutput"
|
||||
- name: flagAccessEntry
|
||||
$ref: "#/components/schemas/RecordAccessReviewEntryDecisionsMCPOutput"
|
||||
- name: flagAccessReviewEntry
|
||||
description: Flag an access entry with one or more flags during review (ORPHANED, INACTIVE, EXCESSIVE, ROLE_MISMATCH, NEW, etc.). Optionally provide reasons.
|
||||
hints:
|
||||
readonly: false
|
||||
inputSchema:
|
||||
$ref: "#/components/schemas/FlagAccessEntryMCPInput"
|
||||
$ref: "#/components/schemas/FlagAccessReviewEntryMCPInput"
|
||||
outputSchema:
|
||||
$ref: "#/components/schemas/FlagAccessEntryMCPOutput"
|
||||
$ref: "#/components/schemas/FlagAccessReviewEntryMCPOutput"
|
||||
- name: closeAccessReviewCampaign
|
||||
description: Close an access review campaign. All entries must have been decided (no PENDING entries).
|
||||
hints:
|
||||
@@ -13351,40 +13351,40 @@ tools:
|
||||
$ref: "#/components/schemas/CloseAccessReviewCampaignMCPInput"
|
||||
outputSchema:
|
||||
$ref: "#/components/schemas/CloseAccessReviewCampaignMCPOutput"
|
||||
- name: listAccessSources
|
||||
- name: listAccessReviewSources
|
||||
description: List access sources for an organization
|
||||
hints:
|
||||
readonly: true
|
||||
idempotent: true
|
||||
inputSchema:
|
||||
$ref: "#/components/schemas/ListAccessSourcesInput"
|
||||
$ref: "#/components/schemas/ListAccessReviewSourcesInput"
|
||||
outputSchema:
|
||||
$ref: "#/components/schemas/ListAccessSourcesOutput"
|
||||
- name: createAccessSource
|
||||
$ref: "#/components/schemas/ListAccessReviewSourcesOutput"
|
||||
- name: createAccessReviewSource
|
||||
description: Create a new access source for an organization
|
||||
hints:
|
||||
readonly: false
|
||||
inputSchema:
|
||||
$ref: "#/components/schemas/CreateAccessSourceMCPInput"
|
||||
$ref: "#/components/schemas/CreateAccessReviewSourceMCPInput"
|
||||
outputSchema:
|
||||
$ref: "#/components/schemas/CreateAccessSourceMCPOutput"
|
||||
- name: updateAccessSource
|
||||
$ref: "#/components/schemas/CreateAccessReviewSourceMCPOutput"
|
||||
- name: updateAccessReviewSource
|
||||
description: Update an existing access source
|
||||
hints:
|
||||
readonly: false
|
||||
inputSchema:
|
||||
$ref: "#/components/schemas/UpdateAccessSourceMCPInput"
|
||||
$ref: "#/components/schemas/UpdateAccessReviewSourceMCPInput"
|
||||
outputSchema:
|
||||
$ref: "#/components/schemas/UpdateAccessSourceMCPOutput"
|
||||
- name: deleteAccessSource
|
||||
$ref: "#/components/schemas/UpdateAccessReviewSourceMCPOutput"
|
||||
- name: deleteAccessReviewSource
|
||||
description: Delete an access source
|
||||
hints:
|
||||
readonly: false
|
||||
destructive: true
|
||||
inputSchema:
|
||||
$ref: "#/components/schemas/DeleteAccessSourceMCPInput"
|
||||
$ref: "#/components/schemas/DeleteAccessReviewSourceMCPInput"
|
||||
outputSchema:
|
||||
$ref: "#/components/schemas/DeleteAccessSourceMCPOutput"
|
||||
$ref: "#/components/schemas/DeleteAccessReviewSourceMCPOutput"
|
||||
- name: createAccessReviewCampaign
|
||||
description: Create a new access review campaign for an organization
|
||||
hints:
|
||||
@@ -13426,22 +13426,22 @@ tools:
|
||||
$ref: "#/components/schemas/CancelAccessReviewCampaignMCPInput"
|
||||
outputSchema:
|
||||
$ref: "#/components/schemas/CancelAccessReviewCampaignMCPOutput"
|
||||
- name: addAccessReviewCampaignScopeSource
|
||||
- name: addAccessReviewCampaignSource
|
||||
description: Add an access source to an access review campaign's scope
|
||||
hints:
|
||||
readonly: false
|
||||
inputSchema:
|
||||
$ref: "#/components/schemas/AddAccessReviewCampaignScopeSourceMCPInput"
|
||||
$ref: "#/components/schemas/AddAccessReviewCampaignSourceMCPInput"
|
||||
outputSchema:
|
||||
$ref: "#/components/schemas/AddAccessReviewCampaignScopeSourceMCPOutput"
|
||||
- name: removeAccessReviewCampaignScopeSource
|
||||
$ref: "#/components/schemas/AddAccessReviewCampaignSourceMCPOutput"
|
||||
- name: removeAccessReviewCampaignSource
|
||||
description: Remove an access source from an access review campaign's scope
|
||||
hints:
|
||||
readonly: false
|
||||
inputSchema:
|
||||
$ref: "#/components/schemas/RemoveAccessReviewCampaignScopeSourceMCPInput"
|
||||
$ref: "#/components/schemas/RemoveAccessReviewCampaignSourceMCPInput"
|
||||
outputSchema:
|
||||
$ref: "#/components/schemas/RemoveAccessReviewCampaignScopeSourceMCPOutput"
|
||||
$ref: "#/components/schemas/RemoveAccessReviewCampaignSourceMCPOutput"
|
||||
- name: getOrganizationContext
|
||||
description: Get the organization context containing structured sections about the company
|
||||
hints:
|
||||
|
||||
@@ -19,8 +19,8 @@ import (
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
func NewAccessSource(s *coredata.AccessSource) *AccessSource {
|
||||
return &AccessSource{
|
||||
func NewAccessReviewSource(s *coredata.AccessReviewSource) *AccessReviewSource {
|
||||
return &AccessReviewSource{
|
||||
ID: s.ID,
|
||||
OrganizationID: s.OrganizationID,
|
||||
ConnectorID: s.ConnectorID,
|
||||
@@ -31,12 +31,12 @@ func NewAccessSource(s *coredata.AccessSource) *AccessSource {
|
||||
}
|
||||
}
|
||||
|
||||
func NewListAccessSourcesOutput(
|
||||
p *page.Page[*coredata.AccessSource, coredata.AccessSourceOrderField],
|
||||
) ListAccessSourcesOutput {
|
||||
sources := make([]*AccessSource, 0, len(p.Data))
|
||||
func NewListAccessReviewSourcesOutput(
|
||||
p *page.Page[*coredata.AccessReviewSource, coredata.AccessReviewSourceOrderField],
|
||||
) ListAccessReviewSourcesOutput {
|
||||
sources := make([]*AccessReviewSource, 0, len(p.Data))
|
||||
for _, s := range p.Data {
|
||||
sources = append(sources, NewAccessSource(s))
|
||||
sources = append(sources, NewAccessReviewSource(s))
|
||||
}
|
||||
|
||||
var nextCursor *page.CursorKey
|
||||
@@ -46,9 +46,9 @@ func NewListAccessSourcesOutput(
|
||||
nextCursor = &cursorKey
|
||||
}
|
||||
|
||||
return ListAccessSourcesOutput{
|
||||
NextCursor: nextCursor,
|
||||
AccessSources: sources,
|
||||
return ListAccessReviewSourcesOutput{
|
||||
NextCursor: nextCursor,
|
||||
AccessReviewSources: sources,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,32 +67,32 @@ func NewAccessReviewCampaign(c *coredata.AccessReviewCampaign) *AccessReviewCamp
|
||||
}
|
||||
}
|
||||
|
||||
func NewAccessEntry(e *coredata.AccessEntry) *AccessEntry {
|
||||
entry := &AccessEntry{
|
||||
ID: e.ID,
|
||||
CampaignID: e.AccessReviewCampaignID,
|
||||
AccessSourceID: e.AccessSourceID,
|
||||
Email: e.Email,
|
||||
FullName: e.FullName,
|
||||
Role: e.Role,
|
||||
JobTitle: e.JobTitle,
|
||||
IsAdmin: e.IsAdmin,
|
||||
Active: e.Active,
|
||||
MfaStatus: e.MFAStatus,
|
||||
AuthMethod: e.AuthMethod,
|
||||
AccountType: e.AccountType,
|
||||
LastLogin: e.LastLogin,
|
||||
AccountCreatedAt: e.AccountCreatedAt,
|
||||
ExternalID: e.ExternalID,
|
||||
IncrementalTag: e.IncrementalTag,
|
||||
Flags: e.Flags,
|
||||
FlagReasons: e.FlagReasons,
|
||||
Decision: e.Decision,
|
||||
DecisionNote: e.DecisionNote,
|
||||
DecidedBy: e.DecidedBy,
|
||||
DecidedAt: e.DecidedAt,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
func NewAccessReviewEntry(e *coredata.AccessReviewEntry) *AccessReviewEntry {
|
||||
entry := &AccessReviewEntry{
|
||||
ID: e.ID,
|
||||
CampaignID: e.AccessReviewCampaignID,
|
||||
AccessReviewCampaignSourceID: e.AccessReviewCampaignSourceID,
|
||||
Email: e.Email,
|
||||
FullName: e.FullName,
|
||||
Role: e.Role,
|
||||
JobTitle: e.JobTitle,
|
||||
IsAdmin: e.IsAdmin,
|
||||
Active: e.Active,
|
||||
MfaStatus: e.MFAStatus,
|
||||
AuthMethod: e.AuthMethod,
|
||||
AccountType: e.AccountType,
|
||||
LastLogin: e.LastLogin,
|
||||
AccountCreatedAt: e.AccountCreatedAt,
|
||||
ExternalID: e.ExternalID,
|
||||
IncrementalTag: e.IncrementalTag,
|
||||
Flags: e.Flags,
|
||||
FlagReasons: e.FlagReasons,
|
||||
Decision: e.Decision,
|
||||
DecisionNote: e.DecisionNote,
|
||||
DecidedBy: e.DecidedBy,
|
||||
DecidedAt: e.DecidedAt,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
}
|
||||
|
||||
return entry
|
||||
@@ -120,11 +120,11 @@ func NewListAccessReviewCampaignsOutput(
|
||||
}
|
||||
|
||||
func NewListAccessEntriesOutput(
|
||||
p *page.Page[*coredata.AccessEntry, coredata.AccessEntryOrderField],
|
||||
p *page.Page[*coredata.AccessReviewEntry, coredata.AccessReviewEntryOrderField],
|
||||
) ListAccessEntriesOutput {
|
||||
entries := make([]*AccessEntry, 0, len(p.Data))
|
||||
entries := make([]*AccessReviewEntry, 0, len(p.Data))
|
||||
for _, e := range p.Data {
|
||||
entries = append(entries, NewAccessEntry(e))
|
||||
entries = append(entries, NewAccessReviewEntry(e))
|
||||
}
|
||||
|
||||
var nextCursor *page.CursorKey
|
||||
@@ -140,7 +140,7 @@ func NewListAccessEntriesOutput(
|
||||
}
|
||||
}
|
||||
|
||||
func NewAccessEntryStatistics(s *coredata.AccessEntryStatistics) *AccessEntryStatistics {
|
||||
func NewAccessReviewStatistics(s *coredata.AccessReviewStatistics) *AccessReviewStatistics {
|
||||
decisionCounts := make(map[string]any, len(s.DecisionCounts))
|
||||
for k, v := range s.DecisionCounts {
|
||||
decisionCounts[string(k)] = v
|
||||
@@ -156,7 +156,7 @@ func NewAccessEntryStatistics(s *coredata.AccessEntryStatistics) *AccessEntrySta
|
||||
incrementalTagCounts[string(k)] = v
|
||||
}
|
||||
|
||||
return &AccessEntryStatistics{
|
||||
return &AccessReviewStatistics{
|
||||
TotalCount: s.TotalCount,
|
||||
DecisionCounts: decisionCounts,
|
||||
FlagCounts: flagCounts,
|
||||
|
||||
Reference in New Issue
Block a user