Add role management
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -77,9 +77,9 @@ func ExtractEmailDomain(email string) (string, error) {
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
func MapSAMLRoleToSystemRole(samlRole string) *coredata.Role {
|
||||
func MapSAMLRoleToSystemRole(samlRole string) *coredata.MembershipRole {
|
||||
if samlRole != "" && isValidRole(samlRole) {
|
||||
role := coredata.Role(samlRole)
|
||||
role := coredata.MembershipRole(samlRole)
|
||||
return &role
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ func MapSAMLRoleToSystemRole(samlRole string) *coredata.Role {
|
||||
|
||||
func isValidRole(role string) bool {
|
||||
switch role {
|
||||
case "OWNER", "ADMIN", "MEMBER", "VIEWER":
|
||||
case "OWNER", "ADMIN", "VIEWER":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
||||
@@ -407,7 +407,7 @@ func (s *SAMLService) InitiateSAMLLogin(
|
||||
type SAMLUserInfo struct {
|
||||
Email string
|
||||
FullName string
|
||||
Role *coredata.Role
|
||||
Role *coredata.MembershipRole
|
||||
SAMLSubject string
|
||||
OrganizationID gid.GID
|
||||
SAMLConfigID gid.GID
|
||||
|
||||
@@ -46,6 +46,8 @@ type (
|
||||
invitationTokenValidity time.Duration
|
||||
}
|
||||
|
||||
ErrCreateOrganizationDisabled struct{}
|
||||
|
||||
TenantAuthService struct {
|
||||
pg *pg.Client
|
||||
encryptionKey cipher.EncryptionKey
|
||||
@@ -192,6 +194,10 @@ func (e ErrSAMLAutoSignupDisabled) Error() string {
|
||||
return "SAML auto-signup is disabled for this organization"
|
||||
}
|
||||
|
||||
func (e ErrCreateOrganizationDisabled) Error() string {
|
||||
return "organization creation is disabled for users without existing admin or owner membership"
|
||||
}
|
||||
|
||||
func (e ErrSAMLAuthRequired) Error() string {
|
||||
return "SAML authentication required for this organization"
|
||||
}
|
||||
@@ -232,6 +238,28 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantAuthService {
|
||||
}
|
||||
}
|
||||
|
||||
func (s Service) CanCreateOrganization(ctx context.Context, userID gid.GID) error {
|
||||
if !s.disableSignup {
|
||||
return nil
|
||||
}
|
||||
|
||||
var memberships coredata.Memberships
|
||||
err := s.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||
return memberships.LoadByUserID(ctx, conn, coredata.NewNoScope(), userID)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load user memberships: %w", err)
|
||||
}
|
||||
|
||||
for _, membership := range memberships {
|
||||
if membership.Role == coredata.MembershipRoleOwner || membership.Role == coredata.MembershipRoleAdmin {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return &ErrCreateOrganizationDisabled{}
|
||||
}
|
||||
|
||||
func (s Service) ForgetPassword(
|
||||
ctx context.Context,
|
||||
email string,
|
||||
@@ -1351,13 +1379,20 @@ func (s *Service) CreateUserAPIKey(
|
||||
|
||||
for _, membership := range memberships {
|
||||
scope := coredata.NewScope(membership.MembershipID.TenantID())
|
||||
|
||||
var m coredata.Membership
|
||||
if err := m.LoadByID(ctx, tx, scope, membership.MembershipID); err != nil {
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
|
||||
userAPIKeyMembership := &coredata.UserAPIKeyMembership{
|
||||
ID: gid.New(membership.MembershipID.TenantID(), coredata.UserAPIKeyMembershipEntityType),
|
||||
UserAPIKeyID: userAPIKey.ID,
|
||||
MembershipID: membership.MembershipID,
|
||||
Role: membership.Role,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
ID: gid.New(membership.MembershipID.TenantID(), coredata.UserAPIKeyMembershipEntityType),
|
||||
UserAPIKeyID: userAPIKey.ID,
|
||||
MembershipID: membership.MembershipID,
|
||||
Role: membership.Role,
|
||||
OrganizationID: m.OrganizationID,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := userAPIKeyMembership.Insert(ctx, tx, scope); err != nil {
|
||||
@@ -1513,13 +1548,20 @@ func (s *Service) UpdateUserAPIKeyMemberships(
|
||||
now := time.Now()
|
||||
for _, membership := range memberships {
|
||||
scope := coredata.NewScope(membership.MembershipID.TenantID())
|
||||
|
||||
var m coredata.Membership
|
||||
if err := m.LoadByID(ctx, tx, scope, membership.MembershipID); err != nil {
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
|
||||
userAPIKeyMembership := &coredata.UserAPIKeyMembership{
|
||||
ID: gid.New(membership.MembershipID.TenantID(), coredata.UserAPIKeyMembershipEntityType),
|
||||
UserAPIKeyID: userAPIKey.ID,
|
||||
MembershipID: membership.MembershipID,
|
||||
Role: membership.Role,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
ID: gid.New(membership.MembershipID.TenantID(), coredata.UserAPIKeyMembershipEntityType),
|
||||
UserAPIKeyID: userAPIKey.ID,
|
||||
MembershipID: membership.MembershipID,
|
||||
Role: membership.Role,
|
||||
OrganizationID: m.OrganizationID,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := userAPIKeyMembership.Insert(ctx, tx, scope); err != nil {
|
||||
|
||||
707
pkg/authz/permissions.go
Normal file
707
pkg/authz/permissions.go
Normal file
@@ -0,0 +1,707 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.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 authz
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
type (
|
||||
Role string
|
||||
|
||||
Action string
|
||||
)
|
||||
|
||||
const (
|
||||
RoleOwner Role = "OWNER"
|
||||
RoleAdmin Role = "ADMIN"
|
||||
RoleViewer Role = "VIEWER"
|
||||
RoleFull Role = "FULL"
|
||||
)
|
||||
|
||||
const (
|
||||
ActionGet Action = "get"
|
||||
|
||||
ActionGetAssetType Action = "getAssetType"
|
||||
ActionGetAssignedTo Action = "getAssignedTo"
|
||||
ActionGetAuthMethod Action = "getAuthMethod"
|
||||
ActionGetBusinessAssociateAgreement Action = "getBusinessAssociateAgreement"
|
||||
ActionGetBusinessOwner Action = "getBusinessOwner"
|
||||
ActionGetCustomDomain Action = "getCustomDomain"
|
||||
ActionGetDataPrivacyAgreement Action = "getDataPrivacyAgreement"
|
||||
ActionGetFile Action = "getFile"
|
||||
ActionGetFileUrl Action = "getFileUrl"
|
||||
ActionGetFramework Action = "getFramework"
|
||||
ActionGetHorizontalLogoUrl Action = "getHorizontalLogoUrl"
|
||||
ActionGetLogoUrl Action = "getLogoUrl"
|
||||
ActionGetMeasure Action = "getMeasure"
|
||||
ActionGetNdaFileUrl Action = "getNdaFileUrl"
|
||||
ActionGetOrganization Action = "getOrganization"
|
||||
ActionGetOwner Action = "getOwner"
|
||||
ActionGetSecurityOwner Action = "getSecurityOwner"
|
||||
ActionGetSnapshot Action = "getSnapshot"
|
||||
ActionGetTask Action = "getTask"
|
||||
ActionGetTrustCenter Action = "getTrustCenter"
|
||||
ActionGetTrustCenterFile Action = "getTrustCenterFile"
|
||||
ActionGetVendor Action = "getVendor"
|
||||
|
||||
ActionActiveCount Action = "activeCount"
|
||||
ActionAudit Action = "audit"
|
||||
ActionAvailableDocumentAccesses Action = "availableDocumentAccesses"
|
||||
ActionDocument Action = "document"
|
||||
ActionDocumentVersion Action = "documentVersion"
|
||||
ActionDownloadUrl Action = "downloadUrl"
|
||||
ActionMemberships Action = "memberships"
|
||||
ActionPendingRequestCount Action = "pendingRequestCount"
|
||||
ActionPeoples Action = "peoples"
|
||||
ActionReport Action = "report"
|
||||
ActionReportUrl Action = "reportUrl"
|
||||
ActionSignatures Action = "signatures"
|
||||
ActionSignedBy Action = "signedBy"
|
||||
ActionSpMetadataUrl Action = "spMetadataUrl"
|
||||
ActionTestLoginUrl Action = "testLoginUrl"
|
||||
ActionTotalCount Action = "totalCount"
|
||||
ActionTrustCenterFile Action = "trustCenterFile"
|
||||
|
||||
ActionListAccesses Action = "listAccesses"
|
||||
ActionListAssets Action = "listAssets"
|
||||
ActionListAudits Action = "listAudits"
|
||||
ActionListComplianceReports Action = "listComplianceReports"
|
||||
ActionListContacts Action = "listContacts"
|
||||
ActionListContinualImprovements Action = "listContinualImprovements"
|
||||
ActionListControls Action = "listControls"
|
||||
ActionListData Action = "listData"
|
||||
ActionListDocuments Action = "listDocuments"
|
||||
ActionListEvidences Action = "listEvidences"
|
||||
ActionListFrameworks Action = "listFrameworks"
|
||||
ActionListInvitations Action = "listInvitations"
|
||||
ActionListMeasures Action = "listMeasures"
|
||||
ActionListMeetings Action = "listMeetings"
|
||||
ActionListMembers Action = "listMembers"
|
||||
ActionListNonconformities Action = "listNonconformities"
|
||||
ActionListObligations Action = "listObligations"
|
||||
ActionListPeople Action = "listPeople"
|
||||
ActionListProcessingActivities Action = "listProcessingActivities"
|
||||
ActionListReferences Action = "listReferences"
|
||||
ActionListRiskAssessments Action = "listRiskAssessments"
|
||||
ActionListRisks Action = "listRisks"
|
||||
ActionListSAMLConfigurations Action = "listSAMLConfigurations"
|
||||
ActionListServices Action = "listServices"
|
||||
ActionListSlackConnections Action = "listSlackConnections"
|
||||
ActionListSnapshots Action = "listSnapshots"
|
||||
ActionListTasks Action = "listTasks"
|
||||
ActionListTrustCenterFiles Action = "listTrustCenterFiles"
|
||||
ActionListVendors Action = "listVendors"
|
||||
ActionListVersions Action = "listVersions"
|
||||
|
||||
ActionCreateAsset Action = "createAsset"
|
||||
ActionCreateAudit Action = "createAudit"
|
||||
ActionCreateContinualImprovement Action = "createContinualImprovement"
|
||||
ActionCreateControl Action = "createControl"
|
||||
ActionCreateControlAuditMapping Action = "createControlAuditMapping"
|
||||
ActionCreateControlDocumentMapping Action = "createControlDocumentMapping"
|
||||
ActionCreateControlMeasureMapping Action = "createControlMeasureMapping"
|
||||
ActionCreateControlSnapshotMapping Action = "createControlSnapshotMapping"
|
||||
ActionCreateCustomDomain Action = "createCustomDomain"
|
||||
ActionCreateDatum Action = "createDatum"
|
||||
ActionCreateDocument Action = "createDocument"
|
||||
ActionCreateDraftDocumentVersion Action = "createDraftDocumentVersion"
|
||||
ActionCreateFramework Action = "createFramework"
|
||||
ActionCreateMeasure Action = "createMeasure"
|
||||
ActionCreateMeeting Action = "createMeeting"
|
||||
ActionCreateNonconformity Action = "createNonconformity"
|
||||
ActionCreateObligation Action = "createObligation"
|
||||
ActionCreatePeople Action = "createPeople"
|
||||
ActionCreateProcessingActivity Action = "createProcessingActivity"
|
||||
ActionCreateRisk Action = "createRisk"
|
||||
ActionCreateRiskDocumentMapping Action = "createRiskDocumentMapping"
|
||||
ActionCreateRiskMeasureMapping Action = "createRiskMeasureMapping"
|
||||
ActionCreateRiskObligationMapping Action = "createRiskObligationMapping"
|
||||
ActionCreateSAMLConfiguration Action = "createSAMLConfiguration"
|
||||
ActionCreateSnapshot Action = "createSnapshot"
|
||||
ActionCreateTask Action = "createTask"
|
||||
ActionCreateTrustCenter Action = "createTrustCenter"
|
||||
ActionCreateTrustCenterAccess Action = "createTrustCenterAccess"
|
||||
ActionCreateTrustCenterFile Action = "createTrustCenterFile"
|
||||
ActionCreateTrustCenterReference Action = "createTrustCenterReference"
|
||||
ActionCreateVendor Action = "createVendor"
|
||||
ActionCreateVendorContact Action = "createVendorContact"
|
||||
ActionCreateVendorRiskAssessment Action = "createVendorRiskAssessment"
|
||||
ActionCreateVendorService Action = "createVendorService"
|
||||
|
||||
ActionUpdateAsset Action = "updateAsset"
|
||||
ActionUpdateAudit Action = "updateAudit"
|
||||
ActionUpdateContinualImprovement Action = "updateContinualImprovement"
|
||||
ActionUpdateControl Action = "updateControl"
|
||||
ActionUpdateDatum Action = "updateDatum"
|
||||
ActionUpdateDocument Action = "updateDocument"
|
||||
ActionUpdateDocumentVersion Action = "updateDocumentVersion"
|
||||
ActionUpdateFramework Action = "updateFramework"
|
||||
ActionUpdateMeasure Action = "updateMeasure"
|
||||
ActionUpdateMeeting Action = "updateMeeting"
|
||||
ActionUpdateMembership Action = "updateMembership"
|
||||
ActionUpdateNonconformity Action = "updateNonconformity"
|
||||
ActionUpdateObligation Action = "updateObligation"
|
||||
ActionUpdateOrganization Action = "updateOrganization"
|
||||
ActionUpdatePeople Action = "updatePeople"
|
||||
ActionUpdateProcessingActivity Action = "updateProcessingActivity"
|
||||
ActionUpdateRisk Action = "updateRisk"
|
||||
ActionUpdateSAMLConfiguration Action = "updateSAMLConfiguration"
|
||||
ActionUpdateTask Action = "updateTask"
|
||||
ActionUpdateTrustCenter Action = "updateTrustCenter"
|
||||
ActionUpdateTrustCenterAccess Action = "updateTrustCenterAccess"
|
||||
ActionUpdateTrustCenterFile Action = "updateTrustCenterFile"
|
||||
ActionUpdateTrustCenterReference Action = "updateTrustCenterReference"
|
||||
ActionUpdateVendor Action = "updateVendor"
|
||||
ActionUpdateVendorBusinessAssociateAgreement Action = "updateVendorBusinessAssociateAgreement"
|
||||
ActionUpdateVendorContact Action = "updateVendorContact"
|
||||
ActionUpdateVendorDataPrivacyAgreement Action = "updateVendorDataPrivacyAgreement"
|
||||
ActionUpdateVendorService Action = "updateVendorService"
|
||||
|
||||
ActionDeleteAsset Action = "deleteAsset"
|
||||
ActionDeleteAudit Action = "deleteAudit"
|
||||
ActionDeleteAuditReport Action = "deleteAuditReport"
|
||||
ActionDeleteContinualImprovement Action = "deleteContinualImprovement"
|
||||
ActionDeleteControl Action = "deleteControl"
|
||||
ActionDeleteControlAuditMapping Action = "deleteControlAuditMapping"
|
||||
ActionDeleteControlDocumentMapping Action = "deleteControlDocumentMapping"
|
||||
ActionDeleteControlMeasureMapping Action = "deleteControlMeasureMapping"
|
||||
ActionDeleteControlSnapshotMapping Action = "deleteControlSnapshotMapping"
|
||||
ActionDeleteCustomDomain Action = "deleteCustomDomain"
|
||||
ActionDeleteDatum Action = "deleteDatum"
|
||||
ActionDeleteDocument Action = "deleteDocument"
|
||||
ActionDeleteDraftDocumentVersion Action = "deleteDraftDocumentVersion"
|
||||
ActionDeleteEvidence Action = "deleteEvidence"
|
||||
ActionDeleteFramework Action = "deleteFramework"
|
||||
ActionDeleteInvitation Action = "deleteInvitation"
|
||||
ActionDeleteMeasure Action = "deleteMeasure"
|
||||
ActionDeleteMeeting Action = "deleteMeeting"
|
||||
ActionDeleteNonconformity Action = "deleteNonconformity"
|
||||
ActionDeleteObligation Action = "deleteObligation"
|
||||
ActionDeleteOrganization Action = "deleteOrganization"
|
||||
ActionDeleteOrganizationHorizontalLogo Action = "deleteOrganizationHorizontalLogo"
|
||||
ActionDeletePeople Action = "deletePeople"
|
||||
ActionDeleteProcessingActivity Action = "deleteProcessingActivity"
|
||||
ActionDeleteRisk Action = "deleteRisk"
|
||||
ActionDeleteRiskDocumentMapping Action = "deleteRiskDocumentMapping"
|
||||
ActionDeleteRiskMeasureMapping Action = "deleteRiskMeasureMapping"
|
||||
ActionDeleteRiskObligationMapping Action = "deleteRiskObligationMapping"
|
||||
ActionDeleteSAMLConfiguration Action = "deleteSAMLConfiguration"
|
||||
ActionDeleteSnapshot Action = "deleteSnapshot"
|
||||
ActionDeleteTask Action = "deleteTask"
|
||||
ActionDeleteTrustCenterAccess Action = "deleteTrustCenterAccess"
|
||||
ActionDeleteTrustCenterFile Action = "deleteTrustCenterFile"
|
||||
ActionDeleteTrustCenterNDA Action = "deleteTrustCenterNDA"
|
||||
ActionDeleteTrustCenterReference Action = "deleteTrustCenterReference"
|
||||
ActionDeleteVendor Action = "deleteVendor"
|
||||
ActionDeleteVendorBusinessAssociateAgreement Action = "deleteVendorBusinessAssociateAgreement"
|
||||
ActionDeleteVendorComplianceReport Action = "deleteVendorComplianceReport"
|
||||
ActionDeleteVendorContact Action = "deleteVendorContact"
|
||||
ActionDeleteVendorDataPrivacyAgreement Action = "deleteVendorDataPrivacyAgreement"
|
||||
ActionDeleteVendorService Action = "deleteVendorService"
|
||||
|
||||
ActionAcceptInvitation Action = "acceptInvitation"
|
||||
ActionAssessVendor Action = "assessVendor"
|
||||
ActionAssignTask Action = "assignTask"
|
||||
ActionBulkDeleteDocuments Action = "bulkDeleteDocuments"
|
||||
ActionBulkExportDocuments Action = "bulkExportDocuments"
|
||||
ActionBulkPublishDocumentVersions Action = "bulkPublishDocumentVersions"
|
||||
ActionBulkRequestSignatures Action = "bulkRequestSignatures"
|
||||
ActionCancelSignatureRequest Action = "cancelSignatureRequest"
|
||||
ActionConfirmEmail Action = "confirmEmail"
|
||||
ActionDisableSAML Action = "disableSAML"
|
||||
ActionEnableSAML Action = "enableSAML"
|
||||
ActionExportDocumentVersionPDF Action = "exportDocumentVersionPDF"
|
||||
ActionExportFramework Action = "exportFramework"
|
||||
ActionGenerateDocumentChangelog Action = "generateDocumentChangelog"
|
||||
ActionGenerateFrameworkStateOfApplicability Action = "generateFrameworkStateOfApplicability"
|
||||
ActionImportFramework Action = "importFramework"
|
||||
ActionImportMeasure Action = "importMeasure"
|
||||
ActionInitiateDomainVerification Action = "initiateDomainVerification"
|
||||
ActionInviteUser Action = "inviteUser"
|
||||
ActionPublishDocumentVersion Action = "publishDocumentVersion"
|
||||
ActionRemoveMember Action = "removeMember"
|
||||
ActionRequestSignature Action = "requestSignature"
|
||||
ActionSendSigningNotifications Action = "sendSigningNotifications"
|
||||
ActionUnassignTask Action = "unassignTask"
|
||||
ActionUploadAuditReport Action = "uploadAuditReport"
|
||||
ActionUploadMeasureEvidence Action = "uploadMeasureEvidence"
|
||||
ActionUploadTrustCenterNDA Action = "uploadTrustCenterNDA"
|
||||
ActionUploadVendorBusinessAssociateAgreement Action = "uploadVendorBusinessAssociateAgreement"
|
||||
ActionUploadVendorComplianceReport Action = "uploadVendorComplianceReport"
|
||||
ActionUploadVendorDataPrivacyAgreement Action = "uploadVendorDataPrivacyAgreement"
|
||||
ActionVerifyDomain Action = "verifyDomain"
|
||||
)
|
||||
|
||||
var (
|
||||
AllRoles = []Role{RoleOwner, RoleAdmin, RoleViewer, RoleFull}
|
||||
EditRoles = []Role{RoleOwner, RoleAdmin, RoleFull}
|
||||
)
|
||||
|
||||
var Permissions = map[uint16]map[Action][]Role{
|
||||
coredata.OrganizationEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetLogoUrl: AllRoles,
|
||||
ActionGetHorizontalLogoUrl: AllRoles,
|
||||
ActionMemberships: AllRoles,
|
||||
ActionPeoples: AllRoles,
|
||||
ActionTotalCount: AllRoles,
|
||||
ActionListMembers: AllRoles,
|
||||
ActionListInvitations: AllRoles,
|
||||
ActionListSlackConnections: AllRoles,
|
||||
ActionListFrameworks: AllRoles,
|
||||
ActionListControls: AllRoles,
|
||||
ActionListVendors: AllRoles,
|
||||
ActionListPeople: AllRoles,
|
||||
ActionListDocuments: AllRoles,
|
||||
ActionListMeetings: AllRoles,
|
||||
ActionListMeasures: AllRoles,
|
||||
ActionListRisks: AllRoles,
|
||||
ActionListTasks: AllRoles,
|
||||
ActionListAssets: AllRoles,
|
||||
ActionListData: AllRoles,
|
||||
ActionListAudits: AllRoles,
|
||||
ActionListNonconformities: AllRoles,
|
||||
ActionListObligations: AllRoles,
|
||||
ActionListContinualImprovements: AllRoles,
|
||||
ActionListProcessingActivities: AllRoles,
|
||||
ActionListSnapshots: AllRoles,
|
||||
ActionListTrustCenterFiles: AllRoles,
|
||||
ActionGetTrustCenter: AllRoles,
|
||||
ActionGetCustomDomain: AllRoles,
|
||||
ActionListSAMLConfigurations: AllRoles,
|
||||
ActionConfirmEmail: AllRoles,
|
||||
ActionAcceptInvitation: AllRoles,
|
||||
|
||||
ActionUpdateOrganization: EditRoles,
|
||||
ActionDeleteOrganizationHorizontalLogo: EditRoles,
|
||||
ActionCreateTrustCenter: EditRoles,
|
||||
ActionInviteUser: EditRoles,
|
||||
ActionDeleteInvitation: EditRoles,
|
||||
ActionUpdateMembership: EditRoles,
|
||||
ActionCreatePeople: EditRoles,
|
||||
ActionCreateVendor: EditRoles,
|
||||
ActionCreateFramework: EditRoles,
|
||||
ActionImportFramework: EditRoles,
|
||||
ActionCreateControl: EditRoles,
|
||||
ActionCreateMeasure: EditRoles,
|
||||
ActionImportMeasure: EditRoles,
|
||||
ActionCreateMeeting: EditRoles,
|
||||
ActionCreateTask: EditRoles,
|
||||
ActionCreateRisk: EditRoles,
|
||||
ActionCreateDocument: EditRoles,
|
||||
ActionCreateAsset: EditRoles,
|
||||
ActionCreateDatum: EditRoles,
|
||||
ActionCreateAudit: EditRoles,
|
||||
ActionCreateNonconformity: EditRoles,
|
||||
ActionCreateObligation: EditRoles,
|
||||
ActionCreateContinualImprovement: EditRoles,
|
||||
ActionCreateProcessingActivity: EditRoles,
|
||||
ActionCreateSnapshot: EditRoles,
|
||||
ActionCreateTrustCenterFile: EditRoles,
|
||||
ActionSendSigningNotifications: EditRoles,
|
||||
|
||||
ActionRemoveMember: {RoleOwner, RoleFull},
|
||||
|
||||
ActionCreateCustomDomain: {RoleOwner},
|
||||
ActionInitiateDomainVerification: {RoleOwner},
|
||||
ActionVerifyDomain: {RoleOwner},
|
||||
ActionCreateSAMLConfiguration: {RoleOwner},
|
||||
ActionDeleteOrganization: {RoleOwner},
|
||||
},
|
||||
coredata.TrustCenterEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetNdaFileUrl: AllRoles,
|
||||
ActionGetOrganization: AllRoles,
|
||||
ActionListAccesses: AllRoles,
|
||||
ActionListReferences: AllRoles,
|
||||
|
||||
ActionUpdateTrustCenter: EditRoles,
|
||||
ActionUploadTrustCenterNDA: EditRoles,
|
||||
ActionDeleteTrustCenterNDA: EditRoles,
|
||||
ActionCreateTrustCenterAccess: EditRoles,
|
||||
ActionCreateTrustCenterReference: EditRoles,
|
||||
},
|
||||
coredata.TrustCenterAccessEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionActiveCount: AllRoles,
|
||||
ActionPendingRequestCount: AllRoles,
|
||||
ActionAvailableDocumentAccesses: AllRoles,
|
||||
|
||||
ActionUpdateTrustCenterAccess: EditRoles,
|
||||
ActionDeleteTrustCenterAccess: EditRoles,
|
||||
},
|
||||
coredata.TrustCenterReferenceEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetLogoUrl: AllRoles,
|
||||
|
||||
ActionUpdateTrustCenterReference: EditRoles,
|
||||
ActionDeleteTrustCenterReference: EditRoles,
|
||||
},
|
||||
coredata.TrustCenterFileEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetFileUrl: AllRoles,
|
||||
|
||||
ActionUpdateTrustCenterFile: EditRoles,
|
||||
ActionGetTrustCenterFile: EditRoles,
|
||||
ActionDeleteTrustCenterFile: EditRoles,
|
||||
},
|
||||
coredata.UserEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
},
|
||||
coredata.MembershipEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetAuthMethod: AllRoles,
|
||||
},
|
||||
coredata.InvitationEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetOrganization: AllRoles,
|
||||
},
|
||||
coredata.PeopleEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
|
||||
ActionUpdatePeople: EditRoles,
|
||||
ActionDeletePeople: EditRoles,
|
||||
},
|
||||
coredata.VendorEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetOrganization: AllRoles,
|
||||
ActionListComplianceReports: AllRoles,
|
||||
ActionGetBusinessAssociateAgreement: AllRoles,
|
||||
ActionGetDataPrivacyAgreement: AllRoles,
|
||||
ActionListContacts: AllRoles,
|
||||
ActionListServices: AllRoles,
|
||||
ActionListRiskAssessments: AllRoles,
|
||||
ActionGetBusinessOwner: AllRoles,
|
||||
ActionGetSecurityOwner: AllRoles,
|
||||
|
||||
ActionUpdateVendor: EditRoles,
|
||||
ActionDeleteVendor: EditRoles,
|
||||
ActionCreateVendorContact: EditRoles,
|
||||
ActionCreateVendorService: EditRoles,
|
||||
ActionUploadVendorComplianceReport: EditRoles,
|
||||
ActionUploadVendorBusinessAssociateAgreement: EditRoles,
|
||||
ActionDeleteVendorBusinessAssociateAgreement: EditRoles,
|
||||
ActionUploadVendorDataPrivacyAgreement: EditRoles,
|
||||
ActionCreateVendorRiskAssessment: EditRoles,
|
||||
ActionAssessVendor: EditRoles,
|
||||
},
|
||||
coredata.VendorComplianceReportEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetVendor: AllRoles,
|
||||
ActionGetFile: AllRoles,
|
||||
|
||||
ActionDeleteVendorComplianceReport: EditRoles,
|
||||
},
|
||||
coredata.VendorBusinessAssociateAgreementEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetVendor: AllRoles,
|
||||
ActionGetFileUrl: AllRoles,
|
||||
|
||||
ActionUpdateVendorBusinessAssociateAgreement: EditRoles,
|
||||
ActionDeleteVendorBusinessAssociateAgreement: EditRoles,
|
||||
},
|
||||
coredata.VendorContactEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetVendor: AllRoles,
|
||||
|
||||
ActionUpdateVendorContact: EditRoles,
|
||||
ActionDeleteVendorContact: EditRoles,
|
||||
},
|
||||
coredata.VendorServiceEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetVendor: AllRoles,
|
||||
|
||||
ActionUpdateVendorService: EditRoles,
|
||||
ActionDeleteVendorService: EditRoles,
|
||||
},
|
||||
coredata.VendorDataPrivacyAgreementEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetVendor: AllRoles,
|
||||
ActionGetFileUrl: AllRoles,
|
||||
|
||||
ActionUpdateVendorDataPrivacyAgreement: EditRoles,
|
||||
ActionDeleteVendorDataPrivacyAgreement: EditRoles,
|
||||
},
|
||||
coredata.VendorRiskAssessmentEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
},
|
||||
coredata.FrameworkEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetOrganization: AllRoles,
|
||||
ActionListControls: AllRoles,
|
||||
|
||||
ActionCreateControl: EditRoles,
|
||||
ActionUpdateFramework: EditRoles,
|
||||
ActionDeleteFramework: EditRoles,
|
||||
ActionGenerateFrameworkStateOfApplicability: EditRoles,
|
||||
ActionExportFramework: EditRoles,
|
||||
},
|
||||
coredata.ControlEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetFramework: AllRoles,
|
||||
ActionListMeasures: AllRoles,
|
||||
ActionListDocuments: AllRoles,
|
||||
ActionListAudits: AllRoles,
|
||||
ActionListSnapshots: AllRoles,
|
||||
|
||||
ActionUpdateControl: EditRoles,
|
||||
ActionDeleteControl: EditRoles,
|
||||
ActionCreateControlMeasureMapping: EditRoles,
|
||||
ActionCreateControlDocumentMapping: EditRoles,
|
||||
ActionDeleteControlMeasureMapping: EditRoles,
|
||||
ActionDeleteControlDocumentMapping: EditRoles,
|
||||
ActionCreateControlAuditMapping: EditRoles,
|
||||
ActionDeleteControlAuditMapping: EditRoles,
|
||||
ActionCreateControlSnapshotMapping: EditRoles,
|
||||
ActionDeleteControlSnapshotMapping: EditRoles,
|
||||
},
|
||||
coredata.MeasureEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionListEvidences: AllRoles,
|
||||
ActionListTasks: AllRoles,
|
||||
ActionListRisks: AllRoles,
|
||||
ActionListControls: AllRoles,
|
||||
ActionTotalCount: AllRoles,
|
||||
|
||||
ActionUpdateMeasure: EditRoles,
|
||||
ActionDeleteMeasure: EditRoles,
|
||||
ActionUploadMeasureEvidence: EditRoles,
|
||||
},
|
||||
coredata.TaskEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetAssignedTo: AllRoles,
|
||||
ActionGetOrganization: AllRoles,
|
||||
ActionGetMeasure: AllRoles,
|
||||
ActionListEvidences: AllRoles,
|
||||
|
||||
ActionUpdateTask: EditRoles,
|
||||
ActionDeleteTask: EditRoles,
|
||||
ActionAssignTask: EditRoles,
|
||||
ActionUnassignTask: EditRoles,
|
||||
},
|
||||
coredata.EvidenceEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetFile: AllRoles,
|
||||
ActionGetTask: AllRoles,
|
||||
ActionGetMeasure: AllRoles,
|
||||
|
||||
ActionDeleteEvidence: EditRoles,
|
||||
},
|
||||
coredata.DocumentEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionExportDocumentVersionPDF: AllRoles,
|
||||
ActionGetOwner: AllRoles,
|
||||
ActionGetOrganization: AllRoles,
|
||||
ActionListVersions: AllRoles,
|
||||
ActionListControls: AllRoles,
|
||||
ActionTotalCount: AllRoles,
|
||||
|
||||
ActionUpdateDocument: EditRoles,
|
||||
ActionDeleteDocument: EditRoles,
|
||||
ActionPublishDocumentVersion: EditRoles,
|
||||
ActionBulkPublishDocumentVersions: EditRoles,
|
||||
ActionBulkDeleteDocuments: EditRoles,
|
||||
ActionBulkExportDocuments: EditRoles,
|
||||
ActionGenerateDocumentChangelog: EditRoles,
|
||||
ActionCreateDraftDocumentVersion: EditRoles,
|
||||
ActionDeleteDraftDocumentVersion: EditRoles,
|
||||
ActionUpdateDocumentVersion: EditRoles,
|
||||
ActionRequestSignature: EditRoles,
|
||||
ActionBulkRequestSignatures: EditRoles,
|
||||
ActionSendSigningNotifications: EditRoles,
|
||||
ActionCancelSignatureRequest: EditRoles,
|
||||
},
|
||||
coredata.DocumentVersionEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetFile: AllRoles,
|
||||
ActionGetOwner: AllRoles,
|
||||
ActionDocument: AllRoles,
|
||||
ActionSignatures: AllRoles,
|
||||
|
||||
ActionUpdateDocumentVersion: EditRoles,
|
||||
},
|
||||
coredata.DocumentVersionSignatureEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionDocumentVersion: AllRoles,
|
||||
ActionSignedBy: AllRoles,
|
||||
},
|
||||
coredata.RiskEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetOwner: AllRoles,
|
||||
ActionGetOrganization: AllRoles,
|
||||
ActionTotalCount: AllRoles,
|
||||
ActionListControls: AllRoles,
|
||||
ActionListMeasures: AllRoles,
|
||||
ActionListDocuments: AllRoles,
|
||||
ActionListObligations: AllRoles,
|
||||
|
||||
ActionUpdateRisk: EditRoles,
|
||||
ActionDeleteRisk: EditRoles,
|
||||
ActionCreateRiskMeasureMapping: EditRoles,
|
||||
ActionDeleteRiskMeasureMapping: EditRoles,
|
||||
ActionCreateRiskDocumentMapping: EditRoles,
|
||||
ActionDeleteRiskDocumentMapping: EditRoles,
|
||||
ActionCreateRiskObligationMapping: EditRoles,
|
||||
ActionDeleteRiskObligationMapping: EditRoles,
|
||||
},
|
||||
coredata.AssetEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetOwner: AllRoles,
|
||||
ActionListVendors: AllRoles,
|
||||
ActionGetAssetType: AllRoles,
|
||||
ActionGetOrganization: AllRoles,
|
||||
|
||||
ActionUpdateAsset: EditRoles,
|
||||
ActionDeleteAsset: EditRoles,
|
||||
},
|
||||
coredata.DatumEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetOwner: AllRoles,
|
||||
ActionGetOrganization: AllRoles,
|
||||
ActionListVendors: AllRoles,
|
||||
|
||||
ActionUpdateDatum: EditRoles,
|
||||
ActionDeleteDatum: EditRoles,
|
||||
},
|
||||
coredata.AuditEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetFile: AllRoles,
|
||||
ActionGetFramework: AllRoles,
|
||||
ActionGetOrganization: AllRoles,
|
||||
ActionReport: AllRoles,
|
||||
ActionReportUrl: AllRoles,
|
||||
ActionListControls: AllRoles,
|
||||
|
||||
ActionUpdateAudit: EditRoles,
|
||||
ActionDeleteAudit: EditRoles,
|
||||
ActionUploadAuditReport: EditRoles,
|
||||
ActionDeleteAuditReport: EditRoles,
|
||||
},
|
||||
coredata.ReportEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetFile: AllRoles,
|
||||
ActionGetOrganization: AllRoles,
|
||||
ActionGetSnapshot: AllRoles,
|
||||
ActionDownloadUrl: AllRoles,
|
||||
},
|
||||
coredata.NonconformityEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetOwner: AllRoles,
|
||||
ActionGetOrganization: AllRoles,
|
||||
ActionAudit: AllRoles,
|
||||
|
||||
ActionUpdateNonconformity: EditRoles,
|
||||
ActionDeleteNonconformity: EditRoles,
|
||||
},
|
||||
coredata.ObligationEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetOrganization: AllRoles,
|
||||
ActionGetOwner: AllRoles,
|
||||
ActionListRisks: AllRoles,
|
||||
|
||||
ActionUpdateObligation: EditRoles,
|
||||
ActionDeleteObligation: EditRoles,
|
||||
},
|
||||
coredata.ContinualImprovementEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetOwner: AllRoles,
|
||||
ActionGetOrganization: AllRoles,
|
||||
|
||||
ActionUpdateContinualImprovement: EditRoles,
|
||||
ActionDeleteContinualImprovement: EditRoles,
|
||||
},
|
||||
coredata.ProcessingActivityEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetOrganization: AllRoles,
|
||||
ActionListVendors: AllRoles,
|
||||
|
||||
ActionUpdateProcessingActivity: EditRoles,
|
||||
ActionDeleteProcessingActivity: EditRoles,
|
||||
},
|
||||
coredata.SnapshotEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetOrganization: AllRoles,
|
||||
ActionListControls: AllRoles,
|
||||
|
||||
ActionDeleteSnapshot: EditRoles,
|
||||
},
|
||||
coredata.CustomDomainEntityType: {
|
||||
ActionGet: {RoleOwner, RoleAdmin},
|
||||
|
||||
ActionDeleteCustomDomain: {RoleOwner},
|
||||
},
|
||||
coredata.SAMLConfigurationEntityType: {
|
||||
ActionGet: {RoleOwner, RoleAdmin},
|
||||
ActionSpMetadataUrl: {RoleOwner, RoleAdmin},
|
||||
ActionTestLoginUrl: {RoleOwner, RoleAdmin},
|
||||
|
||||
ActionUpdateSAMLConfiguration: {RoleOwner, RoleAdmin},
|
||||
ActionDeleteSAMLConfiguration: {RoleOwner, RoleAdmin},
|
||||
ActionEnableSAML: {RoleOwner, RoleAdmin},
|
||||
ActionDisableSAML: {RoleOwner, RoleAdmin},
|
||||
},
|
||||
coredata.FileEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionDownloadUrl: AllRoles,
|
||||
},
|
||||
coredata.TrustCenterDocumentAccessEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionReport: AllRoles,
|
||||
ActionTrustCenterFile: AllRoles,
|
||||
},
|
||||
coredata.MeetingEntityType: {
|
||||
ActionGet: AllRoles,
|
||||
ActionGetOrganization: AllRoles,
|
||||
ActionTotalCount: AllRoles,
|
||||
|
||||
ActionUpdateMeeting: EditRoles,
|
||||
ActionDeleteMeeting: EditRoles,
|
||||
},
|
||||
}
|
||||
|
||||
func GetPermissionsForAction(entityType uint16, action Action) []Role {
|
||||
if entityActions, ok := Permissions[entityType]; ok {
|
||||
if roles, ok := entityActions[action]; ok {
|
||||
return roles
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetPermissionsByRole(userRole Role) map[string]map[Action]bool {
|
||||
permissions := make(map[string]map[Action]bool)
|
||||
|
||||
for entityType, actions := range Permissions {
|
||||
entityTypeName, ok := coredata.EntityModel(entityType)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if permissions[entityTypeName] == nil {
|
||||
permissions[entityTypeName] = make(map[Action]bool)
|
||||
}
|
||||
|
||||
for action, allowedRoles := range actions {
|
||||
if slices.Contains(allowedRoles, userRole) {
|
||||
permissions[entityTypeName][action] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return permissions
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"go.gearno.de/kit/pg"
|
||||
@@ -37,6 +38,14 @@ func (e *TenantAccessError) Error() string {
|
||||
return "not authorized"
|
||||
}
|
||||
|
||||
type PermissionDeniedError struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *PermissionDeniedError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
type (
|
||||
Service struct {
|
||||
pg *pg.Client
|
||||
@@ -103,6 +112,27 @@ func (s *Service) GetAllUserOrganizations(
|
||||
return organizations, err
|
||||
}
|
||||
|
||||
func (s *Service) GetUserOrganizationsWithRole(
|
||||
ctx context.Context,
|
||||
userID gid.GID,
|
||||
role coredata.MembershipRole,
|
||||
) (coredata.Organizations, error) {
|
||||
organizations := coredata.Organizations{}
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := organizations.LoadAllByUserIDWithRole(ctx, conn, userID, role); err != nil {
|
||||
return fmt.Errorf("cannot load user organizations with role: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
return organizations, err
|
||||
}
|
||||
|
||||
func (s *Service) GetAllOrganizationsForUserAPIKeyId(
|
||||
ctx context.Context,
|
||||
userAPIKeyID gid.GID,
|
||||
@@ -143,70 +173,6 @@ func (s *Service) GetUserOrganizations(
|
||||
return organizations, err
|
||||
}
|
||||
|
||||
func (s *Service) AcceptInvitation(
|
||||
ctx context.Context,
|
||||
token string,
|
||||
userID gid.GID,
|
||||
) error {
|
||||
payload, err := statelesstoken.ValidateToken[coredata.InvitationData](
|
||||
s.tokenSecret,
|
||||
TokenTypeOrganizationInvitation,
|
||||
token,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid invitation token: %w", err)
|
||||
}
|
||||
|
||||
invitationData := payload.Data
|
||||
scope := coredata.NewScope(invitationData.InvitationID.TenantID())
|
||||
|
||||
return s.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
invitation := &coredata.Invitation{}
|
||||
if err := invitation.LoadByID(ctx, tx, scope, invitationData.InvitationID); err != nil {
|
||||
var errInvitationNotFound *coredata.ErrInvitationNotFound
|
||||
if errors.As(err, &errInvitationNotFound) {
|
||||
return fmt.Errorf("invitation was deleted or no longer exists")
|
||||
}
|
||||
return fmt.Errorf("cannot load invitation: %w", err)
|
||||
}
|
||||
|
||||
if invitation.AcceptedAt != nil {
|
||||
return fmt.Errorf("invitation already accepted")
|
||||
}
|
||||
|
||||
if time.Now().After(invitation.ExpiresAt) {
|
||||
return fmt.Errorf("invitation expired")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
membershipID := gid.New(scope.GetTenantID(), coredata.MembershipEntityType)
|
||||
|
||||
membership := &coredata.Membership{
|
||||
ID: membershipID,
|
||||
UserID: userID,
|
||||
OrganizationID: invitation.OrganizationID,
|
||||
Role: invitation.Role,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := membership.Create(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot add user to organization: %w", err)
|
||||
}
|
||||
|
||||
invitation.AcceptedAt = &now
|
||||
if err := invitation.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot mark invitation as accepted: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Service) AcceptInvitationByID(
|
||||
ctx context.Context,
|
||||
invitationID gid.GID,
|
||||
@@ -274,36 +240,11 @@ func (s *Service) AcceptInvitationByID(
|
||||
return acceptedInvitation, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetUserInvitations(
|
||||
ctx context.Context,
|
||||
email string,
|
||||
cursor *page.Cursor[coredata.InvitationOrderField],
|
||||
filter *coredata.InvitationFilter,
|
||||
) (*page.Page[*coredata.Invitation, coredata.InvitationOrderField], error) {
|
||||
var invitations coredata.Invitations
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := invitations.LoadByEmail(ctx, conn, coredata.NewNoScope(), email, cursor, filter); err != nil {
|
||||
return fmt.Errorf("cannot load invitations: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return page.NewPage(invitations, cursor), nil
|
||||
}
|
||||
|
||||
type UserInvitation struct {
|
||||
ID gid.GID
|
||||
Email string
|
||||
FullName string
|
||||
Role coredata.Role
|
||||
Role coredata.MembershipRole
|
||||
ExpiresAt time.Time
|
||||
AcceptedAt *time.Time
|
||||
CreatedAt time.Time
|
||||
@@ -417,7 +358,7 @@ func (s *TenantAuthzService) AddUserToOrganization(
|
||||
ctx context.Context,
|
||||
userID gid.GID,
|
||||
orgID gid.GID,
|
||||
role coredata.Role,
|
||||
role coredata.MembershipRole,
|
||||
) error {
|
||||
now := time.Now()
|
||||
membershipID := gid.New(s.scope.GetTenantID(), coredata.MembershipEntityType)
|
||||
@@ -538,6 +479,30 @@ func (s *TenantAuthzService) DeleteInvitation(
|
||||
)
|
||||
}
|
||||
|
||||
func (s *TenantAuthzService) GetMembershipByUserAndOrganizationID(
|
||||
ctx context.Context,
|
||||
userID gid.GID,
|
||||
orgID gid.GID,
|
||||
) (*coredata.Membership, error) {
|
||||
membership := &coredata.Membership{}
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := membership.LoadByUserAndOrg(ctx, conn, s.scope, userID, orgID); err != nil {
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return membership, nil
|
||||
}
|
||||
|
||||
func (s *TenantAuthzService) GetMembershipsByOrganizationID(
|
||||
ctx context.Context,
|
||||
orgID gid.GID,
|
||||
@@ -609,41 +574,11 @@ func (s *TenantAuthzService) CountOrganizationUsers(
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s *TenantAuthzService) CanUserAccessOrganization(
|
||||
ctx context.Context,
|
||||
userID gid.GID,
|
||||
orgID gid.GID,
|
||||
) (bool, error) {
|
||||
membership := &coredata.Membership{}
|
||||
|
||||
haveAccess := false
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := membership.LoadByUserAndOrg(ctx, conn, s.scope, userID, orgID); err != nil {
|
||||
if _, ok := err.(coredata.ErrMembershipNotFound); ok {
|
||||
return nil // Not an error, just no access
|
||||
}
|
||||
return fmt.Errorf("cannot check organization access: %w", err)
|
||||
}
|
||||
haveAccess = true
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return haveAccess, nil
|
||||
}
|
||||
|
||||
func (s *TenantAuthzService) GetUserRoleInOrganization(
|
||||
ctx context.Context,
|
||||
userID gid.GID,
|
||||
orgID gid.GID,
|
||||
) (coredata.Role, error) {
|
||||
) (coredata.MembershipRole, error) {
|
||||
membership := &coredata.Membership{}
|
||||
|
||||
err := s.pg.WithConn(
|
||||
@@ -690,30 +625,55 @@ func (s *TenantAuthzService) RemoveMemberFromOrganization(
|
||||
)
|
||||
}
|
||||
|
||||
func (s *TenantAuthzService) UpdateUserRole(
|
||||
func (s *TenantAuthzService) UpdateMembershipRole(
|
||||
ctx context.Context,
|
||||
userID gid.GID,
|
||||
orgID gid.GID,
|
||||
newRole coredata.Role,
|
||||
) error {
|
||||
return s.pg.WithTx(
|
||||
memberID gid.GID,
|
||||
newRole coredata.MembershipRole,
|
||||
) (*coredata.Membership, error) {
|
||||
membership := &coredata.Membership{}
|
||||
|
||||
err := s.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
membership := &coredata.Membership{}
|
||||
if err := membership.LoadByUserAndOrg(ctx, tx, s.scope, userID, orgID); err != nil {
|
||||
return fmt.Errorf("cannot find membership: %w", err)
|
||||
if err := membership.LoadByID(ctx, tx, s.scope, memberID); err != nil {
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
|
||||
if membership.OrganizationID != orgID {
|
||||
return fmt.Errorf("membership does not belong to organization")
|
||||
}
|
||||
|
||||
// If the new role cannot create API keys, delete all related API key memberships
|
||||
if newRole != coredata.MembershipRoleOwner {
|
||||
var apiKeyMemberships coredata.UserAPIKeyMemberships
|
||||
if err := apiKeyMemberships.LoadByMembershipID(ctx, tx, s.scope, memberID); err != nil {
|
||||
return fmt.Errorf("cannot load api key memberships: %w", err)
|
||||
}
|
||||
|
||||
for _, apiKeyMembership := range apiKeyMemberships {
|
||||
if err := apiKeyMembership.Delete(ctx, tx, s.scope); err != nil {
|
||||
return fmt.Errorf("cannot delete api key membership: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
membership.Role = newRole
|
||||
membership.UpdatedAt = time.Now()
|
||||
|
||||
if err := membership.Update(ctx, tx, s.scope); err != nil {
|
||||
return fmt.Errorf("cannot update user role: %w", err)
|
||||
return fmt.Errorf("cannot update membership role: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return membership, nil
|
||||
}
|
||||
|
||||
func (s *TenantAuthzService) InviteUserToOrganization(
|
||||
@@ -721,7 +681,7 @@ func (s *TenantAuthzService) InviteUserToOrganization(
|
||||
organizationID gid.GID,
|
||||
emailAddress string,
|
||||
fullName string,
|
||||
role coredata.Role,
|
||||
role coredata.MembershipRole,
|
||||
) (*coredata.Invitation, error) {
|
||||
var invitation *coredata.Invitation
|
||||
|
||||
@@ -827,7 +787,7 @@ func (s *TenantAuthzService) EnsureSAMLMembership(
|
||||
ctx context.Context,
|
||||
userID gid.GID,
|
||||
organizationID gid.GID,
|
||||
role *coredata.Role,
|
||||
role *coredata.MembershipRole,
|
||||
) error {
|
||||
now := time.Now()
|
||||
|
||||
@@ -842,7 +802,7 @@ func (s *TenantAuthzService) EnsureSAMLMembership(
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
|
||||
membershipRole := coredata.RoleMember
|
||||
membershipRole := coredata.MembershipRoleViewer
|
||||
if role != nil {
|
||||
membershipRole = *role
|
||||
}
|
||||
@@ -878,15 +838,94 @@ func (s *TenantAuthzService) EnsureSAMLMembership(
|
||||
)
|
||||
}
|
||||
|
||||
// This is a placeholder for future permission system
|
||||
func (s *TenantAuthzService) HasPermission(
|
||||
func (s *TenantAuthzService) Authorize(
|
||||
ctx context.Context,
|
||||
userID gid.GID,
|
||||
orgID gid.GID,
|
||||
resource string,
|
||||
action string,
|
||||
) (bool, error) {
|
||||
// For now, just check if user is a member
|
||||
// In the future, this will check specific permissions based on role
|
||||
return s.CanUserAccessOrganization(ctx, userID, orgID)
|
||||
user *coredata.User,
|
||||
apiKey *coredata.UserAPIKey,
|
||||
entityGID gid.GID,
|
||||
action Action,
|
||||
) error {
|
||||
requiredRoles := GetPermissionsForAction(entityGID.EntityType(), action)
|
||||
if requiredRoles == nil {
|
||||
return &PermissionDeniedError{
|
||||
Message: fmt.Sprintf("no permissions defined for action %s on entity type %d", action, entityGID.EntityType()),
|
||||
}
|
||||
}
|
||||
|
||||
role, err := s.GetUserOrAPIKeyRole(ctx, user, apiKey, entityGID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get user or API key role: %w", err)
|
||||
}
|
||||
|
||||
if !slices.Contains(requiredRoles, role) {
|
||||
return &PermissionDeniedError{
|
||||
Message: fmt.Sprintf("role %s not authorized for action %s, requires one of %v", role, action, requiredRoles),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TenantAuthzService) CanAssignRole(
|
||||
ctx context.Context,
|
||||
user *coredata.User,
|
||||
apiKey *coredata.UserAPIKey,
|
||||
entityGID gid.GID,
|
||||
targetRole coredata.MembershipRole,
|
||||
) error {
|
||||
currentRole, err := s.GetUserOrAPIKeyRole(ctx, user, apiKey, entityGID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get user or API key role: %w", err)
|
||||
}
|
||||
|
||||
if currentRole == RoleOwner || currentRole == RoleFull {
|
||||
return nil
|
||||
}
|
||||
|
||||
if currentRole == RoleAdmin {
|
||||
if targetRole == coredata.MembershipRoleOwner {
|
||||
return &PermissionDeniedError{Message: "admin users cannot assign owner role"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return &PermissionDeniedError{Message: fmt.Sprintf("role %s cannot assign roles", currentRole)}
|
||||
}
|
||||
|
||||
func (s *TenantAuthzService) GetUserOrAPIKeyRole(
|
||||
ctx context.Context,
|
||||
user *coredata.User,
|
||||
apiKey *coredata.UserAPIKey,
|
||||
entityGID gid.GID,
|
||||
) (Role, error) {
|
||||
var role Role
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if user != nil {
|
||||
membership := &coredata.Membership{}
|
||||
if err := membership.LoadRoleByUserAndEntityID(ctx, conn, s.scope, user.ID, entityGID); err != nil {
|
||||
return fmt.Errorf("cannot get user role: %w", err)
|
||||
}
|
||||
role = Role(membership.Role.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
if apiKey != nil {
|
||||
apiKeyMembership := &coredata.UserAPIKeyMembership{}
|
||||
if err := apiKeyMembership.LoadRoleByAPIKeyAndEntityID(ctx, conn, s.scope, apiKey.ID, entityGID); err != nil {
|
||||
return fmt.Errorf("cannot get API key role: %w", err)
|
||||
}
|
||||
role = Role(apiKeyMembership.Role.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("no user or API key provided")
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return role, nil
|
||||
}
|
||||
|
||||
@@ -47,13 +47,14 @@ func (a *UserAPIKeyMembership) Insert(
|
||||
) error {
|
||||
q := `
|
||||
INSERT INTO
|
||||
authz_api_keys_memberships (id, tenant_id, auth_user_api_key_id, membership_id, role, created_at, updated_at)
|
||||
authz_api_keys_memberships (id, tenant_id, auth_user_api_key_id, membership_id, role, organization_id, created_at, updated_at)
|
||||
VALUES (
|
||||
@id,
|
||||
@tenant_id,
|
||||
@auth_user_api_key_id,
|
||||
@membership_id,
|
||||
@role,
|
||||
@organization_id,
|
||||
@created_at,
|
||||
@updated_at
|
||||
)
|
||||
@@ -65,6 +66,7 @@ VALUES (
|
||||
"auth_user_api_key_id": a.UserAPIKeyID,
|
||||
"membership_id": a.MembershipID,
|
||||
"role": a.Role,
|
||||
"organization_id": a.OrganizationID,
|
||||
"created_at": a.CreatedAt,
|
||||
"updated_at": a.UpdatedAt,
|
||||
}
|
||||
@@ -127,6 +129,133 @@ ORDER BY akm.created_at DESC
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadRoleByAPIKeyAndEntityID loads an API key's role by querying any entity to extract its organization_id
|
||||
func (a *UserAPIKeyMembership) LoadRoleByAPIKeyAndEntityID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
apiKeyID gid.GID,
|
||||
entityID gid.GID,
|
||||
) error {
|
||||
entityType := entityID.EntityType()
|
||||
|
||||
// For organization, the entity ID is the organization ID
|
||||
if entityType == OrganizationEntityType {
|
||||
return a.LoadByAPIKeyIDAndOrganizationID(ctx, conn, scope, apiKeyID, entityID)
|
||||
}
|
||||
|
||||
tableName, ok := EntityTable(entityType)
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported entity type for API key role lookup: %d", entityType)
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(`
|
||||
SELECT
|
||||
akm.id,
|
||||
akm.auth_user_api_key_id,
|
||||
akm.membership_id,
|
||||
akm.role,
|
||||
akm.created_at,
|
||||
akm.updated_at
|
||||
FROM
|
||||
authz_api_keys_memberships akm
|
||||
INNER JOIN authz_memberships m ON m.id = akm.membership_id
|
||||
INNER JOIN %s e ON e.id = @entity_id
|
||||
WHERE
|
||||
%s
|
||||
AND akm.auth_user_api_key_id = @api_key_id
|
||||
AND m.organization_id = e.organization_id
|
||||
LIMIT 1;
|
||||
`, tableName, scope.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{
|
||||
"api_key_id": apiKeyID,
|
||||
"entity_id": entityID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, query, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query API key membership by entity: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if !rows.Next() {
|
||||
return fmt.Errorf("API key membership not found for key %s and entity %s", apiKeyID, entityID)
|
||||
}
|
||||
|
||||
var membership UserAPIKeyMembership
|
||||
err = rows.Scan(
|
||||
&membership.ID,
|
||||
&membership.UserAPIKeyID,
|
||||
&membership.MembershipID,
|
||||
&membership.Role,
|
||||
&membership.CreatedAt,
|
||||
&membership.UpdatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot scan API key membership: %w", err)
|
||||
}
|
||||
|
||||
*a = membership
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *UserAPIKeyMembership) LoadByAPIKeyIDAndOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
apiKeyID gid.GID,
|
||||
organizationID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
akm.id,
|
||||
akm.auth_user_api_key_id,
|
||||
akm.membership_id,
|
||||
akm.role,
|
||||
akm.created_at,
|
||||
akm.updated_at,
|
||||
m.organization_id,
|
||||
o.name as organization_name
|
||||
FROM
|
||||
authz_api_keys_memberships akm
|
||||
JOIN
|
||||
authz_memberships m ON akm.membership_id = m.id
|
||||
JOIN
|
||||
organizations o ON m.organization_id = o.id
|
||||
WHERE
|
||||
akm.auth_user_api_key_id = @api_key_id
|
||||
AND m.organization_id = @organization_id
|
||||
AND m.%s
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"api_key_id": apiKeyID,
|
||||
"organization_id": organizationID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query user api key membership: %w", err)
|
||||
}
|
||||
|
||||
membership, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[UserAPIKeyMembership])
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return fmt.Errorf("API key does not have access to organization")
|
||||
}
|
||||
return fmt.Errorf("cannot collect user api key membership: %w", err)
|
||||
}
|
||||
|
||||
*a = membership
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *UserAPIKeyMembership) Delete(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
@@ -155,6 +284,56 @@ WHERE
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *UserAPIKeyMemberships) LoadByMembershipID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
membershipID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
akm.id,
|
||||
akm.auth_user_api_key_id,
|
||||
akm.membership_id,
|
||||
akm.role,
|
||||
akm.created_at,
|
||||
akm.updated_at,
|
||||
m.organization_id,
|
||||
o.name as organization_name
|
||||
FROM
|
||||
authz_api_keys_memberships akm
|
||||
JOIN
|
||||
authz_memberships m ON akm.membership_id = m.id
|
||||
JOIN
|
||||
organizations o ON m.organization_id = o.id
|
||||
WHERE
|
||||
akm.membership_id = @membership_id
|
||||
AND m.%s
|
||||
ORDER BY akm.created_at DESC
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"membership_id": membershipID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query user api key memberships by membership id: %w", err)
|
||||
}
|
||||
|
||||
memberships, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[UserAPIKeyMembership])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect user api key memberships: %w", err)
|
||||
}
|
||||
|
||||
*a = memberships
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteAllUserAPIKeyMembershipsByUserAPIKeyID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
|
||||
@@ -46,6 +46,7 @@ func (av AssetVendors) Merge(
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
assetID gid.GID,
|
||||
organizationID gid.GID,
|
||||
vendorIDs []gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
@@ -54,6 +55,7 @@ WITH vendor_ids AS (
|
||||
unnest(@vendor_ids::text[]) AS vendor_id,
|
||||
@tenant_id AS tenant_id,
|
||||
@asset_id AS asset_id,
|
||||
@organization_id AS organization_id,
|
||||
@created_at::timestamptz AS created_at
|
||||
)
|
||||
MERGE INTO asset_vendors AS tgt
|
||||
@@ -62,18 +64,19 @@ ON tgt.tenant_id = src.tenant_id
|
||||
AND tgt.asset_id = src.asset_id
|
||||
AND tgt.vendor_id = src.vendor_id
|
||||
WHEN NOT MATCHED
|
||||
THEN INSERT (tenant_id, asset_id, vendor_id, created_at)
|
||||
VALUES (src.tenant_id, src.asset_id, src.vendor_id, src.created_at)
|
||||
THEN INSERT (tenant_id, asset_id, vendor_id, organization_id, created_at)
|
||||
VALUES (src.tenant_id, src.asset_id, src.vendor_id, src.organization_id, src.created_at)
|
||||
WHEN NOT MATCHED BY SOURCE
|
||||
AND tgt.tenant_id = @tenant_id AND tgt.asset_id = @asset_id
|
||||
THEN DELETE
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"asset_id": assetID,
|
||||
"created_at": time.Now(),
|
||||
"vendor_ids": vendorIDs,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"asset_id": assetID,
|
||||
"organization_id": organizationID,
|
||||
"created_at": time.Now(),
|
||||
"vendor_ids": vendorIDs,
|
||||
}
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
@@ -89,26 +92,29 @@ func (av AssetVendors) Insert(
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
assetID gid.GID,
|
||||
organizationID gid.GID,
|
||||
vendorIDs []gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
WITH vendor_ids AS (
|
||||
SELECT unnest(@vendor_ids::text[]) AS vendor_id
|
||||
)
|
||||
INSERT INTO asset_vendors (tenant_id, asset_id, vendor_id, created_at)
|
||||
INSERT INTO asset_vendors (tenant_id, asset_id, vendor_id, organization_id, created_at)
|
||||
SELECT
|
||||
@tenant_id AS tenant_id,
|
||||
@asset_id AS asset_id,
|
||||
vendor_id,
|
||||
@organization_id AS organization_id,
|
||||
@created_at AS created_at
|
||||
FROM vendor_ids
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"asset_id": assetID,
|
||||
"created_at": time.Now(),
|
||||
"vendor_ids": vendorIDs,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"asset_id": assetID,
|
||||
"organization_id": organizationID,
|
||||
"created_at": time.Now(),
|
||||
"vendor_ids": vendorIDs,
|
||||
}
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
type (
|
||||
Control struct {
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
SectionTitle string `db:"section_title"`
|
||||
FrameworkID gid.GID `db:"framework_id"`
|
||||
Name string `db:"name"`
|
||||
@@ -127,6 +128,7 @@ WITH ctrl AS (
|
||||
c.id,
|
||||
c.section_title,
|
||||
c.framework_id,
|
||||
c.organization_id,
|
||||
c.tenant_id,
|
||||
c.name,
|
||||
c.description,
|
||||
@@ -146,6 +148,7 @@ SELECT
|
||||
id,
|
||||
section_title,
|
||||
framework_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
status,
|
||||
@@ -236,6 +239,7 @@ WITH ctrl AS (
|
||||
c.id,
|
||||
c.section_title,
|
||||
c.framework_id,
|
||||
c.organization_id,
|
||||
c.tenant_id,
|
||||
c.name,
|
||||
c.description,
|
||||
@@ -255,6 +259,7 @@ SELECT
|
||||
id,
|
||||
section_title,
|
||||
framework_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
status,
|
||||
@@ -351,6 +356,7 @@ WITH ctrl AS (
|
||||
c.id,
|
||||
c.section_title,
|
||||
c.framework_id,
|
||||
c.organization_id,
|
||||
c.tenant_id,
|
||||
c.name,
|
||||
c.description,
|
||||
@@ -376,6 +382,7 @@ SELECT
|
||||
id,
|
||||
section_title,
|
||||
framework_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
status,
|
||||
@@ -455,6 +462,7 @@ SELECT
|
||||
id,
|
||||
section_title,
|
||||
framework_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
status,
|
||||
@@ -548,6 +556,7 @@ WITH ctrl AS (
|
||||
c.id,
|
||||
c.section_title,
|
||||
c.framework_id,
|
||||
c.organization_id,
|
||||
c.tenant_id,
|
||||
c.name,
|
||||
c.description,
|
||||
@@ -567,6 +576,7 @@ SELECT
|
||||
id,
|
||||
section_title,
|
||||
framework_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
status,
|
||||
@@ -613,6 +623,7 @@ SELECT
|
||||
id,
|
||||
section_title,
|
||||
framework_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
status,
|
||||
@@ -661,6 +672,7 @@ SELECT
|
||||
id,
|
||||
section_title,
|
||||
framework_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
status,
|
||||
@@ -707,6 +719,7 @@ INSERT INTO
|
||||
controls (
|
||||
tenant_id,
|
||||
id,
|
||||
organization_id,
|
||||
framework_id,
|
||||
section_title,
|
||||
name,
|
||||
@@ -719,6 +732,7 @@ INSERT INTO
|
||||
VALUES (
|
||||
@tenant_id,
|
||||
@control_id,
|
||||
@organization_id,
|
||||
@framework_id,
|
||||
@section_title,
|
||||
@name,
|
||||
@@ -733,6 +747,7 @@ VALUES (
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"control_id": c.ID,
|
||||
"organization_id": c.OrganizationID,
|
||||
"framework_id": c.FrameworkID,
|
||||
"section_title": c.SectionTitle,
|
||||
"name": c.Name,
|
||||
@@ -884,6 +899,7 @@ WITH ctrl AS (
|
||||
c.id,
|
||||
c.section_title,
|
||||
c.framework_id,
|
||||
c.organization_id,
|
||||
c.tenant_id,
|
||||
c.name,
|
||||
c.description,
|
||||
@@ -903,6 +919,7 @@ SELECT
|
||||
id,
|
||||
section_title,
|
||||
framework_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
status,
|
||||
@@ -994,6 +1011,7 @@ WITH ctrl AS (
|
||||
c.id,
|
||||
c.section_title,
|
||||
c.framework_id,
|
||||
c.organization_id,
|
||||
c.tenant_id,
|
||||
c.name,
|
||||
c.description,
|
||||
@@ -1013,6 +1031,7 @@ SELECT
|
||||
id,
|
||||
section_title,
|
||||
framework_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
status,
|
||||
|
||||
@@ -27,9 +27,10 @@ import (
|
||||
|
||||
type (
|
||||
ControlAudit struct {
|
||||
ControlID gid.GID `db:"control_id"`
|
||||
AuditID gid.GID `db:"audit_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
ControlID gid.GID `db:"control_id"`
|
||||
AuditID gid.GID `db:"audit_id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
}
|
||||
|
||||
ControlAudits []*ControlAudit
|
||||
@@ -45,12 +46,14 @@ INSERT INTO
|
||||
controls_audits (
|
||||
control_id,
|
||||
audit_id,
|
||||
organization_id,
|
||||
tenant_id,
|
||||
created_at
|
||||
)
|
||||
VALUES (
|
||||
@control_id,
|
||||
@audit_id,
|
||||
@organization_id,
|
||||
@tenant_id,
|
||||
@created_at
|
||||
)
|
||||
@@ -58,10 +61,11 @@ ON CONFLICT (control_id, audit_id) DO NOTHING;
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"control_id": ca.ControlID,
|
||||
"audit_id": ca.AuditID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"created_at": ca.CreatedAt,
|
||||
"control_id": ca.ControlID,
|
||||
"audit_id": ca.AuditID,
|
||||
"organization_id": ca.OrganizationID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"created_at": ca.CreatedAt,
|
||||
}
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
|
||||
@@ -29,10 +29,11 @@ import (
|
||||
|
||||
type (
|
||||
ControlDocument struct {
|
||||
ControlID gid.GID `db:"control_id"`
|
||||
DocumentID gid.GID `db:"document_id"`
|
||||
TenantID gid.TenantID `db:"tenant_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
ControlID gid.GID `db:"control_id"`
|
||||
DocumentID gid.GID `db:"document_id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
TenantID gid.TenantID `db:"tenant_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
}
|
||||
|
||||
ControlDocuments []*ControlDocument
|
||||
@@ -57,22 +58,25 @@ INSERT INTO
|
||||
controls_documents (
|
||||
control_id,
|
||||
document_id,
|
||||
organization_id,
|
||||
tenant_id,
|
||||
created_at
|
||||
)
|
||||
VALUES (
|
||||
@control_id,
|
||||
@document_id,
|
||||
@organization_id,
|
||||
@tenant_id,
|
||||
@created_at
|
||||
);
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"control_id": cp.ControlID,
|
||||
"document_id": cp.DocumentID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"created_at": cp.CreatedAt,
|
||||
"control_id": cp.ControlID,
|
||||
"document_id": cp.DocumentID,
|
||||
"organization_id": cp.OrganizationID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"created_at": cp.CreatedAt,
|
||||
}
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
|
||||
|
||||
@@ -20,17 +20,18 @@ import (
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
type (
|
||||
ControlMeasure struct {
|
||||
ControlID gid.GID `db:"control_id"`
|
||||
MeasureID gid.GID `db:"measure_id"`
|
||||
TenantID gid.TenantID `db:"tenant_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
ControlID gid.GID `db:"control_id"`
|
||||
MeasureID gid.GID `db:"measure_id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
TenantID gid.TenantID `db:"tenant_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
}
|
||||
|
||||
ControlMeasures []*ControlMeasure
|
||||
@@ -46,12 +47,14 @@ INSERT INTO
|
||||
controls_measures (
|
||||
control_id,
|
||||
measure_id,
|
||||
organization_id,
|
||||
tenant_id,
|
||||
created_at
|
||||
)
|
||||
VALUES (
|
||||
@control_id,
|
||||
@measure_id,
|
||||
@organization_id,
|
||||
@tenant_id,
|
||||
@created_at
|
||||
)
|
||||
@@ -59,10 +62,11 @@ ON CONFLICT (control_id, measure_id) DO NOTHING;
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"control_id": cm.ControlID,
|
||||
"measure_id": cm.MeasureID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"created_at": cm.CreatedAt,
|
||||
"control_id": cm.ControlID,
|
||||
"measure_id": cm.MeasureID,
|
||||
"organization_id": cm.OrganizationID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"created_at": cm.CreatedAt,
|
||||
}
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
|
||||
@@ -27,9 +27,10 @@ import (
|
||||
|
||||
type (
|
||||
ControlSnapshot struct {
|
||||
ControlID gid.GID `db:"control_id"`
|
||||
SnapshotID gid.GID `db:"snapshot_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
ControlID gid.GID `db:"control_id"`
|
||||
SnapshotID gid.GID `db:"snapshot_id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
}
|
||||
|
||||
ControlSnapshots []*ControlSnapshot
|
||||
@@ -45,12 +46,14 @@ INSERT INTO
|
||||
controls_snapshots (
|
||||
control_id,
|
||||
snapshot_id,
|
||||
organization_id,
|
||||
tenant_id,
|
||||
created_at
|
||||
)
|
||||
VALUES (
|
||||
@control_id,
|
||||
@snapshot_id,
|
||||
@organization_id,
|
||||
@tenant_id,
|
||||
@created_at
|
||||
)
|
||||
@@ -58,10 +61,11 @@ ON CONFLICT (control_id, snapshot_id) DO NOTHING;
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"control_id": cs.ControlID,
|
||||
"snapshot_id": cs.SnapshotID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"created_at": cs.CreatedAt,
|
||||
"control_id": cs.ControlID,
|
||||
"snapshot_id": cs.SnapshotID,
|
||||
"organization_id": cs.OrganizationID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"created_at": cs.CreatedAt,
|
||||
}
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
|
||||
@@ -22,17 +22,18 @@ import (
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/crypto/cipher"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/crypto/cipher"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
CustomDomain struct {
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
Domain string `db:"domain"`
|
||||
HTTPChallengeToken *string `db:"http_challenge_token"`
|
||||
HTTPChallengeKeyAuth *string `db:"http_challenge_key_auth"`
|
||||
@@ -159,6 +160,7 @@ func (cd *CustomDomain) LoadByID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
domain,
|
||||
http_challenge_token,
|
||||
http_challenge_key_auth,
|
||||
@@ -211,6 +213,7 @@ func (cd *CustomDomain) LoadByIDForUpdate(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
domain,
|
||||
http_challenge_token,
|
||||
http_challenge_key_auth,
|
||||
@@ -263,6 +266,7 @@ func (cd *CustomDomain) LoadByDomain(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
domain,
|
||||
http_challenge_token,
|
||||
http_challenge_key_auth,
|
||||
@@ -320,6 +324,7 @@ func (cd *CustomDomain) Insert(
|
||||
INSERT INTO custom_domains (
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
domain,
|
||||
http_challenge_token,
|
||||
http_challenge_key_auth,
|
||||
@@ -337,6 +342,7 @@ INSERT INTO custom_domains (
|
||||
) VALUES (
|
||||
@id,
|
||||
@tenant_id,
|
||||
@organization_id,
|
||||
@domain,
|
||||
@http_challenge_token,
|
||||
@http_challenge_key_auth,
|
||||
@@ -357,6 +363,7 @@ INSERT INTO custom_domains (
|
||||
args := pgx.NamedArgs{
|
||||
"id": cd.ID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"organization_id": cd.OrganizationID,
|
||||
"domain": cd.Domain,
|
||||
"http_challenge_token": cd.HTTPChallengeToken,
|
||||
"http_challenge_key_auth": cd.HTTPChallengeKeyAuth,
|
||||
@@ -487,6 +494,7 @@ func (cd *CustomDomain) LoadByHTTPChallengeToken(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
domain,
|
||||
http_challenge_token,
|
||||
http_challenge_key_auth,
|
||||
@@ -537,6 +545,7 @@ func (domains *CustomDomains) ListDomainsForRenewal(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
domain,
|
||||
http_challenge_token,
|
||||
http_challenge_key_auth,
|
||||
@@ -589,6 +598,7 @@ func (domains *CustomDomains) ListDomainsWithPendingHTTPChallenges(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
domain,
|
||||
http_challenge_token,
|
||||
http_challenge_key_auth,
|
||||
@@ -644,6 +654,7 @@ func (domains *CustomDomains) LoadActiveCertificates(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
domain,
|
||||
http_challenge_token,
|
||||
http_challenge_key_auth,
|
||||
@@ -693,6 +704,7 @@ func (domains *CustomDomains) ListStaleProvisioningDomains(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
domain,
|
||||
http_challenge_token,
|
||||
http_challenge_key_auth,
|
||||
|
||||
@@ -41,6 +41,7 @@ func (dv DatumVendors) Merge(
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
datumID gid.GID,
|
||||
organizationID gid.GID,
|
||||
vendorIDs []gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
@@ -49,6 +50,7 @@ WITH vendor_ids AS (
|
||||
unnest(@vendor_ids::text[]) AS vendor_id,
|
||||
@tenant_id AS tenant_id,
|
||||
@datum_id AS datum_id,
|
||||
@organization_id AS organization_id,
|
||||
@created_at::timestamptz AS created_at
|
||||
)
|
||||
MERGE INTO data_vendors AS tgt
|
||||
@@ -57,18 +59,19 @@ ON tgt.tenant_id = src.tenant_id
|
||||
AND tgt.datum_id = src.datum_id
|
||||
AND tgt.vendor_id = src.vendor_id
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (tenant_id, datum_id, vendor_id, created_at)
|
||||
VALUES (src.tenant_id, src.datum_id, src.vendor_id, src.created_at)
|
||||
INSERT (tenant_id, datum_id, vendor_id, organization_id, created_at)
|
||||
VALUES (src.tenant_id, src.datum_id, src.vendor_id, src.organization_id, src.created_at)
|
||||
WHEN NOT MATCHED BY SOURCE
|
||||
AND tgt.tenant_id = @tenant_id AND tgt.datum_id = @datum_id
|
||||
THEN DELETE
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"datum_id": datumID,
|
||||
"created_at": time.Now(),
|
||||
"vendor_ids": vendorIDs,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"datum_id": datumID,
|
||||
"organization_id": organizationID,
|
||||
"created_at": time.Now(),
|
||||
"vendor_ids": vendorIDs,
|
||||
}
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
@@ -84,26 +87,29 @@ func (dv DatumVendors) Insert(
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
datumID gid.GID,
|
||||
organizationID gid.GID,
|
||||
vendorIDs []gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
WITH vendor_ids AS (
|
||||
SELECT unnest(@vendor_ids::text[]) AS vendor_id
|
||||
)
|
||||
INSERT INTO data_vendors (tenant_id, datum_id, vendor_id, created_at)
|
||||
INSERT INTO data_vendors (tenant_id, datum_id, vendor_id, organization_id, created_at)
|
||||
SELECT
|
||||
@tenant_id::text AS tenant_id,
|
||||
@datum_id::text AS datum_id,
|
||||
vendor_id,
|
||||
@organization_id::text AS organization_id,
|
||||
@created_at::timestamptz AS created_at
|
||||
FROM vendor_ids
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"datum_id": datumID,
|
||||
"created_at": time.Now(),
|
||||
"vendor_ids": vendorIDs,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"datum_id": datumID,
|
||||
"organization_id": organizationID,
|
||||
"created_at": time.Now(),
|
||||
"vendor_ids": vendorIDs,
|
||||
}
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
|
||||
@@ -21,16 +21,17 @@ import (
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
DocumentVersion struct {
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
DocumentID gid.GID `db:"document_id"`
|
||||
Title string `db:"title"`
|
||||
OwnerID gid.GID `db:"owner_id"`
|
||||
@@ -81,6 +82,7 @@ func (p *DocumentVersions) LoadByDocumentID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
document_id,
|
||||
title,
|
||||
owner_id,
|
||||
@@ -140,6 +142,7 @@ func (p *DocumentVersion) LoadByID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
document_id,
|
||||
title,
|
||||
owner_id,
|
||||
@@ -190,6 +193,7 @@ func (p DocumentVersion) Insert(
|
||||
INSERT INTO document_versions (
|
||||
tenant_id,
|
||||
id,
|
||||
organization_id,
|
||||
document_id,
|
||||
title,
|
||||
owner_id,
|
||||
@@ -204,6 +208,7 @@ INSERT INTO document_versions (
|
||||
VALUES (
|
||||
@tenant_id,
|
||||
@id,
|
||||
@organization_id,
|
||||
@document_id,
|
||||
@title,
|
||||
@owner_id,
|
||||
@@ -217,18 +222,19 @@ VALUES (
|
||||
)
|
||||
`
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"id": p.ID,
|
||||
"document_id": p.DocumentID,
|
||||
"title": p.Title,
|
||||
"owner_id": p.OwnerID,
|
||||
"version_number": p.VersionNumber,
|
||||
"classification": p.Classification,
|
||||
"content": p.Content,
|
||||
"changelog": p.Changelog,
|
||||
"status": p.Status,
|
||||
"created_at": p.CreatedAt,
|
||||
"updated_at": p.UpdatedAt,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"id": p.ID,
|
||||
"organization_id": p.OrganizationID,
|
||||
"document_id": p.DocumentID,
|
||||
"title": p.Title,
|
||||
"owner_id": p.OwnerID,
|
||||
"version_number": p.VersionNumber,
|
||||
"classification": p.Classification,
|
||||
"content": p.Content,
|
||||
"changelog": p.Changelog,
|
||||
"status": p.Status,
|
||||
"created_at": p.CreatedAt,
|
||||
"updated_at": p.UpdatedAt,
|
||||
}
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
@@ -264,6 +270,7 @@ func (p *DocumentVersion) LoadByDocumentIDAndVersionNumber(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
document_id,
|
||||
title,
|
||||
owner_id,
|
||||
@@ -316,6 +323,7 @@ func (p *DocumentVersion) LoadLatestVersion(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
document_id,
|
||||
title,
|
||||
owner_id,
|
||||
@@ -366,6 +374,7 @@ func (p *DocumentVersion) LoadLatestPublishedVersion(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
document_id,
|
||||
title,
|
||||
owner_id,
|
||||
|
||||
@@ -21,16 +21,17 @@ import (
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
DocumentVersionSignature struct {
|
||||
ID gid.GID `json:"id"`
|
||||
OrganizationID gid.GID `json:"-"`
|
||||
DocumentVersionID gid.GID `json:"document_version_id"`
|
||||
State DocumentVersionSignatureState `json:"state"`
|
||||
SignedBy gid.GID `json:"signed_by"`
|
||||
@@ -87,6 +88,7 @@ func (pvs *DocumentVersionSignature) LoadByDocumentVersionIDAndSignatory(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
document_version_id,
|
||||
state,
|
||||
signed_by,
|
||||
@@ -132,6 +134,7 @@ func (pvs *DocumentVersionSignature) LoadByID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
document_version_id,
|
||||
state,
|
||||
signed_by,
|
||||
@@ -175,6 +178,7 @@ func (pvs DocumentVersionSignature) Insert(
|
||||
INSERT INTO document_version_signatures (
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
document_version_id,
|
||||
state,
|
||||
signed_by,
|
||||
@@ -185,6 +189,7 @@ INSERT INTO document_version_signatures (
|
||||
) VALUES (
|
||||
@id,
|
||||
@tenant_id,
|
||||
@organization_id,
|
||||
@document_version_id,
|
||||
@state,
|
||||
@signed_by,
|
||||
@@ -198,6 +203,7 @@ INSERT INTO document_version_signatures (
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": pvs.ID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"organization_id": pvs.OrganizationID,
|
||||
"document_version_id": pvs.DocumentVersionID,
|
||||
"state": pvs.State,
|
||||
"signed_by": pvs.SignedBy,
|
||||
@@ -234,6 +240,7 @@ func (pvss *DocumentVersionSignatures) LoadByDocumentVersionID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
document_version_id,
|
||||
state,
|
||||
signed_by,
|
||||
@@ -346,6 +353,7 @@ func (pvss *DocumentVersionSignaturesWithPeople) LoadByDocumentVersionIDWithPeop
|
||||
WITH sigs AS (
|
||||
SELECT
|
||||
dvs.id,
|
||||
dvs.organization_id,
|
||||
dvs.tenant_id,
|
||||
dvs.document_version_id,
|
||||
dvs.state,
|
||||
@@ -367,6 +375,7 @@ WITH sigs AS (
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
document_version_id,
|
||||
state,
|
||||
signed_by,
|
||||
|
||||
@@ -20,9 +20,9 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
type (
|
||||
|
||||
@@ -68,3 +68,211 @@ const (
|
||||
UserAPIKeyMembershipEntityType uint16 = 44
|
||||
MeetingEntityType uint16 = 45
|
||||
)
|
||||
|
||||
type EntityInfo struct {
|
||||
Model string
|
||||
Table string
|
||||
}
|
||||
|
||||
var entityRegistry = map[uint16]EntityInfo{
|
||||
OrganizationEntityType: {
|
||||
Model: "Organization",
|
||||
Table: "organizations",
|
||||
},
|
||||
FrameworkEntityType: {
|
||||
Model: "Framework",
|
||||
Table: "frameworks",
|
||||
},
|
||||
MeasureEntityType: {
|
||||
Model: "Measure",
|
||||
Table: "measures",
|
||||
},
|
||||
TaskEntityType: {
|
||||
Model: "Task",
|
||||
Table: "tasks",
|
||||
},
|
||||
EvidenceEntityType: {
|
||||
Model: "Evidence",
|
||||
Table: "evidences",
|
||||
},
|
||||
ConnectorEntityType: {
|
||||
Model: "Connector",
|
||||
Table: "connectors",
|
||||
},
|
||||
VendorRiskAssessmentEntityType: {
|
||||
Model: "VendorRiskAssessment",
|
||||
Table: "vendor_risk_assessments",
|
||||
},
|
||||
VendorEntityType: {
|
||||
Model: "Vendor",
|
||||
Table: "vendors",
|
||||
},
|
||||
PeopleEntityType: {
|
||||
Model: "People",
|
||||
Table: "peoples",
|
||||
},
|
||||
VendorComplianceReportEntityType: {
|
||||
Model: "VendorComplianceReport",
|
||||
Table: "vendor_compliance_reports",
|
||||
},
|
||||
DocumentEntityType: {
|
||||
Model: "Document",
|
||||
Table: "documents",
|
||||
},
|
||||
UserEntityType: {
|
||||
Model: "User",
|
||||
Table: "auth_users",
|
||||
},
|
||||
SessionEntityType: {
|
||||
Model: "Session",
|
||||
Table: "auth_sessions",
|
||||
},
|
||||
EmailEntityType: {
|
||||
Model: "Email",
|
||||
Table: "auth_emails",
|
||||
},
|
||||
ControlEntityType: {
|
||||
Model: "Control",
|
||||
Table: "controls",
|
||||
},
|
||||
RiskEntityType: {
|
||||
Model: "Risk",
|
||||
Table: "risks",
|
||||
},
|
||||
DocumentVersionEntityType: {
|
||||
Model: "DocumentVersion",
|
||||
Table: "document_versions",
|
||||
},
|
||||
DocumentVersionSignatureEntityType: {
|
||||
Model: "DocumentVersionSignature",
|
||||
Table: "document_version_signatures",
|
||||
},
|
||||
AssetEntityType: {
|
||||
Model: "Asset",
|
||||
Table: "assets",
|
||||
},
|
||||
DatumEntityType: {
|
||||
Model: "Datum",
|
||||
Table: "data",
|
||||
},
|
||||
AuditEntityType: {
|
||||
Model: "Audit",
|
||||
Table: "audits",
|
||||
},
|
||||
ReportEntityType: {
|
||||
Model: "Report",
|
||||
Table: "reports",
|
||||
},
|
||||
TrustCenterEntityType: {
|
||||
Model: "TrustCenter",
|
||||
Table: "trust_centers",
|
||||
},
|
||||
TrustCenterAccessEntityType: {
|
||||
Model: "TrustCenterAccess",
|
||||
Table: "trust_center_accesses",
|
||||
},
|
||||
VendorBusinessAssociateAgreementEntityType: {
|
||||
Model: "VendorBusinessAssociateAgreement",
|
||||
Table: "vendor_business_associate_agreements",
|
||||
},
|
||||
FileEntityType: {
|
||||
Model: "File",
|
||||
Table: "files",
|
||||
},
|
||||
VendorContactEntityType: {
|
||||
Model: "VendorContact",
|
||||
Table: "vendor_contacts",
|
||||
},
|
||||
VendorDataPrivacyAgreementEntityType: {
|
||||
Model: "VendorDataPrivacyAgreement",
|
||||
Table: "vendor_data_privacy_agreements",
|
||||
},
|
||||
NonconformityEntityType: {
|
||||
Model: "Nonconformity",
|
||||
Table: "nonconformities",
|
||||
},
|
||||
ObligationEntityType: {
|
||||
Model: "Obligation",
|
||||
Table: "obligations",
|
||||
},
|
||||
VendorServiceEntityType: {
|
||||
Model: "VendorService",
|
||||
Table: "vendor_services",
|
||||
},
|
||||
SnapshotEntityType: {
|
||||
Model: "Snapshot",
|
||||
Table: "snapshots",
|
||||
},
|
||||
ContinualImprovementEntityType: {
|
||||
Model: "ContinualImprovement",
|
||||
Table: "continual_improvements",
|
||||
},
|
||||
ProcessingActivityEntityType: {
|
||||
Model: "ProcessingActivity",
|
||||
Table: "processing_activities",
|
||||
},
|
||||
ExportJobEntityType: {
|
||||
Model: "ExportJob",
|
||||
Table: "export_jobs",
|
||||
},
|
||||
TrustCenterReferenceEntityType: {
|
||||
Model: "TrustCenterReference",
|
||||
Table: "trust_center_references",
|
||||
},
|
||||
TrustCenterDocumentAccessEntityType: {
|
||||
Model: "",
|
||||
Table: "trust_center_document_accesses",
|
||||
},
|
||||
CustomDomainEntityType: {
|
||||
Model: "CustomDomain",
|
||||
Table: "custom_domains",
|
||||
},
|
||||
InvitationEntityType: {
|
||||
Model: "Invitation",
|
||||
Table: "authz_invitations",
|
||||
},
|
||||
MembershipEntityType: {
|
||||
Model: "Membership",
|
||||
Table: "authz_memberships",
|
||||
},
|
||||
SlackMessageEntityType: {
|
||||
Model: "SlackMessage",
|
||||
Table: "slack_messages",
|
||||
},
|
||||
TrustCenterFileEntityType: {
|
||||
Model: "TrustCenterFile",
|
||||
Table: "trust_center_files",
|
||||
},
|
||||
SAMLConfigurationEntityType: {
|
||||
Model: "SAMLConfiguration",
|
||||
Table: "auth_saml_configurations",
|
||||
},
|
||||
UserAPIKeyEntityType: {
|
||||
Model: "UserAPIKey",
|
||||
Table: "auth_user_api_keys",
|
||||
},
|
||||
UserAPIKeyMembershipEntityType: {
|
||||
Model: "UserAPIKeyMembership",
|
||||
Table: "authz_api_keys_memberships",
|
||||
},
|
||||
MeetingEntityType: {
|
||||
Model: "Meeting",
|
||||
Table: "meetings",
|
||||
},
|
||||
}
|
||||
|
||||
func EntityTable(entityType uint16) (string, bool) {
|
||||
info, ok := entityRegistry[entityType]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return info.Table, true
|
||||
}
|
||||
|
||||
func EntityModel(entityType uint16) (string, bool) {
|
||||
info, ok := entityRegistry[entityType]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return info.Model, true
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
type (
|
||||
Evidence struct {
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
MeasureID gid.GID `db:"measure_id"`
|
||||
TaskID *gid.GID `db:"task_id"`
|
||||
State EvidenceState `db:"state"`
|
||||
@@ -141,6 +142,7 @@ INSERT INTO
|
||||
evidences (
|
||||
tenant_id,
|
||||
id,
|
||||
organization_id,
|
||||
measure_id,
|
||||
task_id,
|
||||
reference_id,
|
||||
@@ -155,6 +157,7 @@ INSERT INTO
|
||||
VALUES (
|
||||
@tenant_id,
|
||||
@evidence_id,
|
||||
@organization_id,
|
||||
@measure_id,
|
||||
@task_id,
|
||||
@reference_id,
|
||||
@@ -171,6 +174,7 @@ VALUES (
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"evidence_id": e.ID,
|
||||
"organization_id": e.OrganizationID,
|
||||
"measure_id": e.MeasureID,
|
||||
"task_id": e.TaskID,
|
||||
"reference_id": e.ReferenceID,
|
||||
@@ -208,6 +212,7 @@ func (e *Evidence) LoadByID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
task_id,
|
||||
measure_id,
|
||||
reference_id,
|
||||
@@ -288,6 +293,7 @@ func (e *Evidences) LoadByMeasureID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
measure_id,
|
||||
task_id,
|
||||
reference_id,
|
||||
@@ -369,6 +375,7 @@ func (e *Evidences) LoadByTaskID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
measure_id,
|
||||
task_id,
|
||||
reference_id,
|
||||
|
||||
@@ -8,14 +8,15 @@ import (
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
type (
|
||||
ExportJob struct {
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
Type ExportJobType `db:"type"`
|
||||
Arguments json.RawMessage `db:"arguments"`
|
||||
Error *string `db:"error"`
|
||||
@@ -54,6 +55,7 @@ func (ej *ExportJob) Insert(
|
||||
q := `
|
||||
INSERT INTO export_jobs (
|
||||
id,
|
||||
organization_id,
|
||||
tenant_id,
|
||||
type,
|
||||
arguments,
|
||||
@@ -63,6 +65,7 @@ INSERT INTO export_jobs (
|
||||
created_at
|
||||
) VALUES (
|
||||
@id,
|
||||
@organization_id,
|
||||
@tenant_id,
|
||||
@type,
|
||||
@arguments,
|
||||
@@ -73,6 +76,7 @@ INSERT INTO export_jobs (
|
||||
)`
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": ej.ID,
|
||||
"organization_id": ej.OrganizationID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"type": ej.Type,
|
||||
"arguments": ej.Arguments,
|
||||
@@ -126,6 +130,7 @@ func (ej *ExportJob) LoadByID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
type,
|
||||
arguments,
|
||||
error,
|
||||
@@ -167,6 +172,7 @@ func (ej *ExportJob) LoadNextPendingForUpdateSkipLocked(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
type,
|
||||
arguments,
|
||||
error,
|
||||
|
||||
@@ -21,23 +21,24 @@ import (
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
type (
|
||||
File struct {
|
||||
ID gid.GID `db:"id"`
|
||||
BucketName string `db:"bucket_name"`
|
||||
MimeType string `db:"mime_type"`
|
||||
FileName string `db:"file_name"`
|
||||
FileKey string `db:"file_key"`
|
||||
FileSize int64 `db:"file_size"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
DeletedAt *time.Time `db:"deleted_at"`
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
BucketName string `db:"bucket_name"`
|
||||
MimeType string `db:"mime_type"`
|
||||
FileName string `db:"file_name"`
|
||||
FileKey string `db:"file_key"`
|
||||
FileSize int64 `db:"file_size"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
DeletedAt *time.Time `db:"deleted_at"`
|
||||
}
|
||||
|
||||
Files []*File
|
||||
@@ -68,6 +69,7 @@ func (f *File) LoadByID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
bucket_name,
|
||||
mime_type,
|
||||
file_name,
|
||||
@@ -119,6 +121,7 @@ INSERT INTO
|
||||
files (
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
bucket_name,
|
||||
mime_type,
|
||||
file_name,
|
||||
@@ -131,6 +134,7 @@ INSERT INTO
|
||||
VALUES (
|
||||
@file_id,
|
||||
@tenant_id,
|
||||
@organization_id,
|
||||
@bucket_name,
|
||||
@mime_type,
|
||||
@file_name,
|
||||
@@ -143,16 +147,17 @@ VALUES (
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"file_id": f.ID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"bucket_name": f.BucketName,
|
||||
"mime_type": f.MimeType,
|
||||
"file_name": f.FileName,
|
||||
"file_key": f.FileKey,
|
||||
"file_size": f.FileSize,
|
||||
"created_at": f.CreatedAt,
|
||||
"updated_at": f.UpdatedAt,
|
||||
"deleted_at": f.DeletedAt,
|
||||
"file_id": f.ID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"organization_id": f.OrganizationID,
|
||||
"bucket_name": f.BucketName,
|
||||
"mime_type": f.MimeType,
|
||||
"file_name": f.FileName,
|
||||
"file_key": f.FileKey,
|
||||
"file_size": f.FileSize,
|
||||
"created_at": f.CreatedAt,
|
||||
"updated_at": f.UpdatedAt,
|
||||
"deleted_at": f.DeletedAt,
|
||||
}
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ import (
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -33,7 +33,7 @@ type (
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
Email string `db:"email"`
|
||||
FullName string `db:"full_name"`
|
||||
Role Role `db:"role"`
|
||||
Role MembershipRole `db:"role"`
|
||||
Status InvitationStatus `db:"status"`
|
||||
ExpiresAt time.Time `db:"expires_at"`
|
||||
AcceptedAt *time.Time `db:"accepted_at"`
|
||||
@@ -43,11 +43,11 @@ type (
|
||||
Invitations []*Invitation
|
||||
|
||||
InvitationData struct {
|
||||
InvitationID gid.GID `json:"invitation_id"`
|
||||
OrganizationID gid.GID `json:"organization_id"`
|
||||
Email string `json:"email"`
|
||||
FullName string `json:"full_name"`
|
||||
Role Role `json:"role"`
|
||||
InvitationID gid.GID `json:"invitation_id"`
|
||||
OrganizationID gid.GID `json:"organization_id"`
|
||||
Email string `json:"email"`
|
||||
FullName string `json:"full_name"`
|
||||
Role MembershipRole `json:"role"`
|
||||
}
|
||||
|
||||
ErrInvitationNotFound struct {
|
||||
|
||||
@@ -19,20 +19,19 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Role string
|
||||
type MembershipRole string
|
||||
|
||||
const (
|
||||
RoleOwner Role = "OWNER"
|
||||
RoleAdmin Role = "ADMIN"
|
||||
RoleMember Role = "MEMBER"
|
||||
RoleViewer Role = "VIEWER"
|
||||
MembershipRoleOwner MembershipRole = "OWNER"
|
||||
MembershipRoleAdmin MembershipRole = "ADMIN"
|
||||
MembershipRoleViewer MembershipRole = "VIEWER"
|
||||
)
|
||||
|
||||
func (r Role) String() string {
|
||||
func (r MembershipRole) String() string {
|
||||
return string(r)
|
||||
}
|
||||
|
||||
func (r *Role) Scan(value any) error {
|
||||
func (r *MembershipRole) Scan(value any) error {
|
||||
var s string
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
@@ -40,24 +39,22 @@ func (r *Role) Scan(value any) error {
|
||||
case []byte:
|
||||
s = string(v)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type for Role: %T", value)
|
||||
return fmt.Errorf("unsupported type for MembershipRole: %T", value)
|
||||
}
|
||||
|
||||
switch s {
|
||||
case "OWNER":
|
||||
*r = RoleOwner
|
||||
*r = MembershipRoleOwner
|
||||
case "ADMIN":
|
||||
*r = RoleAdmin
|
||||
case "MEMBER":
|
||||
*r = RoleMember
|
||||
*r = MembershipRoleAdmin
|
||||
case "VIEWER":
|
||||
*r = RoleViewer
|
||||
*r = MembershipRoleViewer
|
||||
default:
|
||||
return fmt.Errorf("invalid Role value: %q", s)
|
||||
return fmt.Errorf("invalid MembershipRole value: %q", s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Role) Value() (driver.Value, error) {
|
||||
func (r MembershipRole) Value() (driver.Value, error) {
|
||||
return r.String(), nil
|
||||
}
|
||||
@@ -19,25 +19,26 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
Membership struct {
|
||||
ID gid.GID `db:"id"`
|
||||
UserID gid.GID `db:"user_id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
Role Role `db:"role"`
|
||||
FullName string `db:"full_name"`
|
||||
EmailAddress string `db:"email_address"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
ID gid.GID `db:"id"`
|
||||
UserID gid.GID `db:"user_id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
Role MembershipRole `db:"role"`
|
||||
FullName string `db:"full_name"`
|
||||
EmailAddress string `db:"email_address"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
Memberships []*Membership
|
||||
@@ -185,6 +186,83 @@ JOIN
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadRoleByUserAndEntityID loads a user's role by querying any entity to extract its organization_id
|
||||
func (m *Membership) LoadRoleByUserAndEntityID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
userID gid.GID,
|
||||
entityID gid.GID,
|
||||
) error {
|
||||
entityType := entityID.EntityType()
|
||||
|
||||
// For organization, the entity ID is the organization ID
|
||||
if entityType == OrganizationEntityType {
|
||||
return m.LoadByUserAndOrg(ctx, conn, scope, userID, entityID)
|
||||
}
|
||||
|
||||
tableName, ok := EntityTable(entityType)
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported entity type for role lookup: %d", entityType)
|
||||
}
|
||||
|
||||
// Build scope fragment with table alias to avoid ambiguity
|
||||
scopeFragment := scope.SQLFragment()
|
||||
// Replace column references with table-qualified versions
|
||||
scopeFragment = strings.ReplaceAll(scopeFragment, "tenant_id =", "m.tenant_id =")
|
||||
|
||||
query := fmt.Sprintf(`
|
||||
SELECT
|
||||
m.id,
|
||||
m.user_id,
|
||||
m.organization_id,
|
||||
m.role,
|
||||
m.created_at,
|
||||
m.updated_at
|
||||
FROM
|
||||
authz_memberships m
|
||||
INNER JOIN %s e ON e.id = @entity_id
|
||||
WHERE
|
||||
%s
|
||||
AND m.user_id = @user_id
|
||||
AND m.organization_id = e.organization_id
|
||||
LIMIT 1;
|
||||
`, tableName, scopeFragment)
|
||||
|
||||
args := pgx.NamedArgs{
|
||||
"user_id": userID,
|
||||
"entity_id": entityID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, query, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query membership by entity: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if !rows.Next() {
|
||||
return &ErrMembershipNotFound{UserID: userID, OrgID: entityID}
|
||||
}
|
||||
|
||||
var membership Membership
|
||||
err = rows.Scan(
|
||||
&membership.ID,
|
||||
&membership.UserID,
|
||||
&membership.OrganizationID,
|
||||
&membership.Role,
|
||||
&membership.CreatedAt,
|
||||
&membership.UpdatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot scan membership: %w", err)
|
||||
}
|
||||
|
||||
*m = membership
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Membership) LoadByUserAndOrg(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
@@ -195,17 +273,17 @@ func (m *Membership) LoadByUserAndOrg(
|
||||
query := `
|
||||
WITH mbr AS (
|
||||
SELECT
|
||||
id,
|
||||
user_id,
|
||||
organization_id,
|
||||
role,
|
||||
created_at,
|
||||
updated_at
|
||||
am.id,
|
||||
am.user_id,
|
||||
am.organization_id,
|
||||
am.role,
|
||||
am.created_at,
|
||||
am.updated_at
|
||||
FROM
|
||||
authz_memberships
|
||||
authz_memberships am
|
||||
WHERE
|
||||
user_id = @user_id
|
||||
AND organization_id = @organization_id
|
||||
am.user_id = @user_id
|
||||
AND am.organization_id = @organization_id
|
||||
AND %s
|
||||
)
|
||||
SELECT
|
||||
@@ -223,7 +301,12 @@ JOIN
|
||||
users u ON mbr.user_id = u.id
|
||||
`
|
||||
|
||||
query = fmt.Sprintf(query, scope.SQLFragment())
|
||||
// Build scope fragment with table alias
|
||||
scopeFragment := scope.SQLFragment()
|
||||
// Replace column references with table-qualified versions
|
||||
scopeFragment = strings.ReplaceAll(scopeFragment, "tenant_id =", "am.tenant_id =")
|
||||
|
||||
query = fmt.Sprintf(query, scopeFragment)
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"user_id": userID,
|
||||
@@ -468,67 +551,3 @@ WHERE
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func LoadUserIDsByOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
) ([]gid.GID, error) {
|
||||
query := `
|
||||
SELECT user_id
|
||||
FROM authz_memberships
|
||||
WHERE organization_id = @organization_id AND %s
|
||||
`
|
||||
query = fmt.Sprintf(query, scope.SQLFragment())
|
||||
args := pgx.StrictNamedArgs{"organization_id": organizationID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, query, args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot query memberships: %w", err)
|
||||
}
|
||||
|
||||
var userIDs []gid.GID
|
||||
for rows.Next() {
|
||||
var userID gid.GID
|
||||
if err := rows.Scan(&userID); err != nil {
|
||||
rows.Close()
|
||||
return nil, fmt.Errorf("cannot scan user_id: %w", err)
|
||||
}
|
||||
userIDs = append(userIDs, userID)
|
||||
}
|
||||
rows.Close()
|
||||
|
||||
return userIDs, nil
|
||||
}
|
||||
|
||||
func UpdateMembershipUserID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
oldUserID gid.GID,
|
||||
newUserID gid.GID,
|
||||
organizationID gid.GID,
|
||||
) error {
|
||||
query := `
|
||||
UPDATE authz_memberships
|
||||
SET user_id = @new_user_id, updated_at = @updated_at
|
||||
WHERE user_id = @old_user_id AND organization_id = @organization_id AND %s
|
||||
`
|
||||
query = fmt.Sprintf(query, scope.SQLFragment())
|
||||
args := pgx.StrictNamedArgs{
|
||||
"new_user_id": newUserID,
|
||||
"old_user_id": oldUserID,
|
||||
"organization_id": organizationID,
|
||||
"updated_at": time.Now(),
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, query, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update membership: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
210
pkg/coredata/migrations/20251109T214255Z.sql
Normal file
210
pkg/coredata/migrations/20251109T214255Z.sql
Normal file
@@ -0,0 +1,210 @@
|
||||
-- Set all existing memberships to OWNER role
|
||||
UPDATE authz_memberships SET role = 'OWNER';
|
||||
|
||||
ALTER TABLE controls ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE controls
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE controls.tenant_id = organizations.tenant_id
|
||||
AND controls.organization_id IS NULL;
|
||||
ALTER TABLE controls ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE evidences ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE evidences
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE evidences.tenant_id = organizations.tenant_id
|
||||
AND evidences.organization_id IS NULL;
|
||||
ALTER TABLE evidences ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE files ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE files
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE files.tenant_id = organizations.tenant_id
|
||||
AND files.organization_id IS NULL;
|
||||
ALTER TABLE files ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE document_versions ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE document_versions
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE document_versions.tenant_id = organizations.tenant_id
|
||||
AND document_versions.organization_id IS NULL;
|
||||
ALTER TABLE document_versions ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE document_version_signatures ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE document_version_signatures
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE document_version_signatures.tenant_id = organizations.tenant_id
|
||||
AND document_version_signatures.organization_id IS NULL;
|
||||
ALTER TABLE document_version_signatures ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE trust_center_references ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE trust_center_references
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE trust_center_references.tenant_id = organizations.tenant_id
|
||||
AND trust_center_references.organization_id IS NULL;
|
||||
ALTER TABLE trust_center_references ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE trust_center_accesses ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE trust_center_accesses
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE trust_center_accesses.tenant_id = organizations.tenant_id
|
||||
AND trust_center_accesses.organization_id IS NULL;
|
||||
ALTER TABLE trust_center_accesses ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE trust_center_document_accesses ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE trust_center_document_accesses
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE trust_center_document_accesses.tenant_id = organizations.tenant_id
|
||||
AND trust_center_document_accesses.organization_id IS NULL;
|
||||
ALTER TABLE trust_center_document_accesses ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE vendor_services ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE vendor_services
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE vendor_services.tenant_id = organizations.tenant_id
|
||||
AND vendor_services.organization_id IS NULL;
|
||||
ALTER TABLE vendor_services ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE vendor_contacts ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE vendor_contacts
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE vendor_contacts.tenant_id = organizations.tenant_id
|
||||
AND vendor_contacts.organization_id IS NULL;
|
||||
ALTER TABLE vendor_contacts ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE vendor_risk_assessments ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE vendor_risk_assessments
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE vendor_risk_assessments.tenant_id = organizations.tenant_id
|
||||
AND vendor_risk_assessments.organization_id IS NULL;
|
||||
ALTER TABLE vendor_risk_assessments ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE reports ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE reports
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE reports.tenant_id = organizations.tenant_id
|
||||
AND reports.organization_id IS NULL;
|
||||
ALTER TABLE reports ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE custom_domains ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE custom_domains
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE custom_domains.tenant_id = organizations.tenant_id
|
||||
AND custom_domains.organization_id IS NULL;
|
||||
ALTER TABLE custom_domains ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE asset_vendors ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE asset_vendors
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE asset_vendors.tenant_id = organizations.tenant_id
|
||||
AND asset_vendors.organization_id IS NULL;
|
||||
ALTER TABLE asset_vendors ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE authz_api_keys_memberships ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE authz_api_keys_memberships
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE authz_api_keys_memberships.tenant_id = organizations.tenant_id
|
||||
AND authz_api_keys_memberships.organization_id IS NULL;
|
||||
ALTER TABLE authz_api_keys_memberships ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE controls_audits ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE controls_audits
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE controls_audits.tenant_id = organizations.tenant_id
|
||||
AND controls_audits.organization_id IS NULL;
|
||||
ALTER TABLE controls_audits ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE controls_documents ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE controls_documents
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE controls_documents.tenant_id = organizations.tenant_id
|
||||
AND controls_documents.organization_id IS NULL;
|
||||
ALTER TABLE controls_documents ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE controls_measures ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE controls_measures
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE controls_measures.tenant_id = organizations.tenant_id
|
||||
AND controls_measures.organization_id IS NULL;
|
||||
ALTER TABLE controls_measures ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE controls_snapshots ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE controls_snapshots
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE controls_snapshots.tenant_id = organizations.tenant_id
|
||||
AND controls_snapshots.organization_id IS NULL;
|
||||
ALTER TABLE controls_snapshots ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE data_vendors ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE data_vendors
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE data_vendors.tenant_id = organizations.tenant_id
|
||||
AND data_vendors.organization_id IS NULL;
|
||||
ALTER TABLE data_vendors ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE export_jobs ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE export_jobs
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE export_jobs.tenant_id = organizations.tenant_id
|
||||
AND export_jobs.organization_id IS NULL;
|
||||
ALTER TABLE export_jobs ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE processing_activity_vendors ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE processing_activity_vendors
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE processing_activity_vendors.tenant_id = organizations.tenant_id
|
||||
AND processing_activity_vendors.organization_id IS NULL;
|
||||
ALTER TABLE processing_activity_vendors ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE risks_documents ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE risks_documents
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE risks_documents.tenant_id = organizations.tenant_id
|
||||
AND risks_documents.organization_id IS NULL;
|
||||
ALTER TABLE risks_documents ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE risks_measures ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE risks_measures
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE risks_measures.tenant_id = organizations.tenant_id
|
||||
AND risks_measures.organization_id IS NULL;
|
||||
ALTER TABLE risks_measures ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE risks_obligations ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE risks_obligations
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE risks_obligations.tenant_id = organizations.tenant_id
|
||||
AND risks_obligations.organization_id IS NULL;
|
||||
ALTER TABLE risks_obligations ALTER COLUMN organization_id SET NOT NULL;
|
||||
|
||||
ALTER TABLE vendor_compliance_reports ADD COLUMN IF NOT EXISTS organization_id TEXT;
|
||||
UPDATE vendor_compliance_reports
|
||||
SET organization_id = organizations.id
|
||||
FROM organizations
|
||||
WHERE vendor_compliance_reports.tenant_id = organizations.tenant_id
|
||||
AND vendor_compliance_reports.organization_id IS NULL;
|
||||
ALTER TABLE vendor_compliance_reports ALTER COLUMN organization_id SET NOT NULL;
|
||||
@@ -237,6 +237,63 @@ ORDER BY
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Organizations) LoadAllByUserIDWithRole(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
userID gid.GID,
|
||||
role MembershipRole,
|
||||
) error {
|
||||
q := `
|
||||
WITH user_org AS (
|
||||
SELECT
|
||||
organization_id
|
||||
FROM
|
||||
authz_memberships
|
||||
WHERE
|
||||
user_id = @user_id
|
||||
AND role = @role
|
||||
)
|
||||
SELECT
|
||||
tenant_id,
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
website_url,
|
||||
email,
|
||||
headquarter_address,
|
||||
custom_domain_id,
|
||||
logo_file_id,
|
||||
horizontal_logo_file_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
organizations
|
||||
INNER JOIN
|
||||
user_org ON organizations.id = user_org.organization_id
|
||||
ORDER BY
|
||||
name ASC
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"user_id": userID,
|
||||
"role": role,
|
||||
}
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query organizations: %w", err)
|
||||
}
|
||||
|
||||
organizations, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Organization])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect organizations: %w", err)
|
||||
}
|
||||
|
||||
*o = organizations
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Organizations) LoadAllByUserAPIKeyID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
|
||||
@@ -20,21 +20,22 @@ import (
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
Report struct {
|
||||
ID gid.GID `db:"id"`
|
||||
ObjectKey string `db:"object_key"`
|
||||
MimeType string `db:"mime_type"`
|
||||
Filename string `db:"filename"`
|
||||
Size int64 `db:"size"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
ObjectKey string `db:"object_key"`
|
||||
MimeType string `db:"mime_type"`
|
||||
Filename string `db:"filename"`
|
||||
Size int64 `db:"size"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
Reports []*Report
|
||||
@@ -49,6 +50,7 @@ func (r *Report) LoadByID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
object_key,
|
||||
mime_type,
|
||||
filename,
|
||||
@@ -92,6 +94,7 @@ func (r *Report) Insert(
|
||||
INSERT INTO reports (
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
object_key,
|
||||
mime_type,
|
||||
filename,
|
||||
@@ -101,6 +104,7 @@ INSERT INTO reports (
|
||||
) VALUES (
|
||||
@id,
|
||||
@tenant_id,
|
||||
@organization_id,
|
||||
@object_key,
|
||||
@mime_type,
|
||||
@filename,
|
||||
@@ -111,14 +115,15 @@ INSERT INTO reports (
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": r.ID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"object_key": r.ObjectKey,
|
||||
"mime_type": r.MimeType,
|
||||
"filename": r.Filename,
|
||||
"size": r.Size,
|
||||
"created_at": r.CreatedAt,
|
||||
"updated_at": r.UpdatedAt,
|
||||
"id": r.ID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"organization_id": r.OrganizationID,
|
||||
"object_key": r.ObjectKey,
|
||||
"mime_type": r.MimeType,
|
||||
"filename": r.Filename,
|
||||
"size": r.Size,
|
||||
"created_at": r.CreatedAt,
|
||||
"updated_at": r.UpdatedAt,
|
||||
}
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
|
||||
@@ -27,10 +27,11 @@ import (
|
||||
|
||||
type (
|
||||
RiskDocument struct {
|
||||
RiskID gid.GID `db:"risk_id"`
|
||||
DocumentID gid.GID `db:"document_id"`
|
||||
TenantID gid.TenantID `db:"tenant_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
RiskID gid.GID `db:"risk_id"`
|
||||
DocumentID gid.GID `db:"document_id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
TenantID gid.TenantID `db:"tenant_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
}
|
||||
|
||||
RiskDocuments []*RiskDocument
|
||||
@@ -46,22 +47,25 @@ INSERT INTO
|
||||
risks_documents (
|
||||
risk_id,
|
||||
document_id,
|
||||
organization_id,
|
||||
tenant_id,
|
||||
created_at
|
||||
)
|
||||
VALUES (
|
||||
@risk_id,
|
||||
@document_id,
|
||||
@organization_id,
|
||||
@tenant_id,
|
||||
@created_at
|
||||
);
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"risk_id": rp.RiskID,
|
||||
"document_id": rp.DocumentID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"created_at": rp.CreatedAt,
|
||||
"risk_id": rp.RiskID,
|
||||
"document_id": rp.DocumentID,
|
||||
"organization_id": rp.OrganizationID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"created_at": rp.CreatedAt,
|
||||
}
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
|
||||
@@ -20,17 +20,18 @@ import (
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
type (
|
||||
RiskMeasure struct {
|
||||
RiskID gid.GID `db:"risk_id"`
|
||||
MeasureID gid.GID `db:"measure_id"`
|
||||
TenantID gid.TenantID `db:"tenant_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
RiskID gid.GID `db:"risk_id"`
|
||||
MeasureID gid.GID `db:"measure_id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
TenantID gid.TenantID `db:"tenant_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
}
|
||||
|
||||
RiskMeasures []*RiskMeasure
|
||||
@@ -46,22 +47,25 @@ INSERT INTO
|
||||
risks_measures (
|
||||
risk_id,
|
||||
measure_id,
|
||||
organization_id,
|
||||
tenant_id,
|
||||
created_at
|
||||
)
|
||||
VALUES (
|
||||
@risk_id,
|
||||
@measure_id,
|
||||
@organization_id,
|
||||
@tenant_id,
|
||||
@created_at
|
||||
);
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"risk_id": rm.RiskID,
|
||||
"measure_id": rm.MeasureID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"created_at": rm.CreatedAt,
|
||||
"risk_id": rm.RiskID,
|
||||
"measure_id": rm.MeasureID,
|
||||
"organization_id": rm.OrganizationID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"created_at": rm.CreatedAt,
|
||||
}
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
|
||||
@@ -27,9 +27,10 @@ import (
|
||||
|
||||
type (
|
||||
RiskObligation struct {
|
||||
RiskID gid.GID `db:"risk_id"`
|
||||
ObligationID gid.GID `db:"obligation_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
RiskID gid.GID `db:"risk_id"`
|
||||
ObligationID gid.GID `db:"obligation_id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
}
|
||||
|
||||
RiskObligations []*RiskObligation
|
||||
@@ -44,21 +45,24 @@ func (ro RiskObligation) Insert(
|
||||
INSERT INTO risks_obligations (
|
||||
risk_id,
|
||||
obligation_id,
|
||||
organization_id,
|
||||
tenant_id,
|
||||
created_at
|
||||
) VALUES (
|
||||
@risk_id,
|
||||
@obligation_id,
|
||||
@organization_id,
|
||||
@tenant_id,
|
||||
@created_at
|
||||
)
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"risk_id": ro.RiskID,
|
||||
"obligation_id": ro.ObligationID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"created_at": ro.CreatedAt,
|
||||
"risk_id": ro.RiskID,
|
||||
"obligation_id": ro.ObligationID,
|
||||
"organization_id": ro.OrganizationID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"created_at": ro.CreatedAt,
|
||||
}
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
|
||||
@@ -21,9 +21,9 @@ import (
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
type (
|
||||
|
||||
@@ -22,16 +22,17 @@ import (
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
TrustCenterAccess struct {
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
TenantID gid.TenantID `db:"tenant_id"`
|
||||
TrustCenterID gid.GID `db:"trust_center_id"`
|
||||
Email string `db:"email"`
|
||||
@@ -82,6 +83,7 @@ func (tca *TrustCenterAccess) LoadByID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
tenant_id,
|
||||
trust_center_id,
|
||||
email,
|
||||
@@ -135,6 +137,7 @@ func (tca *TrustCenterAccess) LoadByTrustCenterIDAndEmail(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
tenant_id,
|
||||
trust_center_id,
|
||||
email,
|
||||
@@ -191,6 +194,7 @@ func (tca *TrustCenterAccess) Insert(
|
||||
INSERT INTO trust_center_accesses (
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
trust_center_id,
|
||||
email,
|
||||
name,
|
||||
@@ -201,6 +205,7 @@ INSERT INTO trust_center_accesses (
|
||||
) VALUES (
|
||||
@id,
|
||||
@tenant_id,
|
||||
@organization_id,
|
||||
@trust_center_id,
|
||||
@email,
|
||||
@name,
|
||||
@@ -214,6 +219,7 @@ INSERT INTO trust_center_accesses (
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": tca.ID,
|
||||
"tenant_id": tca.TenantID,
|
||||
"organization_id": tca.OrganizationID,
|
||||
"trust_center_id": tca.TrustCenterID,
|
||||
"email": tca.Email,
|
||||
"name": tca.Name,
|
||||
@@ -317,6 +323,7 @@ func (tcas *TrustCenterAccesses) LoadByTrustCenterID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
tenant_id,
|
||||
trust_center_id,
|
||||
email,
|
||||
|
||||
@@ -21,16 +21,17 @@ import (
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
TrustCenterDocumentAccess struct {
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
TrustCenterAccessID gid.GID `db:"trust_center_access_id"`
|
||||
DocumentID *gid.GID `db:"document_id"`
|
||||
ReportID *gid.GID `db:"report_id"`
|
||||
@@ -78,6 +79,7 @@ func (tcda *TrustCenterDocumentAccess) LoadByID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
@@ -127,6 +129,7 @@ func (tcda *TrustCenterDocumentAccess) LoadByTrustCenterAccessIDAndDocumentID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
@@ -177,6 +180,7 @@ func (tcda *TrustCenterDocumentAccess) LoadByTrustCenterAccessIDAndReportID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
@@ -226,6 +230,7 @@ func (tcda *TrustCenterDocumentAccess) Insert(
|
||||
INSERT INTO trust_center_document_accesses (
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
@@ -237,6 +242,7 @@ INSERT INTO trust_center_document_accesses (
|
||||
) VALUES (
|
||||
@id,
|
||||
@tenant_id,
|
||||
@organization_id,
|
||||
@trust_center_access_id,
|
||||
@document_id,
|
||||
@report_id,
|
||||
@@ -251,6 +257,7 @@ INSERT INTO trust_center_document_accesses (
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": tcda.ID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"organization_id": tcda.OrganizationID,
|
||||
"trust_center_access_id": tcda.TrustCenterAccessID,
|
||||
"document_id": tcda.DocumentID,
|
||||
"report_id": tcda.ReportID,
|
||||
@@ -512,6 +519,7 @@ final_items AS (
|
||||
SELECT
|
||||
COALESCE(tcda.id, ai.item_id) AS id,
|
||||
tcda.tenant_id,
|
||||
(SELECT organization_id FROM organization) AS organization_id,
|
||||
@trust_center_access_id AS trust_center_access_id,
|
||||
ai.document_id,
|
||||
ai.report_id,
|
||||
@@ -532,6 +540,7 @@ final_items AS (
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
@@ -576,6 +585,7 @@ func (tcdas *TrustCenterDocumentAccesses) LoadAllByTrustCenterAccessID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
trust_center_access_id,
|
||||
document_id,
|
||||
report_id,
|
||||
@@ -717,6 +727,7 @@ func (tcdas TrustCenterDocumentAccesses) BulkInsertDocumentAccesses(
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
trustCenterAccessID gid.GID,
|
||||
organizationID gid.GID,
|
||||
documentIDs []gid.GID,
|
||||
requested bool,
|
||||
createdAt time.Time,
|
||||
@@ -730,6 +741,7 @@ WITH document_access_data AS (
|
||||
SELECT
|
||||
generate_gid(decode_base64_unpadded(@tenant_id), @trust_center_document_access_entity_type) AS id,
|
||||
@tenant_id AS tenant_id,
|
||||
@organization_id AS organization_id,
|
||||
@trust_center_access_id AS trust_center_access_id,
|
||||
unnest(@document_ids::text[]) AS document_id,
|
||||
null::text AS report_id,
|
||||
@@ -740,7 +752,7 @@ WITH document_access_data AS (
|
||||
@updated_at::timestamptz AS updated_at
|
||||
)
|
||||
INSERT INTO trust_center_document_accesses (
|
||||
id, tenant_id, trust_center_access_id, document_id, report_id, trust_center_file_id, active, requested, created_at, updated_at
|
||||
id, tenant_id, organization_id, trust_center_access_id, document_id, report_id, trust_center_file_id, active, requested, created_at, updated_at
|
||||
)
|
||||
SELECT * FROM document_access_data
|
||||
ON CONFLICT DO NOTHING
|
||||
@@ -748,6 +760,7 @@ ON CONFLICT DO NOTHING
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"organization_id": organizationID,
|
||||
"trust_center_access_id": trustCenterAccessID,
|
||||
"document_ids": documentIDs,
|
||||
"trust_center_document_access_entity_type": TrustCenterDocumentAccessEntityType,
|
||||
@@ -768,6 +781,7 @@ func (tcdas TrustCenterDocumentAccesses) BulkInsertReportAccesses(
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
trustCenterAccessID gid.GID,
|
||||
organizationID gid.GID,
|
||||
reportIDs []gid.GID,
|
||||
requested bool,
|
||||
createdAt time.Time,
|
||||
@@ -781,6 +795,7 @@ WITH report_access_data AS (
|
||||
SELECT
|
||||
generate_gid(decode_base64_unpadded(@tenant_id), @trust_center_document_access_entity_type) AS id,
|
||||
@tenant_id AS tenant_id,
|
||||
@organization_id AS organization_id,
|
||||
@trust_center_access_id AS trust_center_access_id,
|
||||
null::text AS document_id,
|
||||
unnest(@report_ids::text[]) AS report_id,
|
||||
@@ -791,14 +806,15 @@ WITH report_access_data AS (
|
||||
@updated_at::timestamptz AS updated_at
|
||||
)
|
||||
INSERT INTO trust_center_document_accesses (
|
||||
id, tenant_id, trust_center_access_id, document_id, report_id, trust_center_file_id, active, requested, created_at, updated_at
|
||||
id, tenant_id, organization_id, trust_center_access_id, document_id, report_id, trust_center_file_id, active, requested, created_at, updated_at
|
||||
)
|
||||
SELECT * FROM report_access_data
|
||||
ON CONFLICT DO NOTHING
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"organization_id": organizationID,
|
||||
"trust_center_document_access_entity_type": TrustCenterDocumentAccessEntityType,
|
||||
"trust_center_access_id": trustCenterAccessID,
|
||||
"report_ids": reportIDs,
|
||||
@@ -903,6 +919,7 @@ func (tcdas TrustCenterDocumentAccesses) BulkInsertTrustCenterFileAccesses(
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
trustCenterAccessID gid.GID,
|
||||
organizationID gid.GID,
|
||||
trustCenterFileIDs []gid.GID,
|
||||
requested bool,
|
||||
createdAt time.Time,
|
||||
@@ -912,6 +929,7 @@ WITH trust_center_file_access_data AS (
|
||||
SELECT
|
||||
generate_gid(decode_base64_unpadded(@tenant_id), @trust_center_document_access_entity_type) AS id,
|
||||
@tenant_id AS tenant_id,
|
||||
@organization_id AS organization_id,
|
||||
@trust_center_access_id AS trust_center_access_id,
|
||||
null::text AS document_id,
|
||||
null::text AS report_id,
|
||||
@@ -922,14 +940,15 @@ WITH trust_center_file_access_data AS (
|
||||
@updated_at::timestamptz AS updated_at
|
||||
)
|
||||
INSERT INTO trust_center_document_accesses (
|
||||
id, tenant_id, trust_center_access_id, document_id, report_id, trust_center_file_id, active, requested, created_at, updated_at
|
||||
id, tenant_id, organization_id, trust_center_access_id, document_id, report_id, trust_center_file_id, active, requested, created_at, updated_at
|
||||
)
|
||||
SELECT * FROM trust_center_file_access_data
|
||||
ON CONFLICT DO NOTHING
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"organization_id": organizationID,
|
||||
"trust_center_document_access_entity_type": TrustCenterDocumentAccessEntityType,
|
||||
"trust_center_access_id": trustCenterAccessID,
|
||||
"trust_center_file_ids": trustCenterFileIDs,
|
||||
|
||||
@@ -30,15 +30,16 @@ import (
|
||||
|
||||
type (
|
||||
TrustCenterReference struct {
|
||||
ID gid.GID `db:"id"`
|
||||
TrustCenterID gid.GID `db:"trust_center_id"`
|
||||
Name string `db:"name"`
|
||||
Description *string `db:"description"`
|
||||
WebsiteURL string `db:"website_url"`
|
||||
LogoFileID gid.GID `db:"logo_file_id"`
|
||||
Rank int `db:"rank"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
TrustCenterID gid.GID `db:"trust_center_id"`
|
||||
Name string `db:"name"`
|
||||
Description *string `db:"description"`
|
||||
WebsiteURL string `db:"website_url"`
|
||||
LogoFileID gid.GID `db:"logo_file_id"`
|
||||
Rank int `db:"rank"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
TrustCenterReferences []*TrustCenterReference
|
||||
@@ -83,6 +84,7 @@ func (t *TrustCenterReference) LoadByID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
trust_center_id,
|
||||
name,
|
||||
description,
|
||||
@@ -128,6 +130,7 @@ INSERT INTO
|
||||
trust_center_references (
|
||||
tenant_id,
|
||||
id,
|
||||
organization_id,
|
||||
trust_center_id,
|
||||
name,
|
||||
description,
|
||||
@@ -140,6 +143,7 @@ INSERT INTO
|
||||
VALUES (
|
||||
@tenant_id,
|
||||
@id,
|
||||
@organization_id,
|
||||
@trust_center_id,
|
||||
@name,
|
||||
@description,
|
||||
@@ -155,6 +159,7 @@ RETURNING rank;
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"id": t.ID,
|
||||
"organization_id": t.OrganizationID,
|
||||
"trust_center_id": t.TrustCenterID,
|
||||
"name": t.Name,
|
||||
"description": t.Description,
|
||||
@@ -308,6 +313,7 @@ func (t *TrustCenterReferences) LoadByTrustCenterID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
trust_center_id,
|
||||
name,
|
||||
description,
|
||||
|
||||
@@ -28,16 +28,17 @@ import (
|
||||
|
||||
type (
|
||||
VendorComplianceReport struct {
|
||||
ID gid.GID
|
||||
VendorID gid.GID
|
||||
ReportDate time.Time
|
||||
ValidUntil *time.Time
|
||||
ReportName string
|
||||
ReportFileId *gid.GID
|
||||
SnapshotID *gid.GID
|
||||
SourceID *gid.GID
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
VendorID gid.GID `db:"vendor_id"`
|
||||
ReportDate time.Time `db:"report_date"`
|
||||
ValidUntil *time.Time `db:"valid_until"`
|
||||
ReportName string `db:"report_name"`
|
||||
ReportFileId *gid.GID `db:"report_file_id"`
|
||||
SnapshotID *gid.GID `db:"snapshot_id"`
|
||||
SourceID *gid.GID `db:"source_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
VendorComplianceReports []*VendorComplianceReport
|
||||
@@ -157,6 +158,7 @@ func (vcr *VendorComplianceReport) Insert(
|
||||
INSERT INTO
|
||||
vendor_compliance_reports (
|
||||
id,
|
||||
organization_id,
|
||||
tenant_id,
|
||||
vendor_id,
|
||||
report_date,
|
||||
@@ -168,6 +170,7 @@ INSERT INTO
|
||||
)
|
||||
VALUES (
|
||||
@id,
|
||||
@organization_id,
|
||||
@tenant_id,
|
||||
@vendor_id,
|
||||
@report_date,
|
||||
@@ -179,15 +182,16 @@ VALUES (
|
||||
)
|
||||
`
|
||||
args := pgx.NamedArgs{
|
||||
"id": vcr.ID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"vendor_id": vcr.VendorID,
|
||||
"report_date": vcr.ReportDate,
|
||||
"valid_until": vcr.ValidUntil,
|
||||
"report_name": vcr.ReportName,
|
||||
"report_file_id": vcr.ReportFileId,
|
||||
"created_at": vcr.CreatedAt,
|
||||
"updated_at": vcr.UpdatedAt,
|
||||
"id": vcr.ID,
|
||||
"organization_id": vcr.OrganizationID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"vendor_id": vcr.VendorID,
|
||||
"report_date": vcr.ReportDate,
|
||||
"valid_until": vcr.ValidUntil,
|
||||
"report_name": vcr.ReportName,
|
||||
"report_file_id": vcr.ReportFileId,
|
||||
"created_at": vcr.CreatedAt,
|
||||
"updated_at": vcr.UpdatedAt,
|
||||
}
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
|
||||
@@ -21,24 +21,25 @@ import (
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
VendorContact struct {
|
||||
ID gid.GID `db:"id"`
|
||||
VendorID gid.GID `db:"vendor_id"`
|
||||
FullName *string `db:"full_name"`
|
||||
Email *string `db:"email"`
|
||||
Phone *string `db:"phone"`
|
||||
Role *string `db:"role"`
|
||||
SnapshotID *gid.GID `db:"snapshot_id"`
|
||||
SourceID *gid.GID `db:"source_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
VendorID gid.GID `db:"vendor_id"`
|
||||
FullName *string `db:"full_name"`
|
||||
Email *string `db:"email"`
|
||||
Phone *string `db:"phone"`
|
||||
Role *string `db:"role"`
|
||||
SnapshotID *gid.GID `db:"snapshot_id"`
|
||||
SourceID *gid.GID `db:"source_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
VendorContacts []*VendorContact
|
||||
@@ -74,6 +75,7 @@ func (vc *VendorContact) LoadByID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
vendor_id,
|
||||
full_name,
|
||||
email,
|
||||
@@ -126,6 +128,7 @@ func (vc *VendorContacts) LoadByVendorID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
vendor_id,
|
||||
full_name,
|
||||
email,
|
||||
@@ -176,6 +179,7 @@ INSERT INTO
|
||||
vendor_contacts (
|
||||
tenant_id,
|
||||
id,
|
||||
organization_id,
|
||||
vendor_id,
|
||||
full_name,
|
||||
email,
|
||||
@@ -187,6 +191,7 @@ INSERT INTO
|
||||
VALUES (
|
||||
@tenant_id,
|
||||
@vendor_contact_id,
|
||||
@organization_id,
|
||||
@vendor_id,
|
||||
@full_name,
|
||||
@email,
|
||||
@@ -200,6 +205,7 @@ VALUES (
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"vendor_contact_id": vc.ID,
|
||||
"organization_id": vc.OrganizationID,
|
||||
"vendor_id": vc.VendorID,
|
||||
"full_name": vc.FullName,
|
||||
"email": vc.Email,
|
||||
|
||||
@@ -20,16 +20,17 @@ import (
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
// RiskAssessment represents a point-in-time risk assessment for a vendor
|
||||
VendorRiskAssessment struct {
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
VendorID gid.GID `db:"vendor_id"`
|
||||
ExpiresAt time.Time `db:"expires_at"`
|
||||
DataSensitivity DataSensitivity `db:"data_sensitivity"`
|
||||
@@ -66,6 +67,7 @@ INSERT INTO
|
||||
vendor_risk_assessments (
|
||||
tenant_id,
|
||||
id,
|
||||
organization_id,
|
||||
vendor_id,
|
||||
expires_at,
|
||||
data_sensitivity,
|
||||
@@ -77,6 +79,7 @@ INSERT INTO
|
||||
VALUES (
|
||||
@tenant_id,
|
||||
@id,
|
||||
@organization_id,
|
||||
@vendor_id,
|
||||
@expires_at,
|
||||
@data_sensitivity,
|
||||
@@ -90,6 +93,7 @@ VALUES (
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"id": r.ID,
|
||||
"organization_id": r.OrganizationID,
|
||||
"vendor_id": r.VendorID,
|
||||
"expires_at": r.ExpiresAt,
|
||||
"data_sensitivity": r.DataSensitivity,
|
||||
@@ -112,6 +116,7 @@ func (r *VendorRiskAssessment) LoadByID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
vendor_id,
|
||||
expires_at,
|
||||
data_sensitivity,
|
||||
@@ -160,6 +165,7 @@ func (r *VendorRiskAssessment) LoadLatestByVendorID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
vendor_id,
|
||||
expires_at,
|
||||
data_sensitivity,
|
||||
@@ -211,6 +217,7 @@ func (r *VendorRiskAssessments) LoadByVendorID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
vendor_id,
|
||||
expires_at,
|
||||
data_sensitivity,
|
||||
|
||||
@@ -21,22 +21,23 @@ import (
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
VendorService struct {
|
||||
ID gid.GID `db:"id"`
|
||||
VendorID gid.GID `db:"vendor_id"`
|
||||
Name string `db:"name"`
|
||||
Description *string `db:"description"`
|
||||
SnapshotID *gid.GID `db:"snapshot_id"`
|
||||
SourceID *gid.GID `db:"source_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
VendorID gid.GID `db:"vendor_id"`
|
||||
Name string `db:"name"`
|
||||
Description *string `db:"description"`
|
||||
SnapshotID *gid.GID `db:"snapshot_id"`
|
||||
SourceID *gid.GID `db:"source_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
VendorServices []*VendorService
|
||||
@@ -70,6 +71,7 @@ func (vs *VendorService) LoadByID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
vendor_id,
|
||||
name,
|
||||
description,
|
||||
@@ -120,6 +122,7 @@ func (vs *VendorServices) LoadByVendorID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
vendor_id,
|
||||
name,
|
||||
description,
|
||||
@@ -168,6 +171,7 @@ INSERT INTO
|
||||
vendor_services (
|
||||
tenant_id,
|
||||
id,
|
||||
organization_id,
|
||||
vendor_id,
|
||||
name,
|
||||
description,
|
||||
@@ -177,6 +181,7 @@ INSERT INTO
|
||||
VALUES (
|
||||
@tenant_id,
|
||||
@vendor_service_id,
|
||||
@organization_id,
|
||||
@vendor_id,
|
||||
@name,
|
||||
@description,
|
||||
@@ -188,6 +193,7 @@ VALUES (
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"vendor_service_id": vs.ID,
|
||||
"organization_id": vs.OrganizationID,
|
||||
"vendor_id": vs.VendorID,
|
||||
"name": vs.Name,
|
||||
"description": vs.Description,
|
||||
|
||||
@@ -203,7 +203,7 @@ func (s AssetService) Update(
|
||||
}
|
||||
|
||||
if req.VendorIDs != nil {
|
||||
if err := assetVendors.Merge(ctx, conn, s.svc.scope, asset.ID, req.VendorIDs); err != nil {
|
||||
if err := assetVendors.Merge(ctx, conn, s.svc.scope, asset.ID, asset.OrganizationID, req.VendorIDs); err != nil {
|
||||
return fmt.Errorf("cannot update asset vendors: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -248,7 +248,7 @@ func (s AssetService) Create(
|
||||
}
|
||||
|
||||
if len(req.VendorIDs) > 0 {
|
||||
if err := assetVendors.Insert(ctx, conn, s.svc.scope, asset.ID, req.VendorIDs); err != nil {
|
||||
if err := assetVendors.Insert(ctx, conn, s.svc.scope, asset.ID, asset.OrganizationID, req.VendorIDs); err != nil {
|
||||
return fmt.Errorf("cannot create asset vendors: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,13 +54,12 @@ type (
|
||||
func (ccr *CreateControlRequest) Validate() error {
|
||||
v := validator.New()
|
||||
|
||||
v.Check(ccr.ID, "id", validator.Required(), validator.GID(coredata.ControlEntityType))
|
||||
v.Check(ccr.FrameworkID, "framework_id", validator.Required(), validator.GID(coredata.FrameworkEntityType))
|
||||
v.Check(ccr.Name, "name", validator.Required(), validator.SafeTextNoNewLine(TitleMaxLength))
|
||||
v.Check(ccr.Description, "description", validator.Required(), validator.SafeText(ContentMaxLength))
|
||||
v.Check(ccr.SectionTitle, "section_title", validator.Required(), validator.SafeTextNoNewLine(TitleMaxLength))
|
||||
v.Check(ccr.Status, "status", validator.Required(), validator.OneOfSlice(coredata.ControlStatuses()))
|
||||
v.Check(ccr.ExclusionJustification, "exclusion_justification", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||
v.Check(ccr.ExclusionJustification, "exclusion_justification", validator.SafeText(TitleMaxLength))
|
||||
|
||||
return v.Error()
|
||||
}
|
||||
@@ -367,13 +366,6 @@ func (s ControlService) CreateMeasureMapping(
|
||||
controlID gid.GID,
|
||||
measureID gid.GID,
|
||||
) (*coredata.Control, *coredata.Measure, error) {
|
||||
controlMeasure := &coredata.ControlMeasure{
|
||||
ControlID: controlID,
|
||||
MeasureID: measureID,
|
||||
TenantID: s.svc.scope.GetTenantID(),
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
control := &coredata.Control{}
|
||||
measure := &coredata.Measure{}
|
||||
|
||||
@@ -388,6 +380,14 @@ func (s ControlService) CreateMeasureMapping(
|
||||
return fmt.Errorf("cannot load measure: %w", err)
|
||||
}
|
||||
|
||||
controlMeasure := &coredata.ControlMeasure{
|
||||
ControlID: controlID,
|
||||
MeasureID: measureID,
|
||||
OrganizationID: control.OrganizationID,
|
||||
TenantID: s.svc.scope.GetTenantID(),
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
return controlMeasure.Upsert(ctx, conn, s.svc.scope)
|
||||
},
|
||||
)
|
||||
@@ -454,10 +454,11 @@ func (s ControlService) CreateDocumentMapping(
|
||||
}
|
||||
|
||||
controlDocument := &coredata.ControlDocument{
|
||||
ControlID: control.ID,
|
||||
DocumentID: document.ID,
|
||||
TenantID: s.svc.scope.GetTenantID(),
|
||||
CreatedAt: time.Now(),
|
||||
ControlID: control.ID,
|
||||
DocumentID: document.ID,
|
||||
OrganizationID: control.OrganizationID,
|
||||
TenantID: s.svc.scope.GetTenantID(),
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if err := controlDocument.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
@@ -515,12 +516,6 @@ func (s ControlService) CreateAuditMapping(
|
||||
controlID gid.GID,
|
||||
auditID gid.GID,
|
||||
) (*coredata.Control, *coredata.Audit, error) {
|
||||
controlAudit := &coredata.ControlAudit{
|
||||
ControlID: controlID,
|
||||
AuditID: auditID,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
control := &coredata.Control{}
|
||||
audit := &coredata.Audit{}
|
||||
|
||||
@@ -535,6 +530,13 @@ func (s ControlService) CreateAuditMapping(
|
||||
return fmt.Errorf("cannot load audit: %w", err)
|
||||
}
|
||||
|
||||
controlAudit := &coredata.ControlAudit{
|
||||
ControlID: controlID,
|
||||
AuditID: auditID,
|
||||
OrganizationID: control.OrganizationID,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if err := controlAudit.Upsert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot create control audit mapping: %w", err)
|
||||
}
|
||||
@@ -620,12 +622,6 @@ func (s ControlService) CreateSnapshotMapping(
|
||||
controlID gid.GID,
|
||||
snapshotID gid.GID,
|
||||
) (*coredata.Control, *coredata.Snapshot, error) {
|
||||
controlSnapshot := &coredata.ControlSnapshot{
|
||||
ControlID: controlID,
|
||||
SnapshotID: snapshotID,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
control := &coredata.Control{}
|
||||
snapshot := &coredata.Snapshot{}
|
||||
|
||||
@@ -636,6 +632,13 @@ func (s ControlService) CreateSnapshotMapping(
|
||||
return fmt.Errorf("cannot load control: %w", err)
|
||||
}
|
||||
|
||||
controlSnapshot := &coredata.ControlSnapshot{
|
||||
ControlID: controlID,
|
||||
SnapshotID: snapshotID,
|
||||
OrganizationID: control.OrganizationID,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if err := snapshot.LoadByID(ctx, conn, s.svc.scope, snapshotID); err != nil {
|
||||
return fmt.Errorf("cannot load snapshot: %w", err)
|
||||
}
|
||||
@@ -751,6 +754,7 @@ func (s ControlService) Create(
|
||||
}
|
||||
|
||||
control.FrameworkID = framework.ID
|
||||
control.OrganizationID = framework.OrganizationID
|
||||
|
||||
return control.Insert(ctx, conn, s.svc.scope)
|
||||
},
|
||||
|
||||
@@ -78,6 +78,7 @@ func (s *CustomDomainService) CreateCustomDomain(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
domain = coredata.NewCustomDomain(s.svc.scope.GetTenantID(), req.Domain)
|
||||
domain.OrganizationID = req.OrganizationID
|
||||
|
||||
if err := domain.Insert(ctx, tx, s.svc.scope, s.encryptionKey); err != nil {
|
||||
return fmt.Errorf("cannot insert custom domain: %w", err)
|
||||
|
||||
@@ -205,7 +205,7 @@ func (s DatumService) Update(
|
||||
}
|
||||
|
||||
if req.VendorIDs != nil {
|
||||
if err := datumVendors.Merge(ctx, conn, s.svc.scope, datum.ID, req.VendorIDs); err != nil {
|
||||
if err := datumVendors.Merge(ctx, conn, s.svc.scope, datum.ID, datum.OrganizationID, req.VendorIDs); err != nil {
|
||||
return fmt.Errorf("cannot update data vendors: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -250,7 +250,7 @@ func (s DatumService) Create(
|
||||
}
|
||||
|
||||
if len(req.VendorIDs) > 0 {
|
||||
if err := datumVendors.Insert(ctx, conn, s.svc.scope, datum.ID, req.VendorIDs); err != nil {
|
||||
if err := datumVendors.Insert(ctx, conn, s.svc.scope, datum.ID, datum.OrganizationID, req.VendorIDs); err != nil {
|
||||
return fmt.Errorf("cannot create data vendors: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,6 +405,8 @@ func (s *DocumentService) Create(
|
||||
return fmt.Errorf("cannot insert document: %w", err)
|
||||
}
|
||||
|
||||
documentVersion.OrganizationID = organization.ID
|
||||
|
||||
if err := documentVersion.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot create document version: %w", err)
|
||||
}
|
||||
@@ -716,6 +718,11 @@ func (s *DocumentService) createSignatureRequestInTx(
|
||||
ignoreExisting bool,
|
||||
) (*coredata.DocumentVersionSignature, error) {
|
||||
signatory := &coredata.People{}
|
||||
documentVersion := &coredata.DocumentVersion{}
|
||||
|
||||
if err := documentVersion.LoadByID(ctx, tx, s.svc.scope, documentVersionID); err != nil {
|
||||
return nil, fmt.Errorf("cannot load document version: %w", err)
|
||||
}
|
||||
|
||||
if err := signatory.LoadByID(ctx, tx, s.svc.scope, signatoryID); err != nil {
|
||||
return nil, fmt.Errorf("cannot load signatory: %w", err)
|
||||
@@ -731,6 +738,7 @@ func (s *DocumentService) createSignatureRequestInTx(
|
||||
now := time.Now()
|
||||
documentVersionSignature := &coredata.DocumentVersionSignature{
|
||||
ID: documentVersionSignatureID,
|
||||
OrganizationID: documentVersion.OrganizationID,
|
||||
DocumentVersionID: documentVersionID,
|
||||
State: coredata.DocumentVersionSignatureStateRequested,
|
||||
RequestedAt: now,
|
||||
@@ -829,6 +837,7 @@ func (s *DocumentService) CreateDraft(
|
||||
}
|
||||
|
||||
draftVersion.ID = draftVersionID
|
||||
draftVersion.OrganizationID = document.OrganizationID
|
||||
draftVersion.DocumentID = documentID
|
||||
draftVersion.Title = document.Title
|
||||
draftVersion.OwnerID = document.OwnerID
|
||||
@@ -936,11 +945,14 @@ func (s *DocumentService) RequestExport(
|
||||
}
|
||||
|
||||
err := s.svc.pg.WithTx(ctx, func(conn pg.Conn) error {
|
||||
var organizationID gid.GID
|
||||
for _, documentID := range documentIDs {
|
||||
document := &coredata.Document{}
|
||||
if err := document.LoadByID(ctx, conn, s.svc.scope, documentID); err != nil {
|
||||
return fmt.Errorf("cannot load document %q: %w", documentID, err)
|
||||
}
|
||||
|
||||
organizationID = document.OrganizationID
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
@@ -959,6 +971,7 @@ func (s *DocumentService) RequestExport(
|
||||
|
||||
exportJob = &coredata.ExportJob{
|
||||
ID: exportJobID,
|
||||
OrganizationID: organizationID,
|
||||
Type: coredata.ExportJobTypeDocument,
|
||||
Arguments: argsJSON,
|
||||
Status: coredata.ExportJobStatusPending,
|
||||
|
||||
@@ -126,6 +126,7 @@ func (s EvidenceService) UploadMeasureEvidence(
|
||||
return fmt.Errorf("cannot upload or file: %w", err)
|
||||
}
|
||||
|
||||
evidence.OrganizationID = measure.OrganizationID
|
||||
evidence.EvidenceFileId = &file.ID
|
||||
evidence.MeasureID = req.MeasureID
|
||||
|
||||
|
||||
@@ -120,18 +120,30 @@ func (s FileService) UploadAndSaveFile(
|
||||
fileID := gid.New(s.svc.scope.GetTenantID(), coredata.FileEntityType)
|
||||
var file *coredata.File
|
||||
|
||||
// Extract organization ID from S3 metadata
|
||||
organizationIDStr, hasOrgID := s3Metadata["organization-id"]
|
||||
var organizationID gid.GID
|
||||
if hasOrgID {
|
||||
var err error
|
||||
organizationID, err = gid.ParseGID(organizationIDStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid organization-id in metadata: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
file = &coredata.File{
|
||||
ID: fileID,
|
||||
BucketName: s.svc.bucket,
|
||||
MimeType: mimeType,
|
||||
FileName: req.Filename,
|
||||
FileKey: objectKey.String(),
|
||||
FileSize: *headOutput.ContentLength,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
ID: fileID,
|
||||
OrganizationID: organizationID,
|
||||
BucketName: s.svc.bucket,
|
||||
MimeType: mimeType,
|
||||
FileName: req.Filename,
|
||||
FileKey: objectKey.String(),
|
||||
FileSize: *headOutput.ContentLength,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := file.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
|
||||
@@ -122,6 +122,7 @@ func (s FrameworkService) RequestExport(
|
||||
|
||||
exportJob = &coredata.ExportJob{
|
||||
ID: exportJobID,
|
||||
OrganizationID: framework.OrganizationID,
|
||||
Type: coredata.ExportJobTypeFramework,
|
||||
Arguments: argsJSON,
|
||||
Status: coredata.ExportJobStatusPending,
|
||||
@@ -518,14 +519,15 @@ func (s FrameworkService) Import(
|
||||
now := time.Now()
|
||||
description := control.Description
|
||||
control := &coredata.Control{
|
||||
ID: controlID,
|
||||
FrameworkID: frameworkID,
|
||||
SectionTitle: control.ID,
|
||||
Name: control.Name,
|
||||
Description: &description,
|
||||
Status: coredata.ControlStatusIncluded,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
ID: controlID,
|
||||
FrameworkID: frameworkID,
|
||||
OrganizationID: organization.ID,
|
||||
SectionTitle: control.ID,
|
||||
Name: control.Name,
|
||||
Description: &description,
|
||||
Status: coredata.ControlStatusIncluded,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := control.Insert(ctx, tx, s.svc.scope); err != nil {
|
||||
|
||||
@@ -386,15 +386,16 @@ func (s MeasureService) Import(
|
||||
continue
|
||||
}
|
||||
|
||||
controlMeasure := &coredata.ControlMeasure{
|
||||
ControlID: control.ID,
|
||||
MeasureID: measure.ID,
|
||||
CreatedAt: now,
|
||||
}
|
||||
controlMeasure := &coredata.ControlMeasure{
|
||||
ControlID: control.ID,
|
||||
MeasureID: measure.ID,
|
||||
OrganizationID: measure.OrganizationID,
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
if err := controlMeasure.Upsert(ctx, tx, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert control measure: %w", err)
|
||||
}
|
||||
if err := controlMeasure.Upsert(ctx, tx, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert control measure: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -221,9 +221,10 @@ func (s RiskService) CreateDocumentMapping(
|
||||
}
|
||||
|
||||
riskDocument := &coredata.RiskDocument{
|
||||
RiskID: risk.ID,
|
||||
DocumentID: document.ID,
|
||||
CreatedAt: time.Now(),
|
||||
RiskID: risk.ID,
|
||||
DocumentID: document.ID,
|
||||
OrganizationID: risk.OrganizationID,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
return riskDocument.Insert(ctx, conn, s.svc.scope)
|
||||
@@ -288,9 +289,10 @@ func (s RiskService) CreateMeasureMapping(
|
||||
}
|
||||
|
||||
riskMeasure := &coredata.RiskMeasure{
|
||||
RiskID: risk.ID,
|
||||
MeasureID: measure.ID,
|
||||
CreatedAt: time.Now(),
|
||||
RiskID: risk.ID,
|
||||
MeasureID: measure.ID,
|
||||
OrganizationID: risk.OrganizationID,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
return riskMeasure.Insert(ctx, conn, s.svc.scope)
|
||||
@@ -324,9 +326,10 @@ func (s RiskService) DeleteMeasureMapping(
|
||||
}
|
||||
|
||||
riskMeasure := &coredata.RiskMeasure{
|
||||
RiskID: riskID,
|
||||
MeasureID: measureID,
|
||||
CreatedAt: time.Now(),
|
||||
RiskID: riskID,
|
||||
MeasureID: measureID,
|
||||
OrganizationID: risk.OrganizationID,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
return riskMeasure.Delete(ctx, conn, s.svc.scope, risk.ID, measure.ID)
|
||||
@@ -360,9 +363,10 @@ func (s RiskService) CreateObligationMapping(
|
||||
}
|
||||
|
||||
riskObligation := &coredata.RiskObligation{
|
||||
RiskID: risk.ID,
|
||||
ObligationID: obligation.ID,
|
||||
CreatedAt: time.Now(),
|
||||
RiskID: risk.ID,
|
||||
ObligationID: obligation.ID,
|
||||
OrganizationID: risk.OrganizationID,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
return riskObligation.Insert(ctx, conn, s.svc.scope)
|
||||
|
||||
@@ -268,7 +268,7 @@ func (s *Service) ExportJob(ctx context.Context) error {
|
||||
return unknownTypeErr
|
||||
}
|
||||
|
||||
exportJob, buildErr := exportService.BuildAndUploadExport(ctx, exportJob.ID)
|
||||
updatedExportJob, buildErr := exportService.BuildAndUploadExport(ctx, exportJob.ID)
|
||||
if buildErr != nil {
|
||||
if err := s.commitFailedExport(ctx, exportJob, buildErr); err != nil {
|
||||
return fmt.Errorf(
|
||||
@@ -280,6 +280,7 @@ func (s *Service) ExportJob(ctx context.Context) error {
|
||||
}
|
||||
return fmt.Errorf("cannot build and upload %s export: %w", exportJob.Type, buildErr)
|
||||
}
|
||||
exportJob = updatedExportJob
|
||||
|
||||
if emailErr := exportService.SendExportEmail(ctx, *exportJob.FileID, exportJob.RecipientName, exportJob.RecipientEmail); emailErr != nil {
|
||||
if err := s.commitFailedExport(ctx, exportJob, emailErr); err != nil {
|
||||
|
||||
@@ -272,8 +272,14 @@ func (s TrustCenterAccessService) Create(
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
trustCenter := &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByID(ctx, tx, s.svc.scope, req.TrustCenterID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
}
|
||||
|
||||
access = &coredata.TrustCenterAccess{
|
||||
ID: gid.New(s.svc.scope.GetTenantID(), coredata.TrustCenterAccessEntityType),
|
||||
OrganizationID: trustCenter.OrganizationID,
|
||||
TenantID: s.svc.scope.GetTenantID(),
|
||||
TrustCenterID: req.TrustCenterID,
|
||||
Email: req.Email,
|
||||
@@ -332,9 +338,9 @@ func (s TrustCenterAccessService) Update(
|
||||
return fmt.Errorf("cannot update trust center access: %w", err)
|
||||
}
|
||||
|
||||
if err := s.upsertDocumentAccesses(ctx, tx, access.ID, req.DocumentIDs, req.ReportIDs, req.TrustCenterFileIDs, now); err != nil {
|
||||
return fmt.Errorf("cannot upsert document accesses: %w", err)
|
||||
}
|
||||
if err := s.upsertDocumentAccesses(ctx, tx, access.ID, access.OrganizationID, req.DocumentIDs, req.ReportIDs, req.TrustCenterFileIDs, now); err != nil {
|
||||
return fmt.Errorf("cannot upsert document accesses: %w", err)
|
||||
}
|
||||
|
||||
if req.ReportIDs != nil {
|
||||
if err := coredata.ActivateByReportIDs(ctx, tx, s.svc.scope, access.ID, req.ReportIDs, now); err != nil {
|
||||
@@ -387,6 +393,7 @@ func (s TrustCenterAccessService) upsertDocumentAccesses(
|
||||
ctx context.Context,
|
||||
tx pg.Conn,
|
||||
accessID gid.GID,
|
||||
organizationID gid.GID,
|
||||
documentIDs []gid.GID,
|
||||
reportIDs []gid.GID,
|
||||
trustCenterFileIDs []gid.GID,
|
||||
@@ -402,7 +409,7 @@ func (s TrustCenterAccessService) upsertDocumentAccesses(
|
||||
|
||||
if documentIDs != nil {
|
||||
var documentAccesses coredata.TrustCenterDocumentAccesses
|
||||
if err := documentAccesses.BulkInsertDocumentAccesses(ctx, tx, s.svc.scope, accessID, documentIDs, false, now); err != nil {
|
||||
if err := documentAccesses.BulkInsertDocumentAccesses(ctx, tx, s.svc.scope, accessID, organizationID, documentIDs, false, now); err != nil {
|
||||
return fmt.Errorf("cannot create document accesses: %w", err)
|
||||
}
|
||||
if err := coredata.ActivateByDocumentIDs(ctx, tx, s.svc.scope, accessID, documentIDs, now); err != nil {
|
||||
@@ -412,7 +419,7 @@ func (s TrustCenterAccessService) upsertDocumentAccesses(
|
||||
|
||||
if reportIDs != nil {
|
||||
var documentAccesses coredata.TrustCenterDocumentAccesses
|
||||
if err := documentAccesses.BulkInsertReportAccesses(ctx, tx, s.svc.scope, accessID, reportIDs, false, now); err != nil {
|
||||
if err := documentAccesses.BulkInsertReportAccesses(ctx, tx, s.svc.scope, accessID, organizationID, reportIDs, false, now); err != nil {
|
||||
return fmt.Errorf("cannot create report accesses: %w", err)
|
||||
}
|
||||
if err := coredata.ActivateByReportIDs(ctx, tx, s.svc.scope, accessID, reportIDs, now); err != nil {
|
||||
@@ -422,7 +429,7 @@ func (s TrustCenterAccessService) upsertDocumentAccesses(
|
||||
|
||||
if trustCenterFileIDs != nil {
|
||||
var documentAccesses coredata.TrustCenterDocumentAccesses
|
||||
if err := documentAccesses.BulkInsertTrustCenterFileAccesses(ctx, tx, s.svc.scope, accessID, trustCenterFileIDs, false, now); err != nil {
|
||||
if err := documentAccesses.BulkInsertTrustCenterFileAccesses(ctx, tx, s.svc.scope, accessID, organizationID, trustCenterFileIDs, false, now); err != nil {
|
||||
return fmt.Errorf("cannot create trust center file accesses: %w", err)
|
||||
}
|
||||
if err := coredata.ActivateByTrustCenterFileIDs(ctx, tx, s.svc.scope, accessID, trustCenterFileIDs, now); err != nil {
|
||||
|
||||
@@ -164,6 +164,11 @@ func (s TrustCenterReferenceService) Create(
|
||||
var logoKey string
|
||||
|
||||
err := s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
|
||||
trustCenter := &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByID(ctx, tx, s.svc.scope, req.TrustCenterID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
}
|
||||
|
||||
fileID, s3Key, err := s.uploadLogoFile(ctx, tx, req.LogoFile, referenceID, req.TrustCenterID, now)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot upload logo file: %w", err)
|
||||
@@ -171,14 +176,15 @@ func (s TrustCenterReferenceService) Create(
|
||||
logoKey = s3Key
|
||||
|
||||
reference = &coredata.TrustCenterReference{
|
||||
ID: referenceID,
|
||||
TrustCenterID: req.TrustCenterID,
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
WebsiteURL: req.WebsiteURL,
|
||||
LogoFileID: fileID,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
ID: referenceID,
|
||||
OrganizationID: trustCenter.OrganizationID,
|
||||
TrustCenterID: req.TrustCenterID,
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
WebsiteURL: req.WebsiteURL,
|
||||
LogoFileID: fileID,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := reference.Insert(ctx, tx, s.svc.scope); err != nil {
|
||||
|
||||
@@ -103,14 +103,15 @@ func (s VendorComplianceReportService) Upload(
|
||||
vendorComplianceReportID := gid.New(s.svc.scope.GetTenantID(), coredata.VendorComplianceReportEntityType)
|
||||
|
||||
vendorComplianceReport := &coredata.VendorComplianceReport{
|
||||
ID: vendorComplianceReportID,
|
||||
VendorID: vendorID,
|
||||
ReportDate: req.ReportDate,
|
||||
ValidUntil: req.ValidUntil,
|
||||
ReportName: req.ReportName,
|
||||
ReportFileId: &f.ID,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
ID: vendorComplianceReportID,
|
||||
OrganizationID: vendor.OrganizationID,
|
||||
VendorID: vendorID,
|
||||
ReportDate: req.ReportDate,
|
||||
ValidUntil: req.ValidUntil,
|
||||
ReportName: req.ReportName,
|
||||
ReportFileId: &f.ID,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
err = s.svc.pg.WithConn(
|
||||
|
||||
@@ -143,9 +143,16 @@ func (s VendorContactService) Create(
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
vendor := &coredata.Vendor{}
|
||||
if err := vendor.LoadByID(ctx, conn, s.svc.scope, req.VendorID); err != nil {
|
||||
return fmt.Errorf("cannot load vendor: %w", err)
|
||||
}
|
||||
|
||||
vendorContact.OrganizationID = vendor.OrganizationID
|
||||
|
||||
if err := vendorContact.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert vendor contact: %w", err)
|
||||
}
|
||||
|
||||
@@ -638,7 +638,13 @@ func (s VendorService) CreateRiskAssessment(
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
vendor := coredata.Vendor{ID: req.VendorID}
|
||||
vendor := coredata.Vendor{}
|
||||
if err := vendor.LoadByID(ctx, tx, s.svc.scope, req.VendorID); err != nil {
|
||||
return fmt.Errorf("cannot load vendor: %w", err)
|
||||
}
|
||||
|
||||
vendorRiskAssessment.OrganizationID = vendor.OrganizationID
|
||||
|
||||
if err := vendor.ExpireNonExpiredRiskAssessments(ctx, tx, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot expire vendor risk assessments: %w", err)
|
||||
}
|
||||
|
||||
@@ -133,9 +133,16 @@ func (s VendorServiceService) Create(
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
vendor := &coredata.Vendor{}
|
||||
if err := vendor.LoadByID(ctx, conn, s.svc.scope, req.VendorID); err != nil {
|
||||
return fmt.Errorf("cannot load vendor: %w", err)
|
||||
}
|
||||
|
||||
vendorService.OrganizationID = vendor.OrganizationID
|
||||
|
||||
if err := vendorService.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert vendor service: %w", err)
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ package api
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
"go.gearno.de/kit/httpserver"
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/auth"
|
||||
@@ -92,6 +92,11 @@ var (
|
||||
ErrMissingAuthzService = errors.New("server configuration requires a valid authz.Service instance")
|
||||
)
|
||||
|
||||
// GetConsoleSchema returns the GraphQL schema for the console API
|
||||
func GetConsoleSchema() *ast.Schema {
|
||||
return console_v1.GetSchema()
|
||||
}
|
||||
|
||||
func methodNotAllowed(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
|
||||
|
||||
@@ -17,6 +17,10 @@ resolver:
|
||||
autobind: []
|
||||
call_argument_directives_with_null: true
|
||||
|
||||
directives:
|
||||
mustBeAuthorized:
|
||||
skip_runtime: false
|
||||
|
||||
models:
|
||||
ID:
|
||||
model:
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/99designs/gqlgen/graphql/handler/transport"
|
||||
"github.com/99designs/gqlgen/graphql/playground"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
"go.gearno.de/crypto/uuid"
|
||||
"go.gearno.de/kit/httpserver"
|
||||
@@ -64,6 +65,7 @@ type (
|
||||
samlSvc *auth.SAMLService
|
||||
authCfg AuthConfig
|
||||
customDomainCname string
|
||||
schema *ast.Schema
|
||||
}
|
||||
|
||||
ctxKey struct{ name string }
|
||||
@@ -307,21 +309,32 @@ func NewMux(
|
||||
return r
|
||||
}
|
||||
|
||||
// GetSchema returns the parsed GraphQL schema for the console API
|
||||
// This is used by other services like authz to extract permissions from @mustBeAuthorized directives
|
||||
func GetSchema() *ast.Schema {
|
||||
execSchema := schema.NewExecutableSchema(schema.Config{})
|
||||
return execSchema.Schema()
|
||||
}
|
||||
|
||||
func graphqlHandler(logger *log.Logger, proboSvc *probo.Service, authSvc *auth.Service, authzSvc *authz.Service, samlSvc *auth.SAMLService, authCfg AuthConfig, customDomainCname string) http.HandlerFunc {
|
||||
var mb int64 = 1 << 20
|
||||
|
||||
es := schema.NewExecutableSchema(
|
||||
schema.Config{
|
||||
Resolvers: &Resolver{
|
||||
proboSvc: proboSvc,
|
||||
authSvc: authSvc,
|
||||
authzSvc: authzSvc,
|
||||
samlSvc: samlSvc,
|
||||
authCfg: authCfg,
|
||||
customDomainCname: customDomainCname,
|
||||
},
|
||||
// Parse the schema first to make it available to resolvers
|
||||
execSchema := schema.NewExecutableSchema(schema.Config{})
|
||||
|
||||
cfg := schema.Config{
|
||||
Resolvers: &Resolver{
|
||||
proboSvc: proboSvc,
|
||||
authSvc: authSvc,
|
||||
authzSvc: authzSvc,
|
||||
samlSvc: samlSvc,
|
||||
authCfg: authCfg,
|
||||
customDomainCname: customDomainCname,
|
||||
schema: execSchema.Schema(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
es := schema.NewExecutableSchema(cfg)
|
||||
srv := handler.New(es)
|
||||
srv.AddTransport(transport.POST{})
|
||||
srv.AddTransport(
|
||||
@@ -504,3 +517,14 @@ func validateTenantAccess(ctx context.Context, tenantID gid.TenantID) {
|
||||
panic(&authz.TenantAccessError{Message: "tenant not found"})
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Resolver) MustBeAuthorized(ctx context.Context, entityID gid.GID, action authz.Action) {
|
||||
user := UserFromContext(ctx)
|
||||
apiKey := UserAPIKeyFromContext(ctx)
|
||||
|
||||
authzSvc := r.AuthzService(ctx, entityID.TenantID())
|
||||
err := authzSvc.Authorize(ctx, user, apiKey, entityID, action)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,14 @@ type PageInfo {
|
||||
endCursor: CursorKey
|
||||
}
|
||||
|
||||
# Roles
|
||||
enum Role {
|
||||
OWNER
|
||||
ADMIN
|
||||
VIEWER
|
||||
FULL
|
||||
}
|
||||
|
||||
# Enums
|
||||
enum OrderDirection
|
||||
@goModel(model: "go.probo.inc/probo/pkg/page.OrderDirection") {
|
||||
@@ -83,13 +91,17 @@ enum InvitationStatus
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusExpired")
|
||||
}
|
||||
|
||||
enum Role @goModel(model: "go.probo.inc/probo/pkg/coredata.Role") {
|
||||
OWNER @goEnum(value: "go.probo.inc/probo/pkg/coredata.RoleOwner")
|
||||
ADMIN @goEnum(value: "go.probo.inc/probo/pkg/coredata.RoleAdmin")
|
||||
MEMBER @goEnum(value: "go.probo.inc/probo/pkg/coredata.RoleMember")
|
||||
VIEWER @goEnum(value: "go.probo.inc/probo/pkg/coredata.RoleViewer")
|
||||
enum MembershipRole @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipRole") {
|
||||
OWNER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleOwner")
|
||||
ADMIN @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAdmin")
|
||||
VIEWER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleViewer")
|
||||
}
|
||||
|
||||
enum APIRole @goModel(model: "go.probo.inc/probo/pkg/coredata.APIRole") {
|
||||
FULL @goEnum(value: "go.probo.inc/probo/pkg/coredata.APIRoleFull")
|
||||
}
|
||||
|
||||
|
||||
enum DocumentStatus
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentStatus") {
|
||||
DRAFT @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentStatusDraft")
|
||||
@@ -1662,7 +1674,7 @@ type Membership implements Node {
|
||||
id: ID!
|
||||
userID: ID!
|
||||
organizationID: ID!
|
||||
role: Role!
|
||||
role: MembershipRole!
|
||||
fullName: String!
|
||||
emailAddress: String!
|
||||
authMethod: UserAuthMethod! @goField(forceResolver: true)
|
||||
@@ -1674,7 +1686,7 @@ type Invitation implements Node {
|
||||
id: ID!
|
||||
email: String!
|
||||
fullName: String!
|
||||
role: Role!
|
||||
role: MembershipRole!
|
||||
status: InvitationStatus!
|
||||
expiresAt: Datetime!
|
||||
acceptedAt: Datetime
|
||||
@@ -1731,7 +1743,7 @@ type Vendor implements Node {
|
||||
): VendorComplianceReportConnection! @goField(forceResolver: true)
|
||||
|
||||
businessAssociateAgreement: VendorBusinessAssociateAgreement
|
||||
@goField(forceResolver: true)
|
||||
@goField(forceResolver: true)
|
||||
dataPrivacyAgreement: VendorDataPrivacyAgreement @goField(forceResolver: true)
|
||||
|
||||
contacts(
|
||||
@@ -1855,7 +1867,9 @@ type Framework implements Node {
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
|
||||
type Control implements Node {
|
||||
type Control implements Node @goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.Control"
|
||||
) {
|
||||
id: ID!
|
||||
sectionTitle: String!
|
||||
name: String!
|
||||
@@ -2349,7 +2363,7 @@ type TrustCenterReferenceEdge {
|
||||
node: TrustCenterReference!
|
||||
}
|
||||
|
||||
type TrustCenterFile implements Node {
|
||||
type TrustCenterFile {
|
||||
id: ID!
|
||||
name: String!
|
||||
category: String!
|
||||
@@ -2723,8 +2737,7 @@ type Mutation {
|
||||
# Organization mutations
|
||||
createOrganization(
|
||||
input: CreateOrganizationInput!
|
||||
): CreateOrganizationPayload!
|
||||
updateOrganization(
|
||||
): CreateOrganizationPayload! updateOrganization(
|
||||
input: UpdateOrganizationInput!
|
||||
): UpdateOrganizationPayload!
|
||||
updateOrganizationContext(
|
||||
@@ -2732,218 +2745,145 @@ type Mutation {
|
||||
): UpdateOrganizationContextPayload!
|
||||
deleteOrganizationHorizontalLogo(
|
||||
input: DeleteOrganizationHorizontalLogoInput!
|
||||
): DeleteOrganizationHorizontalLogoPayload!
|
||||
deleteOrganization(
|
||||
): DeleteOrganizationHorizontalLogoPayload! deleteOrganization(
|
||||
input: DeleteOrganizationInput!
|
||||
): DeleteOrganizationPayload!
|
||||
|
||||
updateTrustCenter(input: UpdateTrustCenterInput!): UpdateTrustCenterPayload!
|
||||
|
||||
uploadTrustCenterNDA(
|
||||
input: UploadTrustCenterNDAInput!
|
||||
): UploadTrustCenterNDAPayload!
|
||||
|
||||
deleteTrustCenterNDA(
|
||||
input: DeleteTrustCenterNDAInput!
|
||||
): DeleteTrustCenterNDAPayload!
|
||||
|
||||
# Trust Center Access CRUD mutations
|
||||
createTrustCenterAccess(
|
||||
input: CreateTrustCenterAccessInput!
|
||||
): CreateTrustCenterAccessPayload!
|
||||
|
||||
updateTrustCenterAccess(
|
||||
input: UpdateTrustCenterAccessInput!
|
||||
): UpdateTrustCenterAccessPayload!
|
||||
|
||||
deleteTrustCenterAccess(
|
||||
input: DeleteTrustCenterAccessInput!
|
||||
): DeleteTrustCenterAccessPayload!
|
||||
|
||||
# Trust Center Reference mutations
|
||||
createTrustCenterReference(
|
||||
input: CreateTrustCenterReferenceInput!
|
||||
): CreateTrustCenterReferencePayload!
|
||||
|
||||
updateTrustCenterReference(
|
||||
input: UpdateTrustCenterReferenceInput!
|
||||
): UpdateTrustCenterReferencePayload!
|
||||
|
||||
deleteTrustCenterReference(
|
||||
input: DeleteTrustCenterReferenceInput!
|
||||
): DeleteTrustCenterReferencePayload!
|
||||
|
||||
# Trust Center File mutations
|
||||
createTrustCenterFile(
|
||||
input: CreateTrustCenterFileInput!
|
||||
): CreateTrustCenterFilePayload!
|
||||
|
||||
updateTrustCenterFile(
|
||||
input: UpdateTrustCenterFileInput!
|
||||
): UpdateTrustCenterFilePayload!
|
||||
|
||||
getTrustCenterFile(
|
||||
input: GetTrustCenterFileInput!
|
||||
): GetTrustCenterFilePayload!
|
||||
|
||||
deleteTrustCenterFile(
|
||||
input: DeleteTrustCenterFileInput!
|
||||
): DeleteTrustCenterFilePayload!
|
||||
|
||||
# User mutations
|
||||
confirmEmail(input: ConfirmEmailInput!): ConfirmEmailPayload!
|
||||
inviteUser(input: InviteUserInput!): InviteUserPayload!
|
||||
acceptInvitation(input: AcceptInvitationInput!): AcceptInvitationPayload!
|
||||
deleteInvitation(input: DeleteInvitationInput!): DeleteInvitationPayload!
|
||||
removeMember(input: RemoveMemberInput!): RemoveMemberPayload!
|
||||
|
||||
deleteInvitation(input: DeleteInvitationInput!): DeleteInvitationPayload! removeMember(input: RemoveMemberInput!): RemoveMemberPayload!
|
||||
updateMembership(input: UpdateMembershipInput!): UpdateMembershipPayload!
|
||||
# People mutations
|
||||
createPeople(input: CreatePeopleInput!): CreatePeoplePayload!
|
||||
updatePeople(input: UpdatePeopleInput!): UpdatePeoplePayload!
|
||||
deletePeople(input: DeletePeopleInput!): DeletePeoplePayload!
|
||||
|
||||
createPeople(input: CreatePeopleInput!): CreatePeoplePayload! updatePeople(input: UpdatePeopleInput!): UpdatePeoplePayload! deletePeople(input: DeletePeopleInput!): DeletePeoplePayload!
|
||||
# Vendor mutations
|
||||
createVendor(input: CreateVendorInput!): CreateVendorPayload!
|
||||
updateVendor(input: UpdateVendorInput!): UpdateVendorPayload!
|
||||
deleteVendor(input: DeleteVendorInput!): DeleteVendorPayload!
|
||||
|
||||
createVendor(input: CreateVendorInput!): CreateVendorPayload! updateVendor(input: UpdateVendorInput!): UpdateVendorPayload! deleteVendor(input: DeleteVendorInput!): DeleteVendorPayload!
|
||||
# Vendor Contact mutations
|
||||
createVendorContact(
|
||||
input: CreateVendorContactInput!
|
||||
): CreateVendorContactPayload!
|
||||
updateVendorContact(
|
||||
): CreateVendorContactPayload! updateVendorContact(
|
||||
input: UpdateVendorContactInput!
|
||||
): UpdateVendorContactPayload!
|
||||
deleteVendorContact(
|
||||
): UpdateVendorContactPayload! deleteVendorContact(
|
||||
input: DeleteVendorContactInput!
|
||||
): DeleteVendorContactPayload!
|
||||
|
||||
# Vendor Service mutations
|
||||
createVendorService(
|
||||
input: CreateVendorServiceInput!
|
||||
): CreateVendorServicePayload!
|
||||
updateVendorService(
|
||||
): CreateVendorServicePayload! updateVendorService(
|
||||
input: UpdateVendorServiceInput!
|
||||
): UpdateVendorServicePayload!
|
||||
deleteVendorService(
|
||||
): UpdateVendorServicePayload! deleteVendorService(
|
||||
input: DeleteVendorServiceInput!
|
||||
): DeleteVendorServicePayload!
|
||||
|
||||
# Framework mutations
|
||||
createFramework(input: CreateFrameworkInput!): CreateFrameworkPayload!
|
||||
updateFramework(input: UpdateFrameworkInput!): UpdateFrameworkPayload!
|
||||
importFramework(input: ImportFrameworkInput!): ImportFrameworkPayload!
|
||||
deleteFramework(input: DeleteFrameworkInput!): DeleteFrameworkPayload!
|
||||
generateFrameworkStateOfApplicability(
|
||||
createFramework(input: CreateFrameworkInput!): CreateFrameworkPayload! updateFramework(input: UpdateFrameworkInput!): UpdateFrameworkPayload! importFramework(input: ImportFrameworkInput!): ImportFrameworkPayload! deleteFramework(input: DeleteFrameworkInput!): DeleteFrameworkPayload! generateFrameworkStateOfApplicability(
|
||||
input: GenerateFrameworkStateOfApplicabilityInput!
|
||||
): GenerateFrameworkStateOfApplicabilityPayload!
|
||||
exportFramework(input: ExportFrameworkInput!): ExportFrameworkPayload!
|
||||
|
||||
): GenerateFrameworkStateOfApplicabilityPayload! exportFramework(input: ExportFrameworkInput!): ExportFrameworkPayload!
|
||||
# Control mutations
|
||||
createControl(input: CreateControlInput!): CreateControlPayload!
|
||||
updateControl(input: UpdateControlInput!): UpdateControlPayload!
|
||||
deleteControl(input: DeleteControlInput!): DeleteControlPayload!
|
||||
|
||||
createControl(input: CreateControlInput!): CreateControlPayload! updateControl(input: UpdateControlInput!): UpdateControlPayload! deleteControl(input: DeleteControlInput!): DeleteControlPayload!
|
||||
# Measure mutations
|
||||
createMeasure(input: CreateMeasureInput!): CreateMeasurePayload!
|
||||
updateMeasure(input: UpdateMeasureInput!): UpdateMeasurePayload!
|
||||
importMeasure(input: ImportMeasureInput!): ImportMeasurePayload!
|
||||
deleteMeasure(input: DeleteMeasureInput!): DeleteMeasurePayload!
|
||||
|
||||
createMeasure(input: CreateMeasureInput!): CreateMeasurePayload! updateMeasure(input: UpdateMeasureInput!): UpdateMeasurePayload! importMeasure(input: ImportMeasureInput!): ImportMeasurePayload! deleteMeasure(input: DeleteMeasureInput!): DeleteMeasurePayload!
|
||||
# Control mutations
|
||||
createControlMeasureMapping(
|
||||
input: CreateControlMeasureMappingInput!
|
||||
): CreateControlMeasureMappingPayload!
|
||||
createControlDocumentMapping(
|
||||
): CreateControlMeasureMappingPayload! createControlDocumentMapping(
|
||||
input: CreateControlDocumentMappingInput!
|
||||
): CreateControlDocumentMappingPayload!
|
||||
deleteControlMeasureMapping(
|
||||
): CreateControlDocumentMappingPayload! deleteControlMeasureMapping(
|
||||
input: DeleteControlMeasureMappingInput!
|
||||
): DeleteControlMeasureMappingPayload!
|
||||
deleteControlDocumentMapping(
|
||||
): DeleteControlMeasureMappingPayload! deleteControlDocumentMapping(
|
||||
input: DeleteControlDocumentMappingInput!
|
||||
): DeleteControlDocumentMappingPayload!
|
||||
createControlAuditMapping(
|
||||
): DeleteControlDocumentMappingPayload! createControlAuditMapping(
|
||||
input: CreateControlAuditMappingInput!
|
||||
): CreateControlAuditMappingPayload!
|
||||
deleteControlAuditMapping(
|
||||
): CreateControlAuditMappingPayload! deleteControlAuditMapping(
|
||||
input: DeleteControlAuditMappingInput!
|
||||
): DeleteControlAuditMappingPayload!
|
||||
createControlSnapshotMapping(
|
||||
): DeleteControlAuditMappingPayload! createControlSnapshotMapping(
|
||||
input: CreateControlSnapshotMappingInput!
|
||||
): CreateControlSnapshotMappingPayload!
|
||||
deleteControlSnapshotMapping(
|
||||
): CreateControlSnapshotMappingPayload! deleteControlSnapshotMapping(
|
||||
input: DeleteControlSnapshotMappingInput!
|
||||
): DeleteControlSnapshotMappingPayload!
|
||||
|
||||
# Task mutations
|
||||
createTask(input: CreateTaskInput!): CreateTaskPayload!
|
||||
updateTask(input: UpdateTaskInput!): UpdateTaskPayload!
|
||||
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
|
||||
assignTask(input: AssignTaskInput!): AssignTaskPayload!
|
||||
unassignTask(input: UnassignTaskInput!): UnassignTaskPayload!
|
||||
|
||||
createTask(input: CreateTaskInput!): CreateTaskPayload! updateTask(input: UpdateTaskInput!): UpdateTaskPayload! deleteTask(input: DeleteTaskInput!): DeleteTaskPayload! assignTask(input: AssignTaskInput!): AssignTaskPayload! unassignTask(input: UnassignTaskInput!): UnassignTaskPayload!
|
||||
# Risk mutations
|
||||
createRisk(input: CreateRiskInput!): CreateRiskPayload!
|
||||
updateRisk(input: UpdateRiskInput!): UpdateRiskPayload!
|
||||
deleteRisk(input: DeleteRiskInput!): DeleteRiskPayload!
|
||||
createRiskMeasureMapping(
|
||||
createRisk(input: CreateRiskInput!): CreateRiskPayload! updateRisk(input: UpdateRiskInput!): UpdateRiskPayload! deleteRisk(input: DeleteRiskInput!): DeleteRiskPayload! createRiskMeasureMapping(
|
||||
input: CreateRiskMeasureMappingInput!
|
||||
): CreateRiskMeasureMappingPayload!
|
||||
deleteRiskMeasureMapping(
|
||||
): CreateRiskMeasureMappingPayload! deleteRiskMeasureMapping(
|
||||
input: DeleteRiskMeasureMappingInput!
|
||||
): DeleteRiskMeasureMappingPayload!
|
||||
|
||||
createRiskDocumentMapping(
|
||||
input: CreateRiskDocumentMappingInput!
|
||||
): CreateRiskDocumentMappingPayload!
|
||||
deleteRiskDocumentMapping(
|
||||
): CreateRiskDocumentMappingPayload! deleteRiskDocumentMapping(
|
||||
input: DeleteRiskDocumentMappingInput!
|
||||
): DeleteRiskDocumentMappingPayload!
|
||||
|
||||
createRiskObligationMapping(
|
||||
input: CreateRiskObligationMappingInput!
|
||||
): CreateRiskObligationMappingPayload!
|
||||
deleteRiskObligationMapping(
|
||||
): CreateRiskObligationMappingPayload! deleteRiskObligationMapping(
|
||||
input: DeleteRiskObligationMappingInput!
|
||||
): DeleteRiskObligationMappingPayload!
|
||||
|
||||
# Evidence mutations
|
||||
deleteEvidence(input: DeleteEvidenceInput!): DeleteEvidencePayload!
|
||||
uploadMeasureEvidence(
|
||||
deleteEvidence(input: DeleteEvidenceInput!): DeleteEvidencePayload! uploadMeasureEvidence(
|
||||
input: UploadMeasureEvidenceInput!
|
||||
): UploadMeasureEvidencePayload!
|
||||
|
||||
# Vendor Compliance Report mutations
|
||||
uploadVendorComplianceReport(
|
||||
input: UploadVendorComplianceReportInput!
|
||||
): UploadVendorComplianceReportPayload!
|
||||
deleteVendorComplianceReport(
|
||||
): UploadVendorComplianceReportPayload! deleteVendorComplianceReport(
|
||||
input: DeleteVendorComplianceReportInput!
|
||||
): DeleteVendorComplianceReportPayload!
|
||||
|
||||
# Vendor Business Associate Agreement mutations
|
||||
uploadVendorBusinessAssociateAgreement(
|
||||
input: UploadVendorBusinessAssociateAgreementInput!
|
||||
): UploadVendorBusinessAssociateAgreementPayload!
|
||||
updateVendorBusinessAssociateAgreement(
|
||||
): UploadVendorBusinessAssociateAgreementPayload! updateVendorBusinessAssociateAgreement(
|
||||
input: UpdateVendorBusinessAssociateAgreementInput!
|
||||
): UpdateVendorBusinessAssociateAgreementPayload!
|
||||
deleteVendorBusinessAssociateAgreement(
|
||||
): UpdateVendorBusinessAssociateAgreementPayload! deleteVendorBusinessAssociateAgreement(
|
||||
input: DeleteVendorBusinessAssociateAgreementInput!
|
||||
): DeleteVendorBusinessAssociateAgreementPayload!
|
||||
|
||||
# Vendor Data Privacy Agreement mutations
|
||||
uploadVendorDataPrivacyAgreement(
|
||||
input: UploadVendorDataPrivacyAgreementInput!
|
||||
): UploadVendorDataPrivacyAgreementPayload!
|
||||
updateVendorDataPrivacyAgreement(
|
||||
): UploadVendorDataPrivacyAgreementPayload! updateVendorDataPrivacyAgreement(
|
||||
input: UpdateVendorDataPrivacyAgreementInput!
|
||||
): UpdateVendorDataPrivacyAgreementPayload!
|
||||
deleteVendorDataPrivacyAgreement(
|
||||
): UpdateVendorDataPrivacyAgreementPayload! deleteVendorDataPrivacyAgreement(
|
||||
input: DeleteVendorDataPrivacyAgreementInput!
|
||||
): DeleteVendorDataPrivacyAgreementPayload!
|
||||
|
||||
# Document mutations
|
||||
createDocument(input: CreateDocumentInput!): CreateDocumentPayload!
|
||||
updateDocument(input: UpdateDocumentInput!): UpdateDocumentPayload!
|
||||
@@ -2954,134 +2894,85 @@ type Mutation {
|
||||
deleteMeeting(input: DeleteMeetingInput!): DeleteMeetingPayload!
|
||||
publishDocumentVersion(
|
||||
input: PublishDocumentVersionInput!
|
||||
): PublishDocumentVersionPayload!
|
||||
bulkPublishDocumentVersions(
|
||||
): PublishDocumentVersionPayload! bulkPublishDocumentVersions(
|
||||
input: BulkPublishDocumentVersionsInput!
|
||||
): BulkPublishDocumentVersionsPayload!
|
||||
bulkDeleteDocuments(
|
||||
): BulkPublishDocumentVersionsPayload! bulkDeleteDocuments(
|
||||
input: BulkDeleteDocumentsInput!
|
||||
): BulkDeleteDocumentsPayload!
|
||||
bulkExportDocuments(
|
||||
): BulkDeleteDocumentsPayload! bulkExportDocuments(
|
||||
input: BulkExportDocumentsInput!
|
||||
): BulkExportDocumentsPayload!
|
||||
generateDocumentChangelog(
|
||||
): BulkExportDocumentsPayload! generateDocumentChangelog(
|
||||
input: GenerateDocumentChangelogInput!
|
||||
): GenerateDocumentChangelogPayload!
|
||||
createDraftDocumentVersion(
|
||||
): GenerateDocumentChangelogPayload! createDraftDocumentVersion(
|
||||
input: CreateDraftDocumentVersionInput!
|
||||
): CreateDraftDocumentVersionPayload!
|
||||
deleteDraftDocumentVersion(
|
||||
): CreateDraftDocumentVersionPayload! deleteDraftDocumentVersion(
|
||||
input: DeleteDraftDocumentVersionInput!
|
||||
): DeleteDraftDocumentVersionPayload!
|
||||
updateDocumentVersion(
|
||||
): DeleteDraftDocumentVersionPayload! updateDocumentVersion(
|
||||
input: UpdateDocumentVersionInput!
|
||||
): UpdateDocumentVersionPayload!
|
||||
requestSignature(input: RequestSignatureInput!): RequestSignaturePayload!
|
||||
bulkRequestSignatures(
|
||||
): UpdateDocumentVersionPayload! requestSignature(input: RequestSignatureInput!): RequestSignaturePayload! bulkRequestSignatures(
|
||||
input: BulkRequestSignaturesInput!
|
||||
): BulkRequestSignaturesPayload!
|
||||
sendSigningNotifications(
|
||||
): BulkRequestSignaturesPayload! sendSigningNotifications(
|
||||
input: SendSigningNotificationsInput!
|
||||
): SendSigningNotificationsPayload!
|
||||
cancelSignatureRequest(
|
||||
): SendSigningNotificationsPayload! cancelSignatureRequest(
|
||||
input: CancelSignatureRequestInput!
|
||||
): CancelSignatureRequestPayload!
|
||||
exportDocumentVersionPDF(
|
||||
): CancelSignatureRequestPayload! exportDocumentVersionPDF(
|
||||
input: ExportDocumentVersionPDFInput!
|
||||
): ExportDocumentVersionPDFPayload!
|
||||
|
||||
createVendorRiskAssessment(
|
||||
input: CreateVendorRiskAssessmentInput!
|
||||
): CreateVendorRiskAssessmentPayload!
|
||||
|
||||
assessVendor(input: AssessVendorInput!): AssessVendorPayload!
|
||||
|
||||
createAsset(input: CreateAssetInput!): CreateAssetPayload!
|
||||
updateAsset(input: UpdateAssetInput!): UpdateAssetPayload!
|
||||
deleteAsset(input: DeleteAssetInput!): DeleteAssetPayload!
|
||||
|
||||
createDatum(input: CreateDatumInput!): CreateDatumPayload!
|
||||
updateDatum(input: UpdateDatumInput!): UpdateDatumPayload!
|
||||
deleteDatum(input: DeleteDatumInput!): DeleteDatumPayload!
|
||||
|
||||
createAudit(input: CreateAuditInput!): CreateAuditPayload!
|
||||
updateAudit(input: UpdateAuditInput!): UpdateAuditPayload!
|
||||
deleteAudit(input: DeleteAuditInput!): DeleteAuditPayload!
|
||||
uploadAuditReport(input: UploadAuditReportInput!): UploadAuditReportPayload!
|
||||
deleteAuditReport(input: DeleteAuditReportInput!): DeleteAuditReportPayload!
|
||||
|
||||
createAsset(input: CreateAssetInput!): CreateAssetPayload! updateAsset(input: UpdateAssetInput!): UpdateAssetPayload! deleteAsset(input: DeleteAssetInput!): DeleteAssetPayload!
|
||||
createDatum(input: CreateDatumInput!): CreateDatumPayload! updateDatum(input: UpdateDatumInput!): UpdateDatumPayload! deleteDatum(input: DeleteDatumInput!): DeleteDatumPayload!
|
||||
createAudit(input: CreateAuditInput!): CreateAuditPayload! updateAudit(input: UpdateAuditInput!): UpdateAuditPayload! deleteAudit(input: DeleteAuditInput!): DeleteAuditPayload! uploadAuditReport(input: UploadAuditReportInput!): UploadAuditReportPayload! deleteAuditReport(input: DeleteAuditReportInput!): DeleteAuditReportPayload!
|
||||
# Nonconformity mutations
|
||||
createNonconformity(
|
||||
input: CreateNonconformityInput!
|
||||
): CreateNonconformityPayload!
|
||||
updateNonconformity(
|
||||
): CreateNonconformityPayload! updateNonconformity(
|
||||
input: UpdateNonconformityInput!
|
||||
): UpdateNonconformityPayload!
|
||||
deleteNonconformity(
|
||||
): UpdateNonconformityPayload! deleteNonconformity(
|
||||
input: DeleteNonconformityInput!
|
||||
): DeleteNonconformityPayload!
|
||||
|
||||
# Obligation mutations
|
||||
createObligation(input: CreateObligationInput!): CreateObligationPayload!
|
||||
updateObligation(input: UpdateObligationInput!): UpdateObligationPayload!
|
||||
deleteObligation(input: DeleteObligationInput!): DeleteObligationPayload!
|
||||
|
||||
createObligation(input: CreateObligationInput!): CreateObligationPayload! updateObligation(input: UpdateObligationInput!): UpdateObligationPayload! deleteObligation(input: DeleteObligationInput!): DeleteObligationPayload!
|
||||
# Continual Improvement mutations
|
||||
createContinualImprovement(
|
||||
input: CreateContinualImprovementInput!
|
||||
): CreateContinualImprovementPayload!
|
||||
updateContinualImprovement(
|
||||
): CreateContinualImprovementPayload! updateContinualImprovement(
|
||||
input: UpdateContinualImprovementInput!
|
||||
): UpdateContinualImprovementPayload!
|
||||
deleteContinualImprovement(
|
||||
): UpdateContinualImprovementPayload! deleteContinualImprovement(
|
||||
input: DeleteContinualImprovementInput!
|
||||
): DeleteContinualImprovementPayload!
|
||||
|
||||
# Processing Activity mutations
|
||||
createProcessingActivity(
|
||||
input: CreateProcessingActivityInput!
|
||||
): CreateProcessingActivityPayload!
|
||||
updateProcessingActivity(
|
||||
): CreateProcessingActivityPayload! updateProcessingActivity(
|
||||
input: UpdateProcessingActivityInput!
|
||||
): UpdateProcessingActivityPayload!
|
||||
deleteProcessingActivity(
|
||||
): UpdateProcessingActivityPayload! deleteProcessingActivity(
|
||||
input: DeleteProcessingActivityInput!
|
||||
): DeleteProcessingActivityPayload!
|
||||
|
||||
# Snapshot mutations
|
||||
createSnapshot(input: CreateSnapshotInput!): CreateSnapshotPayload!
|
||||
deleteSnapshot(input: DeleteSnapshotInput!): DeleteSnapshotPayload!
|
||||
|
||||
createSnapshot(input: CreateSnapshotInput!): CreateSnapshotPayload! deleteSnapshot(input: DeleteSnapshotInput!): DeleteSnapshotPayload!
|
||||
# Custom Domain mutations
|
||||
createCustomDomain(
|
||||
input: CreateCustomDomainInput!
|
||||
): CreateCustomDomainPayload!
|
||||
deleteCustomDomain(
|
||||
): CreateCustomDomainPayload! deleteCustomDomain(
|
||||
input: DeleteCustomDomainInput!
|
||||
): DeleteCustomDomainPayload!
|
||||
|
||||
# SAML Configuration mutations (OWNER/ADMIN only)
|
||||
# Step 1: Initiate domain verification (creates SAML config with unverified domain)
|
||||
initiateDomainVerification(
|
||||
input: InitiateDomainVerificationInput!
|
||||
): InitiateDomainVerificationPayload!
|
||||
|
||||
# Step 2: Verify domain ownership via DNS TXT record
|
||||
verifyDomain(input: VerifyDomainInput!): VerifyDomainPayload!
|
||||
|
||||
# Step 3: Configure SAML (only allowed after domain is verified)
|
||||
createSAMLConfiguration(
|
||||
input: CreateSAMLConfigurationInput!
|
||||
): CreateSAMLConfigurationPayload!
|
||||
updateSAMLConfiguration(
|
||||
): CreateSAMLConfigurationPayload! updateSAMLConfiguration(
|
||||
input: UpdateSAMLConfigurationInput!
|
||||
): UpdateSAMLConfigurationPayload!
|
||||
deleteSAMLConfiguration(
|
||||
): UpdateSAMLConfigurationPayload! deleteSAMLConfiguration(
|
||||
input: DeleteSAMLConfigurationInput!
|
||||
): DeleteSAMLConfigurationPayload!
|
||||
enableSAML(input: EnableSAMLInput!): EnableSAMLPayload!
|
||||
disableSAML(input: DisableSAMLInput!): DisableSAMLPayload!
|
||||
}
|
||||
): DeleteSAMLConfigurationPayload! enableSAML(input: EnableSAMLInput!): EnableSAMLPayload! disableSAML(input: DisableSAMLInput!): DisableSAMLPayload!}
|
||||
|
||||
# Input Types
|
||||
input GenerateFrameworkStateOfApplicabilityInput {
|
||||
@@ -3629,6 +3520,7 @@ input InviteUserInput {
|
||||
organizationId: ID!
|
||||
email: String!
|
||||
fullName: String!
|
||||
role: MembershipRole!
|
||||
createPeople: Boolean!
|
||||
}
|
||||
|
||||
@@ -3645,6 +3537,12 @@ input RemoveMemberInput {
|
||||
memberId: ID!
|
||||
}
|
||||
|
||||
input UpdateMembershipInput {
|
||||
organizationId: ID!
|
||||
memberId: ID!
|
||||
role: MembershipRole!
|
||||
}
|
||||
|
||||
input CreateControlInput {
|
||||
frameworkId: ID!
|
||||
sectionTitle: String!
|
||||
@@ -4214,6 +4112,10 @@ type RemoveMemberPayload {
|
||||
deletedMemberId: ID!
|
||||
}
|
||||
|
||||
type UpdateMembershipPayload {
|
||||
membership: Membership!
|
||||
}
|
||||
|
||||
input VendorRiskAssessmentOrder {
|
||||
field: VendorRiskAssessmentOrderField!
|
||||
direction: OrderDirection!
|
||||
@@ -4264,7 +4166,7 @@ type DeleteMeasurePayload {
|
||||
deletedMeasureId: ID!
|
||||
}
|
||||
|
||||
type DocumentVersion implements Node {
|
||||
type DocumentVersion implements Node @goModel(model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentVersion") {
|
||||
id: ID!
|
||||
document: Document! @goField(forceResolver: true)
|
||||
status: DocumentStatus!
|
||||
@@ -4549,7 +4451,7 @@ type DeleteAssetPayload {
|
||||
deletedAssetId: ID!
|
||||
}
|
||||
|
||||
type Datum implements Node {
|
||||
type Datum implements Node @goModel(model: "go.probo.inc/probo/pkg/server/api/console/v1/types.Datum") {
|
||||
id: ID!
|
||||
snapshotId: ID
|
||||
name: String!
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,25 +1,33 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.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 (
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type Control struct {
|
||||
ID gid.GID `json:"id"`
|
||||
OrganizationID gid.GID `json:"-"`
|
||||
SectionTitle string `json:"sectionTitle"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Status coredata.ControlStatus `json:"status"`
|
||||
ExclusionJustification *string `json:"exclusionJustification,omitempty"`
|
||||
Framework *Framework `json:"framework"`
|
||||
Measures *MeasureConnection `json:"measures"`
|
||||
Documents *DocumentConnection `json:"documents"`
|
||||
Audits *AuditConnection `json:"audits"`
|
||||
Snapshots *SnapshotConnection `json:"snapshots"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (Control) IsNode() {}
|
||||
func (c Control) GetID() gid.GID { return c.ID }
|
||||
|
||||
type (
|
||||
ControlOrderBy OrderBy[coredata.ControlOrderField]
|
||||
|
||||
@@ -36,42 +44,42 @@ type (
|
||||
|
||||
func NewControlConnection(
|
||||
p *page.Page[*coredata.Control, coredata.ControlOrderField],
|
||||
parentType any,
|
||||
resolver any,
|
||||
parentID gid.GID,
|
||||
filters *coredata.ControlFilter,
|
||||
filter *coredata.ControlFilter,
|
||||
) *ControlConnection {
|
||||
var edges = make([]*ControlEdge, len(p.Data))
|
||||
|
||||
for i := range edges {
|
||||
edges[i] = NewControlEdge(p.Data[i], p.Cursor.OrderBy.Field)
|
||||
edges := make([]*ControlEdge, len(p.Data))
|
||||
for i, control := range p.Data {
|
||||
edges[i] = NewControlEdge(control, p.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &ControlConnection{
|
||||
Edges: edges,
|
||||
PageInfo: *NewPageInfo(p),
|
||||
|
||||
Resolver: parentType,
|
||||
Resolver: resolver,
|
||||
ParentID: parentID,
|
||||
Filters: filters,
|
||||
Filters: filter,
|
||||
}
|
||||
}
|
||||
|
||||
func NewControlEdge(c *coredata.Control, orderBy coredata.ControlOrderField) *ControlEdge {
|
||||
return &ControlEdge{
|
||||
Cursor: c.CursorKey(orderBy),
|
||||
Node: NewControl(c),
|
||||
}
|
||||
}
|
||||
|
||||
func NewControl(c *coredata.Control) *Control {
|
||||
func NewControl(control *coredata.Control) *Control {
|
||||
return &Control{
|
||||
ID: c.ID,
|
||||
SectionTitle: c.SectionTitle,
|
||||
Name: c.Name,
|
||||
Description: c.Description,
|
||||
Status: c.Status,
|
||||
ExclusionJustification: c.ExclusionJustification,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: c.UpdatedAt,
|
||||
ID: control.ID,
|
||||
OrganizationID: control.OrganizationID,
|
||||
SectionTitle: control.SectionTitle,
|
||||
Name: control.Name,
|
||||
Description: control.Description,
|
||||
Status: control.Status,
|
||||
ExclusionJustification: control.ExclusionJustification,
|
||||
CreatedAt: control.CreatedAt,
|
||||
UpdatedAt: control.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewControlEdge(control *coredata.Control, orderField coredata.ControlOrderField) *ControlEdge {
|
||||
return &ControlEdge{
|
||||
Node: NewControl(control),
|
||||
Cursor: control.CursorKey(orderField),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,29 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type Datum struct {
|
||||
ID gid.GID `json:"id"`
|
||||
OrganizationID gid.GID `json:"-"`
|
||||
SnapshotID *gid.GID `json:"snapshotId,omitempty"`
|
||||
Name string `json:"name"`
|
||||
DataClassification coredata.DataClassification `json:"dataClassification"`
|
||||
Owner *People `json:"owner"`
|
||||
Vendors *VendorConnection `json:"vendors"`
|
||||
Organization *Organization `json:"organization"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (Datum) IsNode() {}
|
||||
func (this Datum) GetID() gid.GID { return this.ID }
|
||||
|
||||
type (
|
||||
DatumOrderBy OrderBy[coredata.DatumOrderField]
|
||||
|
||||
@@ -58,12 +76,12 @@ func NewDataConnection(
|
||||
func NewDatum(d *coredata.Datum) *Datum {
|
||||
return &Datum{
|
||||
ID: d.ID,
|
||||
OrganizationID: d.OrganizationID,
|
||||
Name: d.Name,
|
||||
SnapshotID: d.SnapshotID,
|
||||
DataClassification: d.DataClassification,
|
||||
CreatedAt: d.CreatedAt,
|
||||
UpdatedAt: d.UpdatedAt,
|
||||
Organization: &Organization{ID: d.OrganizationID},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,14 +15,40 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
DocumentVersion struct {
|
||||
ID gid.GID `json:"id"`
|
||||
OrganizationID gid.GID `json:"-"`
|
||||
Document *Document `json:"document"`
|
||||
Status coredata.DocumentStatus `json:"status"`
|
||||
Version int `json:"version"`
|
||||
Content string `json:"content"`
|
||||
Changelog string `json:"changelog"`
|
||||
Title string `json:"title"`
|
||||
Classification coredata.DocumentClassification `json:"classification"`
|
||||
Owner *People `json:"owner"`
|
||||
Signatures *DocumentVersionSignatureConnection `json:"signatures"`
|
||||
PublishedAt *time.Time `json:"publishedAt"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
DocumentVersionOrderBy OrderBy[coredata.DocumentVersionOrderField]
|
||||
)
|
||||
|
||||
func (DocumentVersion) IsNode() {}
|
||||
|
||||
func (d DocumentVersion) GetID() gid.GID {
|
||||
return d.ID
|
||||
}
|
||||
|
||||
func NewDocumentVersionConnection(page *page.Page[*coredata.DocumentVersion, coredata.DocumentVersionOrderField]) *DocumentVersionConnection {
|
||||
edges := make([]*DocumentVersionEdge, len(page.Data))
|
||||
for i, documentVersion := range page.Data {
|
||||
@@ -55,6 +81,7 @@ func NewDocumentVersionEdge(documentVersion *coredata.DocumentVersion, orderBy c
|
||||
func NewDocumentVersion(documentVersion *coredata.DocumentVersion) *DocumentVersion {
|
||||
return &DocumentVersion{
|
||||
ID: documentVersion.ID,
|
||||
OrganizationID: documentVersion.OrganizationID,
|
||||
Version: documentVersion.VersionNumber,
|
||||
Title: documentVersion.Title,
|
||||
Content: documentVersion.Content,
|
||||
|
||||
@@ -59,6 +59,7 @@ func NewProcessingActivity(par *coredata.ProcessingActivity) *ProcessingActivity
|
||||
return &ProcessingActivity{
|
||||
ID: par.ID,
|
||||
SnapshotID: par.SnapshotID,
|
||||
SourceID: par.SourceID,
|
||||
Name: par.Name,
|
||||
Purpose: par.Purpose,
|
||||
DataSubjectCategory: par.DataSubjectCategory,
|
||||
|
||||
@@ -30,6 +30,7 @@ func NewTrustCenterAccess(tca *coredata.TrustCenterAccess) *TrustCenterAccess {
|
||||
HasAcceptedNonDisclosureAgreement: tca.HasAcceptedNonDisclosureAgreement,
|
||||
CreatedAt: tca.CreatedAt,
|
||||
UpdatedAt: tca.UpdatedAt,
|
||||
LastTokenExpiresAt: tca.LastTokenExpiresAt,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ type (
|
||||
|
||||
TrustCenterDocumentAccess struct {
|
||||
ID gid.GID `json:"id"`
|
||||
OrganizationID gid.GID `json:"-"`
|
||||
Active bool `json:"active"`
|
||||
Requested bool `json:"requested"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
@@ -55,6 +56,7 @@ type (
|
||||
func NewTrustCenterDocumentAccess(tcda *coredata.TrustCenterDocumentAccess) *TrustCenterDocumentAccess {
|
||||
return &TrustCenterDocumentAccess{
|
||||
ID: tcda.ID,
|
||||
OrganizationID: tcda.OrganizationID,
|
||||
Active: tcda.Active,
|
||||
Requested: tcda.Requested,
|
||||
CreatedAt: tcda.CreatedAt,
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
@@ -174,25 +178,6 @@ type ContinualImprovementFilter struct {
|
||||
SnapshotID *gid.GID `json:"snapshotId,omitempty"`
|
||||
}
|
||||
|
||||
type Control struct {
|
||||
ID gid.GID `json:"id"`
|
||||
SectionTitle string `json:"sectionTitle"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Status coredata.ControlStatus `json:"status"`
|
||||
ExclusionJustification *string `json:"exclusionJustification,omitempty"`
|
||||
Framework *Framework `json:"framework"`
|
||||
Measures *MeasureConnection `json:"measures"`
|
||||
Documents *DocumentConnection `json:"documents"`
|
||||
Audits *AuditConnection `json:"audits"`
|
||||
Snapshots *SnapshotConnection `json:"snapshots"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (Control) IsNode() {}
|
||||
func (this Control) GetID() gid.GID { return this.ID }
|
||||
|
||||
type ControlEdge struct {
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *Control `json:"node"`
|
||||
@@ -685,21 +670,6 @@ type DNSRecordInstruction struct {
|
||||
Purpose string `json:"purpose"`
|
||||
}
|
||||
|
||||
type Datum struct {
|
||||
ID gid.GID `json:"id"`
|
||||
SnapshotID *gid.GID `json:"snapshotId,omitempty"`
|
||||
Name string `json:"name"`
|
||||
DataClassification coredata.DataClassification `json:"dataClassification"`
|
||||
Owner *People `json:"owner"`
|
||||
Vendors *VendorConnection `json:"vendors"`
|
||||
Organization *Organization `json:"organization"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (Datum) IsNode() {}
|
||||
func (this Datum) GetID() gid.GID { return this.ID }
|
||||
|
||||
type DatumEdge struct {
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *Datum `json:"node"`
|
||||
@@ -1087,25 +1057,6 @@ type DocumentFilter struct {
|
||||
Query *string `json:"query,omitempty"`
|
||||
}
|
||||
|
||||
type DocumentVersion struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Document *Document `json:"document"`
|
||||
Status coredata.DocumentStatus `json:"status"`
|
||||
Version int `json:"version"`
|
||||
Content string `json:"content"`
|
||||
Changelog string `json:"changelog"`
|
||||
Title string `json:"title"`
|
||||
Classification coredata.DocumentClassification `json:"classification"`
|
||||
Owner *People `json:"owner"`
|
||||
Signatures *DocumentVersionSignatureConnection `json:"signatures"`
|
||||
PublishedAt *time.Time `json:"publishedAt,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (DocumentVersion) IsNode() {}
|
||||
func (this DocumentVersion) GetID() gid.GID { return this.ID }
|
||||
|
||||
type DocumentVersionConnection struct {
|
||||
Edges []*DocumentVersionEdge `json:"edges"`
|
||||
PageInfo *PageInfo `json:"pageInfo"`
|
||||
@@ -1297,7 +1248,7 @@ type Invitation struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Email string `json:"email"`
|
||||
FullName string `json:"fullName"`
|
||||
Role coredata.Role `json:"role"`
|
||||
Role coredata.MembershipRole `json:"role"`
|
||||
Status coredata.InvitationStatus `json:"status"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
AcceptedAt *time.Time `json:"acceptedAt,omitempty"`
|
||||
@@ -1323,10 +1274,11 @@ type InvitationOrder struct {
|
||||
}
|
||||
|
||||
type InviteUserInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
Email string `json:"email"`
|
||||
FullName string `json:"fullName"`
|
||||
CreatePeople bool `json:"createPeople"`
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
Email string `json:"email"`
|
||||
FullName string `json:"fullName"`
|
||||
Role coredata.MembershipRole `json:"role"`
|
||||
CreatePeople bool `json:"createPeople"`
|
||||
}
|
||||
|
||||
type InviteUserPayload struct {
|
||||
@@ -1383,7 +1335,7 @@ type Membership struct {
|
||||
ID gid.GID `json:"id"`
|
||||
UserID gid.GID `json:"userID"`
|
||||
OrganizationID gid.GID `json:"organizationID"`
|
||||
Role coredata.Role `json:"role"`
|
||||
Role coredata.MembershipRole `json:"role"`
|
||||
FullName string `json:"fullName"`
|
||||
EmailAddress string `json:"emailAddress"`
|
||||
AuthMethod coredata.UserAuthMethod `json:"authMethod"`
|
||||
@@ -1855,9 +1807,6 @@ type TrustCenterFile struct {
|
||||
Organization *Organization `json:"organization"`
|
||||
}
|
||||
|
||||
func (TrustCenterFile) IsNode() {}
|
||||
func (this TrustCenterFile) GetID() gid.GID { return this.ID }
|
||||
|
||||
type TrustCenterFileEdge struct {
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *TrustCenterFile `json:"node"`
|
||||
@@ -2014,6 +1963,16 @@ type UpdateMeetingPayload struct {
|
||||
Meeting *Meeting `json:"meeting"`
|
||||
}
|
||||
|
||||
type UpdateMembershipInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
MemberID gid.GID `json:"memberId"`
|
||||
Role coredata.MembershipRole `json:"role"`
|
||||
}
|
||||
|
||||
type UpdateMembershipPayload struct {
|
||||
Membership *Membership `json:"membership"`
|
||||
}
|
||||
|
||||
type UpdateNonconformityInput struct {
|
||||
ID gid.GID `json:"id"`
|
||||
ReferenceID *string `json:"referenceId,omitempty"`
|
||||
@@ -2551,3 +2510,62 @@ type Viewer struct {
|
||||
User *User `json:"user"`
|
||||
Organizations *OrganizationConnection `json:"organizations"`
|
||||
}
|
||||
|
||||
type Role string
|
||||
|
||||
const (
|
||||
RoleOwner Role = "OWNER"
|
||||
RoleAdmin Role = "ADMIN"
|
||||
RoleViewer Role = "VIEWER"
|
||||
RoleFull Role = "FULL"
|
||||
)
|
||||
|
||||
var AllRole = []Role{
|
||||
RoleOwner,
|
||||
RoleAdmin,
|
||||
RoleViewer,
|
||||
RoleFull,
|
||||
}
|
||||
|
||||
func (e Role) IsValid() bool {
|
||||
switch e {
|
||||
case RoleOwner, RoleAdmin, RoleViewer, RoleFull:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e Role) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *Role) UnmarshalGQL(v any) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = Role(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid Role", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Role) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
func (e *Role) UnmarshalJSON(b []byte) error {
|
||||
s, err := strconv.Unquote(string(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return e.UnmarshalGQL(s)
|
||||
}
|
||||
|
||||
func (e Role) MarshalJSON() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
e.MarshalGQL(&buf)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -58,9 +58,9 @@ func NewServer(cfg Config) (*Server, error) {
|
||||
router.Post("/invitations/accept", AcceptInvitationHandler(cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret, cfg.CookieSecure))
|
||||
|
||||
router.Get("/api-keys", RequireAuth(cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret, cfg.CookieSecure, ListUserAPIKeysHandler(cfg.Auth, cfg.Authz)))
|
||||
router.Post("/api-keys", RequireAuth(cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret, cfg.CookieSecure, CreateUserAPIKeyHandler(cfg.Auth)))
|
||||
router.Post("/api-keys", RequireAuth(cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret, cfg.CookieSecure, CreateUserAPIKeyHandler(cfg.Auth, cfg.Authz)))
|
||||
router.Get("/api-keys/{id}", RequireAuth(cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret, cfg.CookieSecure, GetUserAPIKeyHandler(cfg.Auth)))
|
||||
router.Put("/api-keys", RequireAuth(cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret, cfg.CookieSecure, UpdateUserAPIKeyHandler(cfg.Auth)))
|
||||
router.Put("/api-keys", RequireAuth(cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret, cfg.CookieSecure, UpdateUserAPIKeyHandler(cfg.Auth, cfg.Authz)))
|
||||
router.Delete("/api-keys", RequireAuth(cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret, cfg.CookieSecure, DeleteUserAPIKeyHandler(cfg.Auth)))
|
||||
|
||||
router.Get("/saml/login/{samlConfigID}", SAMLLoginHandler(cfg.SAML, cfg.Auth, cfg.Logger))
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"go.gearno.de/kit/httpserver"
|
||||
authsvc "go.probo.inc/probo/pkg/auth"
|
||||
"go.probo.inc/probo/pkg/authz"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
@@ -44,7 +45,7 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func CreateUserAPIKeyHandler(authSvc *authsvc.Service) http.HandlerFunc {
|
||||
func CreateUserAPIKeyHandler(authSvc *authsvc.Service, authzSvc *authz.Service) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
user := UserFromContext(ctx)
|
||||
@@ -92,6 +93,24 @@ func CreateUserAPIKeyHandler(authSvc *authsvc.Service) http.HandlerFunc {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user is an OWNER for this organization
|
||||
tenantAuthzSvc := authzSvc.WithTenant(orgID.TenantID())
|
||||
role, err := tenantAuthzSvc.GetUserRoleInOrganization(ctx, user.ID, orgID)
|
||||
if err != nil {
|
||||
httpserver.RenderJSON(w, http.StatusForbidden, map[string]string{
|
||||
"error": "user does not have access to this organization",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if role != coredata.MembershipRoleOwner {
|
||||
httpserver.RenderJSON(w, http.StatusForbidden, map[string]string{
|
||||
"error": "only owners can create API keys for this organization",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
orgInputs[i] = authsvc.UserAPIKeyOrganizationRequest{
|
||||
OrganizationID: orgID,
|
||||
Role: coredata.APIRole(org.Role),
|
||||
|
||||
@@ -18,11 +18,11 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/httpserver"
|
||||
authsvc "go.probo.inc/probo/pkg/auth"
|
||||
"go.probo.inc/probo/pkg/authz"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.gearno.de/kit/httpserver"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -106,9 +106,21 @@ func ListOrganizationsHandler(authSvc *authsvc.Service, authzSvc *authz.Service)
|
||||
user := UserFromContext(ctx)
|
||||
sess := SessionFromContext(ctx)
|
||||
|
||||
organizations, err := authzSvc.GetAllUserOrganizations(ctx, user.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list organizations for user: %w", err))
|
||||
var organizations coredata.Organizations
|
||||
var err error
|
||||
|
||||
roleFilter := r.URL.Query().Get("role")
|
||||
if roleFilter != "" {
|
||||
role := coredata.MembershipRole(roleFilter)
|
||||
organizations, err = authzSvc.GetUserOrganizationsWithRole(ctx, user.ID, role)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list organizations for user with role: %w", err))
|
||||
}
|
||||
} else {
|
||||
organizations, err = authzSvc.GetAllUserOrganizations(ctx, user.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list organizations for user: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
orgIDs := make([]gid.GID, len(organizations))
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"go.gearno.de/kit/httpserver"
|
||||
authsvc "go.probo.inc/probo/pkg/auth"
|
||||
"go.probo.inc/probo/pkg/authz"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
@@ -32,7 +33,7 @@ type UpdateUserAPIKeyRequest struct {
|
||||
Organizations []UserAPIKeyOrganizationMembershipRequest `json:"organizations"`
|
||||
}
|
||||
|
||||
func UpdateUserAPIKeyHandler(authSvc *authsvc.Service) http.HandlerFunc {
|
||||
func UpdateUserAPIKeyHandler(authSvc *authsvc.Service, authzSvc *authz.Service) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
user := UserFromContext(ctx)
|
||||
@@ -100,6 +101,22 @@ func UpdateUserAPIKeyHandler(authSvc *authsvc.Service) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user is an OWNER for this organization
|
||||
tenantAuthzSvc := authzSvc.WithTenant(orgID.TenantID())
|
||||
role, err := tenantAuthzSvc.GetUserRoleInOrganization(ctx, user.ID, orgID)
|
||||
if err != nil {
|
||||
httpserver.RenderJSON(w, http.StatusForbidden, map[string]string{
|
||||
"error": "user does not have access to this organization",
|
||||
})
|
||||
return
|
||||
}
|
||||
if role != coredata.MembershipRoleOwner {
|
||||
httpserver.RenderJSON(w, http.StatusForbidden, map[string]string{
|
||||
"error": "only owners can update API keys for this organization",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
orgInputs[i] = authsvc.UserAPIKeyOrganizationRequest{
|
||||
OrganizationID: orgID,
|
||||
Role: coredata.APIRole(org.Role),
|
||||
|
||||
121
pkg/server/authz/authz.go
Normal file
121
pkg/server/authz/authz.go
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.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 authz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.gearno.de/kit/httpserver"
|
||||
"go.gearno.de/kit/log"
|
||||
authsvc "go.probo.inc/probo/pkg/auth"
|
||||
"go.probo.inc/probo/pkg/authz"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/server/session"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Auth *authsvc.Service
|
||||
Authz *authz.Service
|
||||
Logger *log.Logger
|
||||
CookieName string
|
||||
CookieSecret string
|
||||
CookieSecure bool
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
router *chi.Mux
|
||||
}
|
||||
|
||||
type ctxKey struct{ name string }
|
||||
|
||||
var (
|
||||
userContextKey = &ctxKey{name: "user"}
|
||||
)
|
||||
|
||||
func NewServer(cfg Config) (*Server, error) {
|
||||
router := chi.NewRouter()
|
||||
|
||||
// Apply authentication middleware to all routes
|
||||
router.Use(requireAuth(cfg.Auth, cfg.Authz, cfg.CookieName, cfg.CookieSecret, cfg.CookieSecure))
|
||||
|
||||
router.Get("/{organizationID}/permissions", PermissionsHandler(cfg.Authz, UserFromContext, cfg.Logger))
|
||||
|
||||
return &Server{
|
||||
router: router,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.router.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// requireAuth is a middleware that requires authentication
|
||||
func requireAuth(
|
||||
authService *authsvc.Service,
|
||||
authzService *authz.Service,
|
||||
cookieName string,
|
||||
cookieSecret string,
|
||||
cookieSecure bool,
|
||||
) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
sessionAuthCfg := session.AuthConfig{
|
||||
CookieName: cookieName,
|
||||
CookieSecret: cookieSecret,
|
||||
CookieSecure: cookieSecure,
|
||||
}
|
||||
|
||||
errorHandler := session.ErrorHandler{
|
||||
OnCookieError: func(err error) {
|
||||
httpserver.RenderError(w, http.StatusUnauthorized, fmt.Errorf("invalid session"))
|
||||
},
|
||||
OnParseError: func(w http.ResponseWriter, authCfg session.AuthConfig) {
|
||||
session.ClearCookie(w, authCfg)
|
||||
httpserver.RenderError(w, http.StatusUnauthorized, fmt.Errorf("invalid session"))
|
||||
},
|
||||
OnSessionError: func(w http.ResponseWriter, authCfg session.AuthConfig) {
|
||||
session.ClearCookie(w, authCfg)
|
||||
httpserver.RenderError(w, http.StatusUnauthorized, fmt.Errorf("session expired"))
|
||||
},
|
||||
OnUserError: func(w http.ResponseWriter, authCfg session.AuthConfig) {
|
||||
session.ClearCookie(w, authCfg)
|
||||
httpserver.RenderError(w, http.StatusUnauthorized, fmt.Errorf("user not found"))
|
||||
},
|
||||
OnTenantError: func(err error) {
|
||||
panic(fmt.Errorf("cannot list tenants for user: %w", err))
|
||||
},
|
||||
}
|
||||
|
||||
authResult := session.TryAuth(ctx, w, r, authService, authzService, sessionAuthCfg, errorHandler)
|
||||
if authResult == nil {
|
||||
httpserver.RenderError(w, http.StatusUnauthorized, fmt.Errorf("authentication required"))
|
||||
return
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, userContextKey, authResult.User)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func UserFromContext(ctx context.Context) *coredata.User {
|
||||
user, _ := ctx.Value(userContextKey).(*coredata.User)
|
||||
return user
|
||||
}
|
||||
76
pkg/server/authz/permissions_handler.go
Normal file
76
pkg/server/authz/permissions_handler.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.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 authz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.gearno.de/kit/httpserver"
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/authz"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
// PermissionsHandler returns permissions for the current user's role in an organization
|
||||
// It uses the centralized permissions map
|
||||
func PermissionsHandler(
|
||||
authzService *authz.Service,
|
||||
userFromContext func(ctx context.Context) *coredata.User,
|
||||
logger *log.Logger,
|
||||
) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
orgIDStr := chi.URLParam(r, "organizationID")
|
||||
if orgIDStr == "" {
|
||||
http.Error(w, "organizationID parameter required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
orgID, err := gid.ParseGID(orgIDStr)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid organizationID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user := userFromContext(ctx)
|
||||
if user == nil {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
tenantAuthzSvc := authzService.WithTenant(orgID.TenantID())
|
||||
|
||||
memberRole, err := tenantAuthzSvc.GetUserRoleInOrganization(ctx, user.ID, orgID)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("cannot get user role: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
userRole := authz.Role(memberRole.String())
|
||||
|
||||
permissions := authz.GetPermissionsByRole(userRole)
|
||||
|
||||
response := map[string]any{
|
||||
"permissions": permissions,
|
||||
"role": memberRole.String(),
|
||||
}
|
||||
|
||||
httpserver.RenderJSON(w, http.StatusOK, response)
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,15 @@ func Unauthorized() *gqlerror.Error {
|
||||
}
|
||||
}
|
||||
|
||||
func Forbidden(err error) *gqlerror.Error {
|
||||
return &gqlerror.Error{
|
||||
Message: err.Error(),
|
||||
Extensions: map[string]any{
|
||||
"code": "FORBIDDEN",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func AuthenticationRequired(details map[string]any) *gqlerror.Error {
|
||||
extensions := map[string]any{"code": "AUTHENTICATION_REQUIRED"}
|
||||
maps.Copy(extensions, details)
|
||||
|
||||
@@ -77,6 +77,11 @@ func RecoverFunc(ctx context.Context, err any) error {
|
||||
return Unauthorized()
|
||||
}
|
||||
|
||||
var permissionDeniedErr *authz.PermissionDeniedError
|
||||
if errTyped, ok := err.(error); ok && errors.As(errTyped, &permissionDeniedErr) {
|
||||
return Forbidden(permissionDeniedErr)
|
||||
}
|
||||
|
||||
logger := httpserver.LoggerFromContext(ctx)
|
||||
logger.Error("resolver panic", log.Any("error", err), log.Any("stack", string(debug.Stack())))
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
"go.probo.inc/probo/pkg/server/api"
|
||||
trust_v1 "go.probo.inc/probo/pkg/server/api/trust/v1"
|
||||
auth_server "go.probo.inc/probo/pkg/server/auth"
|
||||
authz_server "go.probo.inc/probo/pkg/server/authz"
|
||||
"go.probo.inc/probo/pkg/server/trust"
|
||||
"go.probo.inc/probo/pkg/server/web"
|
||||
trust_pkg "go.probo.inc/probo/pkg/trust"
|
||||
@@ -65,6 +66,7 @@ type Server struct {
|
||||
webServer *web.Server
|
||||
trustServer *trust.Server
|
||||
authServer *auth_server.Server
|
||||
authzServer *authz_server.Server
|
||||
router *chi.Mux
|
||||
extraHeaderFields map[string]string
|
||||
proboService *probo.Service
|
||||
@@ -118,6 +120,18 @@ func NewServer(cfg Config) (*Server, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authzServer, err := authz_server.NewServer(authz_server.Config{
|
||||
Auth: cfg.Auth,
|
||||
Authz: cfg.Authz,
|
||||
Logger: cfg.Logger.Named("authz"),
|
||||
CookieName: cfg.ConsoleAuth.CookieName,
|
||||
CookieSecret: cfg.ConsoleAuth.CookieSecret,
|
||||
CookieSecure: cfg.ConsoleAuth.CookieSecure,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
router := chi.NewRouter()
|
||||
|
||||
server := &Server{
|
||||
@@ -125,6 +139,7 @@ func NewServer(cfg Config) (*Server, error) {
|
||||
webServer: webServer,
|
||||
trustServer: trustServer,
|
||||
authServer: authServer,
|
||||
authzServer: authzServer,
|
||||
router: router,
|
||||
extraHeaderFields: cfg.ExtraHeaderFields,
|
||||
proboService: cfg.Probo,
|
||||
@@ -139,6 +154,7 @@ func NewServer(cfg Config) (*Server, error) {
|
||||
func (s *Server) setupRoutes() {
|
||||
s.router.Mount("/api", s.apiServer)
|
||||
s.router.Mount("/connect", s.authServer)
|
||||
s.router.Mount("/authz", s.authzServer)
|
||||
|
||||
s.router.Route("/trust/{slugOrId}", func(r chi.Router) {
|
||||
r.Use(s.loadTrustCenterBySlugOrID)
|
||||
|
||||
@@ -179,17 +179,18 @@ func (s TrustCenterAccessService) Request(
|
||||
return fmt.Errorf("invalid email address")
|
||||
}
|
||||
|
||||
access = &coredata.TrustCenterAccess{
|
||||
ID: gid.New(s.svc.scope.GetTenantID(), coredata.TrustCenterAccessEntityType),
|
||||
TenantID: s.svc.scope.GetTenantID(),
|
||||
TrustCenterID: req.TrustCenterID,
|
||||
Email: req.Email,
|
||||
Name: *req.Name,
|
||||
Active: false,
|
||||
HasAcceptedNonDisclosureAgreement: false,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
access = &coredata.TrustCenterAccess{
|
||||
ID: gid.New(s.svc.scope.GetTenantID(), coredata.TrustCenterAccessEntityType),
|
||||
OrganizationID: organizationID,
|
||||
TenantID: s.svc.scope.GetTenantID(),
|
||||
TrustCenterID: req.TrustCenterID,
|
||||
Email: req.Email,
|
||||
Name: *req.Name,
|
||||
Active: false,
|
||||
HasAcceptedNonDisclosureAgreement: false,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := access.Insert(ctx, tx, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert trust center access: %w", err)
|
||||
@@ -206,19 +207,19 @@ func (s TrustCenterAccessService) Request(
|
||||
newReportIDs := filterExistingIDs(reportIDs, existingReportIDs)
|
||||
newTrustCenterFileIDs := filterExistingIDs(trustCenterFileIDs, existingTrustCenterFileIDs)
|
||||
|
||||
var accesses coredata.TrustCenterDocumentAccesses
|
||||
var accesses coredata.TrustCenterDocumentAccesses
|
||||
|
||||
if err := accesses.BulkInsertDocumentAccesses(ctx, tx, s.svc.scope, access.ID, newDocumentIDs, true, now); err != nil {
|
||||
return fmt.Errorf("cannot bulk insert trust center document accesses: %w", err)
|
||||
}
|
||||
if err := accesses.BulkInsertDocumentAccesses(ctx, tx, s.svc.scope, access.ID, access.OrganizationID, newDocumentIDs, true, now); err != nil {
|
||||
return fmt.Errorf("cannot bulk insert trust center document accesses: %w", err)
|
||||
}
|
||||
|
||||
if err := accesses.BulkInsertReportAccesses(ctx, tx, s.svc.scope, access.ID, newReportIDs, true, now); err != nil {
|
||||
return fmt.Errorf("cannot bulk insert trust center report accesses: %w", err)
|
||||
}
|
||||
if err := accesses.BulkInsertReportAccesses(ctx, tx, s.svc.scope, access.ID, access.OrganizationID, newReportIDs, true, now); err != nil {
|
||||
return fmt.Errorf("cannot bulk insert trust center report accesses: %w", err)
|
||||
}
|
||||
|
||||
if err := accesses.BulkInsertTrustCenterFileAccesses(ctx, tx, s.svc.scope, access.ID, newTrustCenterFileIDs, true, now); err != nil {
|
||||
return fmt.Errorf("cannot bulk insert trust center file accesses: %w", err)
|
||||
}
|
||||
if err := accesses.BulkInsertTrustCenterFileAccesses(ctx, tx, s.svc.scope, access.ID, access.OrganizationID, newTrustCenterFileIDs, true, now); err != nil {
|
||||
return fmt.Errorf("cannot bulk insert trust center file accesses: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user