mirror of
https://github.com/bitwarden/directory-connector
synced 2025-12-05 23:53:21 +00:00
Compare commits
25 Commits
v2025.3.0
...
jmccannon/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
376d4b1c22 | ||
|
|
7450163ff5 | ||
|
|
1deb22a446 | ||
|
|
115a60316d | ||
|
|
e11225b2ce | ||
|
|
4909d306ba | ||
|
|
caa8c4d070 | ||
|
|
ed1d941282 | ||
|
|
f6f874360f | ||
|
|
18b110e70d | ||
|
|
83c42cec73 | ||
|
|
2d80fceb8c | ||
|
|
0489f0cbe9 | ||
|
|
c5d4cb9fb6 | ||
|
|
16d6647090 | ||
|
|
a08673917b | ||
|
|
27e1ab9bcf | ||
|
|
3573e201a6 | ||
|
|
23d285a9f6 | ||
|
|
527d2cb75d | ||
|
|
42efd689e3 | ||
|
|
2fe980dea6 | ||
|
|
9446eedec7 | ||
|
|
41ee0d82d5 | ||
|
|
40a85bb875 |
50
.github/workflows/build.yml
vendored
50
.github/workflows/build.yml
vendored
@@ -48,8 +48,8 @@ jobs:
|
|||||||
needs: setup
|
needs: setup
|
||||||
env:
|
env:
|
||||||
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
_PKG_FETCH_NODE_VERSION: 18.5.0
|
_PKG_FETCH_NODE_VERSION: 22.13.1
|
||||||
_PKG_FETCH_VERSION: 3.4
|
_PKG_FETCH_VERSION: 3.5
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: '**/package-lock.json'
|
cache-dependency-path: '**/package-lock.json'
|
||||||
node-version: '18'
|
node-version: '22'
|
||||||
|
|
||||||
- name: Update NPM
|
- name: Update NPM
|
||||||
run: |
|
run: |
|
||||||
@@ -143,8 +143,8 @@ jobs:
|
|||||||
needs: setup
|
needs: setup
|
||||||
env:
|
env:
|
||||||
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
_PKG_FETCH_NODE_VERSION: 18.5.0
|
_PKG_FETCH_NODE_VERSION: 22.13.1
|
||||||
_PKG_FETCH_VERSION: 3.4
|
_PKG_FETCH_VERSION: 3.5
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
@@ -154,7 +154,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: '**/package-lock.json'
|
cache-dependency-path: '**/package-lock.json'
|
||||||
node-version: '18'
|
node-version: '22'
|
||||||
|
|
||||||
- name: Update NPM
|
- name: Update NPM
|
||||||
run: |
|
run: |
|
||||||
@@ -230,8 +230,8 @@ jobs:
|
|||||||
needs: setup
|
needs: setup
|
||||||
env:
|
env:
|
||||||
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
_WIN_PKG_FETCH_VERSION: 18.5.0
|
_WIN_PKG_FETCH_VERSION: 22.13.1
|
||||||
_WIN_PKG_VERSION: 3.4
|
_WIN_PKG_VERSION: 3.5
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
@@ -246,7 +246,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: '**/package-lock.json'
|
cache-dependency-path: '**/package-lock.json'
|
||||||
node-version: '18'
|
node-version: '22'
|
||||||
|
|
||||||
- name: Update NPM
|
- name: Update NPM
|
||||||
run: |
|
run: |
|
||||||
@@ -386,7 +386,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: '**/package-lock.json'
|
cache-dependency-path: '**/package-lock.json'
|
||||||
node-version: '18'
|
node-version: '22'
|
||||||
|
|
||||||
- name: Update NPM
|
- name: Update NPM
|
||||||
run: |
|
run: |
|
||||||
@@ -404,15 +404,31 @@ jobs:
|
|||||||
- name: Install Node dependencies
|
- name: Install Node dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
|
- name: Login to Azure
|
||||||
|
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||||
|
with:
|
||||||
|
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||||
|
|
||||||
|
- name: Retrieve secrets
|
||||||
|
id: retrieve-secrets
|
||||||
|
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||||
|
with:
|
||||||
|
keyvault: "bitwarden-ci"
|
||||||
|
secrets: "code-signing-vault-url,
|
||||||
|
code-signing-client-id,
|
||||||
|
code-signing-tenant-id,
|
||||||
|
code-signing-client-secret,
|
||||||
|
code-signing-cert-name"
|
||||||
|
|
||||||
- name: Build & Sign
|
- name: Build & Sign
|
||||||
run: npm run dist:win
|
run: npm run dist:win
|
||||||
env:
|
env:
|
||||||
ELECTRON_BUILDER_SIGN: 1
|
ELECTRON_BUILDER_SIGN: 1
|
||||||
SIGNING_VAULT_URL: ${{ secrets.SIGNING_VAULT_URL }}
|
SIGNING_VAULT_URL: ${{ steps.retrieve-secrets.outputs.code-signing-vault-url }}
|
||||||
SIGNING_CLIENT_ID: ${{ secrets.SIGNING_CLIENT_ID }}
|
SIGNING_CLIENT_ID: ${{ steps.retrieve-secrets.outputs.code-signing-client-id }}
|
||||||
SIGNING_TENANT_ID: ${{ secrets.SIGNING_TENANT_ID }}
|
SIGNING_TENANT_ID: ${{ steps.retrieve-secrets.outputs.code-signing-tenant-id }}
|
||||||
SIGNING_CLIENT_SECRET: ${{ secrets.SIGNING_CLIENT_SECRET }}
|
SIGNING_CLIENT_SECRET: ${{ steps.retrieve-secrets.outputs.code-signing-client-secret }}
|
||||||
SIGNING_CERT_NAME: ${{ secrets.SIGNING_CERT_NAME }}
|
SIGNING_CERT_NAME: ${{ steps.retrieve-secrets.outputs.code-signing-cert-name }}
|
||||||
|
|
||||||
- name: Upload Portable Executable to GitHub
|
- name: Upload Portable Executable to GitHub
|
||||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
@@ -460,7 +476,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: '**/package-lock.json'
|
cache-dependency-path: '**/package-lock.json'
|
||||||
node-version: '18'
|
node-version: '22'
|
||||||
|
|
||||||
- name: Update NPM
|
- name: Update NPM
|
||||||
run: |
|
run: |
|
||||||
@@ -514,7 +530,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
cache-dependency-path: '**/package-lock.json'
|
cache-dependency-path: '**/package-lock.json'
|
||||||
node-version: '18'
|
node-version: '22'
|
||||||
|
|
||||||
- name: Update NPM
|
- name: Update NPM
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
2
.github/workflows/integration-test.yml
vendored
2
.github/workflows/integration-test.yml
vendored
@@ -74,5 +74,3 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload results to codecov.io
|
- name: Upload results to codecov.io
|
||||||
uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 # v1.0.2
|
uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 # v1.0.2
|
||||||
env:
|
|
||||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
|
||||||
|
|||||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -64,5 +64,3 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload results to codecov.io
|
- name: Upload results to codecov.io
|
||||||
uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 # v1.0.2
|
uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 # v1.0.2
|
||||||
env:
|
|
||||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
|
||||||
|
|||||||
@@ -8,16 +8,12 @@ export class OrganizationImportRequest {
|
|||||||
overwriteExisting = false;
|
overwriteExisting = false;
|
||||||
largeImport = false;
|
largeImport = false;
|
||||||
|
|
||||||
constructor(
|
constructor(model: {
|
||||||
model:
|
|
||||||
| {
|
|
||||||
groups: Required<OrganizationImportGroupRequest>[];
|
groups: Required<OrganizationImportGroupRequest>[];
|
||||||
users: Required<OrganizationImportMemberRequest>[];
|
users: Required<OrganizationImportMemberRequest>[];
|
||||||
overwriteExisting: boolean;
|
overwriteExisting: boolean;
|
||||||
largeImport: boolean;
|
largeImport: boolean;
|
||||||
}
|
}) {
|
||||||
| ImportDirectoryRequest,
|
|
||||||
) {
|
|
||||||
if (model instanceof ImportDirectoryRequest) {
|
if (model instanceof ImportDirectoryRequest) {
|
||||||
this.groups = model.groups.map((g) => new OrganizationImportGroupRequest(g));
|
this.groups = model.groups.map((g) => new OrganizationImportGroupRequest(g));
|
||||||
this.members = model.users.map((u) => new OrganizationImportMemberRequest(u));
|
this.members = model.users.map((u) => new OrganizationImportMemberRequest(u));
|
||||||
|
|||||||
1584
package-lock.json
generated
1584
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
33
package.json
33
package.json
@@ -2,7 +2,7 @@
|
|||||||
"name": "@bitwarden/directory-connector",
|
"name": "@bitwarden/directory-connector",
|
||||||
"productName": "Bitwarden Directory Connector",
|
"productName": "Bitwarden Directory Connector",
|
||||||
"description": "Sync your user directory to your Bitwarden organization.",
|
"description": "Sync your user directory to your Bitwarden organization.",
|
||||||
"version": "2025.3.0",
|
"version": "2025.5.0",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"bitwarden",
|
"bitwarden",
|
||||||
"password",
|
"password",
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
"test:types": "npx tsc --noEmit"
|
"test:types": "npx tsc --noEmit"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "17.3.11",
|
"@angular-devkit/build-angular": "17.3.17",
|
||||||
"@angular-eslint/eslint-plugin-template": "17.5.3",
|
"@angular-eslint/eslint-plugin-template": "17.5.3",
|
||||||
"@angular-eslint/template-parser": "17.5.3",
|
"@angular-eslint/template-parser": "17.5.3",
|
||||||
"@angular/compiler-cli": "17.3.12",
|
"@angular/compiler-cli": "17.3.12",
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
"@electron/rebuild": "3.7.1",
|
"@electron/rebuild": "3.7.1",
|
||||||
"@fluffy-spoon/substitute": "1.208.0",
|
"@fluffy-spoon/substitute": "1.208.0",
|
||||||
"@microsoft/microsoft-graph-types": "2.40.0",
|
"@microsoft/microsoft-graph-types": "2.40.0",
|
||||||
"@ngtools/webpack": "17.3.11",
|
"@ngtools/webpack": "17.3.17",
|
||||||
"@types/inquirer": "8.2.10",
|
"@types/inquirer": "8.2.10",
|
||||||
"@types/jest": "29.5.14",
|
"@types/jest": "29.5.14",
|
||||||
"@types/lowdb": "1.0.15",
|
"@types/lowdb": "1.0.15",
|
||||||
@@ -90,22 +90,22 @@
|
|||||||
"@types/node-forge": "1.3.11",
|
"@types/node-forge": "1.3.11",
|
||||||
"@types/proper-lockfile": "4.1.4",
|
"@types/proper-lockfile": "4.1.4",
|
||||||
"@types/tldjs": "2.3.4",
|
"@types/tldjs": "2.3.4",
|
||||||
"@typescript-eslint/eslint-plugin": "8.23.0",
|
"@typescript-eslint/eslint-plugin": "8.32.1",
|
||||||
"@typescript-eslint/parser": "8.23.0",
|
"@typescript-eslint/parser": "8.32.1",
|
||||||
"clean-webpack-plugin": "4.0.0",
|
"clean-webpack-plugin": "4.0.0",
|
||||||
"concurrently": "9.1.2",
|
"concurrently": "9.1.2",
|
||||||
"copy-webpack-plugin": "12.0.2",
|
"copy-webpack-plugin": "12.0.2",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"css-loader": "7.1.2",
|
"css-loader": "7.1.2",
|
||||||
"dotenv": "16.4.7",
|
"dotenv": "16.5.0",
|
||||||
"electron": "34.1.1",
|
"electron": "34.1.1",
|
||||||
"electron-builder": "24.13.3",
|
"electron-builder": "24.13.3",
|
||||||
"electron-log": "5.2.4",
|
"electron-log": "5.2.4",
|
||||||
"electron-reload": "2.0.0-alpha.1",
|
"electron-reload": "2.0.0-alpha.1",
|
||||||
"electron-store": "8.2.0",
|
"electron-store": "8.2.0",
|
||||||
"electron-updater": "6.3.9",
|
"electron-updater": "6.6.2",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
"eslint-config-prettier": "10.0.1",
|
"eslint-config-prettier": "10.1.5",
|
||||||
"eslint-import-resolver-typescript": "3.7.0",
|
"eslint-import-resolver-typescript": "3.7.0",
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"eslint-plugin-rxjs": "5.0.3",
|
"eslint-plugin-rxjs": "5.0.3",
|
||||||
@@ -117,23 +117,22 @@
|
|||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-junit": "16.0.0",
|
"jest-junit": "16.0.0",
|
||||||
"jest-mock-extended": "3.0.7",
|
"jest-mock-extended": "3.0.7",
|
||||||
"jest-preset-angular": "14.5.0",
|
"jest-preset-angular": "14.5.5",
|
||||||
"lint-staged": "15.4.1",
|
"lint-staged": "15.5.2",
|
||||||
"mini-css-extract-plugin": "2.9.2",
|
"mini-css-extract-plugin": "2.9.2",
|
||||||
"minimatch": "3.1.2",
|
|
||||||
"node-abi": "3.74.0",
|
"node-abi": "3.74.0",
|
||||||
"node-forge": "1.3.1",
|
"node-forge": "1.3.1",
|
||||||
"node-loader": "2.1.0",
|
"node-loader": "2.1.0",
|
||||||
"pkg": "5.8.1",
|
"pkg": "5.8.1",
|
||||||
"prettier": "3.4.2",
|
"prettier": "3.5.3",
|
||||||
"rimraf": "6.0.1",
|
"rimraf": "6.0.1",
|
||||||
"rxjs": "7.8.1",
|
"rxjs": "7.8.2",
|
||||||
"sass": "1.79.4",
|
"sass": "1.79.4",
|
||||||
"sass-loader": "16.0.4",
|
"sass-loader": "16.0.4",
|
||||||
"ts-jest": "29.2.5",
|
"ts-jest": "29.2.5",
|
||||||
"ts-loader": "9.5.2",
|
"ts-loader": "9.5.2",
|
||||||
"tsconfig-paths-webpack-plugin": "4.2.0",
|
"tsconfig-paths-webpack-plugin": "4.2.0",
|
||||||
"type-fest": "4.32.0",
|
"type-fest": "4.41.0",
|
||||||
"typescript": "5.4.5",
|
"typescript": "5.4.5",
|
||||||
"webpack": "5.97.1",
|
"webpack": "5.97.1",
|
||||||
"webpack-cli": "6.0.1",
|
"webpack-cli": "6.0.1",
|
||||||
@@ -157,19 +156,19 @@
|
|||||||
"browser-hrtime": "1.1.8",
|
"browser-hrtime": "1.1.8",
|
||||||
"chalk": "4.1.2",
|
"chalk": "4.1.2",
|
||||||
"commander": "13.1.0",
|
"commander": "13.1.0",
|
||||||
"core-js": "3.40.0",
|
"core-js": "3.42.0",
|
||||||
"form-data": "4.0.1",
|
"form-data": "4.0.1",
|
||||||
"google-auth-library": "9.15.1",
|
"google-auth-library": "9.15.1",
|
||||||
"googleapis": "144.0.0",
|
"googleapis": "144.0.0",
|
||||||
"https-proxy-agent": "7.0.6",
|
"https-proxy-agent": "7.0.6",
|
||||||
"inquirer": "8.2.6",
|
"inquirer": "8.2.6",
|
||||||
"keytar": "7.9.0",
|
"keytar": "7.9.0",
|
||||||
"ldapts": "7.3.1",
|
"ldapts": "7.4.0",
|
||||||
"lowdb": "1.0.0",
|
"lowdb": "1.0.0",
|
||||||
"ngx-toastr": "19.0.0",
|
"ngx-toastr": "19.0.0",
|
||||||
"node-fetch": "2.7.0",
|
"node-fetch": "2.7.0",
|
||||||
"proper-lockfile": "4.1.2",
|
"proper-lockfile": "4.1.2",
|
||||||
"rxjs": "7.8.1",
|
"rxjs": "7.8.2",
|
||||||
"tldjs": "2.3.1",
|
"tldjs": "2.3.1",
|
||||||
"zone.js": "0.14.10"
|
"zone.js": "0.14.10"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,11 +3,15 @@ import { OrganizationImportRequest } from "@/jslib/common/src/models/request/org
|
|||||||
import { GroupEntry } from "@/src/models/groupEntry";
|
import { GroupEntry } from "@/src/models/groupEntry";
|
||||||
import { UserEntry } from "@/src/models/userEntry";
|
import { UserEntry } from "@/src/models/userEntry";
|
||||||
|
|
||||||
|
export interface RequestBuilderOptions {
|
||||||
|
removeDisabled: boolean;
|
||||||
|
overwriteExisting: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class RequestBuilder {
|
export abstract class RequestBuilder {
|
||||||
buildRequest: (
|
buildRequest: (
|
||||||
groups: GroupEntry[],
|
groups: GroupEntry[],
|
||||||
users: UserEntry[],
|
users: UserEntry[],
|
||||||
removeDisabled: boolean,
|
options: RequestBuilderOptions,
|
||||||
overwriteExisting: boolean,
|
|
||||||
) => OrganizationImportRequest[];
|
) => OrganizationImportRequest[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,18 +22,15 @@
|
|||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
[disabled]="startForm.loading"
|
[disabled]="startForm.loading"
|
||||||
>
|
>
|
||||||
<i class="bwi bwi-play bwi-fw" [hidden]="startForm.loading"></i>
|
|
||||||
<i class="bwi bwi-spinner bwi-fw bwi-spin" [hidden]="!startForm.loading"></i>
|
<i class="bwi bwi-spinner bwi-fw bwi-spin" [hidden]="!startForm.loading"></i>
|
||||||
{{ "startSync" | i18n }}
|
{{ "startSync" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<button type="button" (click)="stop()" class="btn btn-primary">
|
<button type="button" (click)="stop()" class="btn btn-danger text-white">
|
||||||
<i class="bwi bwi-stop bwi-fw"></i>
|
|
||||||
{{ "stopSync" | i18n }}
|
{{ "stopSync" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
<form #syncForm [appApiAction]="syncPromise" class="d-inline">
|
<form #syncForm [appApiAction]="syncPromise" class="d-inline">
|
||||||
<button type="button" (click)="sync()" class="btn btn-primary" [disabled]="syncForm.loading">
|
<button type="button" (click)="sync()" class="btn btn-primary" [disabled]="syncForm.loading">
|
||||||
<i class="bwi bwi-refresh bwi-fw" [ngClass]="{ 'bwi-spin': syncForm.loading }"></i>
|
|
||||||
{{ "syncNow" | i18n }}
|
{{ "syncNow" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -51,7 +48,6 @@
|
|||||||
[disabled]="simForm.loading"
|
[disabled]="simForm.loading"
|
||||||
>
|
>
|
||||||
<i class="bwi bwi-spinner bwi-fw bwi-spin" [hidden]="!simForm.loading"></i>
|
<i class="bwi bwi-spinner bwi-fw bwi-spin" [hidden]="!simForm.loading"></i>
|
||||||
<i class="bwi bwi-bug bwi-fw" [hidden]="simForm.loading"></i>
|
|
||||||
{{ "testNow" | i18n }}
|
{{ "testNow" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -614,7 +614,7 @@
|
|||||||
{{ "ex" | i18n }} exclude:joe@company.com | profile.firstName eq "John"
|
{{ "ex" | i18n }} exclude:joe@company.com | profile.firstName eq "John"
|
||||||
</div>
|
</div>
|
||||||
<div class="form-text" *ngIf="directory === directoryType.GSuite">
|
<div class="form-text" *ngIf="directory === directoryType.GSuite">
|
||||||
{{ "ex" | i18n }} exclude:joe@company.com | orgName=Engineering
|
{{ "ex" | i18n }} exclude:joe@company.com | orgUnitPath=/Engineering
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3" [hidden]="directory != directoryType.Ldap">
|
<div class="mb-3" [hidden]="directory != directoryType.Ldap">
|
||||||
|
|||||||
@@ -2,19 +2,16 @@
|
|||||||
<ul class="nav nav-tabs mb-3">
|
<ul class="nav nav-tabs mb-3">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" routerLink="dashboard" routerLinkActive="active">
|
<a class="nav-link" routerLink="dashboard" routerLinkActive="active">
|
||||||
<i class="bwi bwi-dashboard"></i>
|
|
||||||
{{ "dashboard" | i18n }}
|
{{ "dashboard" | i18n }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" routerLink="settings" routerLinkActive="active">
|
<a class="nav-link" routerLink="settings" routerLinkActive="active">
|
||||||
<i class="bwi bwi-cogs"></i>
|
|
||||||
{{ "settings" | i18n }}
|
{{ "settings" | i18n }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" routerLink="more" routerLinkActive="active">
|
<a class="nav-link" routerLink="more" routerLinkActive="active">
|
||||||
<i class="bwi bwi-sliders"></i>
|
|
||||||
{{ "more" | i18n }}
|
{{ "more" | i18n }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
5
src/scss/bootstrap.scss
vendored
5
src/scss/bootstrap.scss
vendored
@@ -8,8 +8,9 @@ $theme-colors: (
|
|||||||
"secondary": #ced4da,
|
"secondary": #ced4da,
|
||||||
"secondary-alt": #1a3b66,
|
"secondary-alt": #1a3b66,
|
||||||
);
|
);
|
||||||
$font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif,
|
$font-family-sans-serif:
|
||||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji",
|
||||||
|
"Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
|
||||||
$h1-font-size: 2rem;
|
$h1-font-size: 2rem;
|
||||||
$h2-font-size: 1.3rem;
|
$h2-font-size: 1.3rem;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { OrganizationImportRequest } from "@/jslib/common/src/models/request/org
|
|||||||
import { GroupEntry } from "@/src/models/groupEntry";
|
import { GroupEntry } from "@/src/models/groupEntry";
|
||||||
import { UserEntry } from "@/src/models/userEntry";
|
import { UserEntry } from "@/src/models/userEntry";
|
||||||
|
|
||||||
import { RequestBuilder } from "../abstractions/request-builder.service";
|
import { RequestBuilder, RequestBuilderOptions } from "../abstractions/request-builder.service";
|
||||||
|
|
||||||
import { batchSize } from "./sync.service";
|
import { batchSize } from "./sync.service";
|
||||||
|
|
||||||
@@ -16,17 +16,22 @@ export class BatchRequestBuilder implements RequestBuilder {
|
|||||||
buildRequest(
|
buildRequest(
|
||||||
groups: GroupEntry[],
|
groups: GroupEntry[],
|
||||||
users: UserEntry[],
|
users: UserEntry[],
|
||||||
removeDisabled: boolean,
|
options: RequestBuilderOptions,
|
||||||
overwriteExisting: boolean,
|
|
||||||
): OrganizationImportRequest[] {
|
): OrganizationImportRequest[] {
|
||||||
|
if (options.overwriteExisting) {
|
||||||
|
throw new Error(
|
||||||
|
"You cannot use the 'Remove and re-add organization users during the next sync' option with large imports.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const requests: OrganizationImportRequest[] = [];
|
const requests: OrganizationImportRequest[] = [];
|
||||||
|
|
||||||
if (users.length > 0) {
|
if (users?.length > 0) {
|
||||||
const usersRequest = users.map((u) => {
|
const usersRequest = users.map((u) => {
|
||||||
return {
|
return {
|
||||||
email: u.email,
|
email: u.email,
|
||||||
externalId: u.externalId,
|
externalId: u.externalId,
|
||||||
deleted: u.deleted || (removeDisabled && u.disabled),
|
deleted: u.deleted || (options.removeDisabled && u.disabled),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -37,13 +42,13 @@ export class BatchRequestBuilder implements RequestBuilder {
|
|||||||
groups: [],
|
groups: [],
|
||||||
users: u,
|
users: u,
|
||||||
largeImport: true,
|
largeImport: true,
|
||||||
overwriteExisting,
|
overwriteExisting: false,
|
||||||
});
|
});
|
||||||
requests.push(req);
|
requests.push(req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groups.length > 0) {
|
if (groups?.length > 0) {
|
||||||
const groupRequest = groups.map((g) => {
|
const groupRequest = groups.map((g) => {
|
||||||
return {
|
return {
|
||||||
name: g.name,
|
name: g.name,
|
||||||
@@ -59,7 +64,7 @@ export class BatchRequestBuilder implements RequestBuilder {
|
|||||||
groups: g,
|
groups: g,
|
||||||
users: [],
|
users: [],
|
||||||
largeImport: true,
|
largeImport: true,
|
||||||
overwriteExisting,
|
overwriteExisting: false,
|
||||||
});
|
});
|
||||||
requests.push(req);
|
requests.push(req);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,74 @@
|
|||||||
import { GroupEntry } from "@/src/models/groupEntry";
|
import { GetUniqueString } from "@/jslib/common/spec/utils";
|
||||||
|
|
||||||
import { UserEntry } from "@/src/models/userEntry";
|
import { UserEntry } from "@/src/models/userEntry";
|
||||||
|
|
||||||
|
import { RequestBuilderOptions } from "../abstractions/request-builder.service";
|
||||||
|
import { groupSimulator, userSimulator } from "../utils/request-builder-helper";
|
||||||
|
|
||||||
import { BatchRequestBuilder } from "./batch-request-builder";
|
import { BatchRequestBuilder } from "./batch-request-builder";
|
||||||
import { SingleRequestBuilder } from "./single-request-builder";
|
|
||||||
|
|
||||||
describe("BatchRequestBuilder", () => {
|
describe("BatchRequestBuilder", () => {
|
||||||
let batchRequestBuilder: BatchRequestBuilder;
|
let batchRequestBuilder: BatchRequestBuilder;
|
||||||
let singleRequestBuilder: SingleRequestBuilder;
|
|
||||||
|
|
||||||
function userSimulator(userCount: number) {
|
|
||||||
return Array(userCount).fill(new UserEntry());
|
|
||||||
}
|
|
||||||
|
|
||||||
function groupSimulator(groupCount: number) {
|
|
||||||
return Array(groupCount).fill(new GroupEntry());
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
batchRequestBuilder = new BatchRequestBuilder();
|
batchRequestBuilder = new BatchRequestBuilder();
|
||||||
singleRequestBuilder = new SingleRequestBuilder();
|
});
|
||||||
|
|
||||||
|
const defaultOptions: RequestBuilderOptions = Object.freeze({
|
||||||
|
overwriteExisting: false,
|
||||||
|
removeDisabled: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
it("BatchRequestBuilder batches requests for > 2000 users", () => {
|
it("BatchRequestBuilder batches requests for > 2000 users", () => {
|
||||||
const mockGroups = groupSimulator(11000);
|
const mockGroups = groupSimulator(11000);
|
||||||
const mockUsers = userSimulator(11000);
|
const mockUsers = userSimulator(11000);
|
||||||
|
const requests = batchRequestBuilder.buildRequest(mockGroups, mockUsers, defaultOptions);
|
||||||
const requests = batchRequestBuilder.buildRequest(mockGroups, mockUsers, true, true);
|
|
||||||
|
|
||||||
expect(requests.length).toEqual(12);
|
expect(requests.length).toEqual(12);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("SingleRequestBuilder returns single request for 200 users", () => {
|
it("BatchRequestBuilder throws error when overwriteExisting is true", () => {
|
||||||
const mockGroups = groupSimulator(200);
|
const mockGroups = groupSimulator(11000);
|
||||||
const mockUsers = userSimulator(200);
|
const mockUsers = userSimulator(11000);
|
||||||
|
const options = { ...defaultOptions, overwriteExisting: true };
|
||||||
|
|
||||||
const requests = singleRequestBuilder.buildRequest(mockGroups, mockUsers, true, true);
|
const r = () => batchRequestBuilder.buildRequest(mockGroups, mockUsers, options);
|
||||||
|
|
||||||
expect(requests.length).toEqual(1);
|
expect(r).toThrow(
|
||||||
|
"You cannot use the 'Remove and re-add organization users during the next sync' option with large imports.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("BatchRequestBuilder returns requests with deleted users when removeDisabled is true", () => {
|
||||||
|
const mockGroups = groupSimulator(11000);
|
||||||
|
const mockUsers = userSimulator(11000);
|
||||||
|
|
||||||
|
const disabledUser1 = new UserEntry();
|
||||||
|
const disabledUserEmail1 = GetUniqueString() + "@email.com";
|
||||||
|
|
||||||
|
const disabledUser2 = new UserEntry();
|
||||||
|
const disabledUserEmail2 = GetUniqueString() + "@email.com";
|
||||||
|
|
||||||
|
disabledUser1.disabled = true;
|
||||||
|
disabledUser1.email = disabledUserEmail1;
|
||||||
|
disabledUser2.disabled = true;
|
||||||
|
disabledUser2.email = disabledUserEmail2;
|
||||||
|
|
||||||
|
mockUsers[0] = disabledUser1;
|
||||||
|
mockUsers.push(disabledUser2);
|
||||||
|
|
||||||
|
const options = { ...defaultOptions, removeDisabled: true };
|
||||||
|
const requests = batchRequestBuilder.buildRequest(mockGroups, mockUsers, options);
|
||||||
|
|
||||||
|
expect(requests[0].members).toContainEqual({ email: disabledUserEmail1, deleted: true });
|
||||||
|
expect(requests[1].members.find((m) => m.deleted)).toBeUndefined();
|
||||||
|
expect(requests[3].members.find((m) => m.deleted)).toBeUndefined();
|
||||||
|
expect(requests[4].members.find((m) => m.deleted)).toBeUndefined();
|
||||||
|
expect(requests[5].members).toContainEqual({ email: disabledUserEmail2, deleted: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("BatchRequestBuilder retuns an empty array when there are no users or groups", () => {
|
it("BatchRequestBuilder retuns an empty array when there are no users or groups", () => {
|
||||||
const requests = batchRequestBuilder.buildRequest([], [], true, true);
|
const requests = batchRequestBuilder.buildRequest([], [], defaultOptions);
|
||||||
|
|
||||||
expect(requests).toEqual([]);
|
expect(requests).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|||||||
79
src/services/single-request-builder.spec.ts
Normal file
79
src/services/single-request-builder.spec.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { GetUniqueString } from "@/jslib/common/spec/utils";
|
||||||
|
|
||||||
|
import { UserEntry } from "@/src/models/userEntry";
|
||||||
|
|
||||||
|
import { RequestBuilderOptions } from "../abstractions/request-builder.service";
|
||||||
|
import { groupSimulator, userSimulator } from "../utils/request-builder-helper";
|
||||||
|
|
||||||
|
import { SingleRequestBuilder } from "./single-request-builder";
|
||||||
|
|
||||||
|
describe("SingleRequestBuilder", () => {
|
||||||
|
let singleRequestBuilder: SingleRequestBuilder;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
singleRequestBuilder = new SingleRequestBuilder();
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultOptions: RequestBuilderOptions = Object.freeze({
|
||||||
|
overwriteExisting: false,
|
||||||
|
removeDisabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
it("SingleRequestBuilder returns single request for 200 users", () => {
|
||||||
|
const mockGroups = groupSimulator(200);
|
||||||
|
const mockUsers = userSimulator(200);
|
||||||
|
|
||||||
|
const requests = singleRequestBuilder.buildRequest(mockGroups, mockUsers, defaultOptions);
|
||||||
|
|
||||||
|
expect(requests.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("SingleRequestBuilder returns request with overwriteExisting enabled", () => {
|
||||||
|
const mockGroups = groupSimulator(200);
|
||||||
|
const mockUsers = userSimulator(200);
|
||||||
|
|
||||||
|
const options = { ...defaultOptions, overwriteExisting: true };
|
||||||
|
const request = singleRequestBuilder.buildRequest(mockGroups, mockUsers, options)[0];
|
||||||
|
|
||||||
|
expect(request.overwriteExisting).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("SingleRequestBuilder returns request with deleted user when removeDisabled is true", () => {
|
||||||
|
const mockGroups = groupSimulator(200);
|
||||||
|
const mockUsers = userSimulator(200);
|
||||||
|
|
||||||
|
const disabledUser = new UserEntry();
|
||||||
|
const disabledUserEmail = GetUniqueString() + "@example.com";
|
||||||
|
disabledUser.disabled = true;
|
||||||
|
disabledUser.email = disabledUserEmail;
|
||||||
|
mockUsers.push(disabledUser);
|
||||||
|
|
||||||
|
const options = { ...defaultOptions, removeDisabled: true };
|
||||||
|
const request = singleRequestBuilder.buildRequest(mockGroups, mockUsers, options)[0];
|
||||||
|
|
||||||
|
expect(request.members.length).toEqual(201);
|
||||||
|
expect(request.members.pop()).toEqual(
|
||||||
|
expect.objectContaining({ email: disabledUserEmail, deleted: true }),
|
||||||
|
);
|
||||||
|
expect(request.overwriteExisting).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("SingleRequestBuilder returns request with deleted user and overwriteExisting enabled when overwriteExisting and removeDisabled are true", () => {
|
||||||
|
const mockGroups = groupSimulator(200);
|
||||||
|
const mockUsers = userSimulator(200);
|
||||||
|
|
||||||
|
const disabledUser = new UserEntry();
|
||||||
|
const disabledUserEmail = GetUniqueString() + "@example.com";
|
||||||
|
disabledUser.disabled = true;
|
||||||
|
disabledUser.email = disabledUserEmail;
|
||||||
|
mockUsers.push(disabledUser);
|
||||||
|
|
||||||
|
const options = { overwriteExisting: true, removeDisabled: true };
|
||||||
|
const request = singleRequestBuilder.buildRequest(mockGroups, mockUsers, options)[0];
|
||||||
|
|
||||||
|
expect(request.members.pop()).toEqual(
|
||||||
|
expect.objectContaining({ email: disabledUserEmail, deleted: true }),
|
||||||
|
);
|
||||||
|
expect(request.overwriteExisting).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -3,7 +3,7 @@ import { OrganizationImportRequest } from "@/jslib/common/src/models/request/org
|
|||||||
import { GroupEntry } from "@/src/models/groupEntry";
|
import { GroupEntry } from "@/src/models/groupEntry";
|
||||||
import { UserEntry } from "@/src/models/userEntry";
|
import { UserEntry } from "@/src/models/userEntry";
|
||||||
|
|
||||||
import { RequestBuilder } from "../abstractions/request-builder.service";
|
import { RequestBuilder, RequestBuilderOptions } from "../abstractions/request-builder.service";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is responsible for building small (<2k users) syncs as a single
|
* This class is responsible for building small (<2k users) syncs as a single
|
||||||
@@ -15,8 +15,7 @@ export class SingleRequestBuilder implements RequestBuilder {
|
|||||||
buildRequest(
|
buildRequest(
|
||||||
groups: GroupEntry[],
|
groups: GroupEntry[],
|
||||||
users: UserEntry[],
|
users: UserEntry[],
|
||||||
removeDisabled: boolean,
|
options: RequestBuilderOptions,
|
||||||
overwriteExisting: boolean,
|
|
||||||
): OrganizationImportRequest[] {
|
): OrganizationImportRequest[] {
|
||||||
return [
|
return [
|
||||||
new OrganizationImportRequest({
|
new OrganizationImportRequest({
|
||||||
@@ -31,10 +30,10 @@ export class SingleRequestBuilder implements RequestBuilder {
|
|||||||
return {
|
return {
|
||||||
email: u.email,
|
email: u.email,
|
||||||
externalId: u.externalId,
|
externalId: u.externalId,
|
||||||
deleted: u.deleted || (removeDisabled && u.disabled),
|
deleted: u.deleted || (options.removeDisabled && u.disabled),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
overwriteExisting: overwriteExisting,
|
overwriteExisting: options.overwriteExisting,
|
||||||
largeImport: false,
|
largeImport: false,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|||||||
132
src/services/sync.service.integration.spec.ts
Normal file
132
src/services/sync.service.integration.spec.ts
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { ApiService } from "@/jslib/common/src/abstractions/api.service";
|
||||||
|
import { CryptoFunctionService } from "@/jslib/common/src/abstractions/cryptoFunction.service";
|
||||||
|
import { MessagingService } from "@/jslib/common/src/abstractions/messaging.service";
|
||||||
|
import { EnvironmentService } from "@/jslib/common/src/services/environment.service";
|
||||||
|
|
||||||
|
import { I18nService } from "../../jslib/common/src/abstractions/i18n.service";
|
||||||
|
import { LogService } from "../../jslib/common/src/abstractions/log.service";
|
||||||
|
import { groupFixtures } from "../../openldap/group-fixtures";
|
||||||
|
import { userFixtures } from "../../openldap/user-fixtures";
|
||||||
|
import { DirectoryFactoryService } from "../abstractions/directory-factory.service";
|
||||||
|
import { DirectoryType } from "../enums/directoryType";
|
||||||
|
import { getLdapConfiguration, getSyncConfiguration } from "../utils/test-fixtures";
|
||||||
|
|
||||||
|
import { BatchRequestBuilder } from "./batch-request-builder";
|
||||||
|
import { LdapDirectoryService } from "./ldap-directory.service";
|
||||||
|
import { SingleRequestBuilder } from "./single-request-builder";
|
||||||
|
import { StateService } from "./state.service";
|
||||||
|
import { SyncService } from "./sync.service";
|
||||||
|
import * as constants from "./sync.service";
|
||||||
|
|
||||||
|
describe("SyncService", () => {
|
||||||
|
let logService: MockProxy<LogService>;
|
||||||
|
let i18nService: MockProxy<I18nService>;
|
||||||
|
let stateService: MockProxy<StateService>;
|
||||||
|
let cryptoFunctionService: MockProxy<CryptoFunctionService>;
|
||||||
|
let apiService: MockProxy<ApiService>;
|
||||||
|
let messagingService: MockProxy<MessagingService>;
|
||||||
|
let environmentService: MockProxy<EnvironmentService>;
|
||||||
|
let directoryFactory: MockProxy<DirectoryFactoryService>;
|
||||||
|
|
||||||
|
let batchRequestBuilder: BatchRequestBuilder;
|
||||||
|
let singleRequestBuilder: SingleRequestBuilder;
|
||||||
|
let syncService: SyncService;
|
||||||
|
let directoryService: LdapDirectoryService;
|
||||||
|
|
||||||
|
const originalBatchSize = constants.batchSize;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
logService = mock();
|
||||||
|
i18nService = mock();
|
||||||
|
stateService = mock();
|
||||||
|
cryptoFunctionService = mock();
|
||||||
|
apiService = mock();
|
||||||
|
messagingService = mock();
|
||||||
|
environmentService = mock();
|
||||||
|
directoryFactory = mock();
|
||||||
|
|
||||||
|
stateService.getDirectoryType.mockResolvedValue(DirectoryType.Ldap);
|
||||||
|
stateService.getOrganizationId.mockResolvedValue("fakeId");
|
||||||
|
|
||||||
|
directoryService = new LdapDirectoryService(logService, i18nService, stateService);
|
||||||
|
directoryFactory.createService.mockReturnValue(directoryService);
|
||||||
|
|
||||||
|
batchRequestBuilder = new BatchRequestBuilder();
|
||||||
|
singleRequestBuilder = new SingleRequestBuilder();
|
||||||
|
|
||||||
|
syncService = new SyncService(
|
||||||
|
cryptoFunctionService,
|
||||||
|
apiService,
|
||||||
|
messagingService,
|
||||||
|
i18nService,
|
||||||
|
environmentService,
|
||||||
|
stateService,
|
||||||
|
batchRequestBuilder,
|
||||||
|
singleRequestBuilder,
|
||||||
|
directoryFactory,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("OpenLdap integration: ", () => {
|
||||||
|
it("with largeImport disabled matches directory fixture data", async () => {
|
||||||
|
stateService.getDirectory
|
||||||
|
.calledWith(DirectoryType.Ldap)
|
||||||
|
.mockResolvedValue(getLdapConfiguration());
|
||||||
|
stateService.getSync.mockResolvedValue(
|
||||||
|
getSyncConfiguration({
|
||||||
|
users: true,
|
||||||
|
groups: true,
|
||||||
|
largeImport: false,
|
||||||
|
overwriteExisting: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
cryptoFunctionService.hash.mockResolvedValue(new ArrayBuffer(1));
|
||||||
|
// This arranges the last hash to be differet from the ArrayBuffer after it is converted to b64
|
||||||
|
stateService.getLastSyncHash.mockResolvedValue("unique hash");
|
||||||
|
|
||||||
|
const syncResult = await syncService.sync(false, false);
|
||||||
|
|
||||||
|
expect(syncResult).toEqual([groupFixtures, userFixtures]);
|
||||||
|
|
||||||
|
expect(apiService.postPublicImportDirectory).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ overwriteExisting: false }),
|
||||||
|
);
|
||||||
|
expect(apiService.postPublicImportDirectory).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("with largeImport enabled matches directory fixture data", async () => {
|
||||||
|
stateService.getDirectory
|
||||||
|
.calledWith(DirectoryType.Ldap)
|
||||||
|
.mockResolvedValue(getLdapConfiguration());
|
||||||
|
stateService.getSync.mockResolvedValue(
|
||||||
|
getSyncConfiguration({
|
||||||
|
users: true,
|
||||||
|
groups: true,
|
||||||
|
largeImport: true,
|
||||||
|
overwriteExisting: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
cryptoFunctionService.hash.mockResolvedValue(new ArrayBuffer(1));
|
||||||
|
// This arranges the last hash to be differet from the ArrayBuffer after it is converted to b64
|
||||||
|
stateService.getLastSyncHash.mockResolvedValue("unique hash");
|
||||||
|
|
||||||
|
// @ts-expect-error This is a workaround to make the batchsize smaller to trigger the batching logic since its a const.
|
||||||
|
constants.batchSize = 4;
|
||||||
|
|
||||||
|
const syncResult = await syncService.sync(false, false);
|
||||||
|
|
||||||
|
expect(syncResult).toEqual([groupFixtures, userFixtures]);
|
||||||
|
expect(apiService.postPublicImportDirectory).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ overwriteExisting: false }),
|
||||||
|
);
|
||||||
|
expect(apiService.postPublicImportDirectory).toHaveBeenCalledTimes(6);
|
||||||
|
|
||||||
|
// @ts-expect-error Reset batch size to original state.
|
||||||
|
constants.batchSize = originalBatchSize;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -34,6 +34,8 @@ describe("SyncService", () => {
|
|||||||
|
|
||||||
let syncService: SyncService;
|
let syncService: SyncService;
|
||||||
|
|
||||||
|
const originalBatchSize = constants.batchSize;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cryptoFunctionService = mock();
|
cryptoFunctionService = mock();
|
||||||
apiService = mock();
|
apiService = mock();
|
||||||
@@ -115,11 +117,12 @@ describe("SyncService", () => {
|
|||||||
expect(apiService.postPublicImportDirectory).toHaveBeenCalledWith(mockRequests[3]);
|
expect(apiService.postPublicImportDirectory).toHaveBeenCalledWith(mockRequests[3]);
|
||||||
expect(apiService.postPublicImportDirectory).toHaveBeenCalledWith(mockRequests[4]);
|
expect(apiService.postPublicImportDirectory).toHaveBeenCalledWith(mockRequests[4]);
|
||||||
expect(apiService.postPublicImportDirectory).toHaveBeenCalledWith(mockRequests[5]);
|
expect(apiService.postPublicImportDirectory).toHaveBeenCalledWith(mockRequests[5]);
|
||||||
|
|
||||||
|
// @ts-expect-error Reset batch size back to original value.
|
||||||
|
constants.batchSize = originalBatchSize;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not post for the same hash", async () => {
|
it("does not post for the same hash", async () => {
|
||||||
// @ts-expect-error this sets the batch size back to its expexted value for this test.
|
|
||||||
constants.batchSize = 2000;
|
|
||||||
stateService.getSync.mockResolvedValue(getSyncConfiguration({ groups: true, users: true }));
|
stateService.getSync.mockResolvedValue(getSyncConfiguration({ groups: true, users: true }));
|
||||||
cryptoFunctionService.hash.mockResolvedValue(new ArrayBuffer(1));
|
cryptoFunctionService.hash.mockResolvedValue(new ArrayBuffer(1));
|
||||||
// This arranges the last hash to be the same as the ArrayBuffer after it is converted to b64
|
// This arranges the last hash to be the same as the ArrayBuffer after it is converted to b64
|
||||||
|
|||||||
@@ -83,13 +83,7 @@ export class SyncService {
|
|||||||
return [groups, users];
|
return [groups, users];
|
||||||
}
|
}
|
||||||
|
|
||||||
const reqs = this.buildRequest(
|
const reqs = this.buildRequest(groups, users, syncConfig);
|
||||||
groups,
|
|
||||||
users,
|
|
||||||
syncConfig.removeDisabled,
|
|
||||||
syncConfig.overwriteExisting,
|
|
||||||
syncConfig.largeImport,
|
|
||||||
);
|
|
||||||
|
|
||||||
const result: HashResult = await this.generateHash(reqs);
|
const result: HashResult = await this.generateHash(reqs);
|
||||||
|
|
||||||
@@ -219,24 +213,12 @@ export class SyncService {
|
|||||||
private buildRequest(
|
private buildRequest(
|
||||||
groups: GroupEntry[],
|
groups: GroupEntry[],
|
||||||
users: UserEntry[],
|
users: UserEntry[],
|
||||||
removeDisabled: boolean,
|
syncConfig: SyncConfiguration,
|
||||||
overwriteExisting: boolean,
|
|
||||||
largeImport = false,
|
|
||||||
): OrganizationImportRequest[] {
|
): OrganizationImportRequest[] {
|
||||||
if (largeImport && groups.length + users.length > batchSize) {
|
if (syncConfig.largeImport && (groups?.length ?? 0) + (users?.length ?? 0) > batchSize) {
|
||||||
return this.batchRequestBuilder.buildRequest(
|
return this.batchRequestBuilder.buildRequest(groups, users, syncConfig);
|
||||||
groups,
|
|
||||||
users,
|
|
||||||
overwriteExisting,
|
|
||||||
removeDisabled,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return this.singleRequestBuilder.buildRequest(
|
return this.singleRequestBuilder.buildRequest(groups, users, syncConfig);
|
||||||
groups,
|
|
||||||
users,
|
|
||||||
overwriteExisting,
|
|
||||||
removeDisabled,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
26
src/utils/request-builder-helper.ts
Normal file
26
src/utils/request-builder-helper.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { GetUniqueString } from "@/jslib/common/spec/utils";
|
||||||
|
|
||||||
|
import { GroupEntry } from "../models/groupEntry";
|
||||||
|
import { UserEntry } from "../models/userEntry";
|
||||||
|
|
||||||
|
export function userSimulator(userCount: number): UserEntry[] {
|
||||||
|
const users: UserEntry[] = [];
|
||||||
|
while (userCount > 0) {
|
||||||
|
const userEntry = new UserEntry();
|
||||||
|
userEntry.email = GetUniqueString() + "@example.com";
|
||||||
|
users.push(userEntry);
|
||||||
|
userCount--;
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function groupSimulator(groupCount: number): GroupEntry[] {
|
||||||
|
const groups: GroupEntry[] = [];
|
||||||
|
while (groupCount > 0) {
|
||||||
|
const groupEntry = new GroupEntry();
|
||||||
|
groupEntry.name = GetUniqueString();
|
||||||
|
groups.push(groupEntry);
|
||||||
|
groupCount--;
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user