Add log export for audit logs and SCIM events
Route audit-log and SCIM-event exports through export_jobs with typed arguments, an iam BuildAndUploadExport/SendExportEmail implementation, and a concurrent export-job worker with stale recovery. Stream JSONL via page.WalkAll into S3, and expose the request flow on console, connect, MCP, and CLI. Co-authored-by: Bryan Frimin <bryan@getprobo.com> Signed-off-by: Sacha Al Himdani <sacha@probo.com>
This commit is contained in:
@@ -7,9 +7,12 @@ package connect_v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/server/api/authn"
|
||||
"go.probo.inc/probo/pkg/server/api/connect/v1/schema"
|
||||
"go.probo.inc/probo/pkg/server/api/connect/v1/types"
|
||||
"go.probo.inc/probo/pkg/server/gqlutils"
|
||||
@@ -41,6 +44,42 @@ func (r *auditLogEntryConnectionResolver) TotalCount(ctx context.Context, obj *t
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// RequestAuditLogExport is the resolver for the requestAuditLogExport field.
|
||||
func (r *mutationResolver) RequestAuditLogExport(ctx context.Context, input types.RequestAuditLogExportInput) (*types.RequestAuditLogExportPayload, error) {
|
||||
scope, err := r.authorize(ctx, input.OrganizationID, iam.ActionAuditLogExport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
identity := authn.IdentityFromContext(ctx)
|
||||
|
||||
logExport, err := r.iam.OrganizationService.RequestLogExport(
|
||||
ctx,
|
||||
scope,
|
||||
iam.RequestLogExportRequest{
|
||||
OrganizationID: input.OrganizationID,
|
||||
Type: coredata.ExportJobTypeAuditLog,
|
||||
FromTime: input.FromTime,
|
||||
ToTime: input.ToTime,
|
||||
RecipientEmail: identity.EmailAddress,
|
||||
RecipientName: identity.FullName,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if _, ok := errors.AsType[*iam.ErrInvalidLogExportTimeRange](err); ok {
|
||||
return nil, gqlutils.Invalid(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot request audit log export", log.Error(err))
|
||||
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.RequestAuditLogExportPayload{
|
||||
ExportJobID: logExport.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AuditLogEntry returns schema.AuditLogEntryResolver implementation.
|
||||
func (r *Resolver) AuditLogEntry() schema.AuditLogEntryResolver { return &auditLogEntryResolver{r} }
|
||||
|
||||
|
||||
@@ -70,3 +70,19 @@ type AuditLogEntryEdge {
|
||||
cursor: CursorKey!
|
||||
node: AuditLogEntry!
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
requestAuditLogExport(
|
||||
input: RequestAuditLogExportInput!
|
||||
): RequestAuditLogExportPayload @authentication(required: PRESENT) @sessionOnly
|
||||
}
|
||||
|
||||
input RequestAuditLogExportInput {
|
||||
organizationId: ID!
|
||||
fromTime: Datetime!
|
||||
toTime: Datetime!
|
||||
}
|
||||
|
||||
type RequestAuditLogExportPayload {
|
||||
exportJobId: ID!
|
||||
}
|
||||
|
||||
@@ -140,6 +140,9 @@ extend type Mutation {
|
||||
updateSCIMBridge(
|
||||
input: UpdateSCIMBridgeInput!
|
||||
): UpdateSCIMBridgePayload @authentication(required: PRESENT)
|
||||
requestSCIMEventExport(
|
||||
input: RequestSCIMEventExportInput!
|
||||
): RequestSCIMEventExportPayload @authentication(required: PRESENT) @sessionOnly
|
||||
}
|
||||
|
||||
input CreateSCIMConfigurationInput {
|
||||
@@ -181,3 +184,13 @@ type RegenerateSCIMTokenPayload {
|
||||
type UpdateSCIMBridgePayload {
|
||||
scimBridge: SCIMBridge!
|
||||
}
|
||||
|
||||
input RequestSCIMEventExportInput {
|
||||
organizationId: ID!
|
||||
fromTime: Datetime!
|
||||
toTime: Datetime!
|
||||
}
|
||||
|
||||
type RequestSCIMEventExportPayload {
|
||||
exportJobId: ID!
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/server/api/authn"
|
||||
"go.probo.inc/probo/pkg/server/api/connect/v1/schema"
|
||||
"go.probo.inc/probo/pkg/server/api/connect/v1/types"
|
||||
"go.probo.inc/probo/pkg/server/gqlutils"
|
||||
@@ -107,6 +108,42 @@ func (r *mutationResolver) UpdateSCIMBridge(ctx context.Context, input types.Upd
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RequestSCIMEventExport is the resolver for the requestSCIMEventExport field.
|
||||
func (r *mutationResolver) RequestSCIMEventExport(ctx context.Context, input types.RequestSCIMEventExportInput) (*types.RequestSCIMEventExportPayload, error) {
|
||||
scope, err := r.authorize(ctx, input.OrganizationID, iam.ActionSCIMEventExport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
identity := authn.IdentityFromContext(ctx)
|
||||
|
||||
logExport, err := r.iam.OrganizationService.RequestLogExport(
|
||||
ctx,
|
||||
scope,
|
||||
iam.RequestLogExportRequest{
|
||||
OrganizationID: input.OrganizationID,
|
||||
Type: coredata.ExportJobTypeSCIMEvent,
|
||||
FromTime: input.FromTime,
|
||||
ToTime: input.ToTime,
|
||||
RecipientEmail: identity.EmailAddress,
|
||||
RecipientName: identity.FullName,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if _, ok := errors.AsType[*iam.ErrInvalidLogExportTimeRange](err); ok {
|
||||
return nil, gqlutils.Invalid(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot request SCIM event export", log.Error(err))
|
||||
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.RequestSCIMEventExportPayload{
|
||||
ExportJobID: logExport.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ScimConfiguration is the resolver for the scimConfiguration field.
|
||||
func (r *sCIMBridgeResolver) ScimConfiguration(ctx context.Context, obj *types.SCIMBridge) (*types.SCIMConfiguration, error) {
|
||||
if _, err := r.authorize(ctx, obj.ScimConfiguration.ID, iam.ActionSCIMConfigurationGet); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user