Compare commits

..

204 Commits

Author SHA1 Message Date
52d801bce5 Add Infisical SSH to README 2025-01-21 22:36:55 -08:00
86acf88a13 Merge pull request #3021 from akhilmhdh/fix/project-delete
fix: resolved org sidebar not showing in org member detail window
2025-01-21 15:14:12 -05:00
=
63c7c39e21 fix: resolved org sidebar not showing in member detail window and other detail window 2025-01-22 01:39:44 +05:30
151edc7efa Merge pull request #3020 from Infisical/remove-enterprise-identity-limit
Remove Enterprise Member and Identity Limits for SAML/LDAP Login
2025-01-21 13:17:16 -05:00
5fa7f56285 Remove member and identity limits for enterprise saml and ldap login case 2025-01-21 10:12:23 -08:00
810b27d121 Merge pull request #3019 from Infisical/audit-log-stream-setup-error-improvement
Improvement: Propagate Upstream Error on Audit Log Stream Setup Fail
2025-01-21 10:06:23 -08:00
51fe7450ae improvement: propogate upstream error on audit log stream setup 2025-01-21 09:54:33 -08:00
938c06a2ed Merge pull request #3018 from Infisical/misc/added-more-scoping-for-namespace
misc: added more scoping logic for namespace installation
2025-01-21 11:09:45 -05:00
38d431ec77 misc: added more scoping logic for namespace installation 2025-01-21 23:57:11 +08:00
d202fdf5c8 Merge pull request #3017 from akhilmhdh/fix/project-delete
fix: resolved failing project delete
2025-01-21 10:42:59 -05:00
=
f1b2028542 fix: resolved failing project delete 2025-01-21 20:55:19 +05:30
5c9b46dfba Merge pull request #3015 from Infisical/misc/address-scim-email-verification-patch-issue
misc: address scim email verification patch issue
2025-01-21 20:23:08 +08:00
a516e50984 misc: address scim email verification patch issue 2025-01-21 17:01:34 +08:00
569439f208 Merge pull request #2999 from Infisical/misc/added-handling-for-case-enforcement
misc: added handling of secret case enforcement
2025-01-21 13:58:48 +08:00
9afc282679 Update deployment-pipeline.yml 2025-01-21 00:49:31 -05:00
8db85cfb84 Add slack alert 2025-01-21 00:04:14 -05:00
664b2f0089 add concurrency to git workflow 2025-01-20 23:52:47 -05:00
5e9bd3a7c6 Merge pull request #3014 from Infisical/expanded-secret-overview-adjustment
Fix: Adjust Secret Overview Expanded Secret View
2025-01-20 21:39:39 -05:00
2c13af6db3 correct helm verion 2025-01-20 21:37:13 -05:00
ec9171d0bc fix: adjust secret overview expanded secret view to accomodate nav restructure 2025-01-20 18:30:54 -08:00
81362bec8f Merge pull request #3013 from Infisical/daniel/cli-fix-2
fix: saml redirect failing due to blocked pop-ups
2025-01-20 13:21:32 -08:00
5a4d7541a2 Update SelectOrgPage.tsx 2025-01-20 22:13:52 +01:00
3c97c45455 others => other 2025-01-20 15:26:15 -05:00
4f015d77fb Merge pull request #3003 from akhilmhdh/feat/nav-restructure
Navigation restructure
2025-01-20 15:07:39 -05:00
=
78e894c2bb feat: changed user icon 2025-01-21 01:34:21 +05:30
=
23513158ed feat: updated nav ui based on feedback 2025-01-20 22:59:00 +05:30
=
934ef8ab27 feat: resolved plan text generator 2025-01-20 22:59:00 +05:30
=
23e9c52f67 feat: added missing breadcrumbs for integration pages 2025-01-20 22:59:00 +05:30
=
e276752e7c feat: removed trailing comma from ui 2025-01-20 22:59:00 +05:30
=
01ae19fa2b feat: completed all nav restructing and breadcrumbs cleanup 2025-01-20 22:58:59 +05:30
=
9df8cf60ef feat: finished breadcrumbs for all project types except secret manager 2025-01-20 22:58:59 +05:30
=
1b1fe2a700 feat: completed org breadcrumbs 2025-01-20 22:58:59 +05:30
=
338961480c feat: resolved accessibility issue with menu 2025-01-20 22:58:59 +05:30
=
03debcab5a feat: completed sidebar changes 2025-01-20 22:58:59 +05:30
4a6f759900 Merge pull request #3007 from akhilmhdh/feat/base64-decode-in-operator
Base64 decode in operator
2025-01-19 13:50:56 -05:00
b9d06ff686 update operator version 2025-01-19 13:50:24 -05:00
=
5cc5a4f03d feat: updated doc for the function name 2025-01-20 00:08:22 +05:30
=
5ef2be1a9c feat: updated function name 2025-01-20 00:08:06 +05:30
8de9ddfb8b update k8s operator doc 2025-01-19 11:29:55 -05:00
5b40de16cf improve docs and add missing function to create mamaged secret 2025-01-18 17:11:52 -05:00
=
11aac3f5dc docs: updated k8s operator template section with base64DecodeBytes content 2025-01-18 18:58:26 +05:30
=
9823c7d1aa feat: added base64DecodeBytes function to operator template 2025-01-18 18:58:00 +05:30
3ba396f7fa Merge pull request #3005 from Infisical/parameter-store-error-message-improvement
Improvement: Add Secret Key to AWS Parameter Store Integreation Sync Error
2025-01-17 11:56:31 -08:00
9c561266ed improvement: add secret key to aws parameter store integreation error message 2025-01-17 08:51:41 -08:00
36fef11d91 Merge pull request #3004 from Infisical/misc/added-misisng-install-crd-flag-for-multi-installation
misc: added missing install CRD flag for multi-installation
2025-01-17 21:42:09 +08:00
742932c4a0 Merge pull request #2993 from Infisical/misc/add-org-id-to-org-settings
misc: add org id display to org settings
2025-01-17 17:31:43 +05:30
57a77ae5f1 misc: incremented chart and operator versions 2025-01-17 19:16:48 +08:00
7c9564c7dc misc: added missing install CRD flag for multi installation 2025-01-17 19:09:12 +08:00
736aecebf8 Merge pull request #3001 from Infisical/fix/address-org-invite-resend
fix: address org invite resend issues
2025-01-17 18:53:32 +08:00
16748357d7 misc: addressed comments 2025-01-17 03:50:01 +08:00
12863b389b Merge pull request #2997 from Infisical/daniel/unique-group-names
feat(groups): unique names
2025-01-16 20:41:07 +01:00
c592ff00a6 fix: address org invite resend 2025-01-17 01:42:35 +08:00
ef87086272 fix(groups): unique names, requested changes 2025-01-16 17:38:54 +01:00
bd459d994c misc: finalized error message 2025-01-16 19:55:21 +08:00
440f93f392 misc: added handling for case enforcement 2025-01-16 19:10:00 +08:00
b440e918ac Merge pull request #2988 from Infisical/daniel/audit-logs-searchability
feat(radar): pagination and filtering
2025-01-16 01:24:24 +01:00
439f253350 Merge pull request #2981 from Infisical/daniel/secret-scans-export
feat(radar): export radar data
2025-01-16 01:22:05 +01:00
4e68304262 fix: type check 2025-01-16 01:08:43 +01:00
c4d0896609 feat(groups): unique names 2025-01-16 01:01:01 +01:00
b8115d481c Merge pull request #2989 from Infisical/daniel/audit-log-docs
docs(audit-logs): audit log streams structure
2025-01-15 18:07:24 +01:00
5fdec97319 requested changes 2025-01-15 17:50:30 +01:00
755bb1679a requested changes 2025-01-15 17:32:53 +01:00
7142e7a6c6 Update secret-scanning-dal.ts 2025-01-15 16:39:08 +01:00
11ade92b5b Update secret-scanning-service.ts 2025-01-15 16:39:08 +01:00
4147725260 feat(radar): pagination & filtering 2025-01-15 16:39:08 +01:00
c359cf162f requested changes 2025-01-15 16:37:42 +01:00
9d04b648fa misc: add org id display to org settings 2025-01-15 20:21:10 +08:00
9edfdb7234 docs(audit-logs): audit log streams structure 2025-01-15 02:33:21 +01:00
ff74e020fc requested changes 2025-01-15 01:55:10 +01:00
6ee446e574 Merge pull request #2979 from akhilmhdh/refactor/permission-service-fn
Refactored permission service for project to have project operation type validation
2025-01-14 23:50:10 +05:30
c806059b11 Merge pull request #2985 from Infisical/misc/add-oidc-saml-handling-for-login-check
misc: add oidc saml handling for login SA check
2025-01-14 13:11:29 -05:00
=
3a5bb31bde feat: updated all permission argument to actionProjectType 2025-01-14 23:41:06 +05:30
=
6f38d6c76f feat: updated to enum ActionProjectType 2025-01-14 23:26:18 +05:30
=
d721a46ec9 fix: resolved secret and approval count triggered for other project types 2025-01-14 23:26:18 +05:30
=
989065ba34 refactor: updated permission service for projects to check for operation type as well 2025-01-14 23:26:18 +05:30
5ad419c079 misc: updated comment 2025-01-15 00:49:53 +08:00
80f72e8040 misc: added context 2025-01-15 00:48:19 +08:00
0cef728617 Merge pull request #2986 from Infisical/misc/addressed-cf-pages-error-304
fix: addressed cloudflare pages error 304
2025-01-14 10:42:42 -05:00
0a735422ed misc: standardized log 2025-01-14 23:39:47 +08:00
8370a0d9c0 misc: added log 2025-01-14 23:29:04 +08:00
71af662998 fix: addressed cloudflare pages error 304 2025-01-14 23:18:38 +08:00
27e391f7e0 misc: add oidc saml handling for login check 2025-01-14 21:02:41 +08:00
2187c7588c update cache control to be no store 2025-01-13 23:58:36 -05:00
8ab7e8360d add cache busting param 2025-01-13 22:53:04 -05:00
957cab3117 Merge pull request #2982 from Infisical/fix-copy-user-id
Fix: Copy Username on User Details Page
2025-01-13 20:06:11 -05:00
4bf16f68fc fix: correctly copy user email to clipboard 2025-01-13 17:03:08 -08:00
ab3ee775bb feat(radar): export radar data 2025-01-14 00:53:40 +01:00
2a86e6f4d1 add cache contro to cloudflare pages integration 2025-01-13 17:17:39 -05:00
194fbb79f2 Merge pull request #2975 from Infisical/daniel/custom-cors
feat(api): custom cors settings
2025-01-13 14:36:19 -05:00
faaba8deb7 add metabase link to on call guide 2025-01-13 11:16:49 -05:00
ae21b157a9 Merge pull request #2980 from Infisical/misc/added-proper-error-propagation-for-aws-kms
misc: added error propagation for aws kms
2025-01-13 23:46:57 +08:00
6167c70a74 Merge pull request #2973 from Infisical/daniel/push-secret-docs
docs: small k8s docs improvements
2025-01-13 10:39:45 -05:00
0ecf75cbdb misc: scoped down to AWS-related errors 2025-01-13 23:18:24 +08:00
3f8aa0fa4b misc: added error propagation for aws kms 2025-01-13 23:00:40 +08:00
6487c83bda docs: requested changes 2025-01-13 14:35:36 +01:00
c08fbbdab2 Update app.ts 2025-01-13 13:59:25 +01:00
a4aa65bb81 add on call to hand book 2025-01-13 01:27:50 -05:00
258d19cbe4 Merge pull request #2974 from Infisical/daniel/shared-secret-audit-logs
feat(audit-logs): shared secrets audit logs
2025-01-10 22:29:05 +01:00
de91356127 Update envars.mdx 2025-01-10 22:24:57 +01:00
ccb07942de feat(api): custom cors settings 2025-01-10 22:23:19 +01:00
3d278b0925 feat(audit-logs): shared secrets audit logs 2025-01-10 21:37:10 +01:00
956fb2efb4 docs: better k8s pre-req 2025-01-10 20:16:30 +01:00
13894261ce docs: small k8s docs improvements 2025-01-10 20:10:00 +01:00
d7ffa70906 Merge pull request #2972 from Infisical/misc/suppot-array-value-for-oidc-aud-field
misc: add support for array values in OIDC aud field
2025-01-10 13:32:09 -05:00
b8fa7c5bb6 Merge pull request #2969 from Infisical/misc/fix-shared-secret-within-org-and-login-redirect
misc: resolve shared secret within org and added login redirect
2025-01-11 02:16:48 +08:00
2baacfcd8f misc: add support for array values 2025-01-11 02:07:04 +08:00
31c11f7d2a Merge pull request #2964 from Infisical/vmatsiiako-typo-patch-1
Update infisical-dynamic-secret-crd.mdx
2025-01-10 12:43:40 -05:00
c5f06dece4 Merge pull request #2957 from Infisical/misc/made-is-active-optional-for-integration-patch
misc: made is active optional for integration patch
2025-01-11 00:26:21 +08:00
662e79ac98 Merge pull request #2971 from Infisical/daniel/audit-log-timestamp-bug
fix(audit-logs): time conversion bug
2025-01-10 17:23:58 +01:00
17249d603b fix: add delete secrets event type to frontend 2025-01-10 17:15:26 +01:00
9bdff9c504 fix: explicit postgres time conversion 2025-01-10 17:15:14 +01:00
4552ce6ca4 Merge pull request #2970 from Infisical/misc/add-secret-key-indicator-for-failed-integrations-when-possible
misc: add secret key indicator for failed AWS integration syncs
2025-01-10 11:10:25 -05:00
ba4b8801eb Merge pull request #2965 from akhilmhdh/fix/broken-secret-creation
Resolve self signed error for mssql
2025-01-10 11:01:54 -05:00
36a5f728a1 misc: add secret key indicator for failed AWS integration syncs 2025-01-11 00:00:17 +08:00
502429d914 misc: resolve shared secret within org and added login redirect 2025-01-10 23:55:21 +08:00
27abfa4fff Merge pull request #2966 from Infisical/misc/add-pagination-handling-for-gitlab-groups-fetch
misc: add pagination handling for gitlab groups fetch
2025-01-10 19:15:38 +08:00
4bc9bca287 removed undefined type 2025-01-10 19:08:49 +08:00
612c29225d misc: add pagination handling for gitlab groups fetch 2025-01-10 19:06:10 +08:00
=
4d43accc8a fix: did same resolution for dynamic secret ops as well 2025-01-10 15:12:04 +05:30
3c89a69410 Update infisical-dynamic-secret-crd.mdx 2025-01-10 01:39:12 -08:00
=
e741b63e63 fix: resolved mssql self signed error 2025-01-10 14:59:49 +05:30
9cde1995c7 Merge pull request #2962 from Infisical/fix/address-indefinite-hang-for-run-watch-failure
fix: address infinite hang when infisical run watch fails
2025-01-10 11:54:51 +08:00
3ed3856c85 Merge pull request #2963 from akhilmhdh/fix/broken-secret-creation
feat: added validation for secret name to disallow spaces and overview page
2025-01-09 15:38:25 -05:00
=
8054d93851 feat: added validation for secret name to disallow spaces and overview page fix for folder creation 2025-01-10 02:00:39 +05:30
02dc23425c fix: address infinite hang when infisical run watch fails 2025-01-10 02:29:39 +08:00
01534c3525 Merge pull request #2847 from Infisical/daniel/k8s-dynamic-secrets
feat(k8-operator): dynamic secrets
2025-01-09 12:45:30 -05:00
325ce73b9f fix typo 2025-01-09 12:36:52 -05:00
1639bda3f6 remove copy-paste and make redeploy docs specific 2025-01-09 12:23:46 -05:00
b5b91c929f fix kubernetes docs 2025-01-09 12:11:48 -05:00
9bc549ca8c Merge pull request #2960 from Infisical/feat/add-initial-sync-behavior-for-gitlab
feat: add initial sync behavior for gitlab
2025-01-10 01:01:17 +08:00
cedc88d83a misc: removed comment 2025-01-10 00:42:16 +08:00
f866a810c1 Merge pull request #2950 from Infisical/feat/secret-access-list
feat: secret access list
2025-01-10 00:40:56 +08:00
0c034d69ac misc: removed raw from api 2025-01-10 00:32:31 +08:00
e29f7f656c docs(k8s): better documentation layout 2025-01-09 16:07:07 +01:00
858e569d4d feat: add initial sync behavior for gitlab 2025-01-09 20:43:14 +08:00
5d8f32b774 Merge pull request #2955 from thomas-infisical/patch-1
Doc: Update Nov & Dec Changelogs
2025-01-08 21:42:49 -08:00
bb71f5eb7e Merge pull request #2956 from Infisical/daniel/update-k8s-manifest
fix: k8's manifest out of sync
2025-01-08 17:25:21 -05:00
30b431a255 Merge pull request #2958 from Infisical/bump-ecs-deploy-task-definition
ci: Bump aws-actions/amazon-ecs-deploy-task-definition to v2
2025-01-08 16:22:35 -05:00
fd32118685 Bump aws-actions/amazon-ecs-deploy-task-definition to v2
Bump aws-actions/amazon-ecs-deploy-task-definition to resolve:

```
Error: Failed to register task definition in ECS: Unexpected key 'enableFaultInjection' found in params
Error: Unexpected key 'enableFaultInjection' found in params
```

errors.
2025-01-08 13:20:41 -08:00
8528f3fd2a Merge pull request #2897 from Infisical/feat/resource-metadata
feat: resource metadata
2025-01-08 14:47:01 -05:00
60749cfc43 misc: made is active optional for integration patch 2025-01-09 02:19:24 +08:00
eb358bcafd Update install-secrets-operator.yaml 2025-01-08 16:26:50 +01:00
ffbd29c575 doc: update nov & dec changelog
First try at updating the Changelog from these past months.

Let me know if you think of anything I might have forgotten.
2025-01-08 15:50:53 +01:00
a74f0170da Merge pull request #2954 from Infisical/fix/addressed-reported-migration-related-ui-issues
fix: resolved conditions permission not getting added from new policy…
2025-01-08 22:26:45 +08:00
=
a0fad34a6d fix: resolved conditions permission not getting added from new policy button 2025-01-08 19:48:13 +05:30
f0dc5ec876 misc: add secret access insights to license fns 2025-01-08 20:56:33 +08:00
c2453f0c84 misc: finalized ui 2025-01-08 20:50:20 +08:00
2819c8519e misc: added license checks 2025-01-08 20:31:04 +08:00
616b013b12 Merge remote-tracking branch 'origin/main' into feat/secret-access-list 2025-01-08 20:08:16 +08:00
0b9d890a51 Merge pull request #2953 from Infisical/fix/addressed-reported-migration-related-ui-issues
fix: address sso redirect and project role creation
2025-01-08 19:20:19 +08:00
5ba507bc1c misc: made installation ID optional for github 2025-01-08 19:17:38 +08:00
=
0ecc196e5d feat: added missing coerce in azure key vault 2025-01-08 16:30:46 +05:30
=
ddac9f7cc4 feat: made all redirect route to coerce it 2025-01-08 16:27:56 +05:30
f5adc4d9f3 misc: removed unnecessary type assertion 2025-01-08 18:44:58 +08:00
34354994d8 fix: address sso redirect and project role creation 2025-01-08 18:29:25 +08:00
d7c3192099 feat: frontend integration 2025-01-08 18:24:40 +08:00
1576358805 Merge pull request #2947 from Infisical/auth0-saml
Add Support for Auth0 SAML
2025-01-07 15:04:44 -05:00
8bf8bc77c9 Merge pull request #2951 from akhilmhdh/fix/broken-image
feat: resolved app not loading on org no access
2025-01-07 14:29:47 -05:00
=
3219723149 feat: resolved app not loading on org no access 2025-01-08 00:55:22 +05:30
74b95d92ab feat: backend setup 2025-01-08 02:48:47 +08:00
6d3793beff Merge pull request #2949 from akhilmhdh/fix/broken-image
Fixed broke image
2025-01-08 00:18:38 +05:30
0df41f3391 Merge pull request #2948 from Infisical/fix-user-groups-plan
Improvement: Clarify Enterprise Plan for User Group Feature Upgrade Modal
2025-01-07 10:44:32 -08:00
=
1acac9d479 feat: resolved broken gitlab image and hidden the standalone endpoints from api documentation 2025-01-08 00:14:31 +05:30
5e9dc0b98d clarify enterprise plan for user group feature upgrade modal 2025-01-07 10:38:09 -08:00
291d29ec41 Merge remote-tracking branch 'origin/main' into feat/resource-metadata 2025-01-07 19:15:33 +08:00
aa39451bc2 fix: generated files 2025-01-06 22:03:56 +01:00
f5548b3e8c Merge branch 'heads/main' into daniel/k8s-dynamic-secrets 2025-01-06 22:00:21 +01:00
e763a6f683 misc: added default for metadataSyncMode 2024-12-23 21:29:51 +08:00
cb1b006118 misc: add secret metadata to batch update 2024-12-23 14:46:23 +08:00
356e7c5958 feat: added approval handling for metadata in replicate 2024-12-23 14:36:41 +08:00
1a68765f15 feat: secret approval and replication 2024-12-21 02:51:47 +08:00
ae07d38c19 Merge remote-tracking branch 'origin/main' into feat/resource-metadata 2024-12-20 14:23:14 +08:00
025b4b8761 feat: aws secret manager metadata sync 2024-12-19 23:54:47 +08:00
ef688efc8d feat: integrated secret metadata to ui 2024-12-19 22:02:20 +08:00
8c98565715 feat: completed basic CRUD 2024-12-19 17:47:39 +08:00
e9358cd1d8 misc: backend setup + create secret metadata 2024-12-19 15:05:16 +08:00
4daaf80caa fix: better naming 2024-12-18 02:56:13 +01:00
cf7768d8e5 Merge branch 'daniel/k8-push-secret' into daniel/k8s-dynamic-secrets 2024-12-18 01:41:17 +01:00
e76d2f58ea fix: move fixes from different branch 2024-12-18 01:39:32 +01:00
36a13d182f requested changes 2024-12-12 06:13:55 +04:00
8b26670d73 fix(k8s): dynamic secret structual change 2024-12-11 22:25:02 +04:00
35d3581e23 fix(k8s): fixed dynamic secret bugs 2024-12-11 22:22:52 +04:00
0edf0dac98 fix(k8-operator): PushSecret CRd causing endless snapshot updates 2024-12-10 04:31:55 +04:00
a757ea22a1 fix(k8-operator): improvements for dynamic secrets 2024-12-09 00:22:22 +04:00
74df374998 Update infisicaldynamicsecret-crd.yaml 2024-12-08 23:24:38 +04:00
925a594a1b feat(k8-operator): dynamic secrets status conditions logging 2024-12-08 23:24:30 +04:00
36af975594 docs(k8-operator): k8's dynamic secret docs 2024-12-08 22:42:29 +04:00
ee54d460a0 fix(k8-operator): update charts 2024-12-08 21:30:28 +04:00
3c32d8dd90 fix(k8-operator): helm 2024-12-08 21:30:28 +04:00
9b50d451ec fix(k8-operator): common types support 2024-12-08 21:30:28 +04:00
7ede4e2cf5 fix(k8-operator): moved template 2024-12-08 21:30:28 +04:00
4552f0efa4 feat(k8-operator): dynamic secrets 2024-12-08 21:30:28 +04:00
0d35273857 feat(k8-operator): push secrets 2024-12-08 21:30:09 +04:00
5ad8dab250 fix(k8-operator): resource-based finalizer names 2024-12-08 21:29:45 +04:00
92a80b3314 fix(k8-operator): helm and cleanup 2024-12-08 21:21:14 +04:00
01dcbb0122 updated env slugs 2024-12-07 05:22:21 +04:00
adb0819102 Added RBAC 2024-12-07 05:22:21 +04:00
41ba111a69 Update PROJECT 2024-12-07 05:22:21 +04:00
1b48ce21be Update conditions.go 2024-12-07 05:22:21 +04:00
2f922d6343 fix: requested changes 2024-12-07 05:22:21 +04:00
e67b0540dd cleanup and resource seperation 2024-12-07 05:22:21 +04:00
a78455fde6 remove print 2024-12-07 05:22:21 +04:00
967dac9be6 docs(k8-operator): push secrets 2024-12-07 05:22:21 +04:00
922b245780 feat(k8-operator): push secrets 2024-12-07 05:22:21 +04:00
432 changed files with 15476 additions and 6575 deletions

View File

@ -5,6 +5,10 @@ permissions:
id-token: write
contents: read
concurrency:
group: "infisical-core-deployment"
cancel-in-progress: false
jobs:
infisical-tests:
name: Integration tests
@ -97,7 +101,7 @@ jobs:
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
environment-variables: "LOG_LEVEL=info"
- name: Deploy to Amazon ECS service
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
service: infisical-core-gamma-stage
@ -113,10 +117,6 @@ jobs:
steps:
- uses: twingate/github-action@v1
with:
# The Twingate Service Key used to connect Twingate to the proper service
# Learn more about [Twingate Services](https://docs.twingate.com/docs/services)
#
# Required
service-key: ${{ secrets.TWINGATE_SERVICE_KEY }}
- name: Checkout code
uses: actions/checkout@v2
@ -153,12 +153,37 @@ jobs:
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
environment-variables: "LOG_LEVEL=info"
- name: Deploy to Amazon ECS service
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
service: infisical-core-platform
cluster: infisical-core-platform
wait-for-service-stability: true
- name: Post slack message
uses: slackapi/slack-github-action@v2.0.0
with:
webhook: ${{ secrets.SLACK_DEPLOYMENT_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
text: "*Deployment Status Update*: ${{ job.status }}"
blocks:
- type: "section"
text:
type: "mrkdwn"
text: "*Deployment Status Update*: ${{ job.status }}"
- type: "section"
fields:
- type: "mrkdwn"
text: "*Application:*\nInfisical Core"
- type: "mrkdwn"
text: "*Instance Type:*\nShared Infisical Cloud"
- type: "section"
fields:
- type: "mrkdwn"
text: "*Region:*\nUS"
- type: "mrkdwn"
text: "*Git Tag:*\n<https://github.com/Infisical/infisical/commit/${{ steps.commit.outputs.short }}>"
production-eu:
name: EU production deploy
@ -204,9 +229,34 @@ jobs:
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
environment-variables: "LOG_LEVEL=info"
- name: Deploy to Amazon ECS service
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
service: infisical-core-platform
cluster: infisical-core-platform
wait-for-service-stability: true
- name: Post slack message
uses: slackapi/slack-github-action@v2.0.0
with:
webhook: ${{ secrets.SLACK_DEPLOYMENT_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
text: "*Deployment Status Update*: ${{ job.status }}"
blocks:
- type: "section"
text:
type: "mrkdwn"
text: "*Deployment Status Update*: ${{ job.status }}"
- type: "section"
fields:
- type: "mrkdwn"
text: "*Application:*\nInfisical Core"
- type: "mrkdwn"
text: "*Instance Type:*\nShared Infisical Cloud"
- type: "section"
fields:
- type: "mrkdwn"
text: "*Region:*\nEU"
- type: "mrkdwn"
text: "*Git Tag:*\n<https://github.com/Infisical/infisical/commit/${{ steps.commit.outputs.short }}>"

View File

@ -7,3 +7,4 @@ docs/self-hosting/configuration/envars.mdx:generic-api-key:106
frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:451
docs/mint.json:generic-api-key:651
backend/src/ee/services/hsm/hsm-service.ts:generic-api-key:134
docs/documentation/platform/audit-log-streams/audit-log-streams.mdx:generic-api-key:104

View File

@ -56,7 +56,7 @@ We're on a mission to make security tooling more accessible to everyone, not jus
- **[Infisical Kubernetes Operator](https://infisical.com/docs/documentation/getting-started/kubernetes)**: Deliver secrets to your Kubernetes workloads and automatically reload deployments.
- **[Infisical Agent](https://infisical.com/docs/infisical-agent/overview)**: Inject secrets into applications without modifying any code logic.
### Internal PKI:
### Infisical (Internal) PKI:
- **[Private Certificate Authority](https://infisical.com/docs/documentation/platform/pki/private-ca)**: Create CA hierarchies, configure [certificate templates](https://infisical.com/docs/documentation/platform/pki/certificates#guide-to-issuing-certificates) for policy enforcement, and start issuing X.509 certificates.
- **[Certificate Management](https://infisical.com/docs/documentation/platform/pki/certificates)**: Manage the certificate lifecycle from [issuance](https://infisical.com/docs/documentation/platform/pki/certificates#guide-to-issuing-certificates) to [revocation](https://infisical.com/docs/documentation/platform/pki/certificates#guide-to-revoking-certificates) with support for CRL.
@ -64,12 +64,17 @@ We're on a mission to make security tooling more accessible to everyone, not jus
- **[Infisical PKI Issuer for Kubernetes](https://infisical.com/docs/documentation/platform/pki/pki-issuer)**: Deliver TLS certificates to your Kubernetes workloads with automatic renewal.
- **[Enrollment over Secure Transport](https://infisical.com/docs/documentation/platform/pki/est)**: Enroll and manage certificates via EST protocol.
### Key Management (KMS):
### Infisical Key Management System (KMS):
- **[Cryptographic Keys](https://infisical.com/docs/documentation/platform/kms)**: Centrally manage keys across projects through a user-friendly interface or via the API.
- **[Encrypt and Decrypt Data](https://infisical.com/docs/documentation/platform/kms#guide-to-encrypting-data)**: Use symmetric keys to encrypt and decrypt data.
### Infisical SSH
- **[Signed SSH Certificates](https://infisical.com/docs/documentation/platform/ssh)**: Issue ephemeral SSH credentials for secure, short-lived, and centralized access to infrastructure.
### General Platform:
- **Authentication Methods**: Authenticate machine identities with Infisical using a cloud-native or platform agnostic authentication method ([Kubernetes Auth](https://infisical.com/docs/documentation/platform/identities/kubernetes-auth), [GCP Auth](https://infisical.com/docs/documentation/platform/identities/gcp-auth), [Azure Auth](https://infisical.com/docs/documentation/platform/identities/azure-auth), [AWS Auth](https://infisical.com/docs/documentation/platform/identities/aws-auth), [OIDC Auth](https://infisical.com/docs/documentation/platform/identities/oidc-auth/general), [Universal Auth](https://infisical.com/docs/documentation/platform/identities/universal-auth)).
- **[Access Controls](https://infisical.com/docs/documentation/platform/access-controls/overview)**: Define advanced authorization controls for users and machine identities with [RBAC](https://infisical.com/docs/documentation/platform/access-controls/role-based-access-controls), [additional privileges](https://infisical.com/docs/documentation/platform/access-controls/additional-privileges), [temporary access](https://infisical.com/docs/documentation/platform/access-controls/temporary-access), [access requests](https://infisical.com/docs/documentation/platform/access-controls/access-requests), [approval workflows](https://infisical.com/docs/documentation/platform/pr-workflows), and more.
- **[Audit logs](https://infisical.com/docs/documentation/platform/audit-logs)**: Track every action taken on the platform.

View File

@ -218,6 +218,9 @@ import {
TRateLimit,
TRateLimitInsert,
TRateLimitUpdate,
TResourceMetadata,
TResourceMetadataInsert,
TResourceMetadataUpdate,
TSamlConfigs,
TSamlConfigsInsert,
TSamlConfigsUpdate,
@ -887,6 +890,11 @@ declare module "knex/types/tables" {
TProjectSplitBackfillIdsInsert,
TProjectSplitBackfillIdsUpdate
>;
[TableName.ResourceMetadata]: KnexOriginal.CompositeTableType<
TResourceMetadata,
TResourceMetadataInsert,
TResourceMetadataUpdate
>;
[TableName.AppConnection]: KnexOriginal.CompositeTableType<
TAppConnections,
TAppConnectionsInsert,

View File

@ -0,0 +1,40 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.ResourceMetadata))) {
await knex.schema.createTable(TableName.ResourceMetadata, (tb) => {
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
tb.string("key").notNullable();
tb.string("value", 1020).notNullable();
tb.uuid("orgId").notNullable();
tb.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
tb.uuid("userId");
tb.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
tb.uuid("identityId");
tb.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
tb.uuid("secretId");
tb.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("CASCADE");
tb.timestamps(true, true, true);
});
}
const hasSecretMetadataField = await knex.schema.hasColumn(TableName.SecretApprovalRequestSecretV2, "secretMetadata");
if (!hasSecretMetadataField) {
await knex.schema.alterTable(TableName.SecretApprovalRequestSecretV2, (t) => {
t.jsonb("secretMetadata");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.ResourceMetadata);
const hasSecretMetadataField = await knex.schema.hasColumn(TableName.SecretApprovalRequestSecretV2, "secretMetadata");
if (hasSecretMetadataField) {
await knex.schema.alterTable(TableName.SecretApprovalRequestSecretV2, (t) => {
t.dropColumn("secretMetadata");
});
}
}

View File

@ -0,0 +1,49 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
// find any duplicate group names within organizations
const duplicates = await knex(TableName.Groups)
.select("orgId", "name")
.count("* as count")
.groupBy("orgId", "name")
.having(knex.raw("count(*) > 1"));
// for each set of duplicates, update all but one with a numbered suffix
for await (const duplicate of duplicates) {
const groups = await knex(TableName.Groups)
.select("id", "name")
.where({
orgId: duplicate.orgId,
name: duplicate.name
})
.orderBy("createdAt", "asc"); // keep original name for oldest group
// skip the first (oldest) group, rename others with numbered suffix
for (let i = 1; i < groups.length; i += 1) {
// eslint-disable-next-line no-await-in-loop
await knex(TableName.Groups)
.where("id", groups[i].id)
.update({
name: `${groups[i].name} (${i})`,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore TS doesn't know about Knex's timestamp types
updatedAt: new Date()
});
}
}
// add the unique constraint
await knex.schema.alterTable(TableName.Groups, (t) => {
t.unique(["orgId", "name"]);
});
}
export async function down(knex: Knex): Promise<void> {
// Remove the unique constraint
await knex.schema.alterTable(TableName.Groups, (t) => {
t.dropUnique(["orgId", "name"]);
});
}

View File

@ -0,0 +1,33 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasEnforceCapitalizationCol = await knex.schema.hasColumn(TableName.Project, "enforceCapitalization");
const hasAutoCapitalizationCol = await knex.schema.hasColumn(TableName.Project, "autoCapitalization");
await knex.schema.alterTable(TableName.Project, (t) => {
if (!hasEnforceCapitalizationCol) {
t.boolean("enforceCapitalization").defaultTo(false).notNullable();
}
if (hasAutoCapitalizationCol) {
t.boolean("autoCapitalization").defaultTo(false).alter();
}
});
}
export async function down(knex: Knex): Promise<void> {
const hasEnforceCapitalizationCol = await knex.schema.hasColumn(TableName.Project, "enforceCapitalization");
const hasAutoCapitalizationCol = await knex.schema.hasColumn(TableName.Project, "autoCapitalization");
await knex.schema.alterTable(TableName.Project, (t) => {
if (hasEnforceCapitalizationCol) {
t.dropColumn("enforceCapitalization");
}
if (hasAutoCapitalizationCol) {
t.boolean("autoCapitalization").defaultTo(true).alter();
}
});
}

View File

@ -71,6 +71,7 @@ export * from "./project-user-additional-privilege";
export * from "./project-user-membership-roles";
export * from "./projects";
export * from "./rate-limit";
export * from "./resource-metadata";
export * from "./saml-configs";
export * from "./scim-tokens";
export * from "./secret-approval-policies";

View File

@ -80,6 +80,7 @@ export enum TableName {
IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege",
// used by both identity and users
IdentityMetadata = "identity_metadata",
ResourceMetadata = "resource_metadata",
ScimToken = "scim_tokens",
AccessApprovalPolicy = "access_approval_policies",
AccessApprovalPolicyApprover = "access_approval_policies_approvers",
@ -214,3 +215,12 @@ export enum ProjectType {
KMS = "kms",
SSH = "ssh"
}
export enum ActionProjectType {
SecretManager = ProjectType.SecretManager,
CertificateManager = ProjectType.CertificateManager,
KMS = ProjectType.KMS,
SSH = ProjectType.SSH,
// project operations that happen on all types
Any = "any"
}

View File

@ -13,7 +13,7 @@ export const ProjectsSchema = z.object({
id: z.string(),
name: z.string(),
slug: z.string(),
autoCapitalization: z.boolean().default(true).nullable().optional(),
autoCapitalization: z.boolean().default(false).nullable().optional(),
orgId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
@ -25,7 +25,8 @@ export const ProjectsSchema = z.object({
kmsSecretManagerKeyId: z.string().uuid().nullable().optional(),
kmsSecretManagerEncryptedDataKey: zodBuffer.nullable().optional(),
description: z.string().nullable().optional(),
type: z.string()
type: z.string(),
enforceCapitalization: z.boolean().default(false)
});
export type TProjects = z.infer<typeof ProjectsSchema>;

View File

@ -0,0 +1,24 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const ResourceMetadataSchema = z.object({
id: z.string().uuid(),
key: z.string(),
value: z.string(),
orgId: z.string().uuid(),
userId: z.string().uuid().nullable().optional(),
identityId: z.string().uuid().nullable().optional(),
secretId: z.string().uuid().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TResourceMetadata = z.infer<typeof ResourceMetadataSchema>;
export type TResourceMetadataInsert = Omit<z.input<typeof ResourceMetadataSchema>, TImmutableDBKeys>;
export type TResourceMetadataUpdate = Partial<Omit<z.input<typeof ResourceMetadataSchema>, TImmutableDBKeys>>;

View File

@ -24,7 +24,8 @@ export const SecretApprovalRequestsSecretsV2Schema = z.object({
requestId: z.string().uuid(),
op: z.string(),
secretId: z.string().uuid().nullable().optional(),
secretVersion: z.string().uuid().nullable().optional()
secretVersion: z.string().uuid().nullable().optional(),
secretMetadata: z.unknown().nullable().optional()
});
export type TSecretApprovalRequestsSecretsV2 = z.infer<typeof SecretApprovalRequestsSecretsV2Schema>;

View File

@ -22,6 +22,7 @@ import { registerSecretApprovalPolicyRouter } from "./secret-approval-policy-rou
import { registerSecretApprovalRequestRouter } from "./secret-approval-request-router";
import { registerSecretRotationProviderRouter } from "./secret-rotation-provider-router";
import { registerSecretRotationRouter } from "./secret-rotation-router";
import { registerSecretRouter } from "./secret-router";
import { registerSecretScanningRouter } from "./secret-scanning-router";
import { registerSecretVersionRouter } from "./secret-version-router";
import { registerSnapshotRouter } from "./snapshot-router";
@ -92,6 +93,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
await server.register(registerLdapRouter, { prefix: "/ldap" });
await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" });
await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" });
await server.register(registerSecretRouter, { prefix: "/secrets" });
await server.register(registerSecretVersionRouter, { prefix: "/secret" });
await server.register(registerGroupRouter, { prefix: "/groups" });
await server.register(registerAuditLogStreamRouter, { prefix: "/audit-log-streams" });

View File

@ -12,6 +12,7 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { secretRawSchema } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
const approvalRequestUser = z.object({ userId: z.string().nullable().optional() }).merge(
UsersSchema.pick({
@ -274,6 +275,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
.extend({
op: z.string(),
tags: tagSchema,
secretMetadata: ResourceMetadataSchema.nullish(),
secret: z
.object({
id: z.string(),
@ -291,7 +293,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
secretKey: z.string(),
secretValue: z.string().optional(),
secretComment: z.string().optional(),
tags: tagSchema
tags: tagSchema,
secretMetadata: ResourceMetadataSchema.nullish()
})
.optional()
})

View File

@ -0,0 +1,71 @@
import z from "zod";
import { ProjectPermissionActions } from "@app/ee/services/permission/project-permission";
import { RAW_SECRETS } from "@app/lib/api-docs";
import { removeTrailingSlash } from "@app/lib/fn";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
const AccessListEntrySchema = z
.object({
allowedActions: z.nativeEnum(ProjectPermissionActions).array(),
id: z.string(),
membershipId: z.string(),
name: z.string()
})
.array();
export const registerSecretRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:secretName/access-list",
config: {
rateLimit: readLimit
},
schema: {
description: "Get list of users, machine identities, and groups with access to a secret",
security: [
{
bearerAuth: []
}
],
params: z.object({
secretName: z.string().trim().describe(RAW_SECRETS.GET_ACCESS_LIST.secretName)
}),
querystring: z.object({
workspaceId: z.string().trim().describe(RAW_SECRETS.GET_ACCESS_LIST.workspaceId),
environment: z.string().trim().describe(RAW_SECRETS.GET_ACCESS_LIST.environment),
secretPath: z
.string()
.trim()
.default("/")
.transform(removeTrailingSlash)
.describe(RAW_SECRETS.GET_ACCESS_LIST.secretPath)
}),
response: {
200: z.object({
groups: AccessListEntrySchema,
identities: AccessListEntrySchema,
users: AccessListEntrySchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { secretName } = req.params;
const { secretPath, environment, workspaceId: projectId } = req.query;
return server.services.secret.getSecretAccessList({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
secretPath,
environment,
projectId,
secretName
});
}
});
};

View File

@ -1,9 +1,13 @@
import { z } from "zod";
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
import { SecretScanningRiskStatus } from "@app/ee/services/secret-scanning/secret-scanning-types";
import {
SecretScanningResolvedStatus,
SecretScanningRiskStatus
} from "@app/ee/services/secret-scanning/secret-scanning-types";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors";
import { OrderByDirection } from "@app/lib/types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@ -97,6 +101,45 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
}
});
server.route({
url: "/organization/:organizationId/risks/export",
method: "GET",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({ organizationId: z.string().trim() }),
querystring: z.object({
repositoryNames: z
.string()
.optional()
.nullable()
.transform((val) => (val ? val.split(",") : undefined)),
resolvedStatus: z.nativeEnum(SecretScanningResolvedStatus).optional()
}),
response: {
200: z.object({
risks: SecretScanningGitRisksSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const risks = await server.services.secretScanning.getAllRisksByOrg({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId,
filter: {
repositoryNames: req.query.repositoryNames,
resolvedStatus: req.query.resolvedStatus
}
});
return { risks };
}
});
server.route({
url: "/organization/:organizationId/risks",
method: "GET",
@ -105,20 +148,46 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
},
schema: {
params: z.object({ organizationId: z.string().trim() }),
querystring: z.object({
offset: z.coerce.number().min(0).default(0),
limit: z.coerce.number().min(1).max(20000).default(100),
orderBy: z.enum(["createdAt", "name"]).default("createdAt"),
orderDirection: z.nativeEnum(OrderByDirection).default(OrderByDirection.DESC),
repositoryNames: z
.string()
.optional()
.nullable()
.transform((val) => (val ? val.split(",") : undefined)),
resolvedStatus: z.nativeEnum(SecretScanningResolvedStatus).optional()
}),
response: {
200: z.object({ risks: SecretScanningGitRisksSchema.array() })
200: z.object({
risks: SecretScanningGitRisksSchema.array(),
totalCount: z.number(),
repos: z.array(z.string())
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { risks } = await server.services.secretScanning.getRisksByOrg({
const { risks, totalCount, repos } = await server.services.secretScanning.getRisksByOrg({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId
orgId: req.params.organizationId,
filter: {
limit: req.query.limit,
offset: req.query.offset,
orderBy: req.query.orderBy,
orderDirection: req.query.orderDirection,
repositoryNames: req.query.repositoryNames,
resolvedStatus: req.query.resolvedStatus
}
});
return { risks };
return { risks, totalCount, repos };
}
});

View File

@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
@ -87,14 +87,14 @@ export const accessApprovalPolicyServiceFactory = ({
if (!groupApprovers && approvals > userApprovers.length + userApproverNames.length)
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -193,7 +193,14 @@ export const accessApprovalPolicyServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
// Anyone in the project should be able to get the policies.
await permissionService.getProjectPermission(actor, actorId, project.id, actorAuthMethod, actorOrgId);
await permissionService.getProjectPermission({
actor,
actorId,
projectId: project.id,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
return accessApprovalPolicies;
@ -237,14 +244,14 @@ export const accessApprovalPolicyServiceFactory = ({
if (!accessApprovalPolicy) {
throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
accessApprovalPolicy.projectId,
projectId: accessApprovalPolicy.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
@ -321,14 +328,14 @@ export const accessApprovalPolicyServiceFactory = ({
const policy = await accessApprovalPolicyDAL.findById(policyId);
if (!policy) throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
policy.projectId,
projectId: policy.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretApproval
@ -372,13 +379,14 @@ export const accessApprovalPolicyServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const { membership } = await permissionService.getProjectPermission(
const { membership } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
}
@ -411,13 +419,14 @@ export const accessApprovalPolicyServiceFactory = ({
});
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
policy.projectId,
projectId: policy.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);

View File

@ -1,7 +1,7 @@
import slugify from "@sindresorhus/slugify";
import ms from "ms";
import { ProjectMembershipRole } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
@ -100,13 +100,14 @@ export const accessApprovalRequestServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
// Anyone can create an access approval request.
const { membership } = await permissionService.getProjectPermission(
const { membership } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
}
@ -273,13 +274,14 @@ export const accessApprovalRequestServiceFactory = ({
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const { membership } = await permissionService.getProjectPermission(
const { membership } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
}
@ -318,13 +320,14 @@ export const accessApprovalRequestServiceFactory = ({
});
}
const { membership, hasRole } = await permissionService.getProjectPermission(
const { membership, hasRole } = await permissionService.getProjectPermission({
actor,
actorId,
accessApprovalRequest.projectId,
projectId: accessApprovalRequest.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
@ -422,13 +425,14 @@ export const accessApprovalRequestServiceFactory = ({
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const { membership } = await permissionService.getProjectPermission(
const { membership } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
}

View File

@ -93,7 +93,7 @@ export const auditLogStreamServiceFactory = ({
}
)
.catch((err) => {
throw new Error(`Failed to connect with the source ${(err as Error)?.message}`);
throw new BadRequestError({ message: `Failed to connect with upstream source: ${(err as Error)?.message}` });
});
const encryptedHeaders = headers ? infisicalSymmetricEncypt(JSON.stringify(headers)) : undefined;
const logStream = await auditLogStreamDAL.create({

View File

@ -100,10 +100,10 @@ export const auditLogDALFactory = (db: TDbClient) => {
// Filter by date range
if (startDate) {
void sqlQuery.where(`${TableName.AuditLog}.createdAt`, ">=", startDate);
void sqlQuery.whereRaw(`"${TableName.AuditLog}"."createdAt" >= ?::timestamptz`, [startDate]);
}
if (endDate) {
void sqlQuery.where(`${TableName.AuditLog}.createdAt`, "<=", endDate);
void sqlQuery.whereRaw(`"${TableName.AuditLog}"."createdAt" <= ?::timestamptz`, [endDate]);
}
// we timeout long running queries to prevent DB resource issues (2 minutes)

View File

@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors";
@ -26,13 +27,14 @@ export const auditLogServiceFactory = ({
const listAuditLogs = async ({ actorAuthMethod, actorId, actorOrgId, actor, filter }: TListProjectAuditLogDTO) => {
// Filter logs for specific project
if (filter.projectId) {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
filter.projectId,
projectId: filter.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
} else {
// Organization-wide logs

View File

@ -31,7 +31,7 @@ export type TListProjectAuditLogDTO = {
export type TCreateAuditLogDTO = {
event: Event;
actor: UserActor | IdentityActor | ServiceActor | ScimClientActor | PlatformActor;
actor: UserActor | IdentityActor | ServiceActor | ScimClientActor | PlatformActor | UnknownUserActor;
orgId?: string;
projectId?: string;
} & BaseAuthData;
@ -229,7 +229,10 @@ export enum EventType {
GET_APP_CONNECTION = "get-app-connection",
CREATE_APP_CONNECTION = "create-app-connection",
UPDATE_APP_CONNECTION = "update-app-connection",
DELETE_APP_CONNECTION = "delete-app-connection"
DELETE_APP_CONNECTION = "delete-app-connection",
CREATE_SHARED_SECRET = "create-shared-secret",
DELETE_SHARED_SECRET = "delete-shared-secret",
READ_SHARED_SECRET = "read-shared-secret"
}
interface UserActorMetadata {
@ -252,6 +255,8 @@ interface ScimClientActorMetadata {}
interface PlatformActorMetadata {}
interface UnknownUserActorMetadata {}
export interface UserActor {
type: ActorType.USER;
metadata: UserActorMetadata;
@ -267,6 +272,11 @@ export interface PlatformActor {
metadata: PlatformActorMetadata;
}
export interface UnknownUserActor {
type: ActorType.UNKNOWN_USER;
metadata: UnknownUserActorMetadata;
}
export interface IdentityActor {
type: ActorType.IDENTITY;
metadata: IdentityActorMetadata;
@ -1907,6 +1917,35 @@ interface DeleteAppConnectionEvent {
};
}
interface CreateSharedSecretEvent {
type: EventType.CREATE_SHARED_SECRET;
metadata: {
id: string;
accessType: string;
name?: string;
expiresAfterViews?: number;
usingPassword: boolean;
expiresAt: string;
};
}
interface DeleteSharedSecretEvent {
type: EventType.DELETE_SHARED_SECRET;
metadata: {
id: string;
name?: string;
};
}
interface ReadSharedSecretEvent {
type: EventType.READ_SHARED_SECRET;
metadata: {
id: string;
name?: string;
accessType: string;
};
}
export type Event =
| GetSecretsEvent
| GetSecretEvent
@ -2083,4 +2122,7 @@ export type Event =
| GetAppConnectionEvent
| CreateAppConnectionEvent
| UpdateAppConnectionEvent
| DeleteAppConnectionEvent;
| DeleteAppConnectionEvent
| CreateSharedSecretEvent
| DeleteSharedSecretEvent
| ReadSharedSecretEvent;

View File

@ -1,6 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509";
import { ActionProjectType } from "@app/db/schemas";
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@ -66,13 +67,14 @@ export const certificateAuthorityCrlServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,

View File

@ -1,7 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability";
import ms from "ms";
import { ProjectType, SecretKeyEncoding } from "@app/db/schemas";
import { ActionProjectType, SecretKeyEncoding } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
@ -67,14 +67,14 @@ export const dynamicSecretLeaseServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -147,14 +147,14 @@ export const dynamicSecretLeaseServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -227,14 +227,14 @@ export const dynamicSecretLeaseServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -297,13 +297,14 @@ export const dynamicSecretLeaseServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -339,13 +340,14 @@ export const dynamicSecretLeaseServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })

View File

@ -1,6 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability";
import { ProjectType, SecretKeyEncoding } from "@app/db/schemas";
import { ActionProjectType, SecretKeyEncoding } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
@ -73,14 +73,14 @@ export const dynamicSecretServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.CreateRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -145,14 +145,14 @@ export const dynamicSecretServiceFactory = ({
const projectId = project.id;
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.EditRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -229,14 +229,14 @@ export const dynamicSecretServiceFactory = ({
const projectId = project.id;
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -290,13 +290,14 @@ export const dynamicSecretServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.ReadRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -340,13 +341,14 @@ export const dynamicSecretServiceFactory = ({
isInternal
}: TListDynamicSecretsMultiEnvDTO) => {
if (!isInternal) {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
// verify user has access to each env in request
environmentSlugs.forEach((environmentSlug) =>
@ -383,13 +385,14 @@ export const dynamicSecretServiceFactory = ({
search,
projectId
}: TGetDynamicSecretsCountDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.ReadRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -431,13 +434,14 @@ export const dynamicSecretServiceFactory = ({
projectId = project.id;
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.ReadRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -462,13 +466,14 @@ export const dynamicSecretServiceFactory = ({
{ folderMappings, filters, projectId }: TListDynamicSecretsByFolderMappingsDTO,
actor: OrgServiceActor
) => {
const { permission } = await permissionService.getProjectPermission(
actor.type,
actor.id,
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId,
actor.authMethod,
actor.orgId
);
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager
});
const userAccessibleFolderMappings = folderMappings.filter(({ path, environment }) =>
permission.can(
@ -507,13 +512,14 @@ export const dynamicSecretServiceFactory = ({
...params
}: TListDynamicSecretsMultiEnvDTO) => {
if (!isInternal) {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
// verify user has access to each env in request
environmentSlugs.forEach((environmentSlug) =>

View File

@ -34,6 +34,8 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretSqlDBSchema>) => {
const ssl = providerInputs.ca ? { rejectUnauthorized: false, ca: providerInputs.ca } : undefined;
const isMsSQLClient = providerInputs.client === SqlProviders.MsSQL;
const db = knex({
client: providerInputs.client,
connection: {
@ -43,7 +45,16 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
user: providerInputs.username,
password: providerInputs.password,
ssl,
pool: { min: 0, max: 1 }
pool: { min: 0, max: 1 },
// @ts-expect-error this is because of knexjs type signature issue. This is directly passed to driver
// https://github.com/knex/knex/blob/b6507a7129d2b9fafebf5f831494431e64c6a8a0/lib/dialects/mssql/index.js#L66
// https://github.com/tediousjs/tedious/blob/ebb023ed90969a7ec0e4b036533ad52739d921f7/test/config.ci.ts#L19
options: isMsSQLClient
? {
trustServerCertificate: !providerInputs.ca,
cryptoCredentialsDetails: providerInputs.ca ? { ca: providerInputs.ca } : {}
}
: undefined
},
acquireConnectionTimeout: EXTERNAL_REQUEST_TIMEOUT
});

View File

@ -1,7 +1,9 @@
import { KMSServiceException } from "@aws-sdk/client-kms";
import { STSServiceException } from "@aws-sdk/client-sts";
import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
@ -71,7 +73,16 @@ export const externalKmsServiceFactory = ({
switch (provider.type) {
case KmsProviders.Aws:
{
const externalKms = await AwsKmsProviderFactory({ inputs: provider.inputs });
const externalKms = await AwsKmsProviderFactory({ inputs: provider.inputs }).catch((error) => {
if (error instanceof STSServiceException || error instanceof KMSServiceException) {
throw new InternalServerError({
message: error.message ? `AWS error: ${error.message}` : ""
});
}
throw error;
});
// if missing kms key this generate a new kms key id and returns new provider input
const newProviderInput = await externalKms.generateInputKmsKey();
sanitizedProviderInput = JSON.stringify(newProviderInput);

View File

@ -32,7 +32,7 @@ type TGroupServiceFactoryDep = {
userDAL: Pick<TUserDALFactory, "find" | "findUserEncKeyByUserIdsBatch" | "transaction" | "findOne">;
groupDAL: Pick<
TGroupDALFactory,
"create" | "findOne" | "update" | "delete" | "findAllGroupPossibleMembers" | "findById"
"create" | "findOne" | "update" | "delete" | "findAllGroupPossibleMembers" | "findById" | "transaction"
>;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
orgDAL: Pick<TOrgDALFactory, "findMembership" | "countAllOrgMembers">;
@ -88,12 +88,26 @@ export const groupServiceFactory = ({
if (!hasRequiredPriviledges)
throw new ForbiddenRequestError({ message: "Failed to create a more privileged group" });
const group = await groupDAL.create({
name,
slug: slug || slugify(`${name}-${alphaNumericNanoId(4)}`),
orgId: actorOrgId,
role: isCustomRole ? OrgMembershipRole.Custom : role,
roleId: customRole?.id
const group = await groupDAL.transaction(async (tx) => {
const existingGroup = await groupDAL.findOne({ orgId: actorOrgId, name }, tx);
if (existingGroup) {
throw new BadRequestError({
message: `Failed to create group with name '${name}'. Group with the same name already exists`
});
}
const newGroup = await groupDAL.create(
{
name,
slug: slug || slugify(`${name}-${alphaNumericNanoId(4)}`),
orgId: actorOrgId,
role: isCustomRole ? OrgMembershipRole.Custom : role,
roleId: customRole?.id
},
tx
);
return newGroup;
});
return group;
@ -145,21 +159,36 @@ export const groupServiceFactory = ({
if (isCustomRole) customRole = customOrgRole;
}
const [updatedGroup] = await groupDAL.update(
{
id: group.id
},
{
name,
slug: slug ? slugify(slug) : undefined,
...(role
? {
role: customRole ? OrgMembershipRole.Custom : role,
roleId: customRole?.id ?? null
}
: {})
const updatedGroup = await groupDAL.transaction(async (tx) => {
if (name) {
const existingGroup = await groupDAL.findOne({ orgId: actorOrgId, name }, tx);
if (existingGroup && existingGroup.id !== id) {
throw new BadRequestError({
message: `Failed to update group with name '${name}'. Group with the same name already exists`
});
}
}
);
const [updated] = await groupDAL.update(
{
id: group.id
},
{
name,
slug: slug ? slugify(slug) : undefined,
...(role
? {
role: customRole ? OrgMembershipRole.Custom : role,
roleId: customRole?.id ?? null
}
: {})
},
tx
);
return updated;
});
return updatedGroup;
};

View File

@ -2,7 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";
import { packRules } from "@casl/ability/extra";
import ms from "ms";
import { TableName } from "@app/db/schemas";
import { ActionProjectType, TableName } from "@app/db/schemas";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { unpackPermissions } from "@app/server/routes/santizedSchemas/permission";
@ -55,24 +55,26 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId })
);
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
ActorType.IDENTITY,
identityId,
identityProjectMembership.projectId,
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// we need to validate that the privilege given is not higher than the assigning users permission
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
@ -135,24 +137,26 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
});
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
);
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
ActorType.IDENTITY,
identityProjectMembership.identityId,
identityProjectMembership.projectId,
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// we need to validate that the privilege given is not higher than the assigning users permission
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
@ -215,24 +219,26 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
});
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
);
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
ActorType.IDENTITY,
identityProjectMembership.identityId,
identityProjectMembership.projectId,
const { permission: identityRolePermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
if (!hasRequiredPriviledges)
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
@ -260,13 +266,14 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
});
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
@ -294,13 +301,14 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
@ -329,13 +337,14 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })

View File

@ -2,6 +2,7 @@ import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability"
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import ms from "ms";
import { ActionProjectType } from "@app/db/schemas";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
@ -62,25 +63,27 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId })
);
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
ActorType.IDENTITY,
identityId,
identityProjectMembership.projectId,
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// we need to validate that the privilege given is not higher than the assigning users permission
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
@ -143,26 +146,28 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId })
);
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
ActorType.IDENTITY,
identityProjectMembership.identityId,
identityProjectMembership.projectId,
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// we need to validate that the privilege given is not higher than the assigning users permission
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
@ -242,25 +247,27 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId })
);
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
ActorType.IDENTITY,
identityProjectMembership.identityId,
identityProjectMembership.projectId,
const { permission: identityRolePermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
if (!hasRequiredPriviledges)
throw new ForbiddenRequestError({ message: "Failed to edit more privileged identity" });
@ -299,13 +306,14 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Identity, { identityId })
@ -341,13 +349,14 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,

View File

@ -476,14 +476,14 @@ export const ldapConfigServiceFactory = ({
});
} else {
const plan = await licenseService.getPlan(orgId);
if (plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
if (plan?.slug !== "enterprise" && plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
throw new BadRequestError({
message: "Failed to create new member via LDAP due to member limit reached. Upgrade plan to add more members."
});
}
if (plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
throw new BadRequestError({
message: "Failed to create new member via LDAP due to member limit reached. Upgrade plan to add more members."

View File

@ -24,6 +24,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
rbac: false,
customRateLimits: false,
customAlerts: false,
secretAccessInsights: false,
auditLogs: false,
auditLogsRetentionDays: 0,
auditLogStreams: false,

View File

@ -246,8 +246,7 @@ export const licenseServiceFactory = ({
};
const getOrgPlan = async ({ orgId, actor, actorId, actorOrgId, actorAuthMethod, projectId }: TOrgPlanDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
const plan = await getPlan(orgId, projectId);
return plan;
};

View File

@ -48,6 +48,7 @@ export type TFeatureSet = {
samlSSO: false;
hsm: false;
oidcSSO: false;
secretAccessInsights: false;
scim: false;
ldap: false;
groups: false;

View File

@ -125,6 +125,404 @@ export const permissionDALFactory = (db: TDbClient) => {
}
};
const getProjectGroupPermissions = async (projectId: string) => {
try {
const docs = await db
.replicaNode()(TableName.GroupProjectMembership)
.join(TableName.Groups, `${TableName.Groups}.id`, `${TableName.GroupProjectMembership}.groupId`)
.join(
TableName.GroupProjectMembershipRole,
`${TableName.GroupProjectMembershipRole}.projectMembershipId`,
`${TableName.GroupProjectMembership}.id`
)
.leftJoin<TProjectRoles>(
{ groupCustomRoles: TableName.ProjectRoles },
`${TableName.GroupProjectMembershipRole}.customRoleId`,
`groupCustomRoles.id`
)
.where(`${TableName.GroupProjectMembership}.projectId`, "=", projectId)
.select(
db.ref("id").withSchema(TableName.GroupProjectMembership).as("membershipId"),
db.ref("id").withSchema(TableName.Groups).as("groupId"),
db.ref("name").withSchema(TableName.Groups).as("groupName"),
db.ref("slug").withSchema("groupCustomRoles").as("groupProjectMembershipRoleCustomRoleSlug"),
db.ref("permissions").withSchema("groupCustomRoles").as("groupProjectMembershipRolePermission"),
db.ref("id").withSchema(TableName.GroupProjectMembershipRole).as("groupProjectMembershipRoleId"),
db.ref("role").withSchema(TableName.GroupProjectMembershipRole).as("groupProjectMembershipRole"),
db
.ref("customRoleId")
.withSchema(TableName.GroupProjectMembershipRole)
.as("groupProjectMembershipRoleCustomRoleId"),
db
.ref("isTemporary")
.withSchema(TableName.GroupProjectMembershipRole)
.as("groupProjectMembershipRoleIsTemporary"),
db
.ref("temporaryMode")
.withSchema(TableName.GroupProjectMembershipRole)
.as("groupProjectMembershipRoleTemporaryMode"),
db
.ref("temporaryRange")
.withSchema(TableName.GroupProjectMembershipRole)
.as("groupProjectMembershipRoleTemporaryRange"),
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.GroupProjectMembershipRole)
.as("groupProjectMembershipRoleTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.GroupProjectMembershipRole)
.as("groupProjectMembershipRoleTemporaryAccessEndTime")
);
const groupPermissions = sqlNestRelationships({
data: docs,
key: "groupId",
parentMapper: ({ groupId, groupName, membershipId }) => ({
groupId,
username: groupName,
id: membershipId
}),
childrenMapper: [
{
key: "groupProjectMembershipRoleId",
label: "groupRoles" as const,
mapper: ({
groupProjectMembershipRoleId,
groupProjectMembershipRole,
groupProjectMembershipRolePermission,
groupProjectMembershipRoleCustomRoleSlug,
groupProjectMembershipRoleIsTemporary,
groupProjectMembershipRoleTemporaryMode,
groupProjectMembershipRoleTemporaryAccessEndTime,
groupProjectMembershipRoleTemporaryAccessStartTime,
groupProjectMembershipRoleTemporaryRange
}) => ({
id: groupProjectMembershipRoleId,
role: groupProjectMembershipRole,
customRoleSlug: groupProjectMembershipRoleCustomRoleSlug,
permissions: groupProjectMembershipRolePermission,
temporaryRange: groupProjectMembershipRoleTemporaryRange,
temporaryMode: groupProjectMembershipRoleTemporaryMode,
temporaryAccessStartTime: groupProjectMembershipRoleTemporaryAccessStartTime,
temporaryAccessEndTime: groupProjectMembershipRoleTemporaryAccessEndTime,
isTemporary: groupProjectMembershipRoleIsTemporary
})
}
]
});
return groupPermissions
.map((groupPermission) => {
if (!groupPermission) return undefined;
const activeGroupRoles =
groupPermission?.groupRoles?.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
) ?? [];
return {
...groupPermission,
roles: activeGroupRoles
};
})
.filter((item): item is NonNullable<typeof item> => Boolean(item));
} catch (error) {
throw new DatabaseError({ error, name: "GetProjectGroupPermissions" });
}
};
const getProjectUserPermissions = async (projectId: string) => {
try {
const docs = await db
.replicaNode()(TableName.Users)
.where("isGhost", "=", false)
.leftJoin(TableName.GroupProjectMembership, (queryBuilder) => {
void queryBuilder.on(`${TableName.GroupProjectMembership}.projectId`, db.raw("?", [projectId]));
})
.leftJoin(
TableName.GroupProjectMembershipRole,
`${TableName.GroupProjectMembershipRole}.projectMembershipId`,
`${TableName.GroupProjectMembership}.id`
)
.leftJoin<TProjectRoles>(
{ groupCustomRoles: TableName.ProjectRoles },
`${TableName.GroupProjectMembershipRole}.customRoleId`,
`groupCustomRoles.id`
)
.join(TableName.ProjectMembership, (queryBuilder) => {
void queryBuilder
.on(`${TableName.ProjectMembership}.projectId`, db.raw("?", [projectId]))
.andOn(`${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`);
})
.leftJoin(
TableName.ProjectUserMembershipRole,
`${TableName.ProjectUserMembershipRole}.projectMembershipId`,
`${TableName.ProjectMembership}.id`
)
.leftJoin(
TableName.ProjectRoles,
`${TableName.ProjectUserMembershipRole}.customRoleId`,
`${TableName.ProjectRoles}.id`
)
.leftJoin(TableName.ProjectUserAdditionalPrivilege, (queryBuilder) => {
void queryBuilder
.on(`${TableName.ProjectUserAdditionalPrivilege}.projectId`, db.raw("?", [projectId]))
.andOn(`${TableName.ProjectUserAdditionalPrivilege}.userId`, `${TableName.Users}.id`);
})
.join<TProjects>(TableName.Project, `${TableName.Project}.id`, db.raw("?", [projectId]))
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
void queryBuilder
.on(`${TableName.Users}.id`, `${TableName.IdentityMetadata}.userId`)
.andOn(`${TableName.Organization}.id`, `${TableName.IdentityMetadata}.orgId`);
})
.select(
db.ref("id").withSchema(TableName.Users).as("userId"),
db.ref("username").withSchema(TableName.Users).as("username"),
// groups specific
db.ref("id").withSchema(TableName.GroupProjectMembership).as("groupMembershipId"),
db.ref("createdAt").withSchema(TableName.GroupProjectMembership).as("groupMembershipCreatedAt"),
db.ref("updatedAt").withSchema(TableName.GroupProjectMembership).as("groupMembershipUpdatedAt"),
db.ref("slug").withSchema("groupCustomRoles").as("userGroupProjectMembershipRoleCustomRoleSlug"),
db.ref("permissions").withSchema("groupCustomRoles").as("userGroupProjectMembershipRolePermission"),
db.ref("id").withSchema(TableName.GroupProjectMembershipRole).as("userGroupProjectMembershipRoleId"),
db.ref("role").withSchema(TableName.GroupProjectMembershipRole).as("userGroupProjectMembershipRole"),
db
.ref("customRoleId")
.withSchema(TableName.GroupProjectMembershipRole)
.as("userGroupProjectMembershipRoleCustomRoleId"),
db
.ref("isTemporary")
.withSchema(TableName.GroupProjectMembershipRole)
.as("userGroupProjectMembershipRoleIsTemporary"),
db
.ref("temporaryMode")
.withSchema(TableName.GroupProjectMembershipRole)
.as("userGroupProjectMembershipRoleTemporaryMode"),
db
.ref("temporaryRange")
.withSchema(TableName.GroupProjectMembershipRole)
.as("userGroupProjectMembershipRoleTemporaryRange"),
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.GroupProjectMembershipRole)
.as("userGroupProjectMembershipRoleTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.GroupProjectMembershipRole)
.as("userGroupProjectMembershipRoleTemporaryAccessEndTime"),
// user specific
db.ref("id").withSchema(TableName.ProjectMembership).as("membershipId"),
db.ref("createdAt").withSchema(TableName.ProjectMembership).as("membershipCreatedAt"),
db.ref("updatedAt").withSchema(TableName.ProjectMembership).as("membershipUpdatedAt"),
db.ref("slug").withSchema(TableName.ProjectRoles).as("userProjectMembershipRoleCustomRoleSlug"),
db.ref("permissions").withSchema(TableName.ProjectRoles).as("userProjectCustomRolePermission"),
db.ref("id").withSchema(TableName.ProjectUserMembershipRole).as("userProjectMembershipRoleId"),
db.ref("role").withSchema(TableName.ProjectUserMembershipRole).as("userProjectMembershipRole"),
db
.ref("temporaryMode")
.withSchema(TableName.ProjectUserMembershipRole)
.as("userProjectMembershipRoleTemporaryMode"),
db
.ref("isTemporary")
.withSchema(TableName.ProjectUserMembershipRole)
.as("userProjectMembershipRoleIsTemporary"),
db
.ref("temporaryRange")
.withSchema(TableName.ProjectUserMembershipRole)
.as("userProjectMembershipRoleTemporaryRange"),
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.ProjectUserMembershipRole)
.as("userProjectMembershipRoleTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.ProjectUserMembershipRole)
.as("userProjectMembershipRoleTemporaryAccessEndTime"),
db.ref("id").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userAdditionalPrivilegesId"),
db
.ref("permissions")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("userAdditionalPrivilegesPermissions"),
db
.ref("temporaryMode")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("userAdditionalPrivilegesTemporaryMode"),
db
.ref("isTemporary")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("userAdditionalPrivilegesIsTemporary"),
db
.ref("temporaryRange")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("userAdditionalPrivilegesTemporaryRange"),
db.ref("userId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userAdditionalPrivilegesUserId"),
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("userAdditionalPrivilegesTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("userAdditionalPrivilegesTemporaryAccessEndTime"),
// general
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue"),
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
db.ref("orgId").withSchema(TableName.Project),
db.ref("type").withSchema(TableName.Project).as("projectType"),
db.ref("id").withSchema(TableName.Project).as("projectId")
);
const userPermissions = sqlNestRelationships({
data: docs,
key: "userId",
parentMapper: ({
orgId,
username,
orgAuthEnforced,
membershipId,
groupMembershipId,
membershipCreatedAt,
groupMembershipCreatedAt,
groupMembershipUpdatedAt,
membershipUpdatedAt,
projectType,
userId
}) => ({
orgId,
orgAuthEnforced,
userId,
projectId,
username,
projectType,
id: membershipId || groupMembershipId,
createdAt: membershipCreatedAt || groupMembershipCreatedAt,
updatedAt: membershipUpdatedAt || groupMembershipUpdatedAt
}),
childrenMapper: [
{
key: "userGroupProjectMembershipRoleId",
label: "userGroupRoles" as const,
mapper: ({
userGroupProjectMembershipRoleId,
userGroupProjectMembershipRole,
userGroupProjectMembershipRolePermission,
userGroupProjectMembershipRoleCustomRoleSlug,
userGroupProjectMembershipRoleIsTemporary,
userGroupProjectMembershipRoleTemporaryMode,
userGroupProjectMembershipRoleTemporaryAccessEndTime,
userGroupProjectMembershipRoleTemporaryAccessStartTime,
userGroupProjectMembershipRoleTemporaryRange
}) => ({
id: userGroupProjectMembershipRoleId,
role: userGroupProjectMembershipRole,
customRoleSlug: userGroupProjectMembershipRoleCustomRoleSlug,
permissions: userGroupProjectMembershipRolePermission,
temporaryRange: userGroupProjectMembershipRoleTemporaryRange,
temporaryMode: userGroupProjectMembershipRoleTemporaryMode,
temporaryAccessStartTime: userGroupProjectMembershipRoleTemporaryAccessStartTime,
temporaryAccessEndTime: userGroupProjectMembershipRoleTemporaryAccessEndTime,
isTemporary: userGroupProjectMembershipRoleIsTemporary
})
},
{
key: "userProjectMembershipRoleId",
label: "projectMembershipRoles" as const,
mapper: ({
userProjectMembershipRoleId,
userProjectMembershipRole,
userProjectCustomRolePermission,
userProjectMembershipRoleIsTemporary,
userProjectMembershipRoleTemporaryMode,
userProjectMembershipRoleTemporaryRange,
userProjectMembershipRoleTemporaryAccessEndTime,
userProjectMembershipRoleTemporaryAccessStartTime,
userProjectMembershipRoleCustomRoleSlug
}) => ({
id: userProjectMembershipRoleId,
role: userProjectMembershipRole,
customRoleSlug: userProjectMembershipRoleCustomRoleSlug,
permissions: userProjectCustomRolePermission,
temporaryRange: userProjectMembershipRoleTemporaryRange,
temporaryMode: userProjectMembershipRoleTemporaryMode,
temporaryAccessStartTime: userProjectMembershipRoleTemporaryAccessStartTime,
temporaryAccessEndTime: userProjectMembershipRoleTemporaryAccessEndTime,
isTemporary: userProjectMembershipRoleIsTemporary
})
},
{
key: "userAdditionalPrivilegesId",
label: "additionalPrivileges" as const,
mapper: ({
userAdditionalPrivilegesId,
userAdditionalPrivilegesPermissions,
userAdditionalPrivilegesIsTemporary,
userAdditionalPrivilegesTemporaryMode,
userAdditionalPrivilegesTemporaryRange,
userAdditionalPrivilegesTemporaryAccessEndTime,
userAdditionalPrivilegesTemporaryAccessStartTime
}) => ({
id: userAdditionalPrivilegesId,
permissions: userAdditionalPrivilegesPermissions,
temporaryRange: userAdditionalPrivilegesTemporaryRange,
temporaryMode: userAdditionalPrivilegesTemporaryMode,
temporaryAccessStartTime: userAdditionalPrivilegesTemporaryAccessStartTime,
temporaryAccessEndTime: userAdditionalPrivilegesTemporaryAccessEndTime,
isTemporary: userAdditionalPrivilegesIsTemporary
})
},
{
key: "metadataId",
label: "metadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
}
]
});
return userPermissions
.map((userPermission) => {
if (!userPermission) return undefined;
if (!userPermission?.userGroupRoles?.[0] && !userPermission?.projectMembershipRoles?.[0]) return undefined;
// when introducting cron mode change it here
const activeRoles =
userPermission?.projectMembershipRoles?.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
) ?? [];
const activeGroupRoles =
userPermission?.userGroupRoles?.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
) ?? [];
const activeAdditionalPrivileges =
userPermission?.additionalPrivileges?.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
) ?? [];
return {
...userPermission,
roles: [...activeRoles, ...activeGroupRoles],
additionalPrivileges: activeAdditionalPrivileges
};
})
.filter((item): item is NonNullable<typeof item> => Boolean(item));
} catch (error) {
throw new DatabaseError({ error, name: "GetProjectUserPermissions" });
}
};
const getProjectPermission = async (userId: string, projectId: string) => {
try {
const subQueryUserGroups = db(TableName.UserGroupMembership).where("userId", userId).select("groupId");
@ -414,6 +812,163 @@ export const permissionDALFactory = (db: TDbClient) => {
}
};
const getProjectIdentityPermissions = async (projectId: string) => {
try {
const docs = await db
.replicaNode()(TableName.IdentityProjectMembership)
.join(
TableName.IdentityProjectMembershipRole,
`${TableName.IdentityProjectMembershipRole}.projectMembershipId`,
`${TableName.IdentityProjectMembership}.id`
)
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityProjectMembership}.identityId`)
.leftJoin(
TableName.ProjectRoles,
`${TableName.IdentityProjectMembershipRole}.customRoleId`,
`${TableName.ProjectRoles}.id`
)
.leftJoin(
TableName.IdentityProjectAdditionalPrivilege,
`${TableName.IdentityProjectAdditionalPrivilege}.projectMembershipId`,
`${TableName.IdentityProjectMembership}.id`
)
.join(
// Join the Project table to later select orgId
TableName.Project,
`${TableName.IdentityProjectMembership}.projectId`,
`${TableName.Project}.id`
)
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
void queryBuilder
.on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`)
.andOn(`${TableName.Project}.orgId`, `${TableName.IdentityMetadata}.orgId`);
})
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
.select(selectAllTableCols(TableName.IdentityProjectMembershipRole))
.select(
db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"),
db.ref("id").withSchema(TableName.Identity).as("identityId"),
db.ref("name").withSchema(TableName.Identity).as("identityName"),
db.ref("orgId").withSchema(TableName.Project).as("orgId"), // Now you can select orgId from Project
db.ref("type").withSchema(TableName.Project).as("projectType"),
db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"),
db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"),
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
db.ref("permissions").withSchema(TableName.ProjectRoles),
db.ref("id").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApId"),
db.ref("permissions").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApPermissions"),
db
.ref("temporaryMode")
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
.as("identityApTemporaryMode"),
db.ref("isTemporary").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApIsTemporary"),
db
.ref("temporaryRange")
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
.as("identityApTemporaryRange"),
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
.as("identityApTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
.as("identityApTemporaryAccessEndTime"),
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue")
);
const permissions = sqlNestRelationships({
data: docs,
key: "identityId",
parentMapper: ({
membershipId,
membershipCreatedAt,
membershipUpdatedAt,
orgId,
identityName,
projectType,
identityId
}) => ({
id: membershipId,
identityId,
username: identityName,
projectId,
createdAt: membershipCreatedAt,
updatedAt: membershipUpdatedAt,
orgId,
projectType,
// just a prefilled value
orgAuthEnforced: false
}),
childrenMapper: [
{
key: "id",
label: "roles" as const,
mapper: (data) =>
IdentityProjectMembershipRoleSchema.extend({
permissions: z.unknown(),
customRoleSlug: z.string().optional().nullable()
}).parse(data)
},
{
key: "identityApId",
label: "additionalPrivileges" as const,
mapper: ({
identityApId,
identityApPermissions,
identityApIsTemporary,
identityApTemporaryMode,
identityApTemporaryRange,
identityApTemporaryAccessEndTime,
identityApTemporaryAccessStartTime
}) => ({
id: identityApId,
permissions: identityApPermissions,
temporaryRange: identityApTemporaryRange,
temporaryMode: identityApTemporaryMode,
temporaryAccessEndTime: identityApTemporaryAccessEndTime,
temporaryAccessStartTime: identityApTemporaryAccessStartTime,
isTemporary: identityApIsTemporary
})
},
{
key: "metadataId",
label: "metadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
}
]
});
return permissions
.map((permission) => {
if (!permission) {
return undefined;
}
// when introducting cron mode change it here
const activeRoles = permission?.roles.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
);
const activeAdditionalPrivileges = permission?.additionalPrivileges?.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
);
return { ...permission, roles: activeRoles, additionalPrivileges: activeAdditionalPrivileges };
})
.filter((item): item is NonNullable<typeof item> => Boolean(item));
} catch (error) {
throw new DatabaseError({ error, name: "GetProjectIdentityPermissions" });
}
};
const getProjectIdentityPermission = async (identityId: string, projectId: string) => {
try {
const docs = await db
@ -568,6 +1123,9 @@ export const permissionDALFactory = (db: TDbClient) => {
getOrgPermission,
getOrgIdentityPermission,
getProjectPermission,
getProjectIdentityPermission
getProjectIdentityPermission,
getProjectUserPermissions,
getProjectIdentityPermissions,
getProjectGroupPermissions
};
};

View File

@ -1,3 +1,6 @@
import { ActionProjectType } from "@app/db/schemas";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
export type TBuildProjectPermissionDTO = {
permissions?: unknown;
role: string;
@ -7,3 +10,34 @@ export type TBuildOrgPermissionDTO = {
permissions?: unknown;
role: string;
}[];
export type TGetUserProjectPermissionArg = {
userId: string;
projectId: string;
authMethod: ActorAuthMethod;
actionProjectType: ActionProjectType;
userOrgId?: string;
};
export type TGetIdentityProjectPermissionArg = {
identityId: string;
projectId: string;
identityOrgId?: string;
actionProjectType: ActionProjectType;
};
export type TGetServiceTokenProjectPermissionArg = {
serviceTokenId: string;
projectId: string;
actorOrgId?: string;
actionProjectType: ActionProjectType;
};
export type TGetProjectPermissionArg = {
actor: ActorType;
actorId: string;
projectId: string;
actorAuthMethod: ActorAuthMethod;
actorOrgId?: string;
actionProjectType: ActionProjectType;
};

View File

@ -4,9 +4,9 @@ import { MongoQuery } from "@ucast/mongo2js";
import handlebars from "handlebars";
import {
ActionProjectType,
OrgMembershipRole,
ProjectMembershipRole,
ProjectType,
ServiceTokenScopes,
TIdentityProjectMemberships,
TProjectMemberships
@ -23,7 +23,14 @@ import { TServiceTokenDALFactory } from "@app/services/service-token/service-tok
import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission";
import { TPermissionDALFactory } from "./permission-dal";
import { escapeHandlebarsMissingMetadata, validateOrgSSO } from "./permission-fns";
import { TBuildOrgPermissionDTO, TBuildProjectPermissionDTO } from "./permission-service-types";
import {
TBuildOrgPermissionDTO,
TBuildProjectPermissionDTO,
TGetIdentityProjectPermissionArg,
TGetProjectPermissionArg,
TGetServiceTokenProjectPermissionArg,
TGetUserProjectPermissionArg
} from "./permission-service-types";
import {
buildServiceTokenProjectPermission,
projectAdminPermissions,
@ -193,12 +200,13 @@ export const permissionServiceFactory = ({
};
// user permission for a project in an organization
const getUserProjectPermission = async (
userId: string,
projectId: string,
authMethod: ActorAuthMethod,
userOrgId?: string
): Promise<TProjectPermissionRT<ActorType.USER>> => {
const getUserProjectPermission = async ({
userId,
projectId,
authMethod,
userOrgId,
actionProjectType
}: TGetUserProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.USER>> => {
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
if (!userProjectPermission) throw new ForbiddenRequestError({ name: "User not a part of the specified project" });
@ -219,6 +227,12 @@ export const permissionServiceFactory = ({
validateOrgSSO(authMethod, userProjectPermission.orgAuthEnforced);
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== userProjectPermission.projectType) {
throw new BadRequestError({
message: `The project is of type ${userProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.`
});
}
// join two permissions and pass to build the final permission set
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
const additionalPrivileges =
@ -256,13 +270,6 @@ export const permissionServiceFactory = ({
return {
permission,
membership: userProjectPermission,
ForbidOnInvalidProjectType: (productType: ProjectType) => {
if (productType !== userProjectPermission.projectType) {
throw new BadRequestError({
message: `The project is of type ${userProjectPermission.projectType}. Operations of type ${productType} are not allowed.`
});
}
},
hasRole: (role: string) =>
userProjectPermission.roles.findIndex(
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
@ -270,11 +277,12 @@ export const permissionServiceFactory = ({
};
};
const getIdentityProjectPermission = async (
identityId: string,
projectId: string,
identityOrgId: string | undefined
): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
const getIdentityProjectPermission = async ({
identityId,
projectId,
identityOrgId,
actionProjectType
}: TGetIdentityProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
if (!identityProjectPermission)
throw new ForbiddenRequestError({
@ -293,6 +301,12 @@ export const permissionServiceFactory = ({
throw new ForbiddenRequestError({ name: "Identity is not a member of the specified organization" });
}
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== identityProjectPermission.projectType) {
throw new BadRequestError({
message: `The project is of type ${identityProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.`
});
}
const rolePermissions =
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
const additionalPrivileges =
@ -331,13 +345,6 @@ export const permissionServiceFactory = ({
return {
permission,
membership: identityProjectPermission,
ForbidOnInvalidProjectType: (productType: ProjectType) => {
if (productType !== identityProjectPermission.projectType) {
throw new BadRequestError({
message: `The project is of type ${identityProjectPermission.projectType}. Operations of type ${productType} are not allowed.`
});
}
},
hasRole: (role: string) =>
identityProjectPermission.roles.findIndex(
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
@ -345,11 +352,12 @@ export const permissionServiceFactory = ({
};
};
const getServiceTokenProjectPermission = async (
serviceTokenId: string,
projectId: string,
actorOrgId: string | undefined
) => {
const getServiceTokenProjectPermission = async ({
serviceTokenId,
projectId,
actorOrgId,
actionProjectType
}: TGetServiceTokenProjectPermissionArg) => {
const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
if (!serviceToken) throw new NotFoundError({ message: `Service token with ID '${serviceTokenId}' not found` });
@ -373,17 +381,16 @@ export const permissionServiceFactory = ({
});
}
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== serviceTokenProject.type) {
throw new BadRequestError({
message: `The project is of type ${serviceTokenProject.type}. Operations of type ${actionProjectType} are not allowed.`
});
}
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
return {
permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions),
membership: undefined,
ForbidOnInvalidProjectType: (productType: ProjectType) => {
if (productType !== serviceTokenProject.type) {
throw new BadRequestError({
message: `The project is of type ${serviceTokenProject.type}. Operations of type ${productType} are not allowed.`
});
}
}
membership: undefined
};
};
@ -392,7 +399,6 @@ export const permissionServiceFactory = ({
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
membership: undefined;
hasRole: (arg: string) => boolean;
ForbidOnInvalidProjectType: (type: ProjectType) => void;
} // service token doesn't have both membership and roles
: {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
@ -402,23 +408,156 @@ export const permissionServiceFactory = ({
roles: Array<{ role: string }>;
};
hasRole: (role: string) => boolean;
ForbidOnInvalidProjectType: (type: ProjectType) => void;
};
const getProjectPermission = async <T extends ActorType>(
type: T,
id: string,
projectId: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId: string | undefined
): Promise<TProjectPermissionRT<T>> => {
switch (type) {
const getProjectPermissions = async (projectId: string) => {
// fetch user permissions
const rawUserProjectPermissions = await permissionDAL.getProjectUserPermissions(projectId);
const userPermissions = rawUserProjectPermissions.map((userProjectPermission) => {
const rolePermissions =
userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
const additionalPrivileges =
userProjectPermission.additionalPrivileges?.map(({ permissions }) => ({
role: ProjectMembershipRole.Custom,
permissions
})) || [];
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
const metadataKeyValuePair = escapeHandlebarsMissingMetadata(
objectify(
userProjectPermission.metadata,
(i) => i.key,
(i) => i.value
)
);
const interpolateRules = templatedRules(
{
identity: {
id: userProjectPermission.userId,
username: userProjectPermission.username,
metadata: metadataKeyValuePair
}
},
{ data: false }
);
const permission = createMongoAbility<ProjectPermissionSet>(
JSON.parse(interpolateRules) as RawRuleOf<MongoAbility<ProjectPermissionSet>>[],
{
conditionsMatcher
}
);
return {
permission,
id: userProjectPermission.userId,
name: userProjectPermission.username,
membershipId: userProjectPermission.id
};
});
// fetch identity permissions
const rawIdentityProjectPermissions = await permissionDAL.getProjectIdentityPermissions(projectId);
const identityPermissions = rawIdentityProjectPermissions.map((identityProjectPermission) => {
const rolePermissions =
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
const additionalPrivileges =
identityProjectPermission.additionalPrivileges?.map(({ permissions }) => ({
role: ProjectMembershipRole.Custom,
permissions
})) || [];
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
const metadataKeyValuePair = escapeHandlebarsMissingMetadata(
objectify(
identityProjectPermission.metadata,
(i) => i.key,
(i) => i.value
)
);
const interpolateRules = templatedRules(
{
identity: {
id: identityProjectPermission.identityId,
username: identityProjectPermission.username,
metadata: metadataKeyValuePair
}
},
{ data: false }
);
const permission = createMongoAbility<ProjectPermissionSet>(
JSON.parse(interpolateRules) as RawRuleOf<MongoAbility<ProjectPermissionSet>>[],
{
conditionsMatcher
}
);
return {
permission,
id: identityProjectPermission.identityId,
name: identityProjectPermission.username,
membershipId: identityProjectPermission.id
};
});
// fetch group permissions
const rawGroupProjectPermissions = await permissionDAL.getProjectGroupPermissions(projectId);
const groupPermissions = rawGroupProjectPermissions.map((groupProjectPermission) => {
const rolePermissions =
groupProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
const rules = buildProjectPermissionRules(rolePermissions);
const permission = createMongoAbility<ProjectPermissionSet>(rules, {
conditionsMatcher
});
return {
permission,
id: groupProjectPermission.groupId,
name: groupProjectPermission.username,
membershipId: groupProjectPermission.id
};
});
return {
userPermissions,
identityPermissions,
groupPermissions
};
};
const getProjectPermission = async <T extends ActorType>({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType
}: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => {
switch (actor) {
case ActorType.USER:
return getUserProjectPermission(id, projectId, actorAuthMethod, actorOrgId) as Promise<TProjectPermissionRT<T>>;
return getUserProjectPermission({
userId: actorId,
projectId,
authMethod: actorAuthMethod,
userOrgId: actorOrgId,
actionProjectType
}) as Promise<TProjectPermissionRT<T>>;
case ActorType.SERVICE:
return getServiceTokenProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>;
return getServiceTokenProjectPermission({
serviceTokenId: actorId,
projectId,
actorOrgId,
actionProjectType
}) as Promise<TProjectPermissionRT<T>>;
case ActorType.IDENTITY:
return getIdentityProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>;
return getIdentityProjectPermission({
identityId: actorId,
projectId,
identityOrgId: actorOrgId,
actionProjectType
}) as Promise<TProjectPermissionRT<T>>;
default:
throw new BadRequestError({
message: "Invalid actor provided",
@ -455,6 +594,7 @@ export const permissionServiceFactory = ({
getOrgPermission,
getUserProjectPermission,
getProjectPermission,
getProjectPermissions,
getOrgPermissionByRole,
getProjectPermissionByRole,
buildOrgPermission,

View File

@ -2,7 +2,7 @@ import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import ms from "ms";
import { TableName } from "@app/db/schemas";
import { ActionProjectType, TableName } from "@app/db/schemas";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
@ -55,21 +55,23 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
if (!projectMembership)
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectMembership.projectId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
const { permission: targetUserPermission } = await permissionService.getProjectPermission(
ActorType.USER,
projectMembership.userId,
projectMembership.projectId,
const { permission: targetUserPermission } = await permissionService.getProjectPermission({
actor: ActorType.USER,
actorId: projectMembership.userId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// we need to validate that the privilege given is not higher than the assigning users permission
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
@ -140,21 +142,23 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
});
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectMembership.projectId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
const { permission: targetUserPermission } = await permissionService.getProjectPermission(
ActorType.USER,
projectMembership.userId,
projectMembership.projectId,
const { permission: targetUserPermission } = await permissionService.getProjectPermission({
actor: ActorType.USER,
actorId: projectMembership.userId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// we need to validate that the privilege given is not higher than the assigning users permission
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
@ -224,13 +228,14 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
});
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectMembership.projectId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
@ -260,13 +265,14 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
});
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectMembership.projectId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
return {
@ -286,13 +292,14 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
if (!projectMembership)
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectMembership.projectId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
const userPrivileges = await projectUserAdditionalPrivilegeDAL.find(

View File

@ -421,14 +421,14 @@ export const samlConfigServiceFactory = ({
});
} else {
const plan = await licenseService.getPlan(orgId);
if (plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
if (plan?.slug !== "enterprise" && plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
throw new BadRequestError({
message: "Failed to create new member via SAML due to member limit reached. Upgrade plan to add more members."
});
}
if (plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
throw new BadRequestError({
message: "Failed to create new member via SAML due to member limit reached. Upgrade plan to add more members."

View File

@ -531,7 +531,7 @@ export const scimServiceFactory = ({
firstName: scimUser.name.givenName,
email: scimUser.emails[0].value,
lastName: scimUser.name.familyName,
isEmailVerified: hasEmailChanged ? trustScimEmails : true
isEmailVerified: hasEmailChanged ? trustScimEmails : undefined
},
tx
);
@ -790,6 +790,21 @@ export const scimServiceFactory = ({
});
const newGroup = await groupDAL.transaction(async (tx) => {
const conflictingGroup = await groupDAL.findOne(
{
name: displayName,
orgId
},
tx
);
if (conflictingGroup) {
throw new ScimRequestError({
detail: `Group with name '${displayName}' already exists in the organization`,
status: 409
});
}
const group = await groupDAL.create(
{
name: displayName,

View File

@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import picomatch from "picomatch";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
@ -79,14 +79,14 @@ export const secretApprovalPolicyServiceFactory = ({
if (!groupApprovers.length && approvals > approvers.length)
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretApproval
@ -193,14 +193,14 @@ export const secretApprovalPolicyServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
secretApprovalPolicy.projectId,
projectId: secretApprovalPolicy.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
const plan = await licenseService.getPlan(actorOrgId);
@ -288,14 +288,14 @@ export const secretApprovalPolicyServiceFactory = ({
if (!sapPolicy)
throw new NotFoundError({ message: `Secret approval policy with ID '${secretPolicyId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
sapPolicy.projectId,
projectId: sapPolicy.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretApproval
@ -328,13 +328,14 @@ export const secretApprovalPolicyServiceFactory = ({
actorAuthMethod,
projectId
}: TListSapDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
const sapPolicies = await secretApprovalPolicyDAL.find({ projectId, deletedAt: null });
@ -372,7 +373,14 @@ export const secretApprovalPolicyServiceFactory = ({
environment,
secretPath
}: TGetBoardSapDTO) => {
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
return getSecretApprovalPolicy(projectId, environment, secretPath);
};
@ -392,13 +400,14 @@ export const secretApprovalPolicyServiceFactory = ({
});
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
sapPolicy.projectId,
projectId: sapPolicy.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);

View File

@ -256,6 +256,7 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
`${TableName.SecretVersionV2Tag}.${TableName.SecretTag}Id`,
db.ref("id").withSchema("secVerTag")
)
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
.select(selectAllTableCols(TableName.SecretApprovalRequestSecretV2))
.select({
secVerTagId: "secVerTag.id",
@ -279,6 +280,11 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
db.ref("key").withSchema(TableName.SecretVersionV2).as("secVerKey"),
db.ref("encryptedValue").withSchema(TableName.SecretVersionV2).as("secVerValue"),
db.ref("encryptedComment").withSchema(TableName.SecretVersionV2).as("secVerComment")
)
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
);
const formatedDoc = sqlNestRelationships({
data: doc,
@ -338,9 +344,19 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
})
}
]
},
{
key: "metadataId",
label: "oldSecretMetadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
}
]
});
return formatedDoc?.map(({ secret, secretVersion, ...el }) => ({
...el,
secret: secret?.[0],

View File

@ -1,8 +1,8 @@
import { ForbiddenError, subject } from "@casl/ability";
import {
ActionProjectType,
ProjectMembershipRole,
ProjectType,
SecretEncryptionAlgo,
SecretKeyEncoding,
SecretType,
@ -22,6 +22,8 @@ import { KmsDataKey } from "@app/services/kms/kms-types";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
import { TResourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
import {
decryptSecretWithBot,
@ -91,6 +93,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
smtpService: Pick<TSmtpService, "sendMail">;
userDAL: Pick<TUserDALFactory, "find" | "findOne" | "findById">;
@ -138,18 +141,20 @@ export const secretApprovalRequestServiceFactory = ({
secretVersionV2BridgeDAL,
secretVersionTagV2BridgeDAL,
licenseService,
projectSlackConfigDAL
projectSlackConfigDAL,
resourceMetadataDAL
}: TSecretApprovalRequestServiceFactoryDep) => {
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
await permissionService.getProjectPermission(
actor as ActorType.USER,
await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId);
return count;
@ -169,7 +174,14 @@ export const secretApprovalRequestServiceFactory = ({
}: TListApprovalsDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
if (shouldUseSecretV2Bridge) {
@ -212,13 +224,14 @@ export const secretApprovalRequestServiceFactory = ({
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
const { policy } = secretApprovalRequest;
const { hasRole } = await permissionService.getProjectPermission(
const { hasRole } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (
!hasRole(ProjectMembershipRole.Admin) &&
secretApprovalRequest.committerUserId !== actorId &&
@ -241,6 +254,7 @@ export const secretApprovalRequestServiceFactory = ({
secretKey: el.key,
id: el.id,
version: el.version,
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
secretValue: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
secretComment: el.encryptedComment
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
@ -269,7 +283,8 @@ export const secretApprovalRequestServiceFactory = ({
secretComment: el.secretVersion.encryptedComment
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString()
: "",
tags: el.secretVersion.tags
tags: el.secretVersion.tags,
secretMetadata: el.oldSecretMetadata as ResourceMetadataDTO
}
: undefined
}));
@ -330,13 +345,14 @@ export const secretApprovalRequestServiceFactory = ({
});
}
const { hasRole } = await permissionService.getProjectPermission(
ActorType.USER,
const { hasRole } = await permissionService.getProjectPermission({
actor: ActorType.USER,
actorId,
secretApprovalRequest.projectId,
projectId: secretApprovalRequest.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (
!hasRole(ProjectMembershipRole.Admin) &&
secretApprovalRequest.committerUserId !== actorId &&
@ -396,13 +412,14 @@ export const secretApprovalRequestServiceFactory = ({
});
}
const { hasRole } = await permissionService.getProjectPermission(
ActorType.USER,
const { hasRole } = await permissionService.getProjectPermission({
actor: ActorType.USER,
actorId,
secretApprovalRequest.projectId,
projectId: secretApprovalRequest.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (
!hasRole(ProjectMembershipRole.Admin) &&
secretApprovalRequest.committerUserId !== actorId &&
@ -452,13 +469,14 @@ export const secretApprovalRequestServiceFactory = ({
});
}
const { hasRole } = await permissionService.getProjectPermission(
ActorType.USER,
const { hasRole } = await permissionService.getProjectPermission({
actor: ActorType.USER,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (
!hasRole(ProjectMembershipRole.Admin) &&
@ -543,6 +561,7 @@ export const secretApprovalRequestServiceFactory = ({
? await fnSecretV2BridgeBulkInsert({
tx,
folderId,
orgId: actorOrgId,
inputSecrets: secretCreationCommits.map((el) => ({
tagIds: el?.tags.map(({ id }) => id),
version: 1,
@ -550,6 +569,7 @@ export const secretApprovalRequestServiceFactory = ({
encryptedValue: el.encryptedValue,
skipMultilineEncoding: el.skipMultilineEncoding,
key: el.key,
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
references: el.encryptedValue
? getAllSecretReferencesV2Bridge(
secretManagerDecryptor({
@ -559,6 +579,7 @@ export const secretApprovalRequestServiceFactory = ({
: [],
type: SecretType.Shared
})),
resourceMetadataDAL,
secretDAL: secretV2BridgeDAL,
secretVersionDAL: secretVersionV2BridgeDAL,
secretTagDAL,
@ -568,6 +589,7 @@ export const secretApprovalRequestServiceFactory = ({
const updatedSecrets = secretUpdationCommits.length
? await fnSecretV2BridgeBulkUpdate({
folderId,
orgId: actorOrgId,
tx,
inputSecrets: secretUpdationCommits.map((el) => {
const encryptedValue =
@ -592,6 +614,7 @@ export const secretApprovalRequestServiceFactory = ({
skipMultilineEncoding: el.skipMultilineEncoding,
key: el.key,
tags: el?.tags.map(({ id }) => id),
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
...encryptedValue
}
};
@ -599,7 +622,8 @@ export const secretApprovalRequestServiceFactory = ({
secretDAL: secretV2BridgeDAL,
secretVersionDAL: secretVersionV2BridgeDAL,
secretTagDAL,
secretVersionTagDAL: secretVersionTagV2BridgeDAL
secretVersionTagDAL: secretVersionTagV2BridgeDAL,
resourceMetadataDAL
})
: [];
const deletedSecret = secretDeletionCommits.length
@ -824,6 +848,7 @@ export const secretApprovalRequestServiceFactory = ({
}
await secretQueueService.syncSecrets({
projectId,
orgId: actorOrgId,
secretPath: folder.path,
environmentSlug: folder.environmentSlug,
actorId,
@ -876,14 +901,14 @@ export const secretApprovalRequestServiceFactory = ({
}: TGenerateSecretApprovalRequestDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
@ -1157,14 +1182,14 @@ export const secretApprovalRequestServiceFactory = ({
if (actor === ActorType.SERVICE || actor === ActorType.Machine)
throw new BadRequestError({ message: "Cannot use service token or machine token over protected branches" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder)
throw new NotFoundError({
@ -1208,6 +1233,7 @@ export const secretApprovalRequestServiceFactory = ({
),
skipMultilineEncoding: createdSecret.skipMultilineEncoding,
key: createdSecret.secretKey,
secretMetadata: createdSecret.secretMetadata,
type: SecretType.Shared
}))
);
@ -1241,9 +1267,10 @@ export const secretApprovalRequestServiceFactory = ({
type: SecretType.Shared
}))
);
if (secrets.length)
if (secrets.length !== secretsWithNewName.length)
throw new NotFoundError({
message: `Secret does not exist: ${secretsToUpdateStoredInDB.map((el) => el.key).join(",")}`
message: `Secret does not exist: ${secrets.map((el) => el.key).join(",")}`
});
}
@ -1263,12 +1290,14 @@ export const secretApprovalRequestServiceFactory = ({
reminderNote,
secretComment,
metadata,
skipMultilineEncoding
skipMultilineEncoding,
secretMetadata
}) => {
const secretId = updatingSecretsGroupByKey[secretKey][0].id;
if (tagIds?.length) commitTagIds[secretKey] = tagIds;
return {
...latestSecretVersions[secretId],
secretMetadata,
key: newSecretName || secretKey,
encryptedComment: setKnexStringValue(
secretComment,
@ -1370,7 +1399,8 @@ export const secretApprovalRequestServiceFactory = ({
reminderRepeatDays,
encryptedValue,
secretId,
secretVersion
secretVersion,
secretMetadata
}) => ({
version,
requestId: doc.id,
@ -1383,7 +1413,8 @@ export const secretApprovalRequestServiceFactory = ({
reminderRepeatDays,
reminderNote,
encryptedComment,
key
key,
secretMetadata: JSON.stringify(secretMetadata)
})
),
tx

View File

@ -1,5 +1,6 @@
import { TImmutableDBKeys, TSecretApprovalPolicies, TSecretApprovalRequestsSecrets } from "@app/db/schemas";
import { TProjectPermission } from "@app/lib/types";
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
import { SecretOperations } from "@app/services/secret/secret-types";
export enum RequestState {
@ -34,6 +35,7 @@ export type TApprovalCreateSecretV2Bridge = {
reminderRepeatDays?: number | null;
skipMultilineEncoding?: boolean;
metadata?: Record<string, string>;
secretMetadata?: ResourceMetadataDTO;
tagIds?: string[];
};

View File

@ -13,6 +13,8 @@ import { ActorType } from "@app/services/auth/auth-type";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
import { TResourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
import { fnSecretBulkInsert, fnSecretBulkUpdate } from "@app/services/secret/secret-fns";
import { TSecretQueueFactory, uniqueSecretQueueKey } from "@app/services/secret/secret-queue";
@ -56,6 +58,7 @@ type TSecretReplicationServiceFactoryDep = {
>;
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "find" | "insertMany">;
secretVersionV2TagBridgeDAL: Pick<TSecretVersionV2TagDALFactory, "find" | "insertMany">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "replicateSecrets">;
queueService: Pick<TQueueServiceFactory, "start" | "listen" | "queue" | "stopJobById">;
secretApprovalPolicyService: Pick<TSecretApprovalPolicyServiceFactory, "getSecretApprovalPolicy">;
@ -121,7 +124,8 @@ export const secretReplicationServiceFactory = ({
secretVersionV2TagBridgeDAL,
secretVersionV2BridgeDAL,
secretV2BridgeDAL,
kmsService
kmsService,
resourceMetadataDAL
}: TSecretReplicationServiceFactoryDep) => {
const $getReplicatedSecrets = (
botKey: string,
@ -151,8 +155,10 @@ export const secretReplicationServiceFactory = ({
};
const $getReplicatedSecretsV2 = (
localSecrets: (TSecretsV2 & { secretKey: string; secretValue?: string })[],
importedSecrets: { secrets: (TSecretsV2 & { secretKey: string; secretValue?: string })[] }[]
localSecrets: (TSecretsV2 & { secretKey: string; secretValue?: string; secretMetadata?: ResourceMetadataDTO })[],
importedSecrets: {
secrets: (TSecretsV2 & { secretKey: string; secretValue?: string; secretMetadata?: ResourceMetadataDTO })[];
}[]
) => {
const deDupe = new Set<string>();
const secrets = [...localSecrets];
@ -178,6 +184,7 @@ export const secretReplicationServiceFactory = ({
secretPath,
environmentSlug,
projectId,
orgId,
actorId,
actor,
pickOnlyImportIds,
@ -222,6 +229,7 @@ export const secretReplicationServiceFactory = ({
.map(({ folderId }) =>
secretQueueService.replicateSecrets({
projectId,
orgId,
secretPath: foldersGroupedById[folderId][0]?.path as string,
environmentSlug: foldersGroupedById[folderId][0]?.environmentSlug as string,
actorId,
@ -267,6 +275,7 @@ export const secretReplicationServiceFactory = ({
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
: undefined
}));
const sourceSecrets = $getReplicatedSecretsV2(sourceDecryptedLocalSecrets, sourceImportedSecrets);
const sourceSecretsGroupByKey = groupBy(sourceSecrets, (i) => i.key);
@ -333,13 +342,29 @@ export const secretReplicationServiceFactory = ({
.map((el) => ({ ...el, operation: SecretOperations.Create })); // rewrite update ops to create
const locallyUpdatedSecrets = sourceSecrets
.filter(
({ key, secretKey, secretValue }) =>
.filter(({ key, secretKey, secretValue, secretMetadata }) => {
const sourceSecretMetadataJson = JSON.stringify(
(secretMetadata ?? []).map((entry) => ({
key: entry.key,
value: entry.value
}))
);
const destinationSecretMetadataJson = JSON.stringify(
(destinationLocalSecretsGroupedByKey[key]?.[0]?.secretMetadata ?? []).map((entry) => ({
key: entry.key,
value: entry.value
}))
);
return (
destinationLocalSecretsGroupedByKey[key]?.[0] &&
// if key or value changed
(destinationLocalSecretsGroupedByKey[key]?.[0]?.secretKey !== secretKey ||
destinationLocalSecretsGroupedByKey[key]?.[0]?.secretValue !== secretValue)
)
destinationLocalSecretsGroupedByKey[key]?.[0]?.secretValue !== secretValue ||
sourceSecretMetadataJson !== destinationSecretMetadataJson)
);
})
.map((el) => ({ ...el, operation: SecretOperations.Update })); // rewrite update ops to create
const locallyDeletedSecrets = destinationLocalSecrets
@ -387,6 +412,7 @@ export const secretReplicationServiceFactory = ({
op: operation,
requestId: approvalRequestDoc.id,
metadata: doc.metadata,
secretMetadata: JSON.stringify(doc.secretMetadata),
key: doc.key,
encryptedValue: doc.encryptedValue,
encryptedComment: doc.encryptedComment,
@ -406,10 +432,12 @@ export const secretReplicationServiceFactory = ({
if (locallyCreatedSecrets.length) {
await fnSecretV2BridgeBulkInsert({
folderId: destinationReplicationFolderId,
orgId,
secretVersionDAL: secretVersionV2BridgeDAL,
secretDAL: secretV2BridgeDAL,
tx,
secretTagDAL,
resourceMetadataDAL,
secretVersionTagDAL: secretVersionV2TagBridgeDAL,
inputSecrets: locallyCreatedSecrets.map((doc) => {
return {
@ -419,6 +447,7 @@ export const secretReplicationServiceFactory = ({
encryptedValue: doc.encryptedValue,
encryptedComment: doc.encryptedComment,
skipMultilineEncoding: doc.skipMultilineEncoding,
secretMetadata: doc.secretMetadata,
references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : []
};
})
@ -426,10 +455,12 @@ export const secretReplicationServiceFactory = ({
}
if (locallyUpdatedSecrets.length) {
await fnSecretV2BridgeBulkUpdate({
orgId,
folderId: destinationReplicationFolderId,
secretVersionDAL: secretVersionV2BridgeDAL,
secretDAL: secretV2BridgeDAL,
tx,
resourceMetadataDAL,
secretTagDAL,
secretVersionTagDAL: secretVersionV2TagBridgeDAL,
inputSecrets: locallyUpdatedSecrets.map((doc) => {
@ -445,6 +476,7 @@ export const secretReplicationServiceFactory = ({
encryptedValue: doc.encryptedValue as Buffer,
encryptedComment: doc.encryptedComment,
skipMultilineEncoding: doc.skipMultilineEncoding,
secretMetadata: doc.secretMetadata,
references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : []
}
};
@ -466,6 +498,7 @@ export const secretReplicationServiceFactory = ({
await secretQueueService.syncSecrets({
projectId,
orgId,
secretPath: destinationFolder.path,
environmentSlug: destinationFolder.environmentSlug,
actorId,
@ -751,6 +784,7 @@ export const secretReplicationServiceFactory = ({
await secretQueueService.syncSecrets({
projectId,
orgId,
secretPath: destinationFolder.path,
environmentSlug: destinationFolder.environmentSlug,
actorId,

View File

@ -180,6 +180,8 @@ export const secretRotationQueueFactory = ({
provider.template.client === TDbProviderClients.MsSqlServer
? ({
encrypt: appCfg.ENABLE_MSSQL_SECRET_ROTATION_ENCRYPT,
// when ca is provided use that
trustServerCertificate: !ca,
cryptoCredentialsDetails: ca ? { ca } : {}
} as Record<string, unknown>)
: undefined;

View File

@ -1,7 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability";
import Ajv from "ajv";
import { ProjectType, ProjectVersion, TableName } from "@app/db/schemas";
import { ActionProjectType, ProjectVersion, TableName } from "@app/db/schemas";
import { decryptSymmetric128BitHexKeyUTF8, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TProjectPermission } from "@app/lib/types";
@ -53,14 +53,14 @@ export const secretRotationServiceFactory = ({
actorAuthMethod,
projectId
}: TProjectPermission) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
return {
@ -82,14 +82,14 @@ export const secretRotationServiceFactory = ({
secretPath,
environment
}: TCreateSecretRotationDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRotation
@ -191,13 +191,14 @@ export const secretRotationServiceFactory = ({
};
const getByProjectId = async ({ actorId, projectId, actor, actorOrgId, actorAuthMethod }: TListByProjectIdDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
if (shouldUseSecretV2Bridge) {
@ -236,14 +237,14 @@ export const secretRotationServiceFactory = ({
message: "Failed to add secret rotation due to plan restriction. Upgrade plan to add secret rotation."
});
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
doc.projectId,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation);
await secretRotationQueue.removeFromQueue(doc.id, doc.interval);
await secretRotationQueue.addToQueue(doc.id, doc.interval);
@ -254,14 +255,14 @@ export const secretRotationServiceFactory = ({
const doc = await secretRotationDAL.findById(rotationId);
if (!doc) throw new NotFoundError({ message: `Rotation with ID '${rotationId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
doc.projectId,
projectId: doc.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretRotation

View File

@ -1,9 +1,12 @@
import { Knex } from "knex";
import knex, { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName, TSecretScanningGitRisksInsert } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify } from "@app/lib/knex";
import { DatabaseError, GatewayTimeoutError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
import { OrderByDirection } from "@app/lib/types";
import { SecretScanningResolvedStatus, TGetOrgRisksDTO } from "./secret-scanning-types";
export type TSecretScanningDALFactory = ReturnType<typeof secretScanningDALFactory>;
@ -19,5 +22,70 @@ export const secretScanningDALFactory = (db: TDbClient) => {
}
};
return { ...gitRiskOrm, upsert };
const findByOrgId = async (orgId: string, filter: TGetOrgRisksDTO["filter"], tx?: Knex) => {
try {
// Find statements
const sqlQuery = (tx || db.replicaNode())(TableName.SecretScanningGitRisk)
// eslint-disable-next-line func-names
.where(`${TableName.SecretScanningGitRisk}.orgId`, orgId);
if (filter.repositoryNames) {
void sqlQuery.whereIn(`${TableName.SecretScanningGitRisk}.repositoryFullName`, filter.repositoryNames);
}
if (filter.resolvedStatus) {
if (filter.resolvedStatus !== SecretScanningResolvedStatus.All) {
const isResolved = filter.resolvedStatus === SecretScanningResolvedStatus.Resolved;
void sqlQuery.where(`${TableName.SecretScanningGitRisk}.isResolved`, isResolved);
}
}
// Select statements
void sqlQuery
.select(selectAllTableCols(TableName.SecretScanningGitRisk))
.limit(filter.limit)
.offset(filter.offset);
if (filter.orderBy) {
const orderDirection = filter.orderDirection || OrderByDirection.ASC;
void sqlQuery.orderBy(filter.orderBy, orderDirection);
}
const countQuery = (tx || db.replicaNode())(TableName.SecretScanningGitRisk)
.where(`${TableName.SecretScanningGitRisk}.orgId`, orgId)
.count();
const uniqueReposQuery = (tx || db.replicaNode())(TableName.SecretScanningGitRisk)
.where(`${TableName.SecretScanningGitRisk}.orgId`, orgId)
.distinct("repositoryFullName")
.select("repositoryFullName");
// we timeout long running queries to prevent DB resource issues (2 minutes)
const docs = await sqlQuery.timeout(1000 * 120);
const uniqueRepos = await uniqueReposQuery.timeout(1000 * 120);
const totalCount = await countQuery;
return {
risks: docs,
totalCount: Number(totalCount?.[0].count),
repos: uniqueRepos
.filter(Boolean)
.map((r) => r.repositoryFullName!)
.sort((a, b) => a.localeCompare(b))
};
} catch (error) {
if (error instanceof knex.KnexTimeoutError) {
throw new GatewayTimeoutError({
error,
message: "Failed to fetch secret leaks due to timeout. Add more search filters."
});
}
throw new DatabaseError({ error });
}
};
return { ...gitRiskOrm, upsert, findByOrgId };
};

View File

@ -15,6 +15,7 @@ import { TSecretScanningDALFactory } from "./secret-scanning-dal";
import { TSecretScanningQueueFactory } from "./secret-scanning-queue";
import {
SecretScanningRiskStatus,
TGetAllOrgRisksDTO,
TGetOrgInstallStatusDTO,
TGetOrgRisksDTO,
TInstallAppSessionDTO,
@ -118,11 +119,21 @@ export const secretScanningServiceFactory = ({
return Boolean(appInstallation);
};
const getRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId }: TGetOrgRisksDTO) => {
const getRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId, filter }: TGetOrgRisksDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
const results = await secretScanningDAL.findByOrgId(orgId, filter);
return results;
};
const getAllRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId }: TGetAllOrgRisksDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
const risks = await secretScanningDAL.find({ orgId }, { sort: [["createdAt", "desc"]] });
return { risks };
return risks;
};
const updateRiskStatus = async ({
@ -189,6 +200,7 @@ export const secretScanningServiceFactory = ({
linkInstallationToOrg,
getOrgInstallationStatus,
getRisksByOrg,
getAllRisksByOrg,
updateRiskStatus,
handleRepoPushEvent,
handleRepoDeleteEvent

View File

@ -1,4 +1,4 @@
import { TOrgPermission } from "@app/lib/types";
import { OrderByDirection, TOrgPermission } from "@app/lib/types";
export enum SecretScanningRiskStatus {
FalsePositive = "RESOLVED_FALSE_POSITIVE",
@ -7,6 +7,12 @@ export enum SecretScanningRiskStatus {
Unresolved = "UNRESOLVED"
}
export enum SecretScanningResolvedStatus {
All = "all",
Resolved = "resolved",
Unresolved = "unresolved"
}
export type TInstallAppSessionDTO = TOrgPermission;
export type TLinkInstallSessionDTO = {
@ -16,7 +22,22 @@ export type TLinkInstallSessionDTO = {
export type TGetOrgInstallStatusDTO = TOrgPermission;
export type TGetOrgRisksDTO = TOrgPermission;
type RiskFilter = {
offset: number;
limit: number;
orderBy?: "createdAt" | "name";
orderDirection?: OrderByDirection;
repositoryNames?: string[];
resolvedStatus?: SecretScanningResolvedStatus;
};
export type TGetOrgRisksDTO = {
filter: RiskFilter;
} & TOrgPermission;
export type TGetAllOrgRisksDTO = {
filter: Omit<RiskFilter, "offset" | "limit" | "orderBy" | "orderDirection">;
} & TOrgPermission;
export type TUpdateRiskStatusDTO = {
riskId: string;

View File

@ -1,6 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability";
import { ProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
import { ActionProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
import { InternalServerError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
@ -83,13 +83,14 @@ export const secretSnapshotServiceFactory = ({
actorAuthMethod,
path
}: TProjectSnapshotCountDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
@ -119,13 +120,14 @@ export const secretSnapshotServiceFactory = ({
limit = 20,
offset = 0
}: TProjectSnapshotListDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
@ -147,13 +149,14 @@ export const secretSnapshotServiceFactory = ({
const getSnapshotData = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TGetSnapshotDataDTO) => {
const snapshot = await snapshotDAL.findById(id);
if (!snapshot) throw new NotFoundError({ message: `Snapshot with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
snapshot.projectId,
projectId: snapshot.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
const shouldUseBridge = snapshot.projectVersion === 3;
@ -322,14 +325,14 @@ export const secretSnapshotServiceFactory = ({
if (!snapshot) throw new NotFoundError({ message: `Snapshot with ID '${snapshotId}' not found` });
const shouldUseBridge = snapshot.projectVersion === 3;
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
snapshot.projectId,
projectId: snapshot.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRollback

View File

@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import ms from "ms";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
@ -54,15 +54,15 @@ export const sshCertificateTemplateServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SshCertificateTemplates
@ -127,15 +127,15 @@ export const sshCertificateTemplateServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
certTemplate.projectId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.SshCertificateTemplates
@ -196,15 +196,15 @@ export const sshCertificateTemplateServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
certificateTemplate.projectId,
projectId: certificateTemplate.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SshCertificateTemplates
@ -223,15 +223,15 @@ export const sshCertificateTemplateServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
certTemplate.projectId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SshCertificateTemplates

View File

@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
@ -65,15 +65,15 @@ export const sshCertificateAuthorityServiceFactory = ({
actor,
actorOrgId
}: TCreateSshCaDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SshCertificateAuthorities
@ -118,15 +118,15 @@ export const sshCertificateAuthorityServiceFactory = ({
const ca = await sshCertificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SshCertificateAuthorities
@ -187,15 +187,15 @@ export const sshCertificateAuthorityServiceFactory = ({
const ca = await sshCertificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.SshCertificateAuthorities
@ -226,15 +226,15 @@ export const sshCertificateAuthorityServiceFactory = ({
const ca = await sshCertificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SshCertificateAuthorities
@ -268,15 +268,15 @@ export const sshCertificateAuthorityServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
sshCertificateTemplate.projectId,
projectId: sshCertificateTemplate.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SshCertificates
@ -390,15 +390,15 @@ export const sshCertificateAuthorityServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
sshCertificateTemplate.projectId,
projectId: sshCertificateTemplate.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SshCertificates
@ -488,15 +488,15 @@ export const sshCertificateAuthorityServiceFactory = ({
const ca = await sshCertificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SshCertificateTemplates

View File

@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { BadRequestError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { TProjectPermission } from "@app/lib/types";
@ -27,13 +28,14 @@ export const trustedIpServiceFactory = ({
projectDAL
}: TTrustedIpServiceFactoryDep) => {
const listIpsByProjectId = async ({ projectId, actor, actorId, actorAuthMethod, actorOrgId }: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
const trustedIps = await trustedIpDAL.find({
projectId
@ -51,13 +53,14 @@ export const trustedIpServiceFactory = ({
comment,
isActive
}: TCreateIpDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
const project = await projectDAL.findById(projectId);
@ -96,13 +99,14 @@ export const trustedIpServiceFactory = ({
comment,
trustedIpId
}: TUpdateIpDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
const project = await projectDAL.findById(projectId);
@ -141,13 +145,14 @@ export const trustedIpServiceFactory = ({
actorAuthMethod,
trustedIpId
}: TDeleteIpDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
const project = await projectDAL.findById(projectId);

View File

@ -474,7 +474,7 @@ export const PROJECTS = {
},
ADD_GROUP_TO_PROJECT: {
projectId: "The ID of the project to add the group to.",
groupId: "The ID of the group to add to the project.",
groupIdOrName: "The ID or name of the group to add to the project.",
role: "The role for the group to assume in the project."
},
UPDATE_GROUP_IN_PROJECT: {
@ -741,6 +741,12 @@ export const RAW_SECRETS = {
workspaceId: "The ID of the project where the secret is located.",
environment: "The slug of the environment where the the secret is located.",
secretPath: "The folder path where the secret is located."
},
GET_ACCESS_LIST: {
secretName: "The name of the secret to get the access list for.",
workspaceId: "The ID of the project where the secret is located.",
environment: "The slug of the environment where the the secret is located.",
secretPath: "The folder path where the secret is located."
}
} as const;
@ -1150,7 +1156,8 @@ export const INTEGRATION = {
shouldMaskSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Masked'.",
shouldProtectSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Protected'.",
shouldEnableDelete: "The flag to enable deletion of secrets.",
octopusDeployScopeValues: "Specifies the scope values to set on synced secrets to Octopus Deploy."
octopusDeployScopeValues: "Specifies the scope values to set on synced secrets to Octopus Deploy.",
metadataSyncMode: "The mode for syncing metadata to external system"
}
},
UPDATE: {

View File

@ -199,7 +199,29 @@ const envSchema = z
INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET: zpStr(z.string().optional()),
INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY: zpStr(z.string().optional()),
INF_APP_CONNECTION_GITHUB_APP_SLUG: zpStr(z.string().optional()),
INF_APP_CONNECTION_GITHUB_APP_ID: zpStr(z.string().optional())
INF_APP_CONNECTION_GITHUB_APP_ID: zpStr(z.string().optional()),
/* CORS ----------------------------------------------------------------------------- */
CORS_ALLOWED_ORIGINS: zpStr(
z
.string()
.optional()
.transform((val) => {
if (!val) return undefined;
return JSON.parse(val) as string[];
})
),
CORS_ALLOWED_HEADERS: zpStr(
z
.string()
.optional()
.transform((val) => {
if (!val) return undefined;
return JSON.parse(val) as string[];
})
)
})
// To ensure that basic encryption is always possible.
.refine(

View File

@ -1,3 +1,4 @@
export { isDisposableEmail } from "./validate-email";
export { isValidFolderName, isValidSecretPath } from "./validate-folder-name";
export { blockLocalAndPrivateIpAddresses } from "./validate-url";
export { isUuidV4 } from "./validate-uuid";

View File

@ -0,0 +1,3 @@
import { z } from "zod";
export const isUuidV4 = (uuid: string) => z.string().uuid().safeParse(uuid).success;

View File

@ -87,7 +87,16 @@ export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, key
await server.register<FastifyCorsOptions>(cors, {
credentials: true,
origin: appCfg.SITE_URL || true
...(appCfg.CORS_ALLOWED_ORIGINS?.length
? {
origin: [...appCfg.CORS_ALLOWED_ORIGINS, ...(appCfg.SITE_URL ? [appCfg.SITE_URL] : [])]
}
: {
origin: appCfg.SITE_URL || true
}),
...(appCfg.CORS_ALLOWED_HEADERS?.length && {
allowedHeaders: appCfg.CORS_ALLOWED_HEADERS
})
});
await server.register(addErrorsToResponseSchemas);

View File

@ -32,13 +32,21 @@ export const getUserAgentType = (userAgent: string | undefined) => {
export const injectAuditLogInfo = fp(async (server: FastifyZodProvider) => {
server.decorateRequest("auditLogInfo", null);
server.addHook("onRequest", async (req) => {
if (!req.auth) return;
const userAgent = req.headers["user-agent"] ?? "";
const payload = {
ipAddress: req.realIp,
userAgent,
userAgentType: getUserAgentType(userAgent)
} as typeof req.auditLogInfo;
if (!req.auth) {
payload.actor = {
type: ActorType.UNKNOWN_USER,
metadata: {}
};
req.auditLogInfo = payload;
return;
}
if (req.auth.actor === ActorType.USER) {
payload.actor = {
type: ActorType.USER,

View File

@ -1,3 +1,4 @@
import type { EmitterWebhookEventName } from "@octokit/webhooks/dist-types/types";
import { PushEvent } from "@octokit/webhooks-types";
import { Probot } from "probot";
import SmeeClient from "smee-client";
@ -54,14 +55,14 @@ export const registerSecretScannerGhApp = async (server: FastifyZodProvider) =>
rateLimit: writeLimit
},
handler: async (req, res) => {
const eventName = req.headers["x-github-event"];
const eventName = req.headers["x-github-event"] as EmitterWebhookEventName;
const signatureSHA256 = req.headers["x-hub-signature-256"] as string;
const id = req.headers["x-github-delivery"] as string;
await probot.webhooks.verifyAndReceive({
id,
// @ts-expect-error type
name: eventName,
payload: req.body as string,
payload: JSON.stringify(req.body),
signature: signatureSHA256
});
void res.send("ok");

View File

@ -21,6 +21,9 @@ export const registerServeUI = async (
server.route({
method: "GET",
url: "/runtime-ui-env.js",
schema: {
hide: true
},
handler: (_req, res) => {
const appCfg = getConfig();
void res.type("application/javascript");
@ -43,12 +46,19 @@ export const registerServeUI = async (
wildcard: false
});
server.get("/*", (request, reply) => {
if (request.url.startsWith("/api")) {
reply.callNotFound();
return;
server.route({
method: "GET",
url: "/*",
schema: {
hide: true
},
handler: (request, reply) => {
if (request.url.startsWith("/api")) {
reply.callNotFound();
return;
}
void reply.sendFile("index.html");
}
void reply.sendFile("index.html");
});
}
};

View File

@ -181,6 +181,7 @@ import { projectUserMembershipRoleDALFactory } from "@app/services/project-membe
import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal";
import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service";
import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue";
import { resourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
import { secretDALFactory } from "@app/services/secret/secret-dal";
import { secretQueueFactory } from "@app/services/secret/secret-queue";
import { secretServiceFactory } from "@app/services/secret/secret-service";
@ -374,6 +375,7 @@ export const registerRoutes = async (
const externalGroupOrgRoleMappingDAL = externalGroupOrgRoleMappingDALFactory(db);
const projectTemplateDAL = projectTemplateDALFactory(db);
const resourceMetadataDAL = resourceMetadataDALFactory(db);
const permissionService = permissionServiceFactory({
permissionDAL,
@ -606,6 +608,7 @@ export const registerRoutes = async (
});
const superAdminService = superAdminServiceFactory({
userDAL,
userAliasDAL,
authService: loginService,
serverCfgDAL: superAdminDAL,
kmsRootConfigDAL,
@ -854,7 +857,8 @@ export const registerRoutes = async (
secretApprovalRequestDAL,
projectKeyDAL,
projectUserMembershipRoleDAL,
orgService
orgService,
resourceMetadataDAL
});
const projectService = projectServiceFactory({
@ -891,7 +895,8 @@ export const registerRoutes = async (
certificateTemplateDAL,
projectSlackConfigDAL,
slackIntegrationDAL,
projectTemplateService
projectTemplateService,
groupProjectDAL
});
const projectEnvService = projectEnvServiceFactory({
@ -980,7 +985,8 @@ export const registerRoutes = async (
secretApprovalPolicyService,
secretApprovalRequestSecretDAL,
kmsService,
snapshotService
snapshotService,
resourceMetadataDAL
});
const secretApprovalRequestService = secretApprovalRequestServiceFactory({
@ -1007,7 +1013,8 @@ export const registerRoutes = async (
projectEnvDAL,
userDAL,
licenseService,
projectSlackConfigDAL
projectSlackConfigDAL,
resourceMetadataDAL
});
const secretService = secretServiceFactory({
@ -1028,7 +1035,8 @@ export const registerRoutes = async (
secretApprovalRequestDAL,
secretApprovalRequestSecretDAL,
secretV2BridgeService,
secretApprovalRequestService
secretApprovalRequestService,
licenseService
});
const secretSharingService = secretSharingServiceFactory({
@ -1086,8 +1094,10 @@ export const registerRoutes = async (
kmsService,
secretV2BridgeDAL,
secretVersionV2TagBridgeDAL: secretVersionTagV2BridgeDAL,
secretVersionV2BridgeDAL
secretVersionV2BridgeDAL,
resourceMetadataDAL
});
const secretRotationQueue = secretRotationQueueFactory({
telemetryService,
secretRotationDAL,
@ -1339,7 +1349,8 @@ export const registerRoutes = async (
folderDAL,
secretDAL: secretV2BridgeDAL,
queueService,
secretV2BridgeService
secretV2BridgeService,
resourceMetadataDAL
});
const migrationService = externalMigrationServiceFactory({

View File

@ -1,7 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability";
import { z } from "zod";
import { SecretFoldersSchema, SecretImportsSchema, SecretTagsSchema } from "@app/db/schemas";
import { ActionProjectType, SecretFoldersSchema, SecretImportsSchema, SecretTagsSchema } from "@app/db/schemas";
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
import {
ProjectPermissionDynamicSecretActions,
@ -17,6 +17,7 @@ import { getUserAgentType } from "@app/server/plugins/audit-log";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { SanitizedDynamicSecretSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
import { SecretsOrderBy } from "@app/services/secret/secret-types";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
@ -116,6 +117,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema
.extend({
secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({
id: true,
slug: true,
@ -218,13 +220,14 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
totalCount: totalFolderCount ?? 0
};
const { permission } = await server.services.permission.getProjectPermission(
req.permission.type,
req.permission.id,
const { permission } = await server.services.permission.getProjectPermission({
actor: req.permission.type,
actorId: req.permission.id,
projectId,
req.permission.authMethod,
req.permission.orgId
);
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
actionProjectType: ActionProjectType.SecretManager
});
const allowedDynamicSecretEnvironments = // filter envs user has access to
environments.filter((environment) =>
@ -408,6 +411,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema
.extend({
secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({
id: true,
slug: true,
@ -693,6 +697,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema
.extend({
secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({
id: true,
slug: true,
@ -864,6 +869,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema
.extend({
secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({
id: true,
slug: true,

View File

@ -131,7 +131,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
body: z.object({
app: z.string().trim().optional().describe(INTEGRATION.UPDATE.app),
appId: z.string().trim().optional().describe(INTEGRATION.UPDATE.appId),
isActive: z.boolean().describe(INTEGRATION.UPDATE.isActive),
isActive: z.boolean().optional().describe(INTEGRATION.UPDATE.isActive),
secretPath: z
.string()
.trim()

View File

@ -73,6 +73,40 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
}
});
server.route({
url: "/signup-resend",
config: {
rateLimit: inviteUserRateLimit
},
method: "POST",
schema: {
body: z.object({
membershipId: z.string()
}),
response: {
200: z.object({
signupToken: z
.object({
email: z.string(),
link: z.string()
})
.optional()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
return server.services.org.resendOrgMemberInvitation({
orgId: req.permission.orgId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
membershipId: req.body.membershipId
});
}
});
server.route({
url: "/verify",
method: "POST",

View File

@ -1,6 +1,7 @@
import { z } from "zod";
import { SecretSharingSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { SecretSharingAccessType } from "@app/lib/types";
import {
publicEndpointLimit,
@ -88,6 +89,21 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
orgId: req.permission?.orgId
});
if (sharedSecret.secret?.orgId) {
await server.services.auditLog.createAuditLog({
orgId: sharedSecret.secret.orgId,
...req.auditLogInfo,
event: {
type: EventType.READ_SHARED_SECRET,
metadata: {
id: req.params.id,
name: sharedSecret.secret.name || undefined,
accessType: sharedSecret.secret.accessType
}
}
});
}
return sharedSecret;
}
});
@ -151,6 +167,23 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
actorOrgId: req.permission.orgId,
...req.body
});
await server.services.auditLog.createAuditLog({
orgId: req.permission.orgId,
...req.auditLogInfo,
event: {
type: EventType.CREATE_SHARED_SECRET,
metadata: {
accessType: req.body.accessType,
expiresAt: req.body.expiresAt,
expiresAfterViews: req.body.expiresAfterViews,
name: req.body.name,
id: sharedSecret.id,
usingPassword: !!req.body.password
}
}
});
return { id: sharedSecret.id };
}
});
@ -181,6 +214,18 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
sharedSecretId
});
await server.services.auditLog.createAuditLog({
orgId: req.permission.orgId,
...req.auditLogInfo,
event: {
type: EventType.DELETE_SHARED_SECRET,
metadata: {
id: sharedSecretId,
name: deletedSharedSecret.name || undefined
}
}
});
return { ...deletedSharedSecret };
}
});

View File

@ -16,7 +16,7 @@ import { ProjectUserMembershipTemporaryMode } from "@app/services/project-member
export const registerGroupProjectRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/:projectId/groups/:groupId",
url: "/:projectId/groups/:groupIdOrName",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
config: {
rateLimit: writeLimit
@ -30,7 +30,7 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
],
params: z.object({
projectId: z.string().trim().describe(PROJECTS.ADD_GROUP_TO_PROJECT.projectId),
groupId: z.string().trim().describe(PROJECTS.ADD_GROUP_TO_PROJECT.groupId)
groupIdOrName: z.string().trim().describe(PROJECTS.ADD_GROUP_TO_PROJECT.groupIdOrName)
}),
body: z
.object({
@ -76,7 +76,7 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
actorOrgId: req.permission.orgId,
roles: req.body.roles || [{ role: req.body.role }],
projectId: req.params.projectId,
groupId: req.params.groupId
groupIdOrName: req.params.groupIdOrName
});
return { groupMembership };

View File

@ -18,6 +18,7 @@ import { getUserAgentType } from "@app/server/plugins/audit-log";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
import { ProjectFilterType } from "@app/services/project/project-types";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
import { SecretOperations, SecretProtectionType } from "@app/services/secret/secret-types";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
@ -35,6 +36,12 @@ const SecretReferenceNodeTree: z.ZodType<TSecretReferenceNode> = SecretReference
children: z.lazy(() => SecretReferenceNodeTree.array())
});
const SecretNameSchema = z
.string()
.trim()
.min(1)
.refine((el) => !el.includes(" "), "Secret name cannot contain spaces.");
export const registerSecretRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
@ -50,7 +57,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
secretName: z.string().trim().describe(SECRETS.ATTACH_TAGS.secretName)
secretName: SecretNameSchema.describe(SECRETS.ATTACH_TAGS.secretName)
}),
body: z.object({
projectSlug: z.string().trim().describe(SECRETS.ATTACH_TAGS.projectSlug),
@ -113,7 +120,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
secretName: z.string().trim().describe(SECRETS.DETACH_TAGS.secretName)
secretName: z.string().describe(SECRETS.DETACH_TAGS.secretName)
}),
body: z.object({
projectSlug: z.string().trim().describe(SECRETS.DETACH_TAGS.projectSlug),
@ -205,6 +212,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema
.extend({
secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({
id: true,
slug: true,
@ -220,7 +228,12 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretPath: z.string(),
environment: z.string(),
folderId: z.string().optional(),
secrets: secretRawSchema.omit({ createdAt: true, updatedAt: true }).array()
secrets: secretRawSchema
.omit({ createdAt: true, updatedAt: true })
.extend({
secretMetadata: ResourceMetadataSchema.optional()
})
.array()
})
.array()
.optional()
@ -348,7 +361,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
})
.extend({ name: z.string() })
.array()
.optional()
.optional(),
secretMetadata: ResourceMetadataSchema.optional()
})
})
}
@ -434,7 +448,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
secretName: z.string().trim().describe(RAW_SECRETS.CREATE.secretName)
secretName: SecretNameSchema.describe(RAW_SECRETS.CREATE.secretName)
}),
body: z.object({
workspaceId: z.string().trim().describe(RAW_SECRETS.CREATE.workspaceId),
@ -450,6 +464,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
.describe(RAW_SECRETS.CREATE.secretValue),
secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment),
secretMetadata: ResourceMetadataSchema.optional(),
tagIds: z.string().array().optional().describe(RAW_SECRETS.CREATE.tagIds),
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding),
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.CREATE.type),
@ -484,6 +499,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretValue: req.body.secretValue,
skipMultilineEncoding: req.body.skipMultilineEncoding,
secretComment: req.body.secretComment,
secretMetadata: req.body.secretMetadata,
tagIds: req.body.tagIds,
secretReminderNote: req.body.secretReminderNote,
secretReminderRepeatDays: req.body.secretReminderRepeatDays
@ -539,7 +555,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
secretName: z.string().trim().describe(RAW_SECRETS.UPDATE.secretName)
secretName: SecretNameSchema.describe(RAW_SECRETS.UPDATE.secretName)
}),
body: z.object({
workspaceId: z.string().trim().describe(RAW_SECRETS.UPDATE.workspaceId),
@ -558,13 +574,14 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.UPDATE.type),
tagIds: z.string().array().optional().describe(RAW_SECRETS.UPDATE.tagIds),
metadata: z.record(z.string()).optional(),
secretMetadata: ResourceMetadataSchema.optional(),
secretReminderNote: z.string().optional().nullable().describe(RAW_SECRETS.UPDATE.secretReminderNote),
secretReminderRepeatDays: z
.number()
.optional()
.nullable()
.describe(RAW_SECRETS.UPDATE.secretReminderRepeatDays),
newSecretName: z.string().min(1).optional().describe(RAW_SECRETS.UPDATE.newSecretName),
newSecretName: SecretNameSchema.optional().describe(RAW_SECRETS.UPDATE.newSecretName),
secretComment: z.string().optional().describe(RAW_SECRETS.UPDATE.secretComment)
}),
response: {
@ -595,8 +612,10 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretReminderNote: req.body.secretReminderNote,
metadata: req.body.metadata,
newSecretName: req.body.newSecretName,
secretComment: req.body.secretComment
secretComment: req.body.secretComment,
secretMetadata: req.body.secretMetadata
});
if (secretOperation.type === SecretProtectionType.Approval) {
return { approval: secretOperation.approval };
}
@ -647,7 +666,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
secretName: z.string().trim().describe(RAW_SECRETS.DELETE.secretName)
secretName: z.string().min(1).describe(RAW_SECRETS.DELETE.secretName)
}),
body: z.object({
workspaceId: z.string().trim().describe(RAW_SECRETS.DELETE.workspaceId),
@ -1842,7 +1861,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
.describe(RAW_SECRETS.CREATE.secretPath),
secrets: z
.object({
secretKey: z.string().trim().describe(RAW_SECRETS.CREATE.secretName),
secretKey: SecretNameSchema.describe(RAW_SECRETS.CREATE.secretName),
secretValue: z
.string()
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
@ -1850,6 +1869,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment),
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding),
metadata: z.record(z.string()).optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tagIds: z.string().array().optional().describe(RAW_SECRETS.CREATE.tagIds)
})
.array()
@ -1942,16 +1962,17 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
.describe(RAW_SECRETS.UPDATE.secretPath),
secrets: z
.object({
secretKey: z.string().trim().describe(RAW_SECRETS.UPDATE.secretName),
secretKey: SecretNameSchema.describe(RAW_SECRETS.UPDATE.secretName),
secretValue: z
.string()
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
.describe(RAW_SECRETS.UPDATE.secretValue),
secretComment: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.secretComment),
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.UPDATE.skipMultilineEncoding),
newSecretName: z.string().min(1).optional().describe(RAW_SECRETS.UPDATE.newSecretName),
newSecretName: SecretNameSchema.optional().describe(RAW_SECRETS.UPDATE.newSecretName),
tagIds: z.string().array().optional().describe(RAW_SECRETS.UPDATE.tagIds),
secretReminderNote: z.string().optional().nullable().describe(RAW_SECRETS.UPDATE.secretReminderNote),
secretMetadata: ResourceMetadataSchema.optional(),
secretReminderRepeatDays: z
.number()
.optional()
@ -2047,7 +2068,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
.describe(RAW_SECRETS.DELETE.secretPath),
secrets: z
.object({
secretKey: z.string().trim().describe(RAW_SECRETS.DELETE.secretName),
secretKey: z.string().describe(RAW_SECRETS.DELETE.secretName),
type: z.nativeEnum(SecretType).default(SecretType.Shared)
})
.array()

View File

@ -39,7 +39,8 @@ export enum ActorType { // would extend to AWS, Azure, ...
SERVICE = "service",
IDENTITY = "identity",
Machine = "machine",
SCIM_CLIENT = "scimClient"
SCIM_CLIENT = "scimClient",
UNKNOWN_USER = "unknownUser"
}
// This will be null unless the token-type is JWT

View File

@ -5,7 +5,7 @@ import crypto, { KeyObject } from "crypto";
import ms from "ms";
import { z } from "zod";
import { ProjectType, TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas";
import { ActionProjectType, ProjectType, TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { getConfig } from "@app/lib/config/env";
@ -136,14 +136,14 @@ export const certificateAuthorityServiceFactory = ({
projectId = certManagerProjectFromSplit.id;
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -305,13 +305,14 @@ export const certificateAuthorityServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.CertificateAuthorities
@ -336,14 +337,14 @@ export const certificateAuthorityServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
@ -362,14 +363,14 @@ export const certificateAuthorityServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
@ -388,13 +389,14 @@ export const certificateAuthorityServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -449,14 +451,14 @@ export const certificateAuthorityServiceFactory = ({
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -720,13 +722,14 @@ export const certificateAuthorityServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@ -755,13 +758,14 @@ export const certificateAuthorityServiceFactory = ({
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@ -835,14 +839,14 @@ export const certificateAuthorityServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: "CA not found" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -982,14 +986,14 @@ export const certificateAuthorityServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -1145,14 +1149,14 @@ export const certificateAuthorityServiceFactory = ({
throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Certificates);
@ -1474,14 +1478,14 @@ export const certificateAuthorityServiceFactory = ({
}
if (!dto.isInternal) {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
dto.actor,
dto.actorId,
ca.projectId,
dto.actorAuthMethod,
dto.actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
const { permission } = await permissionService.getProjectPermission({
actor: dto.actor,
actorId: dto.actorId,
projectId: ca.projectId,
actorAuthMethod: dto.actorAuthMethod,
actorOrgId: dto.actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -1832,13 +1836,14 @@ export const certificateAuthorityServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,

View File

@ -2,7 +2,7 @@ import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509";
import bcrypt from "bcrypt";
import { ProjectType, TCertificateTemplateEstConfigsUpdate } from "@app/db/schemas";
import { ActionProjectType, TCertificateTemplateEstConfigsUpdate } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@ -67,14 +67,14 @@ export const certificateTemplateServiceFactory = ({
message: `CA with ID ${caId} not found`
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -129,14 +129,14 @@ export const certificateTemplateServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
certTemplate.projectId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
@ -187,14 +187,14 @@ export const certificateTemplateServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
certTemplate.projectId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
@ -214,13 +214,14 @@ export const certificateTemplateServiceFactory = ({
});
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
certTemplate.projectId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@ -255,14 +256,14 @@ export const certificateTemplateServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
certTemplate.projectId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
@ -340,14 +341,14 @@ export const certificateTemplateServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
certTemplate.projectId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
@ -422,13 +423,14 @@ export const certificateTemplateServiceFactory = ({
}
if (!dto.isInternal) {
const { permission } = await permissionService.getProjectPermission(
dto.actor,
dto.actorId,
certTemplate.projectId,
dto.actorAuthMethod,
dto.actorOrgId
);
const { permission } = await permissionService.getProjectPermission({
actor: dto.actor,
actorId: dto.actorId,
projectId: certTemplate.projectId,
actorAuthMethod: dto.actorAuthMethod,
actorOrgId: dto.actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,

View File

@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType } from "@app/db/schemas";
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@ -50,14 +50,14 @@ export const certificateServiceFactory = ({
const cert = await certificateDAL.findOne({ serialNumber });
const ca = await certificateAuthorityDAL.findById(cert.caId);
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
@ -74,14 +74,14 @@ export const certificateServiceFactory = ({
const cert = await certificateDAL.findOne({ serialNumber });
const ca = await certificateAuthorityDAL.findById(cert.caId);
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates);
@ -109,14 +109,14 @@ export const certificateServiceFactory = ({
const cert = await certificateDAL.findOne({ serialNumber });
const ca = await certificateAuthorityDAL.findById(cert.caId);
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates);
@ -156,13 +156,14 @@ export const certificateServiceFactory = ({
const cert = await certificateDAL.findOne({ serialNumber });
const ca = await certificateAuthorityDAL.findById(cert.caId);
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);

View File

@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType, ProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionCmekActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
@ -34,14 +34,14 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
projectId = cmekProjectFromSplit.id;
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor.type,
actor.id,
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId,
actor.authMethod,
actor.orgId
);
ForbidOnInvalidProjectType(ProjectType.KMS);
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Create, ProjectPermissionSub.Cmek);
const cmek = await kmsService.generateKmsKey({
@ -60,14 +60,14 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor.type,
actor.id,
key.projectId,
actor.authMethod,
actor.orgId
);
ForbidOnInvalidProjectType(ProjectType.KMS);
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Edit, ProjectPermissionSub.Cmek);
@ -83,14 +83,14 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor.type,
actor.id,
key.projectId,
actor.authMethod,
actor.orgId
);
ForbidOnInvalidProjectType(ProjectType.KMS);
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Delete, ProjectPermissionSub.Cmek);
@ -109,13 +109,14 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
projectId = cmekProjectFromSplit.id;
}
const { permission } = await permissionService.getProjectPermission(
actor.type,
actor.id,
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId,
actor.authMethod,
actor.orgId
);
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
@ -133,15 +134,15 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
if (key.isDisabled) throw new BadRequestError({ message: "Key is disabled" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor.type,
actor.id,
key.projectId,
actor.authMethod,
actor.orgId
);
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbidOnInvalidProjectType(ProjectType.KMS);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Encrypt, ProjectPermissionSub.Cmek);
const encrypt = await kmsService.encryptWithKmsKey({ kmsId: keyId });
@ -160,14 +161,14 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
if (key.isDisabled) throw new BadRequestError({ message: "Key is disabled" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor.type,
actor.id,
key.projectId,
actor.authMethod,
actor.orgId
);
ForbidOnInvalidProjectType(ProjectType.KMS);
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Decrypt, ProjectPermissionSub.Cmek);

View File

@ -16,6 +16,7 @@ import { TProjectDALFactory } from "../project/project-dal";
import { TProjectServiceFactory } from "../project/project-service";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
@ -35,6 +36,8 @@ export type TImportDataIntoInfisicalDTO = {
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath" | "findById">;
projectService: Pick<TProjectServiceFactory, "createProject">;
projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">;
@ -503,6 +506,7 @@ export const importDataIntoInfisicalFn = async ({
secretTagDAL,
secretVersionTagDAL,
folderDAL,
resourceMetadataDAL,
input: { data, actor, actorId, actorOrgId, actorAuthMethod }
}: TImportDataIntoInfisicalDTO) => {
// Import data to infisical
@ -762,6 +766,8 @@ export const importDataIntoInfisicalFn = async ({
};
}),
folderId: selectedFolder.id,
orgId: actorOrgId,
resourceMetadataDAL,
secretDAL,
secretVersionDAL,
secretTagDAL,

View File

@ -8,6 +8,7 @@ import { TProjectDALFactory } from "../project/project-dal";
import { TProjectServiceFactory } from "../project/project-service";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
@ -35,6 +36,8 @@ export type TExternalMigrationQueueFactoryDep = {
projectService: Pick<TProjectServiceFactory, "createProject">;
projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">;
secretV2BridgeService: Pick<TSecretV2BridgeServiceFactory, "createManySecret">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
};
export type TExternalMigrationQueueFactory = ReturnType<typeof externalMigrationQueueFactory>;
@ -52,7 +55,8 @@ export const externalMigrationQueueFactory = ({
secretVersionDAL,
secretTagDAL,
secretVersionTagDAL,
folderDAL
folderDAL,
resourceMetadataDAL
}: TExternalMigrationQueueFactoryDep) => {
const startImport = async (dto: {
actorEmail: string;
@ -109,7 +113,8 @@ export const externalMigrationQueueFactory = ({
kmsService,
projectService,
projectEnvService,
secretV2BridgeService
secretV2BridgeService,
resourceMetadataDAL
});
if (projectsNotImported.length) {

View File

@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import ms from "ms";
import { ProjectMembershipRole, SecretKeyEncoding } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole, SecretKeyEncoding, TGroups } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
@ -9,6 +9,7 @@ import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { isUuidV4 } from "@app/lib/validator";
import { TGroupDALFactory } from "../../ee/services/group/group-dal";
import { TUserGroupMembershipDALFactory } from "../../ee/services/group/user-group-membership-dal";
@ -62,29 +63,37 @@ export const groupProjectServiceFactory = ({
actorAuthMethod,
roles,
projectId,
groupId
groupIdOrName
}: TCreateProjectGroupDTO) => {
const project = await projectDAL.findById(projectId);
if (!project) throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
if (project.version < 2) throw new BadRequestError({ message: `Failed to add group to E2EE project` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Groups);
const group = await groupDAL.findOne({ orgId: actorOrgId, id: groupId });
if (!group) throw new NotFoundError({ message: `Failed to find group with ID ${groupId}` });
let group: TGroups | null = null;
if (isUuidV4(groupIdOrName)) {
group = await groupDAL.findOne({ orgId: actorOrgId, id: groupIdOrName });
}
if (!group) {
group = await groupDAL.findOne({ orgId: actorOrgId, name: groupIdOrName });
}
if (!group) throw new NotFoundError({ message: `Failed to find group with ID or name ${groupIdOrName}` });
const existingGroup = await groupProjectDAL.findOne({ groupId: group.id, projectId: project.id });
if (existingGroup)
throw new BadRequestError({
message: `Group with ID ${groupId} already exists in project with id ${project.id}`
message: `Group with ID ${group.id} already exists in project with id ${project.id}`
});
for await (const { role: requestedRoleChange } of roles) {
@ -127,7 +136,7 @@ export const groupProjectServiceFactory = ({
const projectGroup = await groupProjectDAL.transaction(async (tx) => {
const groupProjectMembership = await groupProjectDAL.create(
{
groupId: group.id,
groupId: group!.id,
projectId: project.id
},
tx
@ -162,7 +171,7 @@ export const groupProjectServiceFactory = ({
// share project key with users in group that have not
// individually been added to the project and that are not part of
// other groups that are in the project
const groupMembers = await userGroupMembershipDAL.findGroupMembersNotInProject(group.id, project.id, tx);
const groupMembers = await userGroupMembershipDAL.findGroupMembersNotInProject(group!.id, project.id, tx);
if (groupMembers.length) {
const ghostUser = await projectDAL.findProjectGhostUser(project.id, tx);
@ -237,13 +246,14 @@ export const groupProjectServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Groups);
const group = await groupDAL.findOne({ orgId: actorOrgId, id: groupId });
@ -339,13 +349,14 @@ export const groupProjectServiceFactory = ({
const groupProjectMembership = await groupProjectDAL.findOne({ groupId: group.id, projectId: project.id });
if (!groupProjectMembership) throw new NotFoundError({ message: `Failed to find group with ID ${groupId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Groups);
const deletedProjectGroup = await groupProjectDAL.transaction(async (tx) => {
@ -383,13 +394,14 @@ export const groupProjectServiceFactory = ({
throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
const groupMemberships = await groupProjectDAL.findByProjectId(project.id);
@ -410,13 +422,14 @@ export const groupProjectServiceFactory = ({
throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
const [groupMembership] = await groupProjectDAL.findByProjectId(project.id, {

View File

@ -3,7 +3,7 @@ import { TProjectPermission } from "@app/lib/types";
import { ProjectUserMembershipTemporaryMode } from "../project-membership/project-membership-types";
export type TCreateProjectGroupDTO = {
groupId: string;
groupIdOrName: string;
roles: (
| {
role: string;

View File

@ -2,3 +2,11 @@ import picomatch from "picomatch";
export const doesFieldValueMatchOidcPolicy = (fieldValue: string, policyValue: string) =>
policyValue === fieldValue || picomatch.isMatch(fieldValue, policyValue);
export const doesAudValueMatchOidcPolicy = (fieldValue: string | string[], policyValue: string) => {
if (Array.isArray(fieldValue)) {
return fieldValue.some((entry) => entry === policyValue || picomatch.isMatch(entry, policyValue));
}
return policyValue === fieldValue || picomatch.isMatch(fieldValue, policyValue);
};

View File

@ -27,7 +27,7 @@ import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identit
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TOrgBotDALFactory } from "../org/org-bot-dal";
import { TIdentityOidcAuthDALFactory } from "./identity-oidc-auth-dal";
import { doesFieldValueMatchOidcPolicy } from "./identity-oidc-auth-fns";
import { doesAudValueMatchOidcPolicy, doesFieldValueMatchOidcPolicy } from "./identity-oidc-auth-fns";
import {
TAttachOidcAuthDTO,
TGetOidcAuthDTO,
@ -148,7 +148,7 @@ export const identityOidcAuthServiceFactory = ({
if (
!identityOidcAuth.boundAudiences
.split(", ")
.some((policyValue) => doesFieldValueMatchOidcPolicy(tokenData.aud, policyValue))
.some((policyValue) => doesAudValueMatchOidcPolicy(tokenData.aud, policyValue))
) {
throw new UnauthorizedError({
message: "Access denied: OIDC audience not allowed."

View File

@ -1,7 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability";
import ms from "ms";
import { ProjectMembershipRole } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
@ -54,13 +54,14 @@ export const identityProjectServiceFactory = ({
projectId,
roles
}: TCreateProjectIdentityDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Identity, {
@ -159,13 +160,14 @@ export const identityProjectServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TUpdateProjectIdentityDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId })
@ -254,25 +256,27 @@ export const identityProjectServiceFactory = ({
throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.Identity, { identityId })
);
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
ActorType.IDENTITY,
identityId,
identityProjectMembership.projectId,
const { permission: identityRolePermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
if (!isAtLeastAsPrivileged(permission, identityRolePermission))
throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" });
@ -292,13 +296,14 @@ export const identityProjectServiceFactory = ({
orderDirection,
search
}: TListProjectIdentityDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
const identityMemberships = await identityProjectDAL.findByProjectId(projectId, {
@ -322,13 +327,14 @@ export const identityProjectServiceFactory = ({
actorOrgId,
identityId
}: TGetProjectIdentityByIdentityIdDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,

View File

@ -5,7 +5,7 @@ import { Client as OctopusClient, SpaceRepository as OctopusSpaceRepository } fr
import AWS from "aws-sdk";
import {
ProjectType,
ActionProjectType,
SecretEncryptionAlgo,
SecretKeyEncoding,
TIntegrationAuths,
@ -97,13 +97,14 @@ export const integrationAuthServiceFactory = ({
actorAuthMethod,
projectId
}: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const authorizations = await integrationAuthDAL.find({ projectId });
return authorizations;
@ -114,13 +115,14 @@ export const integrationAuthServiceFactory = ({
return Promise.all(
authorizations.filter(async (auth) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
auth.projectId,
projectId: auth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
return permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
})
@ -131,13 +133,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
return integrationAuth;
};
@ -156,14 +159,14 @@ export const integrationAuthServiceFactory = ({
if (!Object.values(Integrations).includes(integration as Integrations))
throw new BadRequestError({ message: "Invalid integration" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
const tokenExchange = await exchangeCode({ integration, code, url, installationId });
@ -266,14 +269,14 @@ export const integrationAuthServiceFactory = ({
if (!Object.values(Integrations).includes(integration as Integrations))
throw new BadRequestError({ message: "Invalid integration" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
const updateDoc: TIntegrationAuthsInsert = {
@ -401,13 +404,14 @@ export const integrationAuthServiceFactory = ({
throw new NotFoundError({ message: `Integration auth with id ${integrationAuthId} not found.` });
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
const { projectId } = integrationAuth;
@ -662,13 +666,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(integrationAuth.projectId);
@ -696,13 +701,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@ -726,13 +732,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -767,13 +774,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -795,13 +803,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@ -869,13 +878,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -916,13 +926,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -950,13 +961,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessId, accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1008,13 +1020,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1044,13 +1057,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1085,13 +1099,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1125,13 +1140,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1165,13 +1181,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID ${id} not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1204,13 +1221,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1244,13 +1262,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1312,13 +1331,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1386,13 +1406,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1436,13 +1457,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1484,13 +1506,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1552,13 +1575,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1593,13 +1617,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1705,13 +1730,14 @@ export const integrationAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TDeleteIntegrationAuthsDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
const integrations = await integrationAuthDAL.delete({ integration, projectId });
@ -1728,13 +1754,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
const delIntegrationAuth = await integrationAuthDAL.transaction(async (tx) => {
@ -1761,26 +1788,28 @@ export const integrationAuthServiceFactory = ({
throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
}
const { permission: sourcePermission } = await permissionService.getProjectPermission(
const { permission: sourcePermission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(sourcePermission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations
);
const { permission: targetPermission } = await permissionService.getProjectPermission(
const { permission: targetPermission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(targetPermission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -1806,13 +1835,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1846,13 +1876,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);

View File

@ -427,3 +427,8 @@ export const getIntegrationOptions = async () => {
return INTEGRATION_OPTIONS;
};
export enum IntegrationMetadataSyncMode {
CUSTOM = "custom",
SECRET_METADATA = "secret-metadata"
}

View File

@ -38,6 +38,7 @@ import { TCreateManySecretsRawFn, TUpdateManySecretsRawFn } from "@app/services/
import { TIntegrationDALFactory } from "../integration/integration-dal";
import { IntegrationMetadataSchema } from "../integration/integration-schema";
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { IntegrationAuthMetadataSchema } from "./integration-auth-schema";
import {
CircleCiScope,
@ -48,6 +49,7 @@ import {
import {
IntegrationInitialSyncBehavior,
IntegrationMappingBehavior,
IntegrationMetadataSyncMode,
Integrations,
IntegrationUrls
} from "./integration-list";
@ -930,15 +932,22 @@ const syncSecretsAWSParameterStore = async ({
logger.info(
`getIntegrationSecrets: create secret in AWS SSM for [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}]`
);
await ssm
.putParameter({
Name: `${integration.path}${key}`,
Type: "SecureString",
Value: secrets[key].value,
...(metadata.kmsKeyId && { KeyId: metadata.kmsKeyId }),
Overwrite: true
})
.promise();
try {
await ssm
.putParameter({
Name: `${integration.path}${key}`,
Type: "SecureString",
Value: secrets[key].value,
...(metadata.kmsKeyId && { KeyId: metadata.kmsKeyId }),
Overwrite: true
})
.promise();
} catch (error) {
(error as { secretKey: string }).secretKey = key;
throw error;
}
if (metadata.secretAWSTag?.length) {
try {
await ssm
@ -985,15 +994,20 @@ const syncSecretsAWSParameterStore = async ({
// we ensure that the KMS key configured in the integration is applied for ALL parameters on AWS
if (secrets[key].value && (shouldUpdateKms || awsParameterStoreSecretsObj[key].Value !== secrets[key].value)) {
await ssm
.putParameter({
Name: `${integration.path}${key}`,
Type: "SecureString",
Value: secrets[key].value,
Overwrite: true,
...(metadata.kmsKeyId && { KeyId: metadata.kmsKeyId })
})
.promise();
try {
await ssm
.putParameter({
Name: `${integration.path}${key}`,
Type: "SecureString",
Value: secrets[key].value,
Overwrite: true,
...(metadata.kmsKeyId && { KeyId: metadata.kmsKeyId })
})
.promise();
} catch (error) {
(error as { secretKey: string }).secretKey = key;
throw error;
}
}
if (awsParameterStoreSecretsObj[key].Name) {
@ -1082,14 +1096,14 @@ const syncSecretsAWSSecretManager = async ({
projectId
}: {
integration: TIntegrations;
secrets: Record<string, { value: string; comment?: string }>;
secrets: Record<string, { value: string; comment?: string; secretMetadata?: ResourceMetadataDTO }>;
accessId: string | null;
accessToken: string;
awsAssumeRoleArn: string | null;
projectId?: string;
}) => {
const appCfg = getConfig();
const metadata = z.record(z.any()).parse(integration.metadata || {});
const metadata = IntegrationMetadataSchema.parse(integration.metadata || {});
if (!accessId && !awsAssumeRoleArn) {
throw new Error("AWS access ID/AWS Assume Role is required");
@ -1137,8 +1151,25 @@ const syncSecretsAWSSecretManager = async ({
const processAwsSecret = async (
secretId: string,
secretValue: Record<string, string | null | undefined> | string
secretValue: Record<string, string | null | undefined> | string,
secretMetadata?: ResourceMetadataDTO
) => {
const secretAWSTag = metadata.secretAWSTag as { key: string; value: string }[] | undefined;
const shouldTag =
(secretAWSTag && secretAWSTag.length) ||
(metadata.metadataSyncMode === IntegrationMetadataSyncMode.SECRET_METADATA &&
metadata.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE);
const tagArray =
(metadata.metadataSyncMode === IntegrationMetadataSyncMode.SECRET_METADATA ? secretMetadata : secretAWSTag) ?? [];
const integrationTagObj = tagArray.reduce(
(acc, item) => {
acc[item.key] = item.value;
return acc;
},
{} as Record<string, string>
);
try {
const awsSecretManagerSecret = await secretsManager.send(
new GetSecretValueCommand({
@ -1174,9 +1205,7 @@ const syncSecretsAWSSecretManager = async ({
}
}
const secretAWSTag = metadata.secretAWSTag as { key: string; value: string }[] | undefined;
if (secretAWSTag && secretAWSTag.length) {
if (shouldTag) {
const describedSecret = await secretsManager.send(
// requires secretsmanager:DescribeSecret policy
new DescribeSecretCommand({
@ -1186,14 +1215,6 @@ const syncSecretsAWSSecretManager = async ({
if (!describedSecret.Tags) return;
const integrationTagObj = secretAWSTag.reduce(
(acc, item) => {
acc[item.key] = item.value;
return acc;
},
{} as Record<string, string>
);
const awsTagObj = (describedSecret.Tags || []).reduce(
(acc, item) => {
if (item.Key && item.Value) {
@ -1225,7 +1246,7 @@ const syncSecretsAWSSecretManager = async ({
}
});
secretAWSTag?.forEach((tag) => {
tagArray.forEach((tag) => {
if (!(tag.key in awsTagObj)) {
// create tag in AWS secret manager
tagsToUpdate.push({
@ -1262,8 +1283,8 @@ const syncSecretsAWSSecretManager = async ({
Name: secretId,
SecretString: typeof secretValue === "string" ? secretValue : JSON.stringify(secretValue),
...(metadata.kmsKeyId && { KmsKeyId: metadata.kmsKeyId }),
Tags: metadata.secretAWSTag
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({
Tags: shouldTag
? tagArray.map((tag: { key: string; value: string }) => ({
Key: tag.key,
Value: tag.value
}))
@ -1280,7 +1301,10 @@ const syncSecretsAWSSecretManager = async ({
if (metadata.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE) {
for await (const [key, value] of Object.entries(secrets)) {
await processAwsSecret(key, value.value);
await processAwsSecret(key, value.value, value.secretMetadata).catch((error) => {
error.secretKey = key;
throw error;
});
}
} else {
await processAwsSecret(integration.app as string, getSecretKeyValuePair(secrets));
@ -2763,13 +2787,23 @@ const syncSecretsAzureDevops = async ({
* Sync/push [secrets] to GitLab repo with name [integration.app]
*/
const syncSecretsGitLab = async ({
createManySecretsRawFn,
integrationAuth,
integration,
secrets,
accessToken
}: {
createManySecretsRawFn: (params: TCreateManySecretsRawFn) => Promise<Array<{ id: string }>>;
integrationAuth: TIntegrationAuths;
integration: TIntegrations;
integration: TIntegrations & {
projectId: string;
environment: {
id: string;
name: string;
slug: string;
};
secretPath: string;
};
secrets: Record<string, { value: string; comment?: string }>;
accessToken: string;
}) => {
@ -2826,6 +2860,81 @@ const syncSecretsGitLab = async ({
return isValid;
});
if (!integration.lastUsed) {
const secretsToAddToInfisical: { [key: string]: GitLabSecret } = {};
const secretsToRemoveInGitlab: GitLabSecret[] = [];
if (!metadata.initialSyncBehavior) {
metadata.initialSyncBehavior = IntegrationInitialSyncBehavior.OVERWRITE_TARGET;
}
getSecretsRes.forEach((gitlabSecret) => {
// first time using integration
// -> apply initial sync behavior
switch (metadata.initialSyncBehavior) {
// Override all the secrets in GitLab
case IntegrationInitialSyncBehavior.OVERWRITE_TARGET: {
if (!(gitlabSecret.key in secrets)) {
secretsToRemoveInGitlab.push(gitlabSecret);
}
break;
}
case IntegrationInitialSyncBehavior.PREFER_SOURCE: {
// if the secret is not in infisical, we need to add it to infisical
if (!(gitlabSecret.key in secrets)) {
secrets[gitlabSecret.key] = {
value: gitlabSecret.value
};
// need to remove prefix and suffix from what we're saving to Infisical
const prefix = metadata?.secretPrefix || "";
const suffix = metadata?.secretSuffix || "";
let processedKey = gitlabSecret.key;
// Remove prefix if it exists at the start
if (prefix && processedKey.startsWith(prefix)) {
processedKey = processedKey.slice(prefix.length);
}
// Remove suffix if it exists at the end
if (suffix && processedKey.endsWith(suffix)) {
processedKey = processedKey.slice(0, -suffix.length);
}
secretsToAddToInfisical[processedKey] = gitlabSecret;
}
break;
}
default: {
throw new Error(`Invalid initial sync behavior: ${metadata.initialSyncBehavior}`);
}
}
});
if (Object.keys(secretsToAddToInfisical).length) {
await createManySecretsRawFn({
projectId: integration.projectId,
environment: integration.environment.slug,
path: integration.secretPath,
secrets: Object.keys(secretsToAddToInfisical).map((key) => ({
secretName: key,
secretValue: secretsToAddToInfisical[key].value,
type: SecretType.Shared
}))
});
}
for await (const gitlabSecret of secretsToRemoveInGitlab) {
await request.delete(
`${gitLabApiUrl}/v4/projects/${integration?.appId}/variables/${gitlabSecret.key}?filter[environment_scope]=${integration.targetEnvironment}`,
{
headers: {
Authorization: `Bearer ${accessToken}`
}
}
);
}
}
for await (const key of Object.keys(secrets)) {
const existingSecret = getSecretsRes.find((s) => s.key === key);
if (!existingSecret) {
@ -3653,17 +3762,28 @@ const syncSecretsCloudflarePages = async ({
);
const metadata = z.record(z.any()).parse(integration.metadata);
if (metadata.shouldAutoRedeploy) {
await request.post(
`${IntegrationUrls.CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accessId}/pages/projects/${integration.app}/deployments`,
{},
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
if (metadata.shouldAutoRedeploy && integration.targetEnvironment === "production") {
await request
.post(
`${IntegrationUrls.CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accessId}/pages/projects/${integration.app}/deployments`,
{},
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
}
);
)
.catch((error) => {
if (error instanceof AxiosError && error.response?.status === 304) {
logger.info(
`syncSecretsCloudflarePages: CF pages redeployment returned status code 304 for integration [id=${integration.id}]`
);
return;
}
throw error;
});
}
};
@ -4440,7 +4560,7 @@ export const syncIntegrationSecrets = async ({
secretPath: string;
};
integrationAuth: TIntegrationAuths;
secrets: Record<string, { value: string; comment?: string }>;
secrets: Record<string, { value: string; comment?: string; secretMetadata?: ResourceMetadataDTO }>;
accessId: string | null;
awsAssumeRoleArn: string | null;
accessToken: string;
@ -4545,7 +4665,8 @@ export const syncIntegrationSecrets = async ({
integrationAuth,
integration,
secrets,
accessToken
accessToken,
createManySecretsRawFn
});
break;
case Integrations.RENDER:

View File

@ -1,3 +1,5 @@
import { AxiosResponse } from "axios";
import { request } from "@app/lib/config/request";
import { BadRequestError } from "@app/lib/errors";
@ -11,19 +13,27 @@ const getTeamsGitLab = async ({ url, accessToken }: { url: string; accessToken:
const gitLabApiUrl = url ? `${url}/api` : IntegrationUrls.GITLAB_API_URL;
let teams: Team[] = [];
const res = (
await request.get<{ name: string; id: string }[]>(`${gitLabApiUrl}/v4/groups`, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
let page: number = 1;
while (page > 0) {
// eslint-disable-next-line no-await-in-loop
const { data, headers }: AxiosResponse<{ name: string; id: string }[]> = await request.get(
`${gitLabApiUrl}/v4/groups?page=${page}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
})
).data;
);
teams = res.map((t) => ({
name: t.name,
id: t.id.toString()
}));
page = Number(headers["x-next-page"] ?? "");
teams = teams.concat(
data.map((t) => ({
name: t.name,
id: t.id.toString()
}))
);
}
return teams;
};

View File

@ -2,7 +2,7 @@ import { z } from "zod";
import { INTEGRATION } from "@app/lib/api-docs";
import { IntegrationMappingBehavior } from "../integration-auth/integration-list";
import { IntegrationMappingBehavior, IntegrationMetadataSyncMode } from "../integration-auth/integration-list";
export const IntegrationMetadataSchema = z.object({
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
@ -50,6 +50,11 @@ export const IntegrationMetadataSchema = z.object({
shouldMaskSecrets: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldMaskSecrets),
shouldProtectSecrets: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldProtectSecrets),
metadataSyncMode: z
.nativeEnum(IntegrationMetadataSyncMode)
.optional()
.describe(INTEGRATION.CREATE.metadata.metadataSyncMode),
octopusDeployScopeValues: z
.object({
// in Octopus Deploy Scope Value Format

View File

@ -1,6 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { NotFoundError } from "@app/lib/errors";
@ -81,14 +81,14 @@ export const integrationServiceFactory = ({
if (!integrationAuth)
throw new NotFoundError({ message: `Integration auth with ID '${integrationAuthId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
ForbiddenError.from(permission).throwUnlessCan(
@ -160,14 +160,14 @@ export const integrationServiceFactory = ({
const integration = await integrationDAL.findById(id);
if (!integration) throw new NotFoundError({ message: `Integration with ID '${id}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integration.projectId,
projectId: integration.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
const newEnvironment = environment || integration.environment.slug;
@ -227,13 +227,14 @@ export const integrationServiceFactory = ({
});
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integration?.projectId || "",
projectId: integration.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
if (!integration) {
@ -254,13 +255,14 @@ export const integrationServiceFactory = ({
});
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integration?.projectId || "",
projectId: integration.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const integrationAuth = await integrationAuthDAL.findById(integration.integrationAuthId);
@ -296,14 +298,14 @@ export const integrationServiceFactory = ({
const integration = await integrationDAL.findById(id);
if (!integration) throw new NotFoundError({ message: `Integration with ID '${id}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integration.projectId,
projectId: integration.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
const integrationAuth = await integrationAuthDAL.findById(integration.integrationAuthId);
@ -333,13 +335,14 @@ export const integrationServiceFactory = ({
actorAuthMethod,
projectId
}: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const integrations = await integrationDAL.findByProjectId(projectId);
@ -352,13 +355,14 @@ export const integrationServiceFactory = ({
throw new NotFoundError({ message: `Integration with ID '${id}' not found` });
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integration.projectId,
projectId: integration.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
await secretQueueService.syncIntegrations({

View File

@ -5,6 +5,7 @@ import jwt from "jsonwebtoken";
import { Knex } from "knex";
import {
ActionProjectType,
OrgMembershipRole,
OrgMembershipStatus,
ProjectMembershipRole,
@ -68,6 +69,7 @@ import {
TGetOrgMembershipDTO,
TInviteUserToOrgDTO,
TListProjectMembershipsByOrgMembershipIdDTO,
TResendOrgMemberInvitationDTO,
TUpdateOrgDTO,
TUpdateOrgMembershipDTO,
TVerifyUserToOrgDTO
@ -583,6 +585,66 @@ export const orgServiceFactory = ({
});
return membership;
};
const resendOrgMemberInvitation = async ({
orgId,
actorId,
actor,
actorAuthMethod,
actorOrgId,
membershipId
}: TResendOrgMemberInvitationDTO) => {
const appCfg = getConfig();
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
const org = await orgDAL.findOrgById(orgId);
const [inviteeOrgMembership] = await orgDAL.findMembership({
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
[`${TableName.OrgMembership}.id` as "id"]: membershipId
});
if (inviteeOrgMembership.status !== OrgMembershipStatus.Invited) {
throw new BadRequestError({
message: "Organization invitation already accepted"
});
}
const token = await tokenService.createTokenForUser({
type: TokenType.TOKEN_EMAIL_ORG_INVITATION,
userId: inviteeOrgMembership.userId,
orgId
});
if (!appCfg.isSmtpConfigured) {
return {
signupToken: {
email: inviteeOrgMembership.email as string,
link: `${appCfg.SITE_URL}/signupinvite?token=${token}&to=${inviteeOrgMembership.email}&organization_id=${org?.id}`
}
};
}
await smtpService.sendMail({
template: SmtpTemplates.OrgInvite,
subjectLine: "Infisical organization invitation",
recipients: [inviteeOrgMembership.email as string],
substitutions: {
inviterFirstName: inviteeOrgMembership.firstName,
inviterUsername: inviteeOrgMembership.email,
organizationName: org?.name,
email: inviteeOrgMembership.email,
organizationId: org?.id.toString(),
token,
callback_url: `${appCfg.SITE_URL}/signupinvite`
}
});
return { signupToken: undefined };
};
/*
* Invite user to organization
*/
@ -626,6 +688,7 @@ export const orgServiceFactory = ({
}
})
: [];
if (projectsToInvite.length !== invitedProjects?.length) {
throw new ForbiddenRequestError({
message: "Access denied to one or more of the specified projects"
@ -773,13 +836,14 @@ export const orgServiceFactory = ({
// if there exist no project membership we set is as given by the request
for await (const project of projectsToInvite) {
const projectId = project.id;
const { permission: projectPermission } = await permissionService.getProjectPermission(
const { permission: projectPermission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(projectPermission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Member
@ -1219,6 +1283,7 @@ export const orgServiceFactory = ({
deleteIncidentContact,
getOrgGroups,
listProjectMembershipsByOrgMembershipId,
findOrgBySlug
findOrgBySlug,
resendOrgMemberInvitation
};
};

View File

@ -35,6 +35,10 @@ export type TInviteUserToOrgDTO = {
}[];
} & TOrgPermission;
export type TResendOrgMemberInvitationDTO = {
membershipId: string;
} & TOrgPermission;
export type TVerifyUserToOrgDTO = {
email: string;
orgId: string;

View File

@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType, ProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
@ -86,14 +86,14 @@ export const pkiAlertServiceFactory = ({
projectId = certManagerProjectFromSplit.id;
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.PkiAlerts);
@ -116,13 +116,14 @@ export const pkiAlertServiceFactory = ({
const alert = await pkiAlertDAL.findById(alertId);
if (!alert) throw new NotFoundError({ message: `Alert with ID '${alertId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
alert.projectId,
projectId: alert.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts);
return alert;
@ -142,14 +143,14 @@ export const pkiAlertServiceFactory = ({
let alert = await pkiAlertDAL.findById(alertId);
if (!alert) throw new NotFoundError({ message: `Alert with ID '${alertId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
alert.projectId,
projectId: alert.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiAlerts);
@ -175,14 +176,14 @@ export const pkiAlertServiceFactory = ({
let alert = await pkiAlertDAL.findById(alertId);
if (!alert) throw new NotFoundError({ message: `Alert with ID '${alertId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
alert.projectId,
projectId: alert.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.PkiAlerts);
alert = await pkiAlertDAL.deleteById(alertId);

View File

@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ProjectType, TPkiCollectionItems } from "@app/db/schemas";
import { ActionProjectType, ProjectType, TPkiCollectionItems } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
@ -62,14 +62,14 @@ export const pkiCollectionServiceFactory = ({
projectId = certManagerProjectFromSplit.id;
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -95,13 +95,14 @@ export const pkiCollectionServiceFactory = ({
const pkiCollection = await pkiCollectionDAL.findById(collectionId);
if (!pkiCollection) throw new NotFoundError({ message: `PKI collection with ID '${collectionId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
pkiCollection.projectId,
projectId: pkiCollection.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections);
return pkiCollection;
@ -119,14 +120,14 @@ export const pkiCollectionServiceFactory = ({
let pkiCollection = await pkiCollectionDAL.findById(collectionId);
if (!pkiCollection) throw new NotFoundError({ message: `PKI collection with ID '${collectionId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
pkiCollection.projectId,
projectId: pkiCollection.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiCollections);
pkiCollection = await pkiCollectionDAL.updateById(collectionId, {
@ -147,14 +148,14 @@ export const pkiCollectionServiceFactory = ({
let pkiCollection = await pkiCollectionDAL.findById(collectionId);
if (!pkiCollection) throw new NotFoundError({ message: `PKI collection with ID '${collectionId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
pkiCollection.projectId,
projectId: pkiCollection.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
@ -177,13 +178,14 @@ export const pkiCollectionServiceFactory = ({
const pkiCollection = await pkiCollectionDAL.findById(collectionId);
if (!pkiCollection) throw new NotFoundError({ message: `PKI collection with ID '${collectionId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
pkiCollection.projectId,
projectId: pkiCollection.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections);
@ -220,14 +222,14 @@ export const pkiCollectionServiceFactory = ({
const pkiCollection = await pkiCollectionDAL.findById(collectionId);
if (!pkiCollection) throw new NotFoundError({ message: `PKI collection with ID '${collectionId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
pkiCollection.projectId,
projectId: pkiCollection.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -314,14 +316,14 @@ export const pkiCollectionServiceFactory = ({
if (!pkiCollectionItem) throw new NotFoundError({ message: `PKI collection item with ID '${itemId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
pkiCollection.projectId,
projectId: pkiCollection.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,

View File

@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ProjectVersion } from "@app/db/schemas";
import { ActionProjectType, ProjectVersion } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { generateAsymmetricKeyPair } from "@app/lib/crypto";
@ -41,13 +41,14 @@ export const projectBotServiceFactory = ({
botKey,
publicKey
}: TFindBotByProjectIdDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const bot = await projectBotDAL.transaction(async (tx) => {
@ -107,13 +108,14 @@ export const projectBotServiceFactory = ({
const bot = await projectBotDAL.findById(botId);
if (!bot) throw new NotFoundError({ message: `Project bot with ID '${botId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
bot.projectId,
projectId: bot.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
const project = await projectBotDAL.findProjectByBotId(botId);

View File

@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@ -42,14 +42,14 @@ export const projectEnvServiceFactory = ({
name,
slug
}: TCreateEnvDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Environments);
const lock = await keyStore
@ -131,14 +131,14 @@ export const projectEnvServiceFactory = ({
id,
position
}: TUpdateEnvDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Environments);
const lock = await keyStore
@ -195,14 +195,14 @@ export const projectEnvServiceFactory = ({
};
const deleteEnvironment = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod, id }: TDeleteEnvDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Environments);
const lock = await keyStore
@ -251,13 +251,14 @@ export const projectEnvServiceFactory = ({
});
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
environment.projectId,
projectId: environment.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);

View File

@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError } from "@app/lib/errors";
@ -31,13 +32,14 @@ export const projectKeyServiceFactory = ({
nonce,
encryptedKey
}: TUploadProjectKeyDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
const receiverMembership = await projectMembershipDAL.findOne({
@ -60,7 +62,14 @@ export const projectKeyServiceFactory = ({
actorOrgId,
actorAuthMethod
}: TGetLatestProjectKeyDTO) => {
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
const latestKey = await projectKeyDAL.findLatestProjectKey(actorId, projectId);
return latestKey;
};
@ -72,13 +81,14 @@ export const projectKeyServiceFactory = ({
actorAuthMethod,
projectId
}: TGetLatestProjectKeyDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
return projectKeyDAL.findAllProjectUserPubKeys(projectId);
};

View File

@ -2,7 +2,7 @@
import { ForbiddenError } from "@casl/ability";
import ms from "ms";
import { ProjectMembershipRole, ProjectVersion, TableName } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole, ProjectVersion, TableName } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@ -78,13 +78,14 @@ export const projectMembershipServiceFactory = ({
includeGroupMembers,
projectId
}: TGetProjectMembershipDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId);
@ -121,13 +122,14 @@ export const projectMembershipServiceFactory = ({
projectId,
username
}: TGetProjectMembershipByUsernameDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
const [membership] = await projectMembershipDAL.findAllProjectMembers(projectId, { username });
@ -143,13 +145,14 @@ export const projectMembershipServiceFactory = ({
projectId,
id
}: TGetProjectMembershipByIdDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
const [membership] = await projectMembershipDAL.findAllProjectMembers(projectId, { id });
@ -169,13 +172,14 @@ export const projectMembershipServiceFactory = ({
const project = await projectDAL.findById(projectId);
if (!project) throw new NotFoundError({ message: `Project with ID '${projectId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
const orgMembers = await orgDAL.findMembership({
[`${TableName.OrgMembership}.orgId` as "orgId"]: project.orgId,
@ -249,13 +253,14 @@ export const projectMembershipServiceFactory = ({
membershipId,
roles
}: TUpdateProjectMembershipDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
const membershipUser = await userDAL.findUserByProjectMembershipId(membershipId);
@ -348,13 +353,14 @@ export const projectMembershipServiceFactory = ({
projectId,
membershipId
}: TDeleteProjectMembershipOldDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Member);
const member = await userDAL.findUserByProjectMembershipId(membershipId);
@ -383,13 +389,14 @@ export const projectMembershipServiceFactory = ({
emails,
usernames
}: TDeleteProjectMembershipsDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Member);
const project = await projectDAL.findById(projectId);

View File

@ -1,7 +1,7 @@
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import { ProjectMembershipRole, TableName } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole, TableName } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
ProjectPermissionActions,
@ -58,13 +58,14 @@ export const projectRoleServiceFactory = ({
projectId = filter.projectId;
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Role);
const existingRole = await projectRoleDAL.findOne({ slug: data.slug, projectId });
if (existingRole) {
@ -95,13 +96,14 @@ export const projectRoleServiceFactory = ({
projectId = filter.projectId;
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
if (roleSlug !== "custom" && Object.values(ProjectMembershipRole).includes(roleSlug as ProjectMembershipRole)) {
const predefinedRole = getPredefinedRoles(projectId, roleSlug as ProjectMembershipRole)[0];
@ -117,13 +119,14 @@ export const projectRoleServiceFactory = ({
const projectRole = await projectRoleDAL.findById(roleId);
if (!projectRole) throw new NotFoundError({ message: "Project role not found", name: "Delete role" });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectRole.projectId,
projectId: projectRole.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Role);
if (data?.slug) {
@ -144,13 +147,14 @@ export const projectRoleServiceFactory = ({
const deleteRole = async ({ actor, actorId, actorAuthMethod, actorOrgId, roleId }: TDeleteRoleDTO) => {
const projectRole = await projectRoleDAL.findById(roleId);
if (!projectRole) throw new NotFoundError({ message: "Project role not found", name: "Delete role" });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectRole.projectId,
projectId: projectRole.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Role);
const identityRole = await identityProjectMembershipRoleDAL.findOne({ customRoleId: roleId });
@ -185,13 +189,14 @@ export const projectRoleServiceFactory = ({
projectId = filter.projectId;
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
const customRoles = await projectRoleDAL.find(
{ projectId },
@ -208,12 +213,13 @@ export const projectRoleServiceFactory = ({
actorAuthMethod: ActorAuthMethod,
actorOrgId: string | undefined
) => {
const { permission, membership } = await permissionService.getUserProjectPermission(
const { permission, membership } = await permissionService.getUserProjectPermission({
userId,
projectId,
actorAuthMethod,
actorOrgId
);
authMethod: actorAuthMethod,
userOrgId: actorOrgId,
actionProjectType: ActionProjectType.Any
});
return { permissions: packRules(permission.rules), membership };
};

View File

@ -1,7 +1,13 @@
import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import { ProjectMembershipRole, ProjectType, ProjectVersion, TProjectEnvironments } from "@app/db/schemas";
import {
ActionProjectType,
ProjectMembershipRole,
ProjectType,
ProjectVersion,
TProjectEnvironments
} from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
@ -23,6 +29,7 @@ import { ActorType } from "../auth/auth-type";
import { TCertificateDALFactory } from "../certificate/certificate-dal";
import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal";
import { TCertificateTemplateDALFactory } from "../certificate-template/certificate-template-dal";
import { TGroupProjectDALFactory } from "../group-project/group-project-dal";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TIdentityProjectDALFactory } from "../identity-project/identity-project-dal";
import { TIdentityProjectMembershipRoleDALFactory } from "../identity-project/identity-project-membership-role-dal";
@ -94,7 +101,8 @@ type TProjectServiceFactoryDep = {
identityProjectDAL: TIdentityProjectDALFactory;
identityProjectMembershipRoleDAL: Pick<TIdentityProjectMembershipRoleDALFactory, "create">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "create" | "findLatestProjectKey" | "delete" | "find" | "insertMany">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "create" | "findProjectGhostUser" | "findOne">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "create" | "findProjectGhostUser" | "findOne" | "delete">;
groupProjectDAL: Pick<TGroupProjectDALFactory, "delete">;
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "findOne" | "transaction" | "updateById" | "create">;
slackIntegrationDAL: Pick<TSlackIntegrationDALFactory, "findById" | "findByIdWithWorkflowIntegrationDetails">;
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
@ -114,7 +122,7 @@ type TProjectServiceFactoryDep = {
orgDAL: Pick<TOrgDALFactory, "findOne">;
keyStore: Pick<TKeyStoreFactory, "deleteItem">;
projectBotDAL: Pick<TProjectBotDALFactory, "create">;
projectRoleDAL: Pick<TProjectRoleDALFactory, "find" | "insertMany">;
projectRoleDAL: Pick<TProjectRoleDALFactory, "find" | "insertMany" | "delete">;
kmsService: Pick<
TKmsServiceFactory,
| "updateProjectSecretManagerKmsKey"
@ -163,7 +171,8 @@ export const projectServiceFactory = ({
projectBotDAL,
projectSlackConfigDAL,
slackIntegrationDAL,
projectTemplateService
projectTemplateService,
groupProjectDAL
}: TProjectServiceFactoryDep) => {
/*
* Create workspace. Make user the admin
@ -427,23 +436,43 @@ export const projectServiceFactory = ({
const deleteProject = async ({ actor, actorId, actorOrgId, actorAuthMethod, filter }: TDeleteProjectDTO) => {
const project = await projectDAL.findProjectByFilter(filter);
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
const deletedProject = await projectDAL.transaction(async (tx) => {
// delete these so that project custom roles can be deleted in cascade effect
// direct deletion of project without these will cause fk error
await projectMembershipDAL.delete({ projectId: project.id }, tx);
await groupProjectDAL.delete({ projectId: project.id }, tx);
const delProject = await projectDAL.deleteById(project.id, tx);
const projectGhostUser = await projectMembershipDAL.findProjectGhostUser(project.id, tx).catch(() => null);
// akhilmhdh: before removing those kms checking any other project uses it
// happened due to project split
if (delProject.kmsCertificateKeyId) {
await kmsService.deleteInternalKms(delProject.kmsCertificateKeyId, delProject.orgId, tx);
const projectsLinkedToForiegnKey = await projectDAL.find(
{ kmsCertificateKeyId: delProject.kmsCertificateKeyId },
{ tx }
);
if (!projectsLinkedToForiegnKey.length) {
await kmsService.deleteInternalKms(delProject.kmsCertificateKeyId, delProject.orgId, tx);
}
}
if (delProject.kmsSecretManagerKeyId) {
await kmsService.deleteInternalKms(delProject.kmsSecretManagerKeyId, delProject.orgId, tx);
const projectsLinkedToForiegnKey = await projectDAL.find(
{ kmsSecretManagerKeyId: delProject.kmsSecretManagerKeyId },
{ tx }
);
if (!projectsLinkedToForiegnKey.length) {
await kmsService.deleteInternalKms(delProject.kmsSecretManagerKeyId, delProject.orgId, tx);
}
}
// Delete the org membership for the ghost user if it's found.
if (projectGhostUser) {
@ -510,27 +539,37 @@ export const projectServiceFactory = ({
const getAProject = async ({ actorId, actorOrgId, actorAuthMethod, filter, actor }: TGetProjectDTO) => {
const project = await projectDAL.findProjectByFilter(filter);
await permissionService.getProjectPermission(actor, actorId, project.id, actorAuthMethod, actorOrgId);
await permissionService.getProjectPermission({
actor,
actorId,
projectId: project.id,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
return project;
};
const updateProject = async ({ actor, actorId, actorOrgId, actorAuthMethod, update, filter }: TUpdateProjectDTO) => {
const project = await projectDAL.findProjectByFilter(filter);
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
const updatedProject = await projectDAL.updateById(project.id, {
name: update.name,
description: update.description,
autoCapitalization: update.autoCapitalization
autoCapitalization: update.autoCapitalization,
enforceCapitalization: update.autoCapitalization
});
return updatedProject;
};
@ -542,16 +581,21 @@ export const projectServiceFactory = ({
actorAuthMethod,
autoCapitalization
}: TToggleProjectAutoCapitalizationDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
const updatedProject = await projectDAL.updateById(projectId, { autoCapitalization });
const updatedProject = await projectDAL.updateById(projectId, {
autoCapitalization,
enforceCapitalization: autoCapitalization
});
return updatedProject;
};
@ -570,13 +614,14 @@ export const projectServiceFactory = ({
});
}
const { hasRole } = await permissionService.getProjectPermission(
const { hasRole } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
if (!hasRole(ProjectMembershipRole.Admin))
throw new ForbiddenRequestError({
@ -601,13 +646,14 @@ export const projectServiceFactory = ({
});
}
const { hasRole } = await permissionService.getProjectPermission(
const { hasRole } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
if (!hasRole(ProjectMembershipRole.Admin)) {
throw new ForbiddenRequestError({
@ -633,13 +679,14 @@ export const projectServiceFactory = ({
actorAuthMethod,
name
}: TUpdateProjectNameDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
const updatedProject = await projectDAL.updateById(projectId, { name });
@ -654,13 +701,14 @@ export const projectServiceFactory = ({
actorOrgId,
userPrivateKey
}: TUpgradeProjectDTO) => {
const { permission, hasRole } = await permissionService.getProjectPermission(
const { permission, hasRole } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
@ -691,13 +739,14 @@ export const projectServiceFactory = ({
actorOrgId,
actorId
}: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
const project = await projectDAL.findProjectById(projectId);
@ -736,13 +785,14 @@ export const projectServiceFactory = ({
projectId = certManagerProjectFromSplit.id;
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@ -786,14 +836,14 @@ export const projectServiceFactory = ({
projectId = certManagerProjectFromSplit.id;
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
@ -841,14 +891,14 @@ export const projectServiceFactory = ({
projectId = certManagerProjectFromSplit.id;
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts);
@ -877,14 +927,14 @@ export const projectServiceFactory = ({
if (certManagerProjectFromSplit) {
projectId = certManagerProjectFromSplit.id;
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections);
@ -914,14 +964,14 @@ export const projectServiceFactory = ({
projectId = certManagerProjectFromSplit.id;
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@ -945,15 +995,15 @@ export const projectServiceFactory = ({
actor,
projectId
}: TListProjectSshCasDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SshCertificateAuthorities
@ -981,15 +1031,15 @@ export const projectServiceFactory = ({
actor,
projectId
}: TListProjectSshCertificatesDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
const cas = await sshCertificateAuthorityDAL.find({
@ -1020,15 +1070,15 @@ export const projectServiceFactory = ({
actor,
projectId
}: TListProjectSshCertificateTemplatesDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SshCertificateTemplates
@ -1055,13 +1105,14 @@ export const projectServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TUpdateProjectKmsDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms);
@ -1082,13 +1133,14 @@ export const projectServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms);
@ -1111,13 +1163,14 @@ export const projectServiceFactory = ({
actorOrgId,
backup
}: TLoadProjectKmsBackupDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms);
@ -1133,13 +1186,14 @@ export const projectServiceFactory = ({
};
const getProjectKmsKeys = async ({ projectId, actor, actorId, actorAuthMethod, actorOrgId }: TGetProjectKmsKey) => {
const { membership } = await permissionService.getProjectPermission(
const { membership } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
@ -1165,13 +1219,14 @@ export const projectServiceFactory = ({
});
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
@ -1213,13 +1268,14 @@ export const projectServiceFactory = ({
});
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);

View File

@ -0,0 +1,11 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TResourceMetadataDALFactory = ReturnType<typeof resourceMetadataDALFactory>;
export const resourceMetadataDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.ResourceMetadata);
return orm;
};

View File

@ -0,0 +1,10 @@
import z from "zod";
export const ResourceMetadataSchema = z
.object({
key: z.string().trim().min(1),
value: z.string().trim().default("")
})
.array();
export type ResourceMetadataDTO = z.infer<typeof ResourceMetadataSchema>;

View File

@ -1,4 +1,4 @@
import { ProjectMembershipRole } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
@ -31,7 +31,14 @@ export const secretBlindIndexServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TGetProjectBlindIndexStatusDTO) => {
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const secretCount = await secretBlindIndexDAL.countOfSecretsWithNullSecretBlindIndex(projectId);
return Number(secretCount);
@ -44,13 +51,14 @@ export const secretBlindIndexServiceFactory = ({
actorOrgId,
actor
}: TGetProjectSecretsDTO) => {
const { hasRole } = await permissionService.getProjectPermission(
const { hasRole } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!hasRole(ProjectMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Insufficient privileges, user must be admin" });
}
@ -67,13 +75,14 @@ export const secretBlindIndexServiceFactory = ({
actorOrgId,
secretsToUpdate
}: TUpdateProjectSecretNameDTO) => {
const { hasRole } = await permissionService.getProjectPermission(
const { hasRole } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!hasRole(ProjectMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Insufficient privileges, user must be admin" });
}

Some files were not shown because too many files have changed in this diff Show More