mirror of
https://github.com/bitwarden/directory-connector
synced 2025-12-05 23:53:21 +00:00
Compare commits
81 Commits
update-lda
...
refactor/i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acc3473731 | ||
|
|
c80805ded6 | ||
|
|
c75d26b618 | ||
|
|
13a13dd18f | ||
|
|
954b23d91f | ||
|
|
b9d35c3dc7 | ||
|
|
536f48b3c7 | ||
|
|
8cd768c7c2 | ||
|
|
b233d2e87d | ||
|
|
1f6d8c1458 | ||
|
|
ae05183aa3 | ||
|
|
8374103a15 | ||
|
|
dd9e03843a | ||
|
|
e38ce53ed5 | ||
|
|
0c21bcf847 | ||
|
|
1c6b94e640 | ||
|
|
ef1c47ab19 | ||
|
|
64ff16e895 | ||
|
|
89860d6770 | ||
|
|
91ff43a17f | ||
|
|
0f19ebc928 | ||
|
|
b48a1d5856 | ||
|
|
7776009a31 | ||
|
|
ff816035ce | ||
|
|
fe384b14f0 | ||
|
|
adeb84f44e | ||
|
|
dc2e17c5db | ||
|
|
f3d8b39ac5 | ||
|
|
3be1f2eac6 | ||
|
|
1146c8f5bf | ||
|
|
910bfb945d | ||
|
|
4e886c1c15 | ||
|
|
a4b85f1e30 | ||
|
|
7c85c9fddd | ||
|
|
68c964acaa | ||
|
|
1be64836f4 | ||
|
|
f2389189a3 | ||
|
|
bb4be6022b | ||
|
|
f85a0c5ea5 | ||
|
|
5afae04b1d | ||
|
|
d1b182d20b | ||
|
|
9e3d1caee4 | ||
|
|
9a78956b23 | ||
|
|
822655b944 | ||
|
|
6dfbe505d9 | ||
|
|
0809c2c104 | ||
|
|
e30000bd00 | ||
|
|
90a7601960 | ||
|
|
8a800c6d33 | ||
|
|
d0021c9306 | ||
|
|
97673c84da | ||
|
|
771a182235 | ||
|
|
857d725a77 | ||
|
|
25b3e0f691 | ||
|
|
d2ba7631b5 | ||
|
|
a893c78c74 | ||
|
|
5ff041aa7b | ||
|
|
096196fcd5 | ||
|
|
225073aa33 | ||
|
|
f8b26d82d8 | ||
|
|
6b98a46b94 | ||
|
|
13572b94ee | ||
|
|
999b790557 | ||
|
|
7c93d59a42 | ||
|
|
9bec2aa2f0 | ||
|
|
240e1d5813 | ||
|
|
d82f4d90c1 | ||
|
|
abc68e8ef9 | ||
|
|
660ee538ce | ||
|
|
a96144d6dc | ||
|
|
e43d192007 | ||
|
|
74a018edb8 | ||
|
|
07d0049183 | ||
|
|
5f5358ea0f | ||
|
|
36cc6552bf | ||
|
|
05b5fd2eb4 | ||
|
|
95f1e86509 | ||
|
|
378dd06274 | ||
|
|
314adeb164 | ||
|
|
cc4f8c9f8d | ||
|
|
35b0e81beb |
@@ -7,10 +7,9 @@ root = true
|
|||||||
[*]
|
[*]
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
quote_type = single
|
|
||||||
|
|
||||||
# Set default charset
|
# Set default charset
|
||||||
[*.{js,ts,scss,html}]
|
[*.{js,ts,scss,html}]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 2
|
||||||
|
|||||||
9
.eslintignore
Normal file
9
.eslintignore
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
dist
|
||||||
|
build
|
||||||
|
build-cli
|
||||||
|
jslib
|
||||||
|
webpack.cli.js
|
||||||
|
webpack.main.js
|
||||||
|
webpack.renderer.js
|
||||||
|
|
||||||
|
**/node_modules
|
||||||
32
.eslintrc.json
Normal file
32
.eslintrc.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": ["./jslib/shared/eslintrc.json"],
|
||||||
|
"rules": {
|
||||||
|
"import/order": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"alphabetize": {
|
||||||
|
"order": "asc"
|
||||||
|
},
|
||||||
|
"newlines-between": "always",
|
||||||
|
"pathGroups": [
|
||||||
|
{
|
||||||
|
"pattern": "jslib-*/**",
|
||||||
|
"group": "external",
|
||||||
|
"position": "after"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "src/**/*",
|
||||||
|
"group": "parent",
|
||||||
|
"position": "before"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pathGroupsExcludedImportTypes": ["builtin"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
2
.git-blame-ignore-revs
Normal file
2
.git-blame-ignore-revs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Apply Prettier https://github.com/bitwarden/directory-connector/pull/194
|
||||||
|
096196fcd512944d1c3d9c007647a1319b032639
|
||||||
33
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
33
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
## Type of change
|
||||||
|
|
||||||
|
- [ ] Bug fix
|
||||||
|
- [ ] New feature development
|
||||||
|
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
|
||||||
|
- [ ] Build/deploy pipeline (DevOps)
|
||||||
|
- [ ] Other
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
|
||||||
|
|
||||||
|
## Code changes
|
||||||
|
|
||||||
|
<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes-->
|
||||||
|
<!--Also refer to any related changes or PRs in other repositories-->
|
||||||
|
|
||||||
|
- **file.ext:** Description of what was changed and why
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
<!--Required for any UI changes. Delete if not applicable-->
|
||||||
|
|
||||||
|
## Testing requirements
|
||||||
|
|
||||||
|
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
|
||||||
|
|
||||||
|
## Before you submit
|
||||||
|
|
||||||
|
- [ ] I have checked for **linting** errors (`npm run lint`) (required)
|
||||||
|
- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required)
|
||||||
|
- [ ] This change requires a **documentation update** (notify the documentation team)
|
||||||
|
- [ ] This change has particular **deployment requirements** (notify the DevOps team)
|
||||||
170
.github/workflows/build.yml
vendored
170
.github/workflows/build.yml
vendored
@@ -5,6 +5,9 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches-ignore:
|
branches-ignore:
|
||||||
- 'l10n_master'
|
- 'l10n_master'
|
||||||
|
paths-ignore:
|
||||||
|
- '.github/workflows/**'
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -13,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
- name: Set up CLOC
|
- name: Set up CLOC
|
||||||
run: |
|
run: |
|
||||||
@@ -31,7 +34,7 @@ jobs:
|
|||||||
package_version: ${{ steps.retrieve-version.outputs.package_version }}
|
package_version: ${{ steps.retrieve-version.outputs.package_version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
- name: Get Package Version
|
- name: Get Package Version
|
||||||
id: retrieve-version
|
id: retrieve-version
|
||||||
@@ -46,20 +49,21 @@ 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: 14.17.6
|
_PKG_FETCH_NODE_VERSION: 16.13.0
|
||||||
_PKG_FETCH_VERSION: 3.2
|
_PKG_FETCH_VERSION: 3.2
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||||
with:
|
with:
|
||||||
node-version: '14'
|
cache: 'npm'
|
||||||
|
cache-dependency-path: '**/package-lock.json'
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
- name: Update NPM
|
- name: Update NPM
|
||||||
run: |
|
run: |
|
||||||
npm install -g npm@7
|
|
||||||
npm install -g node-gyp
|
npm install -g node-gyp
|
||||||
node-gyp install $(node -v)
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
@@ -74,8 +78,7 @@ jobs:
|
|||||||
- name: Keytar
|
- name: Keytar
|
||||||
run: |
|
run: |
|
||||||
keytarVersion=$(cat src/package.json | jq -r '.dependencies.keytar')
|
keytarVersion=$(cat src/package.json | jq -r '.dependencies.keytar')
|
||||||
nodeModVersion=$(node -e "console.log(process.config.variables.node_module_version)")
|
keytarTar="keytar-v$keytarVersion-napi-v3-linux-x64.tar"
|
||||||
keytarTar="keytar-v$keytarVersion-node-v$nodeModVersion-linux-x64.tar"
|
|
||||||
|
|
||||||
keytarTarGz="$keytarTar.gz"
|
keytarTarGz="$keytarTar.gz"
|
||||||
keytarUrl="https://github.com/atom/node-keytar/releases/download/v$keytarVersion/$keytarTarGz"
|
keytarUrl="https://github.com/atom/node-keytar/releases/download/v$keytarVersion/$keytarTarGz"
|
||||||
@@ -91,16 +94,18 @@ jobs:
|
|||||||
run: npm run dist:cli:lin
|
run: npm run dist:cli:lin
|
||||||
|
|
||||||
- name: Zip
|
- name: Zip
|
||||||
run: |
|
run: zip -j ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip ./dist-cli/linux/bwdc ./keytar/linux/build/Release/keytar.node
|
||||||
zip -j ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip ./dist-cli/linux/bwdc ./keytar/linux/build/Release/keytar.node
|
|
||||||
|
|
||||||
- name: Create checksums
|
- name: Create checksums
|
||||||
run: |
|
run: sha256sum ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip | cut -d " " -f 1 > ./dist-cli/bwdc-linux-sha256-$_PACKAGE_VERSION.txt
|
||||||
sha256sum ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip | cut -d " " -f 1 > ./dist-cli/bwdc-linux-sha256-$_PACKAGE_VERSION.txt
|
|
||||||
|
|
||||||
- name: Version Test
|
- name: Version Test
|
||||||
run: |
|
run: |
|
||||||
sudo apt install libsecret-1-0
|
sudo apt install libsecret-1-0 dbus-x11 gnome-keyring
|
||||||
|
eval $(dbus-launch --sh-syntax)
|
||||||
|
|
||||||
|
eval $(echo -n "" | /usr/bin/gnome-keyring-daemon --login)
|
||||||
|
eval $(/usr/bin/gnome-keyring-daemon --components=secrets --start)
|
||||||
|
|
||||||
mkdir -p test/linux
|
mkdir -p test/linux
|
||||||
unzip ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip -d ./test/linux
|
unzip ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip -d ./test/linux
|
||||||
@@ -116,14 +121,14 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Upload Linux Zip to GitHub
|
- name: Upload Linux Zip to GitHub
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
with:
|
with:
|
||||||
name: bwdc-linux-${{ env._PACKAGE_VERSION }}.zip
|
name: bwdc-linux-${{ env._PACKAGE_VERSION }}.zip
|
||||||
path: ./dist-cli/bwdc-linux-${{ env._PACKAGE_VERSION }}.zip
|
path: ./dist-cli/bwdc-linux-${{ env._PACKAGE_VERSION }}.zip
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload Linux checksum to GitHub
|
- name: Upload Linux checksum to GitHub
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
with:
|
with:
|
||||||
name: bwdc-linux-sha256-${{ env._PACKAGE_VERSION }}.txt
|
name: bwdc-linux-sha256-${{ env._PACKAGE_VERSION }}.txt
|
||||||
path: ./dist-cli/bwdc-linux-sha256-${{ env._PACKAGE_VERSION }}.txt
|
path: ./dist-cli/bwdc-linux-sha256-${{ env._PACKAGE_VERSION }}.txt
|
||||||
@@ -136,20 +141,21 @@ 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: 14.17.6
|
_PKG_FETCH_NODE_VERSION: 16.13.0
|
||||||
_PKG_FETCH_VERSION: 3.2
|
_PKG_FETCH_VERSION: 3.2
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||||
with:
|
with:
|
||||||
node-version: '14'
|
cache: 'npm'
|
||||||
|
cache-dependency-path: '**/package-lock.json'
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
- name: Update NPM
|
- name: Update NPM
|
||||||
run: |
|
run: |
|
||||||
npm install -g npm@7
|
|
||||||
npm install -g node-gyp
|
npm install -g node-gyp
|
||||||
node-gyp install $(node -v)
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
@@ -164,8 +170,7 @@ jobs:
|
|||||||
- name: Keytar
|
- name: Keytar
|
||||||
run: |
|
run: |
|
||||||
keytarVersion=$(cat src/package.json | jq -r '.dependencies.keytar')
|
keytarVersion=$(cat src/package.json | jq -r '.dependencies.keytar')
|
||||||
nodeModVersion=$(node -e "console.log(process.config.variables.node_module_version)")
|
keytarTar="keytar-v$keytarVersion-napi-v3-darwin-x64.tar"
|
||||||
keytarTar="keytar-v$keytarVersion-node-v$nodeModVersion-darwin-x64.tar"
|
|
||||||
|
|
||||||
keytarTarGz="$keytarTar.gz"
|
keytarTarGz="$keytarTar.gz"
|
||||||
keytarUrl="https://github.com/atom/node-keytar/releases/download/v$keytarVersion/$keytarTarGz"
|
keytarUrl="https://github.com/atom/node-keytar/releases/download/v$keytarVersion/$keytarTarGz"
|
||||||
@@ -181,12 +186,10 @@ jobs:
|
|||||||
run: npm run dist:cli:mac
|
run: npm run dist:cli:mac
|
||||||
|
|
||||||
- name: Zip
|
- name: Zip
|
||||||
run: |
|
run: zip -j ./dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip ./dist-cli/macos/bwdc ./keytar/macos/build/Release/keytar.node
|
||||||
zip -j ./dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip ./dist-cli/macos/bwdc ./keytar/macos/build/Release/keytar.node
|
|
||||||
|
|
||||||
- name: Create checksums
|
- name: Create checksums
|
||||||
run: |
|
run: sha256sum ./dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip | cut -d " " -f 1 > ./dist-cli/bwdc-macos-sha256-$_PACKAGE_VERSION.txt
|
||||||
sha256sum ./dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip | cut -d " " -f 1 > ./dist-cli/bwdc-macos-sha256-$_PACKAGE_VERSION.txt
|
|
||||||
|
|
||||||
- name: Version Test
|
- name: Version Test
|
||||||
run: |
|
run: |
|
||||||
@@ -204,14 +207,14 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Upload Mac Zip to GitHub
|
- name: Upload Mac Zip to GitHub
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
with:
|
with:
|
||||||
name: bwdc-macos-${{ env._PACKAGE_VERSION }}.zip
|
name: bwdc-macos-${{ env._PACKAGE_VERSION }}.zip
|
||||||
path: ./dist-cli/bwdc-macos-${{ env._PACKAGE_VERSION }}.zip
|
path: ./dist-cli/bwdc-macos-${{ env._PACKAGE_VERSION }}.zip
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload Mac checksum to GitHub
|
- name: Upload Mac checksum to GitHub
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
with:
|
with:
|
||||||
name: bwdc-macos-sha256-${{ env._PACKAGE_VERSION }}.txt
|
name: bwdc-macos-sha256-${{ env._PACKAGE_VERSION }}.txt
|
||||||
path: ./dist-cli/bwdc-macos-sha256-${{ env._PACKAGE_VERSION }}.txt
|
path: ./dist-cli/bwdc-macos-sha256-${{ env._PACKAGE_VERSION }}.txt
|
||||||
@@ -224,11 +227,11 @@ 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: 14.17.6
|
_WIN_PKG_FETCH_VERSION: 16.13.0
|
||||||
_WIN_PKG_VERSION: 3.2
|
_WIN_PKG_VERSION: 3.2
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
- name: Setup Windows builder
|
- name: Setup Windows builder
|
||||||
run: |
|
run: |
|
||||||
@@ -236,13 +239,14 @@ jobs:
|
|||||||
choco install reshack --no-progress
|
choco install reshack --no-progress
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||||
with:
|
with:
|
||||||
node-version: '14'
|
cache: 'npm'
|
||||||
|
cache-dependency-path: '**/package-lock.json'
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
- name: Update NPM
|
- name: Update NPM
|
||||||
run: |
|
run: |
|
||||||
npm install -g npm@7
|
|
||||||
npm install -g node-gyp
|
npm install -g node-gyp
|
||||||
node-gyp install $(node -v)
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
@@ -261,8 +265,7 @@ jobs:
|
|||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
$keytarVersion = (Get-Content -Raw -Path ./src/package.json | ConvertFrom-Json).dependencies.keytar
|
$keytarVersion = (Get-Content -Raw -Path ./src/package.json | ConvertFrom-Json).dependencies.keytar
|
||||||
$nodeModVersion = node -e "console.log(process.config.variables.node_module_version)"
|
$keytarTar = "keytar-v${keytarVersion}-napi-v3-{0}-x64.tar"
|
||||||
$keytarTar = "keytar-v${keytarVersion}-node-v${nodeModVersion}-{0}-x64.tar"
|
|
||||||
$keytarTarGz = "${keytarTar}.gz"
|
$keytarTarGz = "${keytarTar}.gz"
|
||||||
$keytarUrl = "https://github.com/atom/node-keytar/releases/download/v${keytarVersion}/${keytarTarGz}"
|
$keytarUrl = "https://github.com/atom/node-keytar/releases/download/v${keytarVersion}/${keytarTarGz}"
|
||||||
|
|
||||||
@@ -330,8 +333,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Zip
|
- name: Zip
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: 7z a ./dist-cli/bwdc-windows-%_PACKAGE_VERSION%.zip ./dist-cli/windows/bwdc.exe ./keytar/windows/keytar.node
|
||||||
7z a ./dist-cli/bwdc-windows-%_PACKAGE_VERSION%.zip ./dist-cli/windows/bwdc.exe ./keytar/windows/keytar.node
|
|
||||||
|
|
||||||
- name: Version Test
|
- name: Version Test
|
||||||
run: |
|
run: |
|
||||||
@@ -349,14 +351,14 @@ jobs:
|
|||||||
-t sha256 | Out-File ./dist-cli/bwdc-windows-sha256-${env:_PACKAGE_VERSION}.txt
|
-t sha256 | Out-File ./dist-cli/bwdc-windows-sha256-${env:_PACKAGE_VERSION}.txt
|
||||||
|
|
||||||
- name: Upload Windows Zip to GitHub
|
- name: Upload Windows Zip to GitHub
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
with:
|
with:
|
||||||
name: bwdc-windows-${{ env._PACKAGE_VERSION }}.zip
|
name: bwdc-windows-${{ env._PACKAGE_VERSION }}.zip
|
||||||
path: ./dist-cli/bwdc-windows-${{ env._PACKAGE_VERSION }}.zip
|
path: ./dist-cli/bwdc-windows-${{ env._PACKAGE_VERSION }}.zip
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload Windows checksum to GitHub
|
- name: Upload Windows checksum to GitHub
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
with:
|
with:
|
||||||
name: bwdc-windows-sha256-${{ env._PACKAGE_VERSION }}.txt
|
name: bwdc-windows-sha256-${{ env._PACKAGE_VERSION }}.txt
|
||||||
path: ./dist-cli/bwdc-windows-sha256-${{ env._PACKAGE_VERSION }}.txt
|
path: ./dist-cli/bwdc-windows-sha256-${{ env._PACKAGE_VERSION }}.txt
|
||||||
@@ -370,19 +372,23 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
- name: Set up .NET
|
- name: Set up .NET
|
||||||
uses: actions/setup-dotnet@a71d1eb2c86af85faa8c772c03fb365e377e45ea
|
uses: actions/setup-dotnet@9211491ffb35dd6a6657ca4f45d43dfe6e97c829
|
||||||
with:
|
with:
|
||||||
dotnet-version: "3.1.x"
|
dotnet-version: "3.1.x"
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||||
with:
|
with:
|
||||||
node-version: '14'
|
cache: 'npm'
|
||||||
|
cache-dependency-path: '**/package-lock.json'
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
- name: Update NPM
|
- name: Update NPM
|
||||||
run: |
|
run: |
|
||||||
npm install -g npm@7
|
|
||||||
npm install -g node-gyp
|
npm install -g node-gyp
|
||||||
node-gyp install $(node -v)
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
@@ -399,14 +405,11 @@ jobs:
|
|||||||
- name: Install AST
|
- name: Install AST
|
||||||
uses: bitwarden/gh-actions/install-ast@f135c42c8596cb535c5bcb7523c0b2eef89709ac
|
uses: bitwarden/gh-actions/install-ast@f135c42c8596cb535c5bcb7523c0b2eef89709ac
|
||||||
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
|
||||||
|
|
||||||
- name: Install Node dependencies
|
- name: Install Node dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
- name: Run linter
|
# - name: Run linter
|
||||||
run: npm run lint
|
# run: npm run lint
|
||||||
|
|
||||||
- name: Build & Sign
|
- name: Build & Sign
|
||||||
run: npm run dist:win
|
run: npm run dist:win
|
||||||
@@ -418,25 +421,29 @@ jobs:
|
|||||||
SIGNING_CLIENT_SECRET: ${{ secrets.SIGNING_CLIENT_SECRET }}
|
SIGNING_CLIENT_SECRET: ${{ secrets.SIGNING_CLIENT_SECRET }}
|
||||||
SIGNING_CERT_NAME: ${{ secrets.SIGNING_CERT_NAME }}
|
SIGNING_CERT_NAME: ${{ secrets.SIGNING_CERT_NAME }}
|
||||||
|
|
||||||
- name: List Dist
|
|
||||||
run: dir ./dist
|
|
||||||
|
|
||||||
- name: Upload Portable Executable to GitHub
|
- name: Upload Portable Executable to GitHub
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
with:
|
with:
|
||||||
name: Bitwarden-Connector-Portable-${{ env._PACKAGE_VERSION }}.exe
|
name: Bitwarden-Connector-Portable-${{ env._PACKAGE_VERSION }}.exe
|
||||||
path: ./dist/Bitwarden-Connector-Portable-${{ env._PACKAGE_VERSION }}.exe
|
path: ./dist/Bitwarden-Connector-Portable-${{ env._PACKAGE_VERSION }}.exe
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload Installer Executable to GitHub
|
- name: Upload Installer Executable to GitHub
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
with:
|
with:
|
||||||
name: Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe
|
name: Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe
|
||||||
path: ./dist/Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe
|
path: ./dist/Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload Installer Executable Blockmap to GitHub
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe.blockmap
|
||||||
|
path: ./dist/Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe.blockmap
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload latest auto-update artifact
|
- name: Upload latest auto-update artifact
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
with:
|
with:
|
||||||
name: latest.yml
|
name: latest.yml
|
||||||
path: ./dist/latest.yml
|
path: ./dist/latest.yml
|
||||||
@@ -450,14 +457,18 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||||
with:
|
with:
|
||||||
node-version: '14'
|
cache: 'npm'
|
||||||
|
cache-dependency-path: '**/package-lock.json'
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
- name: Update NPM
|
- name: Update NPM
|
||||||
run: |
|
run: |
|
||||||
npm install -g npm@7
|
|
||||||
npm install -g node-gyp
|
npm install -g node-gyp
|
||||||
node-gyp install $(node -v)
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
@@ -470,9 +481,6 @@ jobs:
|
|||||||
sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev
|
sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev
|
||||||
sudo apt-get -y install rpm
|
sudo apt-get -y install rpm
|
||||||
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
|
||||||
|
|
||||||
- name: NPM Install
|
- name: NPM Install
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
@@ -483,14 +491,14 @@ jobs:
|
|||||||
run: npm run dist:lin
|
run: npm run dist:lin
|
||||||
|
|
||||||
- name: Upload AppImage
|
- name: Upload AppImage
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
with:
|
with:
|
||||||
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-x86_64.AppImage
|
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-x86_64.AppImage
|
||||||
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-x86_64.AppImage
|
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-x86_64.AppImage
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload latest auto-update artifact
|
- name: Upload latest auto-update artifact
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
with:
|
with:
|
||||||
name: latest-linux.yml
|
name: latest-linux.yml
|
||||||
path: ./dist/latest-linux.yml
|
path: ./dist/latest-linux.yml
|
||||||
@@ -504,14 +512,18 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
|
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||||
with:
|
with:
|
||||||
node-version: '14'
|
cache: 'npm'
|
||||||
|
cache-dependency-path: '**/package-lock.json'
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
- name: Update NPM
|
- name: Update NPM
|
||||||
run: |
|
run: |
|
||||||
npm install -g npm@7
|
|
||||||
npm install -g node-gyp
|
npm install -g node-gyp
|
||||||
node-gyp install $(node -v)
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
@@ -526,9 +538,6 @@ jobs:
|
|||||||
echo "GitHub event: $GITHUB_EVENT"
|
echo "GitHub event: $GITHUB_EVENT"
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
|
||||||
|
|
||||||
- name: Decrypt secrets
|
- name: Decrypt secrets
|
||||||
env:
|
env:
|
||||||
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
|
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
|
||||||
@@ -579,8 +588,8 @@ jobs:
|
|||||||
- name: Install Node dependencies
|
- name: Install Node dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
- name: Run linter
|
# - name: Run linter
|
||||||
run: npm run lint
|
# run: npm run lint
|
||||||
|
|
||||||
- name: Build application
|
- name: Build application
|
||||||
run: npm run dist:mac
|
run: npm run dist:mac
|
||||||
@@ -595,21 +604,28 @@ jobs:
|
|||||||
"Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip"
|
"Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip"
|
||||||
|
|
||||||
- name: Upload .zip artifact
|
- name: Upload .zip artifact
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
with:
|
with:
|
||||||
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip
|
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip
|
||||||
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip
|
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload .dmg artifact
|
- name: Upload .dmg artifact
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
with:
|
with:
|
||||||
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg
|
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg
|
||||||
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg
|
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload .dmg Blockmap artifact
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg.blockmap
|
||||||
|
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg.blockmap
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload latest auto-update artifact
|
- name: Upload latest auto-update artifact
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.3
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
with:
|
with:
|
||||||
name: latest-mac.yml
|
name: latest-mac.yml
|
||||||
path: ./dist/latest-mac.yml
|
path: ./dist/latest-mac.yml
|
||||||
@@ -660,21 +676,21 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Login to Azure - Prod Subscription
|
- name: Login to Azure - Prod Subscription
|
||||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||||
|
|
||||||
- name: Retrieve secrets
|
- name: Retrieve secrets
|
||||||
id: retrieve-secrets
|
id: retrieve-secrets
|
||||||
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
|
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
keyvault: "bitwarden-prod-kv"
|
keyvault: "bitwarden-prod-kv"
|
||||||
secrets: "devops-alerts-slack-webhook-url"
|
secrets: "devops-alerts-slack-webhook-url"
|
||||||
|
|
||||||
- name: Notify Slack on failure
|
- name: Notify Slack on failure
|
||||||
uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2
|
uses: act10ns/slack@da3191ebe2e67f49b46880b4633f5591a96d1d33
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
env:
|
||||||
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
|
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
|
||||||
|
|||||||
16
.github/workflows/enforce-labels.yml
vendored
Normal file
16
.github/workflows/enforce-labels.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: Enforce PR labels
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [labeled, unlabeled, opened, edited, synchronize]
|
||||||
|
jobs:
|
||||||
|
enforce-label:
|
||||||
|
name: EnforceLabel
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- name: Enforce Label
|
||||||
|
uses: yogevbd/enforce-label-action@8d1e1709b1011e6d90400a0e6cf7c0b77aa5efeb
|
||||||
|
with:
|
||||||
|
BANNED_LABELS: "hold"
|
||||||
|
BANNED_LABELS_DESCRIPTION: "PRs on hold cannot be merged"
|
||||||
33
.github/workflows/release.yml
vendored
33
.github/workflows/release.yml
vendored
@@ -3,6 +3,16 @@ name: Release
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release_type:
|
||||||
|
description: 'Release Options'
|
||||||
|
required: true
|
||||||
|
default: 'Initial Release'
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- Initial Release
|
||||||
|
- Redeploy
|
||||||
|
- Dry Run
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
setup:
|
setup:
|
||||||
@@ -10,18 +20,17 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Branch check
|
- name: Branch check
|
||||||
|
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||||
run: |
|
run: |
|
||||||
if [[ "$GITHUB_REF" != "refs/heads/release" ]]; then
|
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
|
||||||
echo "==================================="
|
echo "==================================="
|
||||||
echo "[!] Can only release from the 'release' branch"
|
echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches"
|
||||||
echo "==================================="
|
echo "==================================="
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
||||||
with:
|
|
||||||
ref: release
|
|
||||||
|
|
||||||
- name: Retrieve Directory Connector release version
|
- name: Retrieve Directory Connector release version
|
||||||
id: retrieve-version
|
id: retrieve-version
|
||||||
@@ -30,6 +39,7 @@ jobs:
|
|||||||
echo "::set-output name=package_version::$PKG_VERSION"
|
echo "::set-output name=package_version::$PKG_VERSION"
|
||||||
|
|
||||||
- name: Check to make sure Mobile release version has been bumped
|
- name: Check to make sure Mobile release version has been bumped
|
||||||
|
if: ${{ github.event.inputs.release_type == 'Initial Release' }}
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
@@ -44,15 +54,22 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
|
- name: Get branch name
|
||||||
|
id: branch
|
||||||
|
run: |
|
||||||
|
BRANCH_NAME=$(basename ${{ github.ref }})
|
||||||
|
echo "::set-output name=branch-name::$BRANCH_NAME"
|
||||||
|
|
||||||
- name: Download all artifacts
|
- name: Download all artifacts
|
||||||
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
|
uses: bitwarden/gh-actions/download-artifacts@23433be15ed6fd046ce12b6889c5184a8d9c8783
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
branch: release
|
branch: ${{ steps.branch.outputs.branch-name }}
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5
|
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||||
|
uses: ncipollo/release-action@40bb172bd05f266cf9ba4ff965cb61e9ee5f6d01 # v1.9.0
|
||||||
env:
|
env:
|
||||||
PKG_VERSION: ${{ steps.retrieve-version.outputs.package_version }}
|
PKG_VERSION: ${{ steps.retrieve-version.outputs.package_version }}
|
||||||
with:
|
with:
|
||||||
@@ -64,9 +81,11 @@ jobs:
|
|||||||
./bwdc-linux-sha256-${{ env.PKG_VERSION }}.txt,
|
./bwdc-linux-sha256-${{ env.PKG_VERSION }}.txt,
|
||||||
./Bitwarden-Connector-Portable-${{ env.PKG_VERSION }}.exe,
|
./Bitwarden-Connector-Portable-${{ env.PKG_VERSION }}.exe,
|
||||||
./Bitwarden-Connector-Installer-${{ env.PKG_VERSION }}.exe,
|
./Bitwarden-Connector-Installer-${{ env.PKG_VERSION }}.exe,
|
||||||
|
./Bitwarden-Connector-Installer-${{ env.PKG_VERSION }}.exe.blockmap,
|
||||||
./Bitwarden-Connector-${{ env.PKG_VERSION }}-x86_64.AppImage,
|
./Bitwarden-Connector-${{ env.PKG_VERSION }}-x86_64.AppImage,
|
||||||
./Bitwarden-Connector-${{ env.PKG_VERSION }}-mac.zip,
|
./Bitwarden-Connector-${{ env.PKG_VERSION }}-mac.zip,
|
||||||
./Bitwarden-Connector-${{ env.PKG_VERSION }}.dmg,
|
./Bitwarden-Connector-${{ env.PKG_VERSION }}.dmg,
|
||||||
|
./Bitwarden-Connector-${{ env.PKG_VERSION }}.dmg.blockmap,
|
||||||
./latest-linux.yml,
|
./latest-linux.yml,
|
||||||
./latest-mac.yml,
|
./latest-mac.yml,
|
||||||
./latest.yml"
|
./latest.yml"
|
||||||
|
|||||||
65
.github/workflows/version-bump.yml
vendored
Normal file
65
.github/workflows/version-bump.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
name: Version Bump
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version_number:
|
||||||
|
description: "New Version"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
bump_version:
|
||||||
|
name: "Create version_bump_${{ github.event.inputs.version_number }} branch"
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout Branch
|
||||||
|
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
||||||
|
|
||||||
|
- name: Create Version Branch
|
||||||
|
run: |
|
||||||
|
git switch -c version_bump_${{ github.event.inputs.version_number }}
|
||||||
|
git push -u origin version_bump_${{ github.event.inputs.version_number }}
|
||||||
|
|
||||||
|
- name: Checkout Version Branch
|
||||||
|
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
||||||
|
with:
|
||||||
|
ref: version_bump_${{ github.event.inputs.version_number }}
|
||||||
|
|
||||||
|
- name: Bump Version - Package
|
||||||
|
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
|
||||||
|
with:
|
||||||
|
version: ${{ github.event.inputs.version_number }}
|
||||||
|
file_path: "./src/package.json"
|
||||||
|
|
||||||
|
- name: Commit files
|
||||||
|
run: |
|
||||||
|
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config --local user.name "github-actions[bot]"
|
||||||
|
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
|
||||||
|
|
||||||
|
- name: Push changes
|
||||||
|
run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
|
||||||
|
|
||||||
|
- name: Create Version PR
|
||||||
|
env:
|
||||||
|
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
|
||||||
|
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
BASE_BRANCH: master
|
||||||
|
TITLE: "Bump version to ${{ github.event.inputs.version_number }}"
|
||||||
|
run: |
|
||||||
|
gh pr create --title "$TITLE" \
|
||||||
|
--base "$BASE" \
|
||||||
|
--head "$PR_BRANCH" \
|
||||||
|
--label "version update" \
|
||||||
|
--label "automated pr" \
|
||||||
|
--body "
|
||||||
|
## Type of change
|
||||||
|
- [ ] Bug fix
|
||||||
|
- [ ] New feature development
|
||||||
|
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
|
||||||
|
- [ ] Build/deploy pipeline (DevOps)
|
||||||
|
- [X] Other
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
Automated version bump to ${{ github.event.inputs.version_number }}"
|
||||||
11
.github/workflows/workflow-linter.yml
vendored
Normal file
11
.github/workflows/workflow-linter.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
name: Workflow Linter
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- .github/workflows/**
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
call-workflow:
|
||||||
|
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@master
|
||||||
1
.husky/.gitignore
vendored
Normal file
1
.husky/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
_
|
||||||
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx lint-staged
|
||||||
12
.prettierignore
Normal file
12
.prettierignore
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Build directories
|
||||||
|
build
|
||||||
|
build-cli
|
||||||
|
dist
|
||||||
|
|
||||||
|
jslib
|
||||||
|
|
||||||
|
# External libraries / auto synced locales
|
||||||
|
src/locales
|
||||||
|
|
||||||
|
# Github Workflows
|
||||||
|
.github/workflows
|
||||||
3
.prettierrc.json
Normal file
3
.prettierrc.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
||||||
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
@@ -7,10 +7,7 @@
|
|||||||
"name": "Electron: Main",
|
"name": "Electron: Main",
|
||||||
"protocol": "inspector",
|
"protocol": "inspector",
|
||||||
"cwd": "${workspaceRoot}/build",
|
"cwd": "${workspaceRoot}/build",
|
||||||
"runtimeArgs": [
|
"runtimeArgs": ["--remote-debugging-port=9223", "."],
|
||||||
"--remote-debugging-port=9223",
|
|
||||||
"."
|
|
||||||
],
|
|
||||||
"windows": {
|
"windows": {
|
||||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
|
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
|
||||||
},
|
},
|
||||||
@@ -31,18 +28,13 @@
|
|||||||
"protocol": "inspector",
|
"protocol": "inspector",
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
"program": "${workspaceFolder}/build-cli/bwdc.js",
|
"program": "${workspaceFolder}/build-cli/bwdc.js",
|
||||||
"args": [
|
"args": ["sync"]
|
||||||
"sync"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"compounds": [
|
"compounds": [
|
||||||
{
|
{
|
||||||
"name": "Electron: All",
|
"name": "Electron: All",
|
||||||
"configurations": [
|
"configurations": ["Electron: Main", "Electron: Renderer"]
|
||||||
"Electron: Main",
|
|
||||||
"Electron: Renderer"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
31
README.md
31
README.md
@@ -6,6 +6,7 @@
|
|||||||
The Bitwarden Directory Connector is a a desktop application used to sync your Bitwarden enterprise organization to an existing directory of users and groups.
|
The Bitwarden Directory Connector is a a desktop application used to sync your Bitwarden enterprise organization to an existing directory of users and groups.
|
||||||
|
|
||||||
Supported directories:
|
Supported directories:
|
||||||
|
|
||||||
- Active Directory
|
- Active Directory
|
||||||
- Any other LDAP-based directory
|
- Any other LDAP-based directory
|
||||||
- Azure Active Directory
|
- Azure Active Directory
|
||||||
@@ -14,7 +15,7 @@ Supported directories:
|
|||||||
|
|
||||||
The application is written using Electron with Angular and installs on Windows, macOS, and Linux distributions.
|
The application is written using Electron with Angular and installs on Windows, macOS, and Linux distributions.
|
||||||
|
|
||||||
[](https://help.bitwarden.com/article/directory-sync/#download-and-install)
|
[](https://bitwarden.com/help/directory-sync/#download-and-install)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -41,13 +42,13 @@ bwdc config --help
|
|||||||
|
|
||||||
**Detailed Documentation**
|
**Detailed Documentation**
|
||||||
|
|
||||||
We provide detailed documentation and examples for using the Directory Connector CLI in our help center at https://help.bitwarden.com/article/directory-sync/#command-line-interface.
|
We provide detailed documentation and examples for using the Directory Connector CLI in our help center at https://bitwarden.com/help/directory-sync-cli/.
|
||||||
|
|
||||||
## Build/Run
|
## Build/Run
|
||||||
|
|
||||||
**Requirements**
|
**Requirements**
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org) v14
|
- [Node.js](https://nodejs.org) v16.13.1 (LTS)
|
||||||
- Windows users: To compile the native node modules used in the app you will need the Visual C++ toolset, available through the standard Visual Studio installer (recommended) or by installing [`windows-build-tools`](https://github.com/felixrieseberg/windows-build-tools) through `npm`. See more at [Compiling native Addon modules](https://github.com/Microsoft/nodejs-guidelines/blob/master/windows-environment.md#compiling-native-addon-modules).
|
- Windows users: To compile the native node modules used in the app you will need the Visual C++ toolset, available through the standard Visual Studio installer (recommended) or by installing [`windows-build-tools`](https://github.com/felixrieseberg/windows-build-tools) through `npm`. See more at [Compiling native Addon modules](https://github.com/Microsoft/nodejs-guidelines/blob/master/windows-environment.md#compiling-native-addon-modules).
|
||||||
|
|
||||||
**Run the app**
|
**Run the app**
|
||||||
@@ -73,8 +74,32 @@ You can then run commands from the `./build-cli` folder:
|
|||||||
node ./build-cli/bwdc.js --help
|
node ./build-cli/bwdc.js --help
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## We're Hiring!
|
||||||
|
|
||||||
|
Interested in contributing in a big way? Consider joining our team! We're hiring for many positions. Please take a look at our [Careers page](https://bitwarden.com/careers/) to see what opportunities are currently open as well as what it's like to work at Bitwarden.
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|
||||||
Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file.
|
Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file.
|
||||||
|
|
||||||
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.
|
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.
|
||||||
|
|
||||||
|
### Prettier
|
||||||
|
|
||||||
|
We recently migrated to using Prettier as code formatter. All previous branches will need to updated to avoid large merge conflicts using the following steps:
|
||||||
|
|
||||||
|
1. Check out your local Branch
|
||||||
|
2. Run `git merge 225073aa335d33ad905877b68336a9288e89ea10`
|
||||||
|
3. Resolve any merge conflicts, commit.
|
||||||
|
4. Run `npm run prettier`
|
||||||
|
5. Commit
|
||||||
|
6. Run `git merge -Xours 096196fcd512944d1c3d9c007647a1319b032639`
|
||||||
|
7. Push
|
||||||
|
|
||||||
|
#### Git blame
|
||||||
|
|
||||||
|
We also recommend that you configure git to ignore the prettier revision using:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||||
|
```
|
||||||
|
|||||||
42
SECURITY.md
42
SECURITY.md
@@ -1,39 +1,11 @@
|
|||||||
Bitwarden believes that working with security researchers across the globe is crucial to keeping our
|
Bitwarden believes that working with security researchers across the globe is crucial to keeping our users safe. If you believe you've found a security issue in our product or service, we encourage you to please submit a report through our [HackerOne Program](https://hackerone.com/bitwarden/). We welcome working with you to resolve the issue promptly. Thanks in advance!
|
||||||
users safe. If you believe you've found a security issue in our product or service, we encourage you to
|
|
||||||
notify us. We welcome working with you to resolve the issue promptly. Thanks in advance!
|
|
||||||
|
|
||||||
# Disclosure Policy
|
# Disclosure Policy
|
||||||
|
|
||||||
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every
|
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every effort to quickly resolve the issue.
|
||||||
effort to quickly resolve the issue.
|
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a third-party. We may publicly disclose the issue before resolving it, if appropriate.
|
||||||
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a
|
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or degradation of our service. Only interact with accounts you own or with explicit permission of the account holder.
|
||||||
third-party. We may publicly disclose the issue before resolving it, if appropriate.
|
- If you would like to encrypt your report, please use the PGP key with long ID `0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
|
||||||
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or
|
|
||||||
degradation of our service. Only interact with accounts you own or with explicit permission of the
|
|
||||||
account holder.
|
|
||||||
- If you would like to encrypt your report, please use the PGP key with long ID
|
|
||||||
`0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
|
|
||||||
|
|
||||||
# In-scope
|
|
||||||
|
|
||||||
- Security issues in any current release of Bitwarden. This includes the web vault, browser extension,
|
|
||||||
and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source
|
|
||||||
code is available at https://github.com/bitwarden.
|
|
||||||
|
|
||||||
# Exclusions
|
|
||||||
|
|
||||||
The following bug classes are out-of scope:
|
|
||||||
|
|
||||||
- Bugs that are already reported on any of Bitwarden's issue trackers (https://github.com/bitwarden),
|
|
||||||
or that we already know of. Note that some of our issue tracking is private.
|
|
||||||
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
|
|
||||||
upstream maintainer.
|
|
||||||
- Attacks requiring physical access to a user's device.
|
|
||||||
- Self-XSS
|
|
||||||
- Issues related to software or protocols not under Bitwarden's control
|
|
||||||
- Vulnerabilities in outdated versions of Bitwarden
|
|
||||||
- Missing security best practices that do not directly lead to a vulnerability
|
|
||||||
- Issues that do not have any impact on the general public
|
|
||||||
|
|
||||||
While researching, we'd like to ask you to refrain from:
|
While researching, we'd like to ask you to refrain from:
|
||||||
|
|
||||||
@@ -42,4 +14,8 @@ While researching, we'd like to ask you to refrain from:
|
|||||||
- Social engineering (including phishing) of Bitwarden staff or contractors
|
- Social engineering (including phishing) of Bitwarden staff or contractors
|
||||||
- Any physical attempts against Bitwarden property or data centers
|
- Any physical attempts against Bitwarden property or data centers
|
||||||
|
|
||||||
|
# We want to help you!
|
||||||
|
|
||||||
|
If you have something that you feel is close to exploitation, or if you'd like some information regarding the internal API, or generally have any questions regarding the app that would help in your efforts, please email us at https://bitwarden.com/contact and ask for that information. As stated above, Bitwarden wants to help you find issues, and is more than willing to help.
|
||||||
|
|
||||||
Thank you for helping keep Bitwarden and our users safe!
|
Thank you for helping keep Bitwarden and our users safe!
|
||||||
|
|||||||
2
jslib
2
jslib
Submodule jslib updated: d28e762c55...9950fb42a1
17637
package-lock.json
generated
17637
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
95
package.json
95
package.json
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "bitwarden-directory-connector",
|
"name": "@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": "0.0.0",
|
"version": "0.0.0",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -27,8 +26,8 @@
|
|||||||
"symlink:lin": "rm -rf ./jslib && ln -s ../jslib ./jslib",
|
"symlink:lin": "rm -rf ./jslib && ln -s ../jslib ./jslib",
|
||||||
"rebuild": "electron-rebuild",
|
"rebuild": "electron-rebuild",
|
||||||
"reset": "rimraf ./node_modules/keytar/* && npm install",
|
"reset": "rimraf ./node_modules/keytar/* && npm install",
|
||||||
"lint": "tslint 'src/**/*.ts' || true",
|
"lint": "eslint . && prettier --check .",
|
||||||
"lint:fix": "tslint 'src/**/*.ts' --fix",
|
"lint:fix": "eslint . --fix",
|
||||||
"build": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\"",
|
"build": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\"",
|
||||||
"build:main": "webpack --config webpack.main.js",
|
"build:main": "webpack --config webpack.main.js",
|
||||||
"build:renderer": "webpack --config webpack.renderer.js",
|
"build:renderer": "webpack --config webpack.renderer.js",
|
||||||
@@ -59,11 +58,17 @@
|
|||||||
"dist:cli:lin": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:lin",
|
"dist:cli:lin": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:lin",
|
||||||
"publish:lin": "npm run build:dist && npm run clean:dist && electron-builder --linux --x64 -p always",
|
"publish:lin": "npm run build:dist && npm run clean:dist && electron-builder --linux --x64 -p always",
|
||||||
"publish:mac": "npm run build:dist && npm run clean:dist && electron-builder --mac -p always",
|
"publish:mac": "npm run build:dist && npm run clean:dist && electron-builder --mac -p always",
|
||||||
"publish:win": "npm run build:dist && npm run clean:dist && electron-builder --win --x64 --ia32 -p always -c.win.certificateSubjectName=\"8bit Solutions LLC\""
|
"publish:win": "npm run build:dist && npm run clean:dist && electron-builder --win --x64 --ia32 -p always -c.win.certificateSubjectName=\"8bit Solutions LLC\"",
|
||||||
|
"prettier": "prettier --write .",
|
||||||
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
|
"extraMetadata": {
|
||||||
|
"name": "bitwarden-directory-connector"
|
||||||
|
},
|
||||||
|
"productName": "Bitwarden Directory Connector",
|
||||||
"appId": "com.bitwarden.directory-connector",
|
"appId": "com.bitwarden.directory-connector",
|
||||||
"copyright": "Copyright © 2015-2020 Bitwarden Inc.",
|
"copyright": "Copyright © 2015-2022 Bitwarden Inc.",
|
||||||
"directories": {
|
"directories": {
|
||||||
"buildResources": "resources",
|
"buildResources": "resources",
|
||||||
"output": "dist",
|
"output": "dist",
|
||||||
@@ -132,51 +137,63 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/compiler-cli": "^11.2.11",
|
"@angular/compiler-cli": "^12.2.13",
|
||||||
"@microsoft/microsoft-graph-types": "^1.4.0",
|
"@microsoft/microsoft-graph-types": "^1.4.0",
|
||||||
"@ngtools/webpack": "^11.2.10",
|
"@ngtools/webpack": "^12.2.13",
|
||||||
"@types/ldapjs": "^1.0.10",
|
"@types/ldapjs": "^1.0.10",
|
||||||
"@types/node": "^14.14.43",
|
"@types/node": "^16.11.12",
|
||||||
"@types/proper-lockfile": "^4.1.1",
|
"@types/proper-lockfile": "^4.1.1",
|
||||||
"clean-webpack-plugin": "^3.0.0",
|
"@typescript-eslint/eslint-plugin": "^5.12.1",
|
||||||
|
"@typescript-eslint/parser": "^5.12.1",
|
||||||
|
"clean-webpack-plugin": "^4.0.0",
|
||||||
"concurrently": "^6.0.2",
|
"concurrently": "^6.0.2",
|
||||||
"copy-webpack-plugin": "^6.4.0",
|
"copy-webpack-plugin": "^10.0.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"css-loader": "^5.2.4",
|
"css-loader": "^6.5.1",
|
||||||
"del": "^6.0.0",
|
"electron-builder": "^22.14.5",
|
||||||
"electron-builder": "^22.11.7",
|
|
||||||
"electron-notarize": "^1.1.1",
|
"electron-notarize": "^1.1.1",
|
||||||
"electron-rebuild": "^3.2.3",
|
"electron-rebuild": "^3.2.5",
|
||||||
"electron-reload": "^1.5.0",
|
"electron-reload": "^1.5.0",
|
||||||
"file-loader": "^6.2.0",
|
"eslint": "^8.9.0",
|
||||||
"font-awesome": "4.7.0",
|
"eslint-config-prettier": "^8.4.0",
|
||||||
"html-loader": "^1.3.2",
|
"eslint-import-resolver-typescript": "^2.5.0",
|
||||||
"html-webpack-plugin": "^4.5.1",
|
"eslint-plugin-import": "^2.25.4",
|
||||||
"mini-css-extract-plugin": "^1.5.0",
|
"html-loader": "^3.0.1",
|
||||||
"node-loader": "^1.0.3",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
"pkg": "^5.1.0",
|
"husky": "^7.0.4",
|
||||||
|
"lint-staged": "^12.1.3",
|
||||||
|
"mini-css-extract-plugin": "^2.4.5",
|
||||||
|
"node-loader": "^2.0.0",
|
||||||
|
"pkg": "^5.5.1",
|
||||||
|
"prebuild-install": "^5.0.0",
|
||||||
|
"prettier": "^2.5.1",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"sass": "^1.32.11",
|
"sass": "^1.32.11",
|
||||||
"sass-loader": "^10.1.1",
|
"sass-loader": "^12.4.0",
|
||||||
"tapable": "^1.1.3",
|
"tapable": "^1.1.3",
|
||||||
"ts-loader": "^8.1.0",
|
"ts-loader": "^9.2.5",
|
||||||
"tsconfig-paths-webpack-plugin": "^3.5.1",
|
"tsconfig-paths-webpack-plugin": "^3.5.1",
|
||||||
"tslint": "~6.1.0",
|
"typescript": "4.3.5",
|
||||||
"tslint-loader": "^3.5.4",
|
"webpack": "^5.64.4",
|
||||||
"typescript": "4.1.5",
|
"webpack-cli": "^4.9.1",
|
||||||
"webpack": "^4.46.0",
|
"webpack-merge": "^5.8.0",
|
||||||
"webpack-cli": "^4.6.0",
|
"webpack-node-externals": "^3.0.0"
|
||||||
"webpack-merge": "^5.7.3",
|
|
||||||
"webpack-node-externals": "^3.0.0",
|
|
||||||
"prebuild-install": "^5.0.0"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@angular/animations": "^12.2.13",
|
||||||
|
"@angular/cdk": "^12.2.13",
|
||||||
|
"@angular/common": "^12.2.13",
|
||||||
|
"@angular/compiler": "^12.2.13",
|
||||||
|
"@angular/core": "^12.2.13",
|
||||||
|
"@angular/forms": "^12.2.13",
|
||||||
|
"@angular/platform-browser": "^12.2.13",
|
||||||
|
"@angular/platform-browser-dynamic": "^12.2.13",
|
||||||
|
"@angular/router": "^12.2.13",
|
||||||
"@bitwarden/jslib-angular": "file:jslib/angular",
|
"@bitwarden/jslib-angular": "file:jslib/angular",
|
||||||
"@bitwarden/jslib-common": "file:jslib/common",
|
"@bitwarden/jslib-common": "file:jslib/common",
|
||||||
"@bitwarden/jslib-electron": "file:jslib/electron",
|
"@bitwarden/jslib-electron": "file:jslib/electron",
|
||||||
"@bitwarden/jslib-node": "file:jslib/node",
|
"@bitwarden/jslib-node": "file:jslib/node",
|
||||||
"@microsoft/microsoft-graph-client": "^2.2.1",
|
"@microsoft/microsoft-graph-client": "^2.2.1",
|
||||||
"angular2-toaster": "^11.0.1",
|
|
||||||
"bootstrap": "^4.6.0",
|
"bootstrap": "^4.6.0",
|
||||||
"chalk": "^4.1.1",
|
"chalk": "^4.1.1",
|
||||||
"commander": "^7.2.0",
|
"commander": "^7.2.0",
|
||||||
@@ -187,11 +204,17 @@
|
|||||||
"inquirer": "8.0.0",
|
"inquirer": "8.0.0",
|
||||||
"ldapjs": "2.3.1",
|
"ldapjs": "2.3.1",
|
||||||
"lunr": "^2.3.9",
|
"lunr": "^2.3.9",
|
||||||
|
"ngx-toastr": "14.1.4",
|
||||||
"open": "^8.0.6",
|
"open": "^8.0.6",
|
||||||
"proper-lockfile": "^4.1.2"
|
"proper-lockfile": "^4.1.2",
|
||||||
|
"rxjs": "^7.4.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "~14",
|
"node": "~16",
|
||||||
"npm": "~7"
|
"npm": "~8"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"./!(jslib)**": "prettier --ignore-unknown --write",
|
||||||
|
"*.ts": "eslint --fix"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
require('dotenv').config();
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
const { notarize } = require('electron-notarize');
|
require("dotenv").config();
|
||||||
|
const { notarize } = require("electron-notarize");
|
||||||
|
|
||||||
exports.default = async function notarizing(context) {
|
exports.default = async function notarizing(context) {
|
||||||
const { electronPlatformName, appOutDir } = context;
|
const { electronPlatformName, appOutDir } = context;
|
||||||
if (electronPlatformName !== 'darwin') {
|
if (electronPlatformName !== "darwin") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const appleId = process.env.APPLE_ID_USERNAME || process.env.APPLEID;
|
const appleId = process.env.APPLE_ID_USERNAME || process.env.APPLEID;
|
||||||
const appleIdPassword = process.env.APPLE_ID_PASSWORD || `@keychain:AC_PASSWORD`;
|
const appleIdPassword = process.env.APPLE_ID_PASSWORD || `@keychain:AC_PASSWORD`;
|
||||||
const appName = context.packager.appInfo.productFilename;
|
const appName = context.packager.appInfo.productFilename;
|
||||||
return await notarize({
|
return await notarize({
|
||||||
appBundleId: 'com.bitwarden.directory-connector',
|
appBundleId: "com.bitwarden.directory-connector",
|
||||||
appPath: `${appOutDir}/${appName}.app`,
|
appPath: `${appOutDir}/${appName}.app`,
|
||||||
appleId: appleId,
|
appleId: appleId,
|
||||||
appleIdPassword: appleIdPassword,
|
appleIdPassword: appleIdPassword,
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires, no-console */
|
||||||
exports.default = async function (configuration) {
|
exports.default = async function (configuration) {
|
||||||
if (
|
if (parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 && configuration.path.slice(-4) == ".exe") {
|
||||||
parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 &&
|
console.log(`[*] Signing file: ${configuration.path}`);
|
||||||
configuration.path.slice(-4) == ".exe"
|
|
||||||
) {
|
|
||||||
console.log(`[*] Signing file: ${configuration.path}`)
|
|
||||||
require("child_process").execSync(
|
require("child_process").execSync(
|
||||||
`azuresigntool sign ` +
|
`azuresigntool sign ` +
|
||||||
`-kvu ${process.env.SIGNING_VAULT_URL} ` +
|
`-kvu ${process.env.SIGNING_VAULT_URL} ` +
|
||||||
@@ -16,7 +14,7 @@ exports.default = async function(configuration) {
|
|||||||
`-tr http://timestamp.digicert.com ` +
|
`-tr http://timestamp.digicert.com ` +
|
||||||
`"${configuration.path}"`,
|
`"${configuration.path}"`,
|
||||||
{
|
{
|
||||||
stdio: "inherit"
|
stdio: "inherit",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
1006
src-cli/package-lock.json
generated
Normal file
1006
src-cli/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"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": "2.9.5",
|
"version": "2.9.5",
|
||||||
@@ -19,6 +19,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"browser-hrtime": "^1.1.8",
|
"browser-hrtime": "^1.1.8",
|
||||||
"keytar": "7.6.0"
|
"keytar": "^7.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
66
src/abstractions/state.service.ts
Normal file
66
src/abstractions/state.service.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { StateService as BaseStateServiceAbstraction } from "jslib-common/abstractions/state.service";
|
||||||
|
import { StorageOptions } from "jslib-common/models/domain/storageOptions";
|
||||||
|
|
||||||
|
import { DirectoryType } from "src/enums/directoryType";
|
||||||
|
import { Account } from "src/models/account";
|
||||||
|
import { AzureConfiguration } from "src/models/azureConfiguration";
|
||||||
|
import { GSuiteConfiguration } from "src/models/gsuiteConfiguration";
|
||||||
|
import { LdapConfiguration } from "src/models/ldapConfiguration";
|
||||||
|
import { OktaConfiguration } from "src/models/oktaConfiguration";
|
||||||
|
import { OneLoginConfiguration } from "src/models/oneLoginConfiguration";
|
||||||
|
import { SyncConfiguration } from "src/models/syncConfiguration";
|
||||||
|
|
||||||
|
export abstract class StateService extends BaseStateServiceAbstraction<Account> {
|
||||||
|
getDirectory: <IConfiguration>(type: DirectoryType) => Promise<IConfiguration>;
|
||||||
|
setDirectory: (
|
||||||
|
type: DirectoryType,
|
||||||
|
config:
|
||||||
|
| LdapConfiguration
|
||||||
|
| GSuiteConfiguration
|
||||||
|
| AzureConfiguration
|
||||||
|
| OktaConfiguration
|
||||||
|
| OneLoginConfiguration
|
||||||
|
) => Promise<any>;
|
||||||
|
getLdapKey: (options?: StorageOptions) => Promise<string>;
|
||||||
|
setLdapKey: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
|
getGsuiteKey: (options?: StorageOptions) => Promise<string>;
|
||||||
|
setGsuiteKey: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
|
getAzureKey: (options?: StorageOptions) => Promise<string>;
|
||||||
|
setAzureKey: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
|
getOktaKey: (options?: StorageOptions) => Promise<string>;
|
||||||
|
setOktaKey: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
|
getOneLoginKey: (options?: StorageOptions) => Promise<string>;
|
||||||
|
setOneLoginKey: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
|
getLdapConfiguration: (options?: StorageOptions) => Promise<LdapConfiguration>;
|
||||||
|
setLdapConfiguration: (value: LdapConfiguration, options?: StorageOptions) => Promise<void>;
|
||||||
|
getGsuiteConfiguration: (options?: StorageOptions) => Promise<GSuiteConfiguration>;
|
||||||
|
setGsuiteConfiguration: (value: GSuiteConfiguration, options?: StorageOptions) => Promise<void>;
|
||||||
|
getAzureConfiguration: (options?: StorageOptions) => Promise<AzureConfiguration>;
|
||||||
|
setAzureConfiguration: (value: AzureConfiguration, options?: StorageOptions) => Promise<void>;
|
||||||
|
getOktaConfiguration: (options?: StorageOptions) => Promise<OktaConfiguration>;
|
||||||
|
setOktaConfiguration: (value: OktaConfiguration, options?: StorageOptions) => Promise<void>;
|
||||||
|
getOneLoginConfiguration: (options?: StorageOptions) => Promise<OneLoginConfiguration>;
|
||||||
|
setOneLoginConfiguration: (
|
||||||
|
value: OneLoginConfiguration,
|
||||||
|
options?: StorageOptions
|
||||||
|
) => Promise<void>;
|
||||||
|
getOrganizationId: (options?: StorageOptions) => Promise<string>;
|
||||||
|
setOrganizationId: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
|
getSync: (options?: StorageOptions) => Promise<SyncConfiguration>;
|
||||||
|
setSync: (value: SyncConfiguration, options?: StorageOptions) => Promise<void>;
|
||||||
|
getDirectoryType: (options?: StorageOptions) => Promise<DirectoryType>;
|
||||||
|
setDirectoryType: (value: DirectoryType, options?: StorageOptions) => Promise<void>;
|
||||||
|
getUserDelta: (options?: StorageOptions) => Promise<string>;
|
||||||
|
setUserDelta: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
|
getLastUserSync: (options?: StorageOptions) => Promise<Date>;
|
||||||
|
setLastUserSync: (value: Date, options?: StorageOptions) => Promise<void>;
|
||||||
|
getLastGroupSync: (options?: StorageOptions) => Promise<Date>;
|
||||||
|
setLastGroupSync: (value: Date, options?: StorageOptions) => Promise<void>;
|
||||||
|
getGroupDelta: (options?: StorageOptions) => Promise<string>;
|
||||||
|
setGroupDelta: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
|
getLastSyncHash: (options?: StorageOptions) => Promise<string>;
|
||||||
|
setLastSyncHash: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
|
getSyncingDir: (options?: StorageOptions) => Promise<boolean>;
|
||||||
|
setSyncingDir: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||||
|
clearSyncSettings: (syncHashToo: boolean) => Promise<void>;
|
||||||
|
}
|
||||||
@@ -2,25 +2,38 @@
|
|||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-8 col-lg-6">
|
<div class="col-md-8 col-lg-6">
|
||||||
<p class="text-center font-weight-bold">{{'welcome' | i18n}}</p>
|
<p class="text-center font-weight-bold">{{ "welcome" | i18n }}</p>
|
||||||
<p class="text-center">{{'logInDesc' | i18n}}</p>
|
<p class="text-center">{{ "logInDesc" | i18n }}</p>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">{{'logIn' | i18n}}</h5>
|
<h5 class="card-header">{{ "logIn" | i18n }}</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="client_id">{{'clientId' | i18n}}</label>
|
<label for="client_id">{{ "clientId" | i18n }}</label>
|
||||||
<input id="client_id" name="ClientId" [(ngModel)]="clientId"
|
<input id="client_id" name="ClientId" [(ngModel)]="clientId" class="form-control" />
|
||||||
class="form-control">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="row-main">
|
<div class="row-main">
|
||||||
<label for="client_secret">{{'clientSecret' | i18n}}</label>
|
<label for="client_secret">{{ "clientSecret" | i18n }}</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="{{showSecret ? 'text' : 'password'}}" id="client_secret" name="ClientSecret"
|
<input
|
||||||
[(ngModel)]="clientSecret" class="form-control">
|
type="{{ showSecret ? 'text' : 'password' }}"
|
||||||
|
id="client_secret"
|
||||||
|
name="ClientSecret"
|
||||||
|
[(ngModel)]="clientSecret"
|
||||||
|
class="form-control"
|
||||||
|
/>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button type="button" class="ml-1 btn btn-link" appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleSecret()">
|
<button
|
||||||
<i class="fa fa-lg" aria-hidden="true"[ngClass]="showSecret ? 'fa-eye-slash' : 'fa-eye'"></i>
|
type="button"
|
||||||
|
class="ml-1 btn btn-link"
|
||||||
|
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||||
|
(click)="toggleSecret()"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="bwi bwi-lg"
|
||||||
|
aria-hidden="true"
|
||||||
|
[ngClass]="showSecret ? 'bwi-eye-slash' : 'bwi-eye'"
|
||||||
|
></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -29,13 +42,13 @@
|
|||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<div>
|
<div>
|
||||||
<button type="submit" class="btn btn-primary" [disabled]="form.loading">
|
<button type="submit" class="btn btn-primary" [disabled]="form.loading">
|
||||||
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!form.loading"></i>
|
<i class="bwi bwi-spinner bwi-fw bwi-spin" [hidden]="!form.loading"></i>
|
||||||
<i class="fa fa-sign-in fa-fw" [hidden]="form.loading"></i>
|
<i class="bwi bwi-sign-in bwi-fw" [hidden]="form.loading"></i>
|
||||||
{{'logIn' | i18n}}
|
{{ "logIn" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-link ml-auto" (click)="settings()">
|
<button type="button" class="btn btn-link ml-auto" (click)="settings()">
|
||||||
{{'settings' | i18n}}
|
{{ "settings" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,77 +1,96 @@
|
|||||||
import {
|
import { Component, Input, ViewChild, ViewContainerRef } from "@angular/core";
|
||||||
Component,
|
import { Router } from "@angular/router";
|
||||||
ComponentFactoryResolver,
|
|
||||||
Input,
|
|
||||||
ViewChild,
|
|
||||||
ViewContainerRef,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
import { EnvironmentComponent } from './environment.component';
|
import { ModalService } from "jslib-angular/services/modal.service";
|
||||||
|
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
|
import { ApiLogInCredentials } from "jslib-common/models/domain/logInCredentials";
|
||||||
|
|
||||||
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service';
|
import { StateService } from "../../abstractions/state.service";
|
||||||
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
|
||||||
|
|
||||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
import { EnvironmentComponent } from "./environment.component";
|
||||||
|
|
||||||
import { Utils } from 'jslib-common/misc/utils';
|
|
||||||
import { ConfigurationService } from '../../services/configuration.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-apiKey',
|
selector: "app-apiKey",
|
||||||
templateUrl: 'apiKey.component.html',
|
templateUrl: "apiKey.component.html",
|
||||||
})
|
})
|
||||||
export class ApiKeyComponent {
|
export class ApiKeyComponent {
|
||||||
@ViewChild('environment', { read: ViewContainerRef, static: true }) environmentModal: ViewContainerRef;
|
@ViewChild("environment", { read: ViewContainerRef, static: true })
|
||||||
@Input() clientId: string = '';
|
environmentModal: ViewContainerRef;
|
||||||
@Input() clientSecret: string = '';
|
@Input() clientId = "";
|
||||||
|
@Input() clientSecret = "";
|
||||||
|
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
successRoute = '/tabs/dashboard';
|
successRoute = "/tabs/dashboard";
|
||||||
showSecret: boolean = false;
|
showSecret = false;
|
||||||
|
|
||||||
constructor(private authService: AuthService, private apiKeyService: ApiKeyService, private router: Router,
|
constructor(
|
||||||
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
private authService: AuthService,
|
||||||
private configurationService: ConfigurationService, private platformUtilsService: PlatformUtilsService,
|
private router: Router,
|
||||||
private modalService: ModalService) { }
|
private i18nService: I18nService,
|
||||||
|
private platformUtilsService: PlatformUtilsService,
|
||||||
|
private modalService: ModalService,
|
||||||
|
private logService: LogService,
|
||||||
|
private stateService: StateService
|
||||||
|
) {}
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
if (this.clientId == null || this.clientId === '') {
|
if (this.clientId == null || this.clientId === "") {
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
this.platformUtilsService.showToast(
|
||||||
this.i18nService.t('clientIdRequired'));
|
"error",
|
||||||
|
this.i18nService.t("errorOccurred"),
|
||||||
|
this.i18nService.t("clientIdRequired")
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.clientId.startsWith('organization')) {
|
if (!this.clientId.startsWith("organization")) {
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
this.platformUtilsService.showToast(
|
||||||
this.i18nService.t('orgApiKeyRequired'));
|
"error",
|
||||||
|
this.i18nService.t("errorOccurred"),
|
||||||
|
this.i18nService.t("orgApiKeyRequired")
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.clientSecret == null || this.clientSecret === '') {
|
if (this.clientSecret == null || this.clientSecret === "") {
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
this.platformUtilsService.showToast(
|
||||||
this.i18nService.t('clientSecretRequired'));
|
"error",
|
||||||
|
this.i18nService.t("errorOccurred"),
|
||||||
|
this.i18nService.t("clientSecretRequired")
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const idParts = this.clientId.split('.');
|
const idParts = this.clientId.split(".");
|
||||||
|
|
||||||
if (idParts.length !== 2 || idParts[0] !== 'organization' || !Utils.isGuid(idParts[1])) {
|
if (idParts.length !== 2 || idParts[0] !== "organization" || !Utils.isGuid(idParts[1])) {
|
||||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
this.platformUtilsService.showToast(
|
||||||
this.i18nService.t('invalidClientId'));
|
"error",
|
||||||
|
this.i18nService.t("errorOccurred"),
|
||||||
|
this.i18nService.t("invalidClientId")
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.formPromise = this.authService.logInApiKey(this.clientId, this.clientSecret);
|
this.formPromise = this.authService.logIn(
|
||||||
|
new ApiLogInCredentials(this.clientId, this.clientSecret)
|
||||||
|
);
|
||||||
await this.formPromise;
|
await this.formPromise;
|
||||||
const organizationId = await this.apiKeyService.getEntityId();
|
const organizationId = await this.stateService.getEntityId();
|
||||||
await this.configurationService.saveOrganizationId(organizationId);
|
await this.stateService.setOrganizationId(organizationId);
|
||||||
this.router.navigate([this.successRoute]);
|
this.router.navigate([this.successRoute]);
|
||||||
} catch { }
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async settings() {
|
async settings() {
|
||||||
const [modalRef, childComponent] = await this.modalService.openViewRef(EnvironmentComponent, this.environmentModal);
|
const [modalRef, childComponent] = await this.modalService.openViewRef(
|
||||||
|
EnvironmentComponent,
|
||||||
|
this.environmentModal
|
||||||
|
);
|
||||||
|
|
||||||
childComponent.onSaved.subscribe(() => {
|
childComponent.onSaved.subscribe(() => {
|
||||||
modalRef.close();
|
modalRef.close();
|
||||||
@@ -79,6 +98,6 @@ export class ApiKeyComponent {
|
|||||||
}
|
}
|
||||||
toggleSecret() {
|
toggleSecret() {
|
||||||
this.showSecret = !this.showSecret;
|
this.showSecret = !this.showSecret;
|
||||||
document.getElementById('client_secret').focus();
|
document.getElementById("client_secret").focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,40 +2,58 @@
|
|||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<form class="modal-content" (ngSubmit)="submit()">
|
<form class="modal-content" (ngSubmit)="submit()">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h3 class="modal-title">{{'settings' | i18n}}</h3>
|
<h3 class="modal-title">{{ "settings" | i18n }}</h3>
|
||||||
<button type="button" class="close" data-dismiss="modal" title="Close">
|
<button type="button" class="close" data-dismiss="modal" title="Close">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<h4>{{'selfHostedEnvironment' | i18n}}</h4>
|
<h4>{{ "selfHostedEnvironment" | i18n }}</h4>
|
||||||
<p>{{'selfHostedEnvironmentFooter' | i18n}}</p>
|
<p>{{ "selfHostedEnvironmentFooter" | i18n }}</p>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="baseUrl">{{'baseUrl' | i18n}}</label>
|
<label for="baseUrl">{{ "baseUrl" | i18n }}</label>
|
||||||
<input id="baseUrl" type="text" name="BaseUrl" [(ngModel)]="baseUrl" class="form-control">
|
<input
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} https://bitwarden.company.com</small>
|
id="baseUrl"
|
||||||
|
type="text"
|
||||||
|
name="BaseUrl"
|
||||||
|
[(ngModel)]="baseUrl"
|
||||||
|
class="form-control"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text"
|
||||||
|
>{{ "ex" | i18n }} https://bitwarden.company.com</small
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<h4>{{'customEnvironment' | i18n}}</h4>
|
<h4>{{ "customEnvironment" | i18n }}</h4>
|
||||||
<p>{{'customEnvironmentFooter' | i18n}}</p>
|
<p>{{ "customEnvironmentFooter" | i18n }}</p>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="webVaultUrl">{{'webVaultUrl' | i18n}}</label>
|
<label for="webVaultUrl">{{ "webVaultUrl" | i18n }}</label>
|
||||||
<input id="webVaultUrl" type="text" name="WebVaultUrl" [(ngModel)]="webVaultUrl"
|
<input
|
||||||
class="form-control">
|
id="webVaultUrl"
|
||||||
|
type="text"
|
||||||
|
name="WebVaultUrl"
|
||||||
|
[(ngModel)]="webVaultUrl"
|
||||||
|
class="form-control"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="apiUrl">{{'apiUrl' | i18n}}</label>
|
<label for="apiUrl">{{ "apiUrl" | i18n }}</label>
|
||||||
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl" class="form-control">
|
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl" class="form-control" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="identityUrl">{{'identityUrl' | i18n}}</label>
|
<label for="identityUrl">{{ "identityUrl" | i18n }}</label>
|
||||||
<input id="identityUrl" type="text" name="IdentityUrl" [(ngModel)]="identityUrl"
|
<input
|
||||||
class="form-control">
|
id="identityUrl"
|
||||||
|
type="text"
|
||||||
|
name="IdentityUrl"
|
||||||
|
[(ngModel)]="identityUrl"
|
||||||
|
class="form-control"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer justify-content-start">
|
<div class="modal-footer justify-content-start">
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
<i class="fa fa-save fa-fw"></i>
|
<i class="bwi bwi-save bwi-fw"></i>
|
||||||
{{'save' | i18n}}
|
{{ "save" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentComponent as BaseEnvironmentComponent } from "jslib-angular/components/environment.component";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { EnvironmentComponent as BaseEnvironmentComponent } from 'jslib-angular/components/environment.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-environment',
|
selector: "app-environment",
|
||||||
templateUrl: 'environment.component.html',
|
templateUrl: "environment.component.html",
|
||||||
})
|
})
|
||||||
export class EnvironmentComponent extends BaseEnvironmentComponent {
|
export class EnvironmentComponent extends BaseEnvironmentComponent {
|
||||||
constructor(environmentService: EnvironmentService, i18nService: I18nService,
|
constructor(
|
||||||
platformUtilsService: PlatformUtilsService) {
|
environmentService: EnvironmentService,
|
||||||
|
i18nService: I18nService,
|
||||||
|
platformUtilsService: PlatformUtilsService
|
||||||
|
) {
|
||||||
super(platformUtilsService, environmentService, i18nService);
|
super(platformUtilsService, environmentService, i18nService);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,42 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from "@angular/core";
|
||||||
import {
|
import { RouterModule, Routes } from "@angular/router";
|
||||||
RouterModule,
|
|
||||||
Routes,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { AuthGuardService } from './services/auth-guard.service';
|
import { ApiKeyComponent } from "./accounts/apiKey.component";
|
||||||
import { LaunchGuardService } from './services/launch-guard.service';
|
import { AuthGuardService } from "./services/auth-guard.service";
|
||||||
|
import { LaunchGuardService } from "./services/launch-guard.service";
|
||||||
import { ApiKeyComponent } from './accounts/apiKey.component';
|
import { DashboardComponent } from "./tabs/dashboard.component";
|
||||||
import { DashboardComponent } from './tabs/dashboard.component';
|
import { MoreComponent } from "./tabs/more.component";
|
||||||
import { MoreComponent } from './tabs/more.component';
|
import { SettingsComponent } from "./tabs/settings.component";
|
||||||
import { SettingsComponent } from './tabs/settings.component';
|
import { TabsComponent } from "./tabs/tabs.component";
|
||||||
import { TabsComponent } from './tabs/tabs.component';
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', redirectTo: '/login', pathMatch: 'full' },
|
{ path: "", redirectTo: "/login", pathMatch: "full" },
|
||||||
{
|
{
|
||||||
path: 'login',
|
path: "login",
|
||||||
component: ApiKeyComponent,
|
component: ApiKeyComponent,
|
||||||
canActivate: [LaunchGuardService],
|
canActivate: [LaunchGuardService],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'tabs',
|
path: "tabs",
|
||||||
component: TabsComponent,
|
component: TabsComponent,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: "",
|
||||||
redirectTo: '/tabs/dashboard',
|
redirectTo: "/tabs/dashboard",
|
||||||
pathMatch: 'full',
|
pathMatch: "full",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'dashboard',
|
path: "dashboard",
|
||||||
component: DashboardComponent,
|
component: DashboardComponent,
|
||||||
canActivate: [AuthGuardService],
|
canActivate: [AuthGuardService],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'settings',
|
path: "settings",
|
||||||
component: SettingsComponent,
|
component: SettingsComponent,
|
||||||
canActivate: [AuthGuardService],
|
canActivate: [AuthGuardService],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'more',
|
path: "more",
|
||||||
component: MoreComponent,
|
component: MoreComponent,
|
||||||
canActivate: [AuthGuardService],
|
canActivate: [AuthGuardService],
|
||||||
},
|
},
|
||||||
@@ -49,10 +45,12 @@ const routes: Routes = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forRoot(routes, {
|
imports: [
|
||||||
|
RouterModule.forRoot(routes, {
|
||||||
useHash: true,
|
useHash: true,
|
||||||
/*enableTracing: true,*/
|
/*enableTracing: true,*/
|
||||||
})],
|
}),
|
||||||
|
],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
})
|
})
|
||||||
export class AppRoutingModule {}
|
export class AppRoutingModule {}
|
||||||
|
|||||||
@@ -1,92 +1,74 @@
|
|||||||
import {
|
|
||||||
BodyOutputType,
|
|
||||||
Toast,
|
|
||||||
ToasterConfig,
|
|
||||||
ToasterContainerComponent,
|
|
||||||
ToasterService,
|
|
||||||
} from 'angular2-toaster';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
ComponentFactoryResolver,
|
|
||||||
NgZone,
|
NgZone,
|
||||||
OnInit,
|
OnInit,
|
||||||
SecurityContext,
|
SecurityContext,
|
||||||
Type,
|
|
||||||
ViewChild,
|
ViewChild,
|
||||||
ViewContainerRef,
|
ViewContainerRef,
|
||||||
} from '@angular/core';
|
} from "@angular/core";
|
||||||
import { DomSanitizer } from '@angular/platform-browser';
|
import { DomSanitizer } from "@angular/platform-browser";
|
||||||
import { Router } from '@angular/router';
|
import { Router } from "@angular/router";
|
||||||
|
import { IndividualConfig, ToastrService } from "ngx-toastr";
|
||||||
|
|
||||||
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
|
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
|
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||||
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
import { TokenService } from "jslib-common/abstractions/token.service";
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { StateService } from "../abstractions/state.service";
|
||||||
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
import { SyncService } from "../services/sync.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
|
||||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
|
||||||
import { TokenService } from 'jslib-common/abstractions/token.service';
|
|
||||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
|
||||||
|
|
||||||
import { ConfigurationService } from '../services/configuration.service';
|
const BroadcasterSubscriptionId = "AppComponent";
|
||||||
import { SyncService } from '../services/sync.service';
|
|
||||||
|
|
||||||
const BroadcasterSubscriptionId = 'AppComponent';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: "app-root",
|
||||||
styles: [],
|
styles: [],
|
||||||
template: `
|
template: ` <ng-template #settings></ng-template>
|
||||||
<toaster-container [toasterconfig]="toasterConfig"></toaster-container>
|
|
||||||
<ng-template #settings></ng-template>
|
|
||||||
<router-outlet></router-outlet>`,
|
<router-outlet></router-outlet>`,
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
@ViewChild('settings', { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef;
|
@ViewChild("settings", { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef;
|
||||||
|
|
||||||
toasterConfig: ToasterConfig = new ToasterConfig({
|
constructor(
|
||||||
showCloseButton: true,
|
private broadcasterService: BroadcasterService,
|
||||||
mouseoverTimerStop: true,
|
private tokenService: TokenService,
|
||||||
animation: 'flyRight',
|
private authService: AuthService,
|
||||||
limit: 5,
|
private router: Router,
|
||||||
});
|
private toastrService: ToastrService,
|
||||||
|
private i18nService: I18nService,
|
||||||
private lastActivity: number = null;
|
private sanitizer: DomSanitizer,
|
||||||
|
private ngZone: NgZone,
|
||||||
constructor(private broadcasterService: BroadcasterService, private userService: UserService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private tokenService: TokenService, private storageService: StorageService,
|
private messagingService: MessagingService,
|
||||||
private authService: AuthService, private router: Router,
|
private syncService: SyncService,
|
||||||
private toasterService: ToasterService, private i18nService: I18nService,
|
private stateService: StateService,
|
||||||
private sanitizer: DomSanitizer, private ngZone: NgZone,
|
private logService: LogService
|
||||||
private componentFactoryResolver: ComponentFactoryResolver, private messagingService: MessagingService,
|
) {}
|
||||||
private configurationService: ConfigurationService, private syncService: SyncService,
|
|
||||||
private stateService: StateService, private apiService: ApiService) {
|
|
||||||
(window as any).BitwardenToasterService = toasterService;
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||||
this.ngZone.run(async () => {
|
this.ngZone.run(async () => {
|
||||||
switch (message.command) {
|
switch (message.command) {
|
||||||
case 'syncScheduleStarted':
|
case "syncScheduleStarted":
|
||||||
case 'syncScheduleStopped':
|
case "syncScheduleStopped":
|
||||||
this.stateService.save('syncingDir', message.command === 'syncScheduleStarted');
|
this.stateService.setSyncingDir(message.command === "syncScheduleStarted");
|
||||||
break;
|
break;
|
||||||
case 'logout':
|
case "logout":
|
||||||
this.logOut(!!message.expired);
|
this.logOut(!!message.expired);
|
||||||
break;
|
break;
|
||||||
case 'checkDirSync':
|
case "checkDirSync":
|
||||||
try {
|
try {
|
||||||
const syncConfig = await this.configurationService.getSync();
|
const syncConfig = await this.stateService.getSync();
|
||||||
if (syncConfig.interval == null || syncConfig.interval < 5) {
|
if (syncConfig.interval == null || syncConfig.interval < 5) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncInterval = syncConfig.interval * 60000;
|
const syncInterval = syncConfig.interval * 60000;
|
||||||
const lastGroupSync = await this.configurationService.getLastGroupSyncDate();
|
const lastGroupSync = await this.stateService.getLastGroupSync();
|
||||||
const lastUserSync = await this.configurationService.getLastUserSyncDate();
|
const lastUserSync = await this.stateService.getLastUserSync();
|
||||||
let lastSync: Date = null;
|
let lastSync: Date = null;
|
||||||
if (lastGroupSync != null && lastUserSync == null) {
|
if (lastGroupSync != null && lastUserSync == null) {
|
||||||
lastSync = lastGroupSync;
|
lastSync = lastGroupSync;
|
||||||
@@ -108,15 +90,19 @@ export class AppComponent implements OnInit {
|
|||||||
if (lastSyncAgo >= syncInterval) {
|
if (lastSyncAgo >= syncInterval) {
|
||||||
await this.syncService.sync(false, false);
|
await this.syncService.sync(false, false);
|
||||||
}
|
}
|
||||||
} catch { }
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
this.messagingService.send('scheduleNextDirSync');
|
this.messagingService.send("scheduleNextDirSync");
|
||||||
break;
|
break;
|
||||||
case 'showToast':
|
case "showToast":
|
||||||
this.showToast(message);
|
this.showToast(message);
|
||||||
break;
|
break;
|
||||||
case 'ssoCallback':
|
case "ssoCallback":
|
||||||
this.router.navigate(['sso'], { queryParams: { code: message.code, state: message.state } });
|
this.router.navigate(["sso"], {
|
||||||
|
queryParams: { code: message.code, state: message.state },
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
@@ -129,44 +115,46 @@ export class AppComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async logOut(expired: boolean) {
|
private async logOut(expired: boolean) {
|
||||||
const userId = await this.userService.getUserId();
|
|
||||||
|
|
||||||
await this.tokenService.clearToken();
|
await this.tokenService.clearToken();
|
||||||
await this.userService.clear();
|
await this.stateService.clean();
|
||||||
|
|
||||||
this.authService.logOut(async () => {
|
this.authService.logOut(async () => {
|
||||||
if (expired) {
|
if (expired) {
|
||||||
this.toasterService.popAsync('warning', this.i18nService.t('loggedOut'),
|
this.platformUtilsService.showToast(
|
||||||
this.i18nService.t('loginExpired'));
|
"warning",
|
||||||
|
this.i18nService.t("loggedOut"),
|
||||||
|
this.i18nService.t("loginExpired")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.router.navigate(['login']);
|
this.router.navigate(["login"]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private showToast(msg: any) {
|
private showToast(msg: any) {
|
||||||
const toast: Toast = {
|
let message = "";
|
||||||
type: msg.type,
|
|
||||||
title: msg.title,
|
const options: Partial<IndividualConfig> = {};
|
||||||
};
|
|
||||||
if (typeof (msg.text) === 'string') {
|
if (typeof msg.text === "string") {
|
||||||
toast.body = msg.text;
|
message = msg.text;
|
||||||
} else if (msg.text.length === 1) {
|
} else if (msg.text.length === 1) {
|
||||||
toast.body = msg.text[0];
|
message = msg.text[0];
|
||||||
} else {
|
} else {
|
||||||
let message = '';
|
msg.text.forEach(
|
||||||
msg.text.forEach((t: string) =>
|
(t: string) =>
|
||||||
message += ('<p>' + this.sanitizer.sanitize(SecurityContext.HTML, t) + '</p>'));
|
(message += "<p>" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "</p>")
|
||||||
toast.body = message;
|
);
|
||||||
toast.bodyOutputType = BodyOutputType.TrustedHtml;
|
options.enableHtml = true;
|
||||||
}
|
}
|
||||||
if (msg.options != null) {
|
if (msg.options != null) {
|
||||||
if (msg.options.trustedHtml === true) {
|
if (msg.options.trustedHtml === true) {
|
||||||
toast.bodyOutputType = BodyOutputType.TrustedHtml;
|
options.enableHtml = true;
|
||||||
}
|
}
|
||||||
if (msg.options.timeout != null && msg.options.timeout > 0) {
|
if (msg.options.timeout != null && msg.options.timeout > 0) {
|
||||||
toast.timeout = msg.options.timeout;
|
options.timeOut = msg.options.timeout;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.toasterService.popAsync(toast);
|
|
||||||
|
this.toastrService.show(message, msg.title, options, "toast-" + msg.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,68 +1,40 @@
|
|||||||
import 'core-js/stable';
|
import "core-js/stable";
|
||||||
import 'zone.js/dist/zone';
|
import "zone.js/dist/zone";
|
||||||
|
|
||||||
import { ToasterModule } from 'angular2-toaster';
|
import { NgModule } from "@angular/core";
|
||||||
|
import { FormsModule } from "@angular/forms";
|
||||||
|
import { BrowserModule } from "@angular/platform-browser";
|
||||||
|
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||||
|
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { JslibModule } from "jslib-angular/jslib.module";
|
||||||
import { ServicesModule } from './services/services.module';
|
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
import { ApiKeyComponent } from "./accounts/apiKey.component";
|
||||||
import { FormsModule } from '@angular/forms';
|
import { EnvironmentComponent } from "./accounts/environment.component";
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { AppRoutingModule } from "./app-routing.module";
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { AppComponent } from "./app.component";
|
||||||
|
import { ServicesModule } from "./services/services.module";
|
||||||
|
import { DashboardComponent } from "./tabs/dashboard.component";
|
||||||
|
import { MoreComponent } from "./tabs/more.component";
|
||||||
|
import { SettingsComponent } from "./tabs/settings.component";
|
||||||
|
import { TabsComponent } from "./tabs/tabs.component";
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
|
|
||||||
import { CalloutComponent } from 'jslib-angular/components/callout.component';
|
|
||||||
import { IconComponent } from 'jslib-angular/components/icon.component';
|
|
||||||
|
|
||||||
import { ApiKeyComponent } from './accounts/apiKey.component';
|
|
||||||
import { EnvironmentComponent } from './accounts/environment.component';
|
|
||||||
import { DashboardComponent } from './tabs/dashboard.component';
|
|
||||||
import { MoreComponent } from './tabs/more.component';
|
|
||||||
import { SettingsComponent } from './tabs/settings.component';
|
|
||||||
import { TabsComponent } from './tabs/tabs.component';
|
|
||||||
|
|
||||||
import { A11yTitleDirective } from 'jslib-angular/directives/a11y-title.directive';
|
|
||||||
import { ApiActionDirective } from 'jslib-angular/directives/api-action.directive';
|
|
||||||
import { AutofocusDirective } from 'jslib-angular/directives/autofocus.directive';
|
|
||||||
import { BlurClickDirective } from 'jslib-angular/directives/blur-click.directive';
|
|
||||||
import { BoxRowDirective } from 'jslib-angular/directives/box-row.directive';
|
|
||||||
import { FallbackSrcDirective } from 'jslib-angular/directives/fallback-src.directive';
|
|
||||||
import { StopClickDirective } from 'jslib-angular/directives/stop-click.directive';
|
|
||||||
import { StopPropDirective } from 'jslib-angular/directives/stop-prop.directive';
|
|
||||||
|
|
||||||
import { I18nPipe } from 'jslib-angular/pipes/i18n.pipe';
|
|
||||||
import { SearchCiphersPipe } from 'jslib-angular/pipes/search-ciphers.pipe';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
|
||||||
BrowserAnimationsModule,
|
|
||||||
FormsModule,
|
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
BrowserModule,
|
||||||
|
FormsModule,
|
||||||
|
JslibModule,
|
||||||
ServicesModule,
|
ServicesModule,
|
||||||
ToasterModule.forRoot(),
|
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
A11yTitleDirective,
|
|
||||||
ApiActionDirective,
|
|
||||||
ApiKeyComponent,
|
ApiKeyComponent,
|
||||||
AppComponent,
|
AppComponent,
|
||||||
AutofocusDirective,
|
|
||||||
BlurClickDirective,
|
|
||||||
BoxRowDirective,
|
|
||||||
CalloutComponent,
|
|
||||||
DashboardComponent,
|
DashboardComponent,
|
||||||
EnvironmentComponent,
|
EnvironmentComponent,
|
||||||
FallbackSrcDirective,
|
|
||||||
I18nPipe,
|
|
||||||
IconComponent,
|
|
||||||
MoreComponent,
|
MoreComponent,
|
||||||
SearchCiphersPipe,
|
|
||||||
SettingsComponent,
|
SettingsComponent,
|
||||||
StopClickDirective,
|
|
||||||
StopPropDirective,
|
|
||||||
TabsComponent,
|
TabsComponent,
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { enableProdMode } from '@angular/core';
|
import { enableProdMode } from "@angular/core";
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
|
||||||
|
|
||||||
import { isDev } from 'jslib-electron/utils';
|
import { isDev } from "jslib-electron/utils";
|
||||||
|
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
require('../scss/styles.scss');
|
require("../scss/styles.scss");
|
||||||
|
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from "./app.module";
|
||||||
|
|
||||||
if (!isDev()) {
|
if (!isDev()) {
|
||||||
enableProdMode();
|
enableProdMode();
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from "@angular/core";
|
||||||
import {
|
import { CanActivate } from "@angular/router";
|
||||||
CanActivate,
|
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service';
|
|
||||||
|
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
|
|
||||||
|
import { StateService } from "../../abstractions/state.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthGuardService implements CanActivate {
|
export class AuthGuardService implements CanActivate {
|
||||||
constructor(private apiKeyService: ApiKeyService, private router: Router,
|
constructor(private stateService: StateService, private messagingService: MessagingService) {}
|
||||||
private messagingService: MessagingService) { }
|
|
||||||
|
|
||||||
async canActivate() {
|
async canActivate() {
|
||||||
const isAuthed = await this.apiKeyService.isAuthenticated();
|
const isAuthed = await this.stateService.getIsAuthenticated();
|
||||||
if (!isAuthed) {
|
if (!isAuthed) {
|
||||||
this.messagingService.send('logout');
|
this.messagingService.send("logout");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
5
src/app/services/injectionTokens.ts
Normal file
5
src/app/services/injectionTokens.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { InjectionToken } from "@angular/core";
|
||||||
|
|
||||||
|
export const USE_SECURE_STORAGE_FOR_SECRETS = new InjectionToken<boolean>(
|
||||||
|
"USE_SECURE_STORAGE_FOR_SECRETS"
|
||||||
|
);
|
||||||
@@ -1,22 +1,19 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from "@angular/core";
|
||||||
import {
|
import { CanActivate, Router } from "@angular/router";
|
||||||
CanActivate,
|
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service';
|
import { StateService } from "../../abstractions/state.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LaunchGuardService implements CanActivate {
|
export class LaunchGuardService implements CanActivate {
|
||||||
constructor(private apiKeyService: ApiKeyService, private router: Router) { }
|
constructor(private stateService: StateService, private router: Router) {}
|
||||||
|
|
||||||
async canActivate() {
|
async canActivate() {
|
||||||
const isAuthed = await this.apiKeyService.isAuthenticated();
|
const isAuthed = await this.stateService.getIsAuthenticated();
|
||||||
if (!isAuthed) {
|
if (!isAuthed) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.router.navigate(['/tabs/dashboard']);
|
this.router.navigate(["/tabs/dashboard"]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,165 +1,189 @@
|
|||||||
|
import { APP_INITIALIZER, Injector, NgModule } from "@angular/core";
|
||||||
|
|
||||||
|
import { JslibServicesModule } from "jslib-angular/services/jslib-services.module";
|
||||||
|
import { ApiService as ApiServiceAbstraction } from "jslib-common/abstractions/api.service";
|
||||||
|
import { AppIdService as AppIdServiceAbstraction } from "jslib-common/abstractions/appId.service";
|
||||||
|
import { AuthService as AuthServiceAbstraction } from "jslib-common/abstractions/auth.service";
|
||||||
|
import { BroadcasterService as BroadcasterServiceAbstraction } from "jslib-common/abstractions/broadcaster.service";
|
||||||
|
import { CryptoService as CryptoServiceAbstraction } from "jslib-common/abstractions/crypto.service";
|
||||||
|
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "jslib-common/abstractions/cryptoFunction.service";
|
||||||
|
import { EnvironmentService as EnvironmentServiceAbstraction } from "jslib-common/abstractions/environment.service";
|
||||||
|
import { I18nService as I18nServiceAbstraction } from "jslib-common/abstractions/i18n.service";
|
||||||
import {
|
import {
|
||||||
APP_INITIALIZER,
|
CLIENT_TYPE,
|
||||||
NgModule,
|
SECURE_STORAGE,
|
||||||
} from '@angular/core';
|
STATE_FACTORY,
|
||||||
|
WINDOW_TOKEN,
|
||||||
|
} from "jslib-common/abstractions/injectionTokens";
|
||||||
|
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "jslib-common/abstractions/keyConnector.service";
|
||||||
|
import { LogService as LogServiceAbstraction } from "jslib-common/abstractions/log.service";
|
||||||
|
import { MessagingService as MessagingServiceAbstraction } from "jslib-common/abstractions/messaging.service";
|
||||||
|
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
import { StateMigrationService as StateMigrationServiceAbstraction } from "jslib-common/abstractions/stateMigration.service";
|
||||||
|
import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
|
||||||
|
import { TokenService as TokenServiceAbstraction } from "jslib-common/abstractions/token.service";
|
||||||
|
import { TwoFactorService as TwoFactorServiceAbstraction } from "jslib-common/abstractions/twoFactor.service";
|
||||||
|
import { ClientType } from "jslib-common/enums/clientType";
|
||||||
|
import { StateFactory } from "jslib-common/factories/stateFactory";
|
||||||
|
import { GlobalState } from "jslib-common/models/domain/globalState";
|
||||||
|
import { ContainerService } from "jslib-common/services/container.service";
|
||||||
|
import { ElectronLogService } from "jslib-electron/services/electronLog.service";
|
||||||
|
import { ElectronPlatformUtilsService } from "jslib-electron/services/electronPlatformUtils.service";
|
||||||
|
import { ElectronRendererMessagingService } from "jslib-electron/services/electronRendererMessaging.service";
|
||||||
|
import { ElectronRendererSecureStorageService } from "jslib-electron/services/electronRendererSecureStorage.service";
|
||||||
|
import { ElectronRendererStorageService } from "jslib-electron/services/electronRendererStorage.service";
|
||||||
|
import { NodeApiService } from "jslib-node/services/nodeApi.service";
|
||||||
|
import { NodeCryptoFunctionService } from "jslib-node/services/nodeCryptoFunction.service";
|
||||||
|
|
||||||
import { ToasterModule } from 'angular2-toaster';
|
import { StateService as StateServiceAbstraction } from "../../abstractions/state.service";
|
||||||
|
import { Account } from "../../models/account";
|
||||||
|
import { refreshToken } from "../../services/api.service";
|
||||||
|
import { AuthService } from "../../services/auth.service";
|
||||||
|
import { I18nService } from "../../services/i18n.service";
|
||||||
|
import { NoopTwoFactorService } from "../../services/noop/noopTwoFactor.service";
|
||||||
|
import { StateService } from "../../services/state.service";
|
||||||
|
import { StateMigrationService } from "../../services/stateMigration.service";
|
||||||
|
import { SyncService } from "../../services/sync.service";
|
||||||
|
|
||||||
import { ElectronLogService } from 'jslib-electron/services/electronLog.service';
|
import { AuthGuardService } from "./auth-guard.service";
|
||||||
import { ElectronPlatformUtilsService } from 'jslib-electron/services/electronPlatformUtils.service';
|
import { USE_SECURE_STORAGE_FOR_SECRETS } from "./injectionTokens";
|
||||||
import { ElectronRendererMessagingService } from 'jslib-electron/services/electronRendererMessaging.service';
|
import { LaunchGuardService } from "./launch-guard.service";
|
||||||
import { ElectronRendererSecureStorageService } from 'jslib-electron/services/electronRendererSecureStorage.service';
|
|
||||||
import { ElectronRendererStorageService } from 'jslib-electron/services/electronRendererStorage.service';
|
|
||||||
|
|
||||||
import { AuthGuardService } from './auth-guard.service';
|
function refreshTokenCallback(injector: Injector) {
|
||||||
import { LaunchGuardService } from './launch-guard.service';
|
return () => {
|
||||||
|
const stateService = injector.get(StateServiceAbstraction);
|
||||||
import { ConfigurationService } from '../../services/configuration.service';
|
const authService = injector.get(AuthServiceAbstraction);
|
||||||
import { I18nService } from '../../services/i18n.service';
|
return refreshToken(stateService, authService);
|
||||||
import { SyncService } from '../../services/sync.service';
|
};
|
||||||
|
|
||||||
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
|
|
||||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
|
||||||
import { ValidationService } from 'jslib-angular/services/validation.service';
|
|
||||||
|
|
||||||
import { ApiKeyService } from 'jslib-common/services/apiKey.service';
|
|
||||||
import { AppIdService } from 'jslib-common/services/appId.service';
|
|
||||||
import { ConstantsService } from 'jslib-common/services/constants.service';
|
|
||||||
import { ContainerService } from 'jslib-common/services/container.service';
|
|
||||||
import { CryptoService } from 'jslib-common/services/crypto.service';
|
|
||||||
import { EnvironmentService } from 'jslib-common/services/environment.service';
|
|
||||||
import { PasswordGenerationService } from 'jslib-common/services/passwordGeneration.service';
|
|
||||||
import { PolicyService } from 'jslib-common/services/policy.service';
|
|
||||||
import { StateService } from 'jslib-common/services/state.service';
|
|
||||||
import { TokenService } from 'jslib-common/services/token.service';
|
|
||||||
import { UserService } from 'jslib-common/services/user.service';
|
|
||||||
|
|
||||||
import { NodeCryptoFunctionService } from 'jslib-node/services/nodeCryptoFunction.service';
|
|
||||||
|
|
||||||
import { ApiService as ApiServiceAbstraction } from 'jslib-common/abstractions/api.service';
|
|
||||||
import { ApiKeyService as ApiKeyServiceAbstraction } from 'jslib-common/abstractions/apiKey.service';
|
|
||||||
import { AuthService as AuthServiceAbstraction } from 'jslib-common/abstractions/auth.service';
|
|
||||||
import { CryptoService as CryptoServiceAbstraction } from 'jslib-common/abstractions/crypto.service';
|
|
||||||
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib-common/abstractions/cryptoFunction.service';
|
|
||||||
import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib-common/abstractions/environment.service';
|
|
||||||
import { I18nService as I18nServiceAbstraction } from 'jslib-common/abstractions/i18n.service';
|
|
||||||
import { LogService as LogServiceAbstraction } from 'jslib-common/abstractions/log.service';
|
|
||||||
import { MessagingService as MessagingServiceAbstraction } from 'jslib-common/abstractions/messaging.service';
|
|
||||||
import {
|
|
||||||
PasswordGenerationService as PasswordGenerationServiceAbstraction,
|
|
||||||
} from 'jslib-common/abstractions/passwordGeneration.service';
|
|
||||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib-common/abstractions/platformUtils.service';
|
|
||||||
import { PolicyService as PolicyServiceAbstraction } from 'jslib-common/abstractions/policy.service';
|
|
||||||
import { StateService as StateServiceAbstraction } from 'jslib-common/abstractions/state.service';
|
|
||||||
import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service';
|
|
||||||
import { TokenService as TokenServiceAbstraction } from 'jslib-common/abstractions/token.service';
|
|
||||||
import { UserService as UserServiceAbstraction } from 'jslib-common/abstractions/user.service';
|
|
||||||
|
|
||||||
import { ApiService, refreshToken } from '../../services/api.service';
|
|
||||||
import { AuthService } from '../../services/auth.service';
|
|
||||||
|
|
||||||
const logService = new ElectronLogService();
|
|
||||||
const i18nService = new I18nService(window.navigator.language, './locales');
|
|
||||||
const stateService = new StateService();
|
|
||||||
const broadcasterService = new BroadcasterService();
|
|
||||||
const messagingService = new ElectronRendererMessagingService(broadcasterService);
|
|
||||||
const storageService: StorageServiceAbstraction = new ElectronRendererStorageService();
|
|
||||||
const platformUtilsService = new ElectronPlatformUtilsService(i18nService, messagingService, false, storageService);
|
|
||||||
const secureStorageService: StorageServiceAbstraction = new ElectronRendererSecureStorageService();
|
|
||||||
const cryptoFunctionService: CryptoFunctionServiceAbstraction = new NodeCryptoFunctionService();
|
|
||||||
const cryptoService = new CryptoService(storageService, secureStorageService, cryptoFunctionService,
|
|
||||||
platformUtilsService, logService);
|
|
||||||
const appIdService = new AppIdService(storageService);
|
|
||||||
const tokenService = new TokenService(storageService);
|
|
||||||
const environmentService = new EnvironmentService(storageService);
|
|
||||||
const apiService = new ApiService(tokenService, platformUtilsService, environmentService, refreshTokenCallback,
|
|
||||||
async (expired: boolean) => messagingService.send('logout', { expired: expired }));
|
|
||||||
const userService = new UserService(tokenService, storageService);
|
|
||||||
const apiKeyService = new ApiKeyService(tokenService, storageService);
|
|
||||||
const containerService = new ContainerService(cryptoService);
|
|
||||||
const authService = new AuthService(cryptoService, apiService, userService, tokenService, appIdService,
|
|
||||||
i18nService, platformUtilsService, messagingService, null, logService, apiKeyService, false);
|
|
||||||
const configurationService = new ConfigurationService(storageService, secureStorageService);
|
|
||||||
const syncService = new SyncService(configurationService, logService, cryptoFunctionService, apiService,
|
|
||||||
messagingService, i18nService, environmentService);
|
|
||||||
const passwordGenerationService = new PasswordGenerationService(cryptoService, storageService, null);
|
|
||||||
const policyService = new PolicyService(userService, storageService);
|
|
||||||
|
|
||||||
containerService.attachToWindow(window);
|
|
||||||
|
|
||||||
function refreshTokenCallback(): Promise<any> {
|
|
||||||
return refreshToken(apiKeyService, authService);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initFactory(): Function {
|
export function initFactory(
|
||||||
|
environmentService: EnvironmentServiceAbstraction,
|
||||||
|
i18nService: I18nService,
|
||||||
|
platformUtilsService: PlatformUtilsServiceAbstraction,
|
||||||
|
stateService: StateServiceAbstraction,
|
||||||
|
cryptoService: CryptoServiceAbstraction
|
||||||
|
): () => Promise<void> {
|
||||||
return async () => {
|
return async () => {
|
||||||
|
await stateService.init();
|
||||||
await environmentService.setUrlsFromStorage();
|
await environmentService.setUrlsFromStorage();
|
||||||
await i18nService.init();
|
await i18nService.init();
|
||||||
authService.init();
|
|
||||||
apiKeyService.migrateApiKeyStorage();
|
|
||||||
const htmlEl = window.document.documentElement;
|
const htmlEl = window.document.documentElement;
|
||||||
htmlEl.classList.add('os_' + platformUtilsService.getDeviceString());
|
htmlEl.classList.add("os_" + platformUtilsService.getDeviceString());
|
||||||
htmlEl.classList.add('locale_' + i18nService.translationLocale);
|
htmlEl.classList.add("locale_" + i18nService.translationLocale);
|
||||||
window.document.title = i18nService.t('bitwardenDirectoryConnector');
|
window.document.title = i18nService.t("bitwardenDirectoryConnector");
|
||||||
|
|
||||||
let installAction = null;
|
let installAction = null;
|
||||||
const installedVersion = await storageService.get<string>(ConstantsService.installedVersionKey);
|
const installedVersion = await stateService.getInstalledVersion();
|
||||||
const currentVersion = await platformUtilsService.getApplicationVersion();
|
const currentVersion = await platformUtilsService.getApplicationVersion();
|
||||||
if (installedVersion == null) {
|
if (installedVersion == null) {
|
||||||
installAction = 'install';
|
installAction = "install";
|
||||||
} else if (installedVersion !== currentVersion) {
|
} else if (installedVersion !== currentVersion) {
|
||||||
installAction = 'update';
|
installAction = "update";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (installAction != null) {
|
if (installAction != null) {
|
||||||
await storageService.save(ConstantsService.installedVersionKey, currentVersion);
|
await stateService.setInstalledVersion(currentVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.setTimeout(async () => {
|
const containerService = new ContainerService(cryptoService);
|
||||||
if (await userService.isAuthenticated()) {
|
containerService.attachToWindow(window);
|
||||||
const profile = await apiService.getProfile();
|
|
||||||
stateService.save('profileOrganizations', profile.organizations);
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [JslibServicesModule],
|
||||||
ToasterModule,
|
|
||||||
],
|
|
||||||
declarations: [],
|
declarations: [],
|
||||||
providers: [
|
providers: [
|
||||||
ValidationService,
|
|
||||||
AuthGuardService,
|
|
||||||
LaunchGuardService,
|
|
||||||
ModalService,
|
|
||||||
{ provide: AuthServiceAbstraction, useValue: authService },
|
|
||||||
{ provide: EnvironmentServiceAbstraction, useValue: environmentService },
|
|
||||||
{ provide: TokenServiceAbstraction, useValue: tokenService },
|
|
||||||
{ provide: I18nServiceAbstraction, useValue: i18nService },
|
|
||||||
{ provide: CryptoServiceAbstraction, useValue: cryptoService },
|
|
||||||
{ provide: PlatformUtilsServiceAbstraction, useValue: platformUtilsService },
|
|
||||||
{ provide: ApiServiceAbstraction, useValue: apiService },
|
|
||||||
{ provide: UserServiceAbstraction, useValue: userService },
|
|
||||||
{ provide: ApiKeyServiceAbstraction, useValue: apiKeyService },
|
|
||||||
{ provide: MessagingServiceAbstraction, useValue: messagingService },
|
|
||||||
{ provide: BroadcasterService, useValue: broadcasterService },
|
|
||||||
{ provide: StorageServiceAbstraction, useValue: storageService },
|
|
||||||
{ provide: StateServiceAbstraction, useValue: stateService },
|
|
||||||
{ provide: LogServiceAbstraction, useValue: logService },
|
|
||||||
{ provide: ConfigurationService, useValue: configurationService },
|
|
||||||
{ provide: SyncService, useValue: syncService },
|
|
||||||
{ provide: PasswordGenerationServiceAbstraction, useValue: passwordGenerationService },
|
|
||||||
{ provide: CryptoFunctionServiceAbstraction, useValue: cryptoFunctionService },
|
|
||||||
{ provide: PolicyServiceAbstraction, useValue: policyService },
|
|
||||||
{
|
{
|
||||||
provide: APP_INITIALIZER,
|
provide: APP_INITIALIZER,
|
||||||
useFactory: initFactory,
|
useFactory: initFactory,
|
||||||
deps: [],
|
deps: [
|
||||||
|
EnvironmentServiceAbstraction,
|
||||||
|
I18nServiceAbstraction,
|
||||||
|
PlatformUtilsServiceAbstraction,
|
||||||
|
StateServiceAbstraction,
|
||||||
|
CryptoServiceAbstraction,
|
||||||
|
],
|
||||||
multi: true,
|
multi: true,
|
||||||
},
|
},
|
||||||
|
{ provide: LogServiceAbstraction, useClass: ElectronLogService, deps: [] },
|
||||||
|
{
|
||||||
|
provide: I18nServiceAbstraction,
|
||||||
|
useFactory: (window: Window) => new I18nService(window.navigator.language, "./locales"),
|
||||||
|
deps: [WINDOW_TOKEN],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: MessagingServiceAbstraction,
|
||||||
|
useClass: ElectronRendererMessagingService,
|
||||||
|
},
|
||||||
|
{ provide: StorageServiceAbstraction, useClass: ElectronRendererStorageService },
|
||||||
|
{ provide: SECURE_STORAGE, useClass: ElectronRendererSecureStorageService },
|
||||||
|
{ provide: CLIENT_TYPE, useValue: ClientType.DirectoryConnector },
|
||||||
|
{
|
||||||
|
provide: PlatformUtilsServiceAbstraction,
|
||||||
|
useClass: ElectronPlatformUtilsService,
|
||||||
|
},
|
||||||
|
{ provide: CryptoFunctionServiceAbstraction, useClass: NodeCryptoFunctionService, deps: [] },
|
||||||
|
{
|
||||||
|
provide: ApiServiceAbstraction,
|
||||||
|
useFactory: (
|
||||||
|
tokenService: TokenServiceAbstraction,
|
||||||
|
platformUtilsService: PlatformUtilsServiceAbstraction,
|
||||||
|
environmentService: EnvironmentServiceAbstraction,
|
||||||
|
messagingService: MessagingServiceAbstraction,
|
||||||
|
injector: Injector
|
||||||
|
) =>
|
||||||
|
new NodeApiService(
|
||||||
|
tokenService,
|
||||||
|
platformUtilsService,
|
||||||
|
environmentService,
|
||||||
|
async (expired: boolean) => messagingService.send("logout", { expired: expired }),
|
||||||
|
"Bitwarden_DC/" +
|
||||||
|
platformUtilsService.getApplicationVersion() +
|
||||||
|
" (" +
|
||||||
|
platformUtilsService.getDeviceString().toUpperCase() +
|
||||||
|
")",
|
||||||
|
refreshTokenCallback(injector)
|
||||||
|
),
|
||||||
|
deps: [
|
||||||
|
TokenServiceAbstraction,
|
||||||
|
PlatformUtilsServiceAbstraction,
|
||||||
|
EnvironmentServiceAbstraction,
|
||||||
|
MessagingServiceAbstraction,
|
||||||
|
Injector,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: AuthServiceAbstraction,
|
||||||
|
useClass: AuthService,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: SyncService,
|
||||||
|
useClass: SyncService,
|
||||||
|
},
|
||||||
|
AuthGuardService,
|
||||||
|
LaunchGuardService,
|
||||||
|
{
|
||||||
|
provide: STATE_FACTORY,
|
||||||
|
useFactory: () => new StateFactory(GlobalState, Account),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: USE_SECURE_STORAGE_FOR_SECRETS,
|
||||||
|
useValue: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: StateMigrationServiceAbstraction,
|
||||||
|
useClass: StateMigrationService,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: StateServiceAbstraction,
|
||||||
|
useClass: StateService,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: TwoFactorServiceAbstraction,
|
||||||
|
useClass: NoopTwoFactorService,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ServicesModule {
|
export class ServicesModule {}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,97 +1,108 @@
|
|||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<h3 class="card-header">{{'sync' | i18n}}</h3>
|
<h3 class="card-header">{{ "sync" | i18n }}</h3>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>
|
<p>
|
||||||
{{'lastGroupSync' | i18n}}:
|
{{ "lastGroupSync" | i18n }}:
|
||||||
<span *ngIf="!lastGroupSync">-</span>
|
<span *ngIf="!lastGroupSync">-</span>
|
||||||
{{lastGroupSync | date:'medium'}}
|
{{ lastGroupSync | date: "medium" }}
|
||||||
<br /> {{'lastUserSync' | i18n}}:
|
<br />
|
||||||
|
{{ "lastUserSync" | i18n }}:
|
||||||
<span *ngIf="!lastUserSync">-</span>
|
<span *ngIf="!lastUserSync">-</span>
|
||||||
{{lastUserSync | date:'medium'}}
|
{{ lastUserSync | date: "medium" }}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{{'syncStatus' | i18n}}:
|
{{ "syncStatus" | i18n }}:
|
||||||
<strong *ngIf="syncRunning" class="text-success">{{'running' | i18n}}</strong>
|
<strong *ngIf="syncRunning" class="text-success">{{ "running" | i18n }}</strong>
|
||||||
<strong *ngIf="!syncRunning" class="text-danger">{{'stopped' | i18n}}</strong>
|
<strong *ngIf="!syncRunning" class="text-danger">{{ "stopped" | i18n }}</strong>
|
||||||
</p>
|
</p>
|
||||||
<form #startForm [appApiAction]="startPromise" class="d-inline">
|
<form #startForm [appApiAction]="startPromise" class="d-inline">
|
||||||
<button (click)="start()" class="btn btn-primary"
|
<button (click)="start()" class="btn btn-primary" [disabled]="startForm.loading">
|
||||||
[disabled]="startForm.loading">
|
<i class="bwi bwi-play bwi-fw" [hidden]="startForm.loading"></i>
|
||||||
<i class="fa fa-play fa-fw" [hidden]="startForm.loading"></i>
|
<i class="bwi bwi-spinner bwi-fw bwi-spin" [hidden]="!startForm.loading"></i>
|
||||||
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!startForm.loading"></i>
|
{{ "startSync" | i18n }}
|
||||||
{{'startSync' | i18n}}
|
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<button (click)="stop()" class="btn btn-primary">
|
<button (click)="stop()" class="btn btn-primary">
|
||||||
<i class="fa fa-stop fa-fw"></i>
|
<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 (click)="sync()" class="btn btn-primary"
|
<button (click)="sync()" class="btn btn-primary" [disabled]="syncForm.loading">
|
||||||
[disabled]="syncForm.loading">
|
<i class="bwi bwi-refresh bwi-fw" [ngClass]="{ 'bwi-spin': syncForm.loading }"></i>
|
||||||
<i class="fa fa-refresh fa-fw" [ngClass]="{'fa-spin': syncForm.loading}"></i>
|
{{ "syncNow" | i18n }}
|
||||||
{{'syncNow' | i18n}}
|
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3 class="card-header">{{'testing' | i18n}}</h3>
|
<h3 class="card-header">{{ "testing" | i18n }}</h3>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>{{'testingDesc' | i18n}}</p>
|
<p>{{ "testingDesc" | i18n }}</p>
|
||||||
<form #simForm [appApiAction]="simPromise" class="d-inline">
|
<form #simForm [appApiAction]="simPromise" class="d-inline">
|
||||||
<button (click)="simulate()" class="btn btn-primary"
|
<button (click)="simulate()" class="btn btn-primary" [disabled]="simForm.loading">
|
||||||
[disabled]="simForm.loading">
|
<i class="bwi bwi-spinner bwi-fw bwi-spin" [hidden]="!simForm.loading"></i>
|
||||||
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!simForm.loading"></i>
|
<i class="bwi bwi-bug bwi-fw" [hidden]="simForm.loading"></i>
|
||||||
<i class="fa fa-bug fa-fw" [hidden]="simForm.loading"></i>
|
{{ "testNow" | i18n }}
|
||||||
{{'testNow' | i18n}}
|
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<div class="form-check mt-2">
|
<div class="form-check mt-2">
|
||||||
<input class="form-check-input" type="checkbox" id="simSinceLast" [(ngModel)]="simSinceLast">
|
<input
|
||||||
<label class="form-check-label" for="simSinceLast">{{'testLastSync' | i18n}}</label>
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="simSinceLast"
|
||||||
|
[(ngModel)]="simSinceLast"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="simSinceLast">{{ "testLastSync" | i18n }}</label>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="!simForm.loading && (simUsers || simGroups)">
|
<ng-container *ngIf="!simForm.loading && (simUsers || simGroups)">
|
||||||
<hr />
|
<hr />
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg">
|
<div class="col-lg">
|
||||||
<h4>{{'users' | i18n}}</h4>
|
<h4>{{ "users" | i18n }}</h4>
|
||||||
<ul class="fa-ul testing-list" *ngIf="simEnabledUsers && simEnabledUsers.length">
|
<ul class="bwi-ul testing-list" *ngIf="simEnabledUsers && simEnabledUsers.length">
|
||||||
<li *ngFor="let u of simEnabledUsers" title="{{ u.referenceId }}">
|
<li *ngFor="let u of simEnabledUsers" title="{{ u.referenceId }}">
|
||||||
<i class="fa-li fa fa-user"></i>
|
<i class="bwi bwi-li bwi-user"></i>
|
||||||
{{ u.displayName }}
|
{{ u.displayName }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p *ngIf="!simEnabledUsers || !simEnabledUsers.length">{{'noUsers' | i18n}}</p>
|
<p *ngIf="!simEnabledUsers || !simEnabledUsers.length">
|
||||||
<h4>{{'disabledUsers' | i18n}}</h4>
|
{{ "noUsers" | i18n }}
|
||||||
<ul class="fa-ul testing-list" *ngIf="simDisabledUsers && simDisabledUsers.length">
|
</p>
|
||||||
|
<h4>{{ "disabledUsers" | i18n }}</h4>
|
||||||
|
<ul class="bwi-ul testing-list" *ngIf="simDisabledUsers && simDisabledUsers.length">
|
||||||
<li *ngFor="let u of simDisabledUsers" title="{{ u.referenceId }}">
|
<li *ngFor="let u of simDisabledUsers" title="{{ u.referenceId }}">
|
||||||
<i class="fa-li fa fa-user"></i>
|
<i class="bwi bwi-li bwi-user"></i>
|
||||||
{{ u.displayName }}
|
{{ u.displayName }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p *ngIf="!simDisabledUsers || !simDisabledUsers.length">{{'noUsers' | i18n}}</p>
|
<p *ngIf="!simDisabledUsers || !simDisabledUsers.length">
|
||||||
<h4>{{'deletedUsers' | i18n}}</h4>
|
{{ "noUsers" | i18n }}
|
||||||
<ul class="fa-ul testing-list" *ngIf="simDeletedUsers && simDeletedUsers.length">
|
</p>
|
||||||
|
<h4>{{ "deletedUsers" | i18n }}</h4>
|
||||||
|
<ul class="bwi-ul testing-list" *ngIf="simDeletedUsers && simDeletedUsers.length">
|
||||||
<li *ngFor="let u of simDeletedUsers" title="{{ u.referenceId }}">
|
<li *ngFor="let u of simDeletedUsers" title="{{ u.referenceId }}">
|
||||||
<i class="fa-li fa fa-user"></i>
|
<i class="bwi bwi-li bwi-user"></i>
|
||||||
{{ u.displayName }}
|
{{ u.displayName }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p *ngIf="!simDeletedUsers || !simDeletedUsers.length">{{'noUsers' | i18n}}</p>
|
<p *ngIf="!simDeletedUsers || !simDeletedUsers.length">
|
||||||
|
{{ "noUsers" | i18n }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg">
|
<div class="col-lg">
|
||||||
<h4>{{'groups' | i18n}}</h4>
|
<h4>{{ "groups" | i18n }}</h4>
|
||||||
<ul class="fa-ul testing-list" *ngIf="simGroups && simGroups.length">
|
<ul class="bwi-ul testing-list" *ngIf="simGroups && simGroups.length">
|
||||||
<li *ngFor="let g of simGroups" title="{{ g.referenceId }}">
|
<li *ngFor="let g of simGroups" title="{{ g.referenceId }}">
|
||||||
<i class="fa-li fa fa-sitemap"></i>
|
<i class="bwi bwi-li bwi-sitemap"></i>
|
||||||
{{ g.displayName }}
|
{{ g.displayName }}
|
||||||
<ul class="small" *ngIf="g.users && g.users.length">
|
<ul class="small" *ngIf="g.users && g.users.length">
|
||||||
<li *ngFor="let u of g.users" title="{{u.referenceId}}">{{u.displayName}}</li>
|
<li *ngFor="let u of g.users" title="{{ u.referenceId }}">
|
||||||
|
{{ u.displayName }}
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p *ngIf="!simGroups || !simGroups.length">{{'noGroups' | i18n}}</p>
|
<p *ngIf="!simGroups || !simGroups.length">{{ "noGroups" | i18n }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -1,33 +1,22 @@
|
|||||||
import {
|
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||||
ChangeDetectorRef,
|
|
||||||
Component,
|
|
||||||
NgZone,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { ToasterService } from 'angular2-toaster';
|
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||||
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { StateService } from "../../abstractions/state.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { GroupEntry } from "../../models/groupEntry";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { SimResult } from "../../models/simResult";
|
||||||
|
import { UserEntry } from "../../models/userEntry";
|
||||||
|
import { SyncService } from "../../services/sync.service";
|
||||||
|
import { ConnectorUtils } from "../../utils";
|
||||||
|
|
||||||
import { SyncService } from '../../services/sync.service';
|
const BroadcasterSubscriptionId = "DashboardComponent";
|
||||||
|
|
||||||
import { GroupEntry } from '../../models/groupEntry';
|
|
||||||
import { SimResult } from '../../models/simResult';
|
|
||||||
import { UserEntry } from '../../models/userEntry';
|
|
||||||
import { ConfigurationService } from '../../services/configuration.service';
|
|
||||||
|
|
||||||
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
|
|
||||||
|
|
||||||
import { ConnectorUtils } from '../../utils';
|
|
||||||
|
|
||||||
const BroadcasterSubscriptionId = 'DashboardComponent';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard',
|
selector: "app-dashboard",
|
||||||
templateUrl: 'dashboard.component.html',
|
templateUrl: "dashboard.component.html",
|
||||||
})
|
})
|
||||||
export class DashboardComponent implements OnInit, OnDestroy {
|
export class DashboardComponent implements OnInit, OnDestroy {
|
||||||
simGroups: GroupEntry[];
|
simGroups: GroupEntry[];
|
||||||
@@ -36,24 +25,29 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
simDisabledUsers: UserEntry[] = [];
|
simDisabledUsers: UserEntry[] = [];
|
||||||
simDeletedUsers: UserEntry[] = [];
|
simDeletedUsers: UserEntry[] = [];
|
||||||
simPromise: Promise<SimResult>;
|
simPromise: Promise<SimResult>;
|
||||||
simSinceLast: boolean = false;
|
simSinceLast = false;
|
||||||
syncPromise: Promise<[GroupEntry[], UserEntry[]]>;
|
syncPromise: Promise<[GroupEntry[], UserEntry[]]>;
|
||||||
startPromise: Promise<any>;
|
startPromise: Promise<any>;
|
||||||
lastGroupSync: Date;
|
lastGroupSync: Date;
|
||||||
lastUserSync: Date;
|
lastUserSync: Date;
|
||||||
syncRunning: boolean;
|
syncRunning: boolean;
|
||||||
|
|
||||||
constructor(private i18nService: I18nService, private syncService: SyncService,
|
constructor(
|
||||||
private configurationService: ConfigurationService, private broadcasterService: BroadcasterService,
|
private i18nService: I18nService,
|
||||||
private ngZone: NgZone, private messagingService: MessagingService,
|
private syncService: SyncService,
|
||||||
private toasterService: ToasterService, private changeDetectorRef: ChangeDetectorRef,
|
private broadcasterService: BroadcasterService,
|
||||||
private stateService: StateService) { }
|
private ngZone: NgZone,
|
||||||
|
private messagingService: MessagingService,
|
||||||
|
private platformUtilsService: PlatformUtilsService,
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private stateService: StateService
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||||
this.ngZone.run(async () => {
|
this.ngZone.run(async () => {
|
||||||
switch (message.command) {
|
switch (message.command) {
|
||||||
case 'dirSyncCompleted':
|
case "dirSyncCompleted":
|
||||||
this.updateLastSync();
|
this.updateLastSync();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -64,7 +58,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.syncRunning = !!(await this.stateService.get('syncingDir'));
|
this.syncRunning = !!(await this.stateService.getSyncingDir());
|
||||||
this.updateLastSync();
|
this.updateLastSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,15 +69,15 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
async start() {
|
async start() {
|
||||||
this.startPromise = this.syncService.sync(false, false);
|
this.startPromise = this.syncService.sync(false, false);
|
||||||
await this.startPromise;
|
await this.startPromise;
|
||||||
this.messagingService.send('scheduleNextDirSync');
|
this.messagingService.send("scheduleNextDirSync");
|
||||||
this.syncRunning = true;
|
this.syncRunning = true;
|
||||||
this.toasterService.popAsync('success', null, this.i18nService.t('syncingStarted'));
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("syncingStarted"));
|
||||||
}
|
}
|
||||||
|
|
||||||
async stop() {
|
async stop() {
|
||||||
this.messagingService.send('cancelDirSync');
|
this.messagingService.send("cancelDirSync");
|
||||||
this.syncRunning = false;
|
this.syncRunning = false;
|
||||||
this.toasterService.popAsync('success', null, this.i18nService.t('syncingStopped'));
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("syncingStopped"));
|
||||||
}
|
}
|
||||||
|
|
||||||
async sync() {
|
async sync() {
|
||||||
@@ -91,8 +85,11 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
const result = await this.syncPromise;
|
const result = await this.syncPromise;
|
||||||
const groupCount = result[0] != null ? result[0].length : 0;
|
const groupCount = result[0] != null ? result[0].length : 0;
|
||||||
const userCount = result[1] != null ? result[1].length : 0;
|
const userCount = result[1] != null ? result[1].length : 0;
|
||||||
this.toasterService.popAsync('success', null,
|
this.platformUtilsService.showToast(
|
||||||
this.i18nService.t('syncCounts', groupCount.toString(), userCount.toString()));
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("syncCounts", groupCount.toString(), userCount.toString())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async simulate() {
|
async simulate() {
|
||||||
@@ -103,7 +100,11 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
this.simDeletedUsers = [];
|
this.simDeletedUsers = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.simPromise = ConnectorUtils.simulate(this.syncService, this.i18nService, this.simSinceLast);
|
this.simPromise = ConnectorUtils.simulate(
|
||||||
|
this.syncService,
|
||||||
|
this.i18nService,
|
||||||
|
this.simSinceLast
|
||||||
|
);
|
||||||
const result = await this.simPromise;
|
const result = await this.simPromise;
|
||||||
this.simGroups = result.groups;
|
this.simGroups = result.groups;
|
||||||
this.simUsers = result.users;
|
this.simUsers = result.users;
|
||||||
@@ -117,7 +118,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async updateLastSync() {
|
private async updateLastSync() {
|
||||||
this.lastGroupSync = await this.configurationService.getLastGroupSyncDate();
|
this.lastGroupSync = await this.stateService.getLastGroupSync();
|
||||||
this.lastUserSync = await this.configurationService.getLastUserSyncDate();
|
this.lastUserSync = await this.stateService.getLastUserSync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,36 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3 class="card-header">{{'about' | i18n}}</h3>
|
<h3 class="card-header">{{ "about" | i18n }}</h3>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>
|
<p>
|
||||||
{{'bitwardenDirectoryConnector' | i18n}}
|
{{ "bitwardenDirectoryConnector" | i18n }}
|
||||||
<br /> {{'version' | i18n : version}}
|
<br />
|
||||||
<br /> © Bitwarden Inc. LLC 2015-{{year}}
|
{{ "version" | i18n: version }} <br />
|
||||||
|
© Bitwarden Inc. LLC 2015-{{ year }}
|
||||||
</p>
|
</p>
|
||||||
<button class="btn btn-primary" type="button" (click)="update()" [disabled]="checkingForUpdate">
|
<button
|
||||||
<i class="fa fa-download fa-fw" [hidden]="checkingForUpdate"></i>
|
class="btn btn-primary"
|
||||||
<i class="fa fa-spinner fa-fw fa-spin" [hidden]="!checkingForUpdate"></i>
|
type="button"
|
||||||
{{'checkForUpdates' | i18n}}
|
(click)="update()"
|
||||||
|
[disabled]="checkingForUpdate"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-download bwi-fw" [hidden]="checkingForUpdate"></i>
|
||||||
|
<i class="bwi bwi-spinner bwi-fw bwi-spin" [hidden]="!checkingForUpdate"></i>
|
||||||
|
{{ "checkForUpdates" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3 class="card-header">{{'other' | i18n}}</h3>
|
<h3 class="card-header">{{ "other" | i18n }}</h3>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<button class="btn btn-primary" type="button" (click)="logOut()">
|
<button class="btn btn-primary" type="button" (click)="logOut()">
|
||||||
{{'logOut' | i18n}}
|
{{ "logOut" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-primary" type="button" (click)="clearCache()">
|
<button class="btn btn-primary" type="button" (click)="clearCache()">
|
||||||
{{'clearSyncCache' | i18n}}
|
{{ "clearSyncCache" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,45 +1,41 @@
|
|||||||
import {
|
import { ChangeDetectorRef, Component, NgZone, OnInit } from "@angular/core";
|
||||||
ChangeDetectorRef,
|
|
||||||
Component,
|
|
||||||
NgZone,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { ToasterService } from 'angular2-toaster';
|
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||||
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
import { BroadcasterService } from 'jslib-angular/services/broadcaster.service';
|
import { StateService } from "../../abstractions/state.service";
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
const BroadcasterSubscriptionId = "MoreComponent";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
|
||||||
|
|
||||||
import { ConfigurationService } from '../../services/configuration.service';
|
|
||||||
|
|
||||||
const BroadcasterSubscriptionId = 'MoreComponent';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-more',
|
selector: "app-more",
|
||||||
templateUrl: 'more.component.html',
|
templateUrl: "more.component.html",
|
||||||
})
|
})
|
||||||
export class MoreComponent implements OnInit {
|
export class MoreComponent implements OnInit {
|
||||||
version: string;
|
version: string;
|
||||||
year: string;
|
year: string;
|
||||||
checkingForUpdate = false;
|
checkingForUpdate = false;
|
||||||
|
|
||||||
constructor(private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
|
constructor(
|
||||||
private messagingService: MessagingService, private configurationService: ConfigurationService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private toasterService: ToasterService, private broadcasterService: BroadcasterService,
|
private i18nService: I18nService,
|
||||||
private ngZone: NgZone, private changeDetectorRef: ChangeDetectorRef) { }
|
private messagingService: MessagingService,
|
||||||
|
private broadcasterService: BroadcasterService,
|
||||||
|
private ngZone: NgZone,
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private stateService: StateService
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||||
this.ngZone.run(async () => {
|
this.ngZone.run(async () => {
|
||||||
switch (message.command) {
|
switch (message.command) {
|
||||||
case 'checkingForUpdate':
|
case "checkingForUpdate":
|
||||||
this.checkingForUpdate = true;
|
this.checkingForUpdate = true;
|
||||||
break;
|
break;
|
||||||
case 'doneCheckingForUpdate':
|
case "doneCheckingForUpdate":
|
||||||
this.checkingForUpdate = false;
|
this.checkingForUpdate = false;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -59,20 +55,23 @@ export class MoreComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
this.messagingService.send('checkForUpdate');
|
this.messagingService.send("checkForUpdate");
|
||||||
}
|
}
|
||||||
|
|
||||||
async logOut() {
|
async logOut() {
|
||||||
const confirmed = await this.platformUtilsService.showDialog(
|
const confirmed = await this.platformUtilsService.showDialog(
|
||||||
this.i18nService.t('logOutConfirmation'), this.i18nService.t('logOut'),
|
this.i18nService.t("logOutConfirmation"),
|
||||||
this.i18nService.t('yes'), this.i18nService.t('cancel'));
|
this.i18nService.t("logOut"),
|
||||||
|
this.i18nService.t("yes"),
|
||||||
|
this.i18nService.t("cancel")
|
||||||
|
);
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
this.messagingService.send('logout');
|
this.messagingService.send("logout");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearCache() {
|
async clearCache() {
|
||||||
await this.configurationService.clearStatefulSettings(true);
|
await this.stateService.clearSyncSettings(true);
|
||||||
this.toasterService.popAsync('success', null, this.i18nService.t('syncCacheCleared'));
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("syncCacheCleared"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,132 +1,245 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<h3 class="card-header">{{'directory' | i18n}}</h3>
|
<h3 class="card-header">{{ "directory" | i18n }}</h3>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="directory">{{'type' | i18n}}</label>
|
<label for="directory">{{ "type" | i18n }}</label>
|
||||||
<select class="form-control" id="directory" name="Directory" [(ngModel)]="directory">
|
<select class="form-control" id="directory" name="Directory" [(ngModel)]="directory">
|
||||||
<option *ngFor="let o of directoryOptions" [ngValue]="o.value">{{o.name}}</option>
|
<option *ngFor="let o of directoryOptions" [ngValue]="o.value">
|
||||||
|
{{ o.name }}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="directory != directoryType.Ldap">
|
<div [hidden]="directory != directoryType.Ldap">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="hostname">{{'serverHostname' | i18n}}</label>
|
<label for="hostname">{{ "serverHostname" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="hostname" name="Hostname"
|
<input
|
||||||
[(ngModel)]="ldap.hostname">
|
type="text"
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} ad.company.com</small>
|
class="form-control"
|
||||||
|
id="hostname"
|
||||||
|
name="Hostname"
|
||||||
|
[(ngModel)]="ldap.hostname"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text">{{ "ex" | i18n }} ad.company.com</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="port">{{'port' | i18n}}</label>
|
<label for="port">{{ "port" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="port" name="Port" [(ngModel)]="ldap.port">
|
<input type="text" class="form-control" id="port" name="Port" [(ngModel)]="ldap.port" />
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} 389</small>
|
<small class="text-muted form-text">{{ "ex" | i18n }} 389</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="rootPath">{{'rootPath' | i18n}}</label>
|
<label for="rootPath">{{ "rootPath" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="rootPath" name="RootPath"
|
<input
|
||||||
[(ngModel)]="ldap.rootPath">
|
type="text"
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} dc=company,dc=com</small>
|
class="form-control"
|
||||||
|
id="rootPath"
|
||||||
|
name="RootPath"
|
||||||
|
[(ngModel)]="ldap.rootPath"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text">{{ "ex" | i18n }} dc=company,dc=com</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="ad" [(ngModel)]="ldap.ad" name="AD">
|
<input
|
||||||
<label class="form-check-label" for="ad">{{'ldapAd' | i18n}}</label>
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="ad"
|
||||||
|
[(ngModel)]="ldap.ad"
|
||||||
|
name="AD"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="ad">{{ "ldapAd" | i18n }}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" *ngIf="!ldap.ad">
|
<div class="form-group" *ngIf="!ldap.ad">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="pagedSearch"
|
<input
|
||||||
[(ngModel)]="ldap.pagedSearch" name="PagedSearch">
|
class="form-check-input"
|
||||||
<label class="form-check-label" for="pagedSearch">{{'ldapPagedResults' | i18n}}</label>
|
type="checkbox"
|
||||||
|
id="pagedSearch"
|
||||||
|
[(ngModel)]="ldap.pagedSearch"
|
||||||
|
name="PagedSearch"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="pagedSearch">{{
|
||||||
|
"ldapPagedResults" | i18n
|
||||||
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="ldapEncrypted" [(ngModel)]="ldap.ssl"
|
<input
|
||||||
name="Encrypted">
|
class="form-check-input"
|
||||||
<label class="form-check-label" for="ldapEncrypted">{{'ldapEncrypted' | i18n}}</label>
|
type="checkbox"
|
||||||
|
id="ldapEncrypted"
|
||||||
|
[(ngModel)]="ldap.ssl"
|
||||||
|
name="Encrypted"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="ldapEncrypted">{{
|
||||||
|
"ldapEncrypted" | i18n
|
||||||
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4" *ngIf="ldap.ssl">
|
<div class="ml-4" *ngIf="ldap.ssl">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-radio">
|
<div class="form-radio">
|
||||||
<input class="form-radio-input" type="radio" [value]="false" id="ssl"
|
<input
|
||||||
[(ngModel)]="ldap.startTls" name="SSL">
|
class="form-radio-input"
|
||||||
<label class="form-radio-label" for="ssl">{{'ldapSsl' | i18n}}</label>
|
type="radio"
|
||||||
|
[value]="false"
|
||||||
|
id="ssl"
|
||||||
|
[(ngModel)]="ldap.startTls"
|
||||||
|
name="SSL"
|
||||||
|
/>
|
||||||
|
<label class="form-radio-label" for="ssl">{{ "ldapSsl" | i18n }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-radio">
|
<div class="form-radio">
|
||||||
<input class="form-radio-input" type="radio" [value]="true" id="startTls"
|
<input
|
||||||
[(ngModel)]="ldap.startTls" name="StartTLS">
|
class="form-radio-input"
|
||||||
<label class="form-radio-label" for="startTls">{{'ldapTls' | i18n}}</label>
|
type="radio"
|
||||||
|
[value]="true"
|
||||||
|
id="startTls"
|
||||||
|
[(ngModel)]="ldap.startTls"
|
||||||
|
name="StartTLS"
|
||||||
|
/>
|
||||||
|
<label class="form-radio-label" for="startTls">{{ "ldapTls" | i18n }}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4" *ngIf="ldap.startTls">
|
<div class="ml-4" *ngIf="ldap.startTls">
|
||||||
<p>{{'ldapTlsUntrustedDesc' | i18n}}</p>
|
<p>{{ "ldapTlsUntrustedDesc" | i18n }}</p>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="tlsCaPath">{{'ldapTlsCa' | i18n}}</label>
|
<label for="tlsCaPath">{{ "ldapTlsCa" | i18n }}</label>
|
||||||
<input type="file" class="form-control-file mb-2" id="tlsCaPath_file"
|
<input
|
||||||
(change)="setSslPath('tlsCaPath')">
|
type="file"
|
||||||
<input type="text" class="form-control" id="tlsCaPath" name="TLSCaPath"
|
class="form-control-file mb-2"
|
||||||
[(ngModel)]="ldap.tlsCaPath">
|
id="tlsCaPath_file"
|
||||||
|
(change)="setSslPath('tlsCaPath')"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="tlsCaPath"
|
||||||
|
name="TLSCaPath"
|
||||||
|
[(ngModel)]="ldap.tlsCaPath"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4" *ngIf="!ldap.startTls">
|
<div class="ml-4" *ngIf="!ldap.startTls">
|
||||||
<p>{{'ldapSslUntrustedDesc' | i18n}}</p>
|
<p>{{ "ldapSslUntrustedDesc" | i18n }}</p>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="sslCertPath">{{'ldapSslCert' | i18n}}</label>
|
<label for="sslCertPath">{{ "ldapSslCert" | i18n }}</label>
|
||||||
<input type="file" class="form-control-file mb-2" id="sslCertPath_file"
|
<input
|
||||||
(change)="setSslPath('sslCertPath')">
|
type="file"
|
||||||
<input type="text" class="form-control" id="sslCertPath" name="SSLCertPath"
|
class="form-control-file mb-2"
|
||||||
[(ngModel)]="ldap.sslCertPath">
|
id="sslCertPath_file"
|
||||||
|
(change)="setSslPath('sslCertPath')"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="sslCertPath"
|
||||||
|
name="SSLCertPath"
|
||||||
|
[(ngModel)]="ldap.sslCertPath"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="sslKeyPath">{{'ldapSslKey' | i18n}}</label>
|
<label for="sslKeyPath">{{ "ldapSslKey" | i18n }}</label>
|
||||||
<input type="file" class="form-control-file mb-2" id="sslKeyPath_file"
|
<input
|
||||||
(change)="setSslPath('sslKeyPath')">
|
type="file"
|
||||||
<input type="text" class="form-control" id="sslKeyPath" name="SSLKeyPath"
|
class="form-control-file mb-2"
|
||||||
[(ngModel)]="ldap.sslKeyPath">
|
id="sslKeyPath_file"
|
||||||
|
(change)="setSslPath('sslKeyPath')"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="sslKeyPath"
|
||||||
|
name="SSLKeyPath"
|
||||||
|
[(ngModel)]="ldap.sslKeyPath"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="sslCaPath">{{'ldapSslCa' | i18n}}</label>
|
<label for="sslCaPath">{{ "ldapSslCa" | i18n }}</label>
|
||||||
<input type="file" class="form-control-file mb-2" id="sslCaPath_file"
|
<input
|
||||||
(change)="setSslPath('sslCaPath')">
|
type="file"
|
||||||
<input type="text" class="form-control" id="sslCaPath" name="SSLCaPath"
|
class="form-control-file mb-2"
|
||||||
[(ngModel)]="ldap.sslCaPath">
|
id="sslCaPath_file"
|
||||||
|
(change)="setSslPath('sslCaPath')"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="sslCaPath"
|
||||||
|
name="SSLCaPath"
|
||||||
|
[(ngModel)]="ldap.sslCaPath"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="certDoNotVerify"
|
<input
|
||||||
[(ngModel)]="ldap.sslAllowUnauthorized" name="CertDoNoVerify">
|
class="form-check-input"
|
||||||
<label class="form-check-label" for="certDoNotVerify">{{'ldapCertDoNotVerify' |
|
type="checkbox"
|
||||||
i18n}}</label>
|
id="certDoNotVerify"
|
||||||
|
[(ngModel)]="ldap.sslAllowUnauthorized"
|
||||||
|
name="CertDoNoVerify"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="certDoNotVerify">{{
|
||||||
|
"ldapCertDoNotVerify" | i18n
|
||||||
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" [hidden]="true">
|
<div class="form-group" [hidden]="true">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="currentUser"
|
<input
|
||||||
[(ngModel)]="ldap.currentUser" name="CurrentUser">
|
class="form-check-input"
|
||||||
<label class="form-check-label" for="currentUser">{{'currentUser' | i18n}}</label>
|
type="checkbox"
|
||||||
|
id="currentUser"
|
||||||
|
[(ngModel)]="ldap.currentUser"
|
||||||
|
name="CurrentUser"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="currentUser">{{ "currentUser" | i18n }}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="ldap.currentUser">
|
<div [hidden]="ldap.currentUser">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="username">{{'username' | i18n}}</label>
|
<label for="username">{{ "username" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="username" name="Username"
|
<input
|
||||||
[(ngModel)]="ldap.username">
|
type="text"
|
||||||
<small class="text-muted form-text" *ngIf="ldap.ad">{{'ex' | i18n}} company\admin</small>
|
class="form-control"
|
||||||
<small class="text-muted form-text" *ngIf="!ldap.ad">{{'ex' | i18n}}
|
id="username"
|
||||||
cn=admin,dc=company,dc=com</small>
|
name="Username"
|
||||||
|
[(ngModel)]="ldap.username"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text" *ngIf="ldap.ad"
|
||||||
|
>{{ "ex" | i18n }} company\admin</small
|
||||||
|
>
|
||||||
|
<small class="text-muted form-text" *ngIf="!ldap.ad"
|
||||||
|
>{{ "ex" | i18n }} cn=admin,dc=company,dc=com</small
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password">{{'password' | i18n}}</label>
|
<label for="password">{{ "password" | i18n }}</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="{{showLdapPassword ? 'text' : 'password'}}" class="form-control" id="password" name="Password"
|
<input
|
||||||
[(ngModel)]="ldap.password">
|
type="{{ showLdapPassword ? 'text' : 'password' }}"
|
||||||
|
class="form-control"
|
||||||
|
id="password"
|
||||||
|
name="Password"
|
||||||
|
[(ngModel)]="ldap.password"
|
||||||
|
/>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button type="button" class="btn btn-outline-secondary" appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleLdapPassword()">
|
<button
|
||||||
<i class="fa fa-lg" aria-hidden="true"[ngClass]="showLdapPassword ? 'fa-eye-slash' : 'fa-eye'"></i>
|
type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||||
|
(click)="toggleLdapPassword()"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="bwi bwi-lg"
|
||||||
|
aria-hidden="true"
|
||||||
|
[ngClass]="showLdapPassword ? 'bwi-eye-slash' : 'bwi-eye'"
|
||||||
|
></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -135,23 +248,60 @@
|
|||||||
</div>
|
</div>
|
||||||
<div [hidden]="directory != directoryType.AzureActiveDirectory">
|
<div [hidden]="directory != directoryType.AzureActiveDirectory">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="tenant">{{'tenant' | i18n}}</label>
|
<label for="identityAuthority">{{ "identityAuthority" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="tenant" name="Tenant" [(ngModel)]="azure.tenant">
|
<select
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} companyad.onmicrosoft.com</small>
|
class="form-control"
|
||||||
|
id="identityAuthority"
|
||||||
|
name="IdentityAuthority"
|
||||||
|
[(ngModel)]="azure.identityAuthority"
|
||||||
|
>
|
||||||
|
<option value="login.microsoftonline.com">Azure AD Public</option>
|
||||||
|
<option value="login.microsoftonline.us">Azure AD Government</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="applicationId">{{'applicationId' | i18n}}</label>
|
<label for="tenant">{{ "tenant" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="applicationId" name="ApplicationId"
|
<input
|
||||||
[(ngModel)]="azure.applicationId">
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="tenant"
|
||||||
|
name="Tenant"
|
||||||
|
[(ngModel)]="azure.tenant"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text">{{ "ex" | i18n }} companyad.onmicrosoft.com</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="secretKey">{{'secretKey' | i18n}}</label>
|
<label for="applicationId">{{ "applicationId" | i18n }}</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="applicationId"
|
||||||
|
name="ApplicationId"
|
||||||
|
[(ngModel)]="azure.applicationId"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="secretKey">{{ "secretKey" | i18n }}</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="{{showAzureKey ? 'text' : 'password'}}" class="form-control" id="secretKey" name="SecretKey"
|
<input
|
||||||
[(ngModel)]="azure.key">
|
type="{{ showAzureKey ? 'text' : 'password' }}"
|
||||||
|
class="form-control"
|
||||||
|
id="secretKey"
|
||||||
|
name="SecretKey"
|
||||||
|
[(ngModel)]="azure.key"
|
||||||
|
/>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button type="button" class="btn btn-outline-secondary" appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleAzureKey()">
|
<button
|
||||||
<i class="fa fa-lg" aria-hidden="true"[ngClass]="showAzureKey ? 'fa-eye-slash' : 'fa-eye'"></i>
|
type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||||
|
(click)="toggleAzureKey()"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="bwi bwi-lg"
|
||||||
|
aria-hidden="true"
|
||||||
|
[ngClass]="showAzureKey ? 'bwi-eye-slash' : 'bwi-eye'"
|
||||||
|
></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -159,18 +309,40 @@
|
|||||||
</div>
|
</div>
|
||||||
<div [hidden]="directory != directoryType.Okta">
|
<div [hidden]="directory != directoryType.Okta">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="orgUrl">{{'organizationUrl' | i18n}}</label>
|
<label for="orgUrl">{{ "organizationUrl" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="orgUrl" name="OrgUrl" [(ngModel)]="okta.orgUrl">
|
<input
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} https://mycompany.okta.com/</small>
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="orgUrl"
|
||||||
|
name="OrgUrl"
|
||||||
|
[(ngModel)]="okta.orgUrl"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text"
|
||||||
|
>{{ "ex" | i18n }} https://mycompany.okta.com/</small
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="oktaToken">{{'token' | i18n}}</label>
|
<label for="oktaToken">{{ "token" | i18n }}</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="{{showOktaKey ? 'text' : 'password'}}" class="form-control" id="oktaToken" name="OktaToken"
|
<input
|
||||||
[(ngModel)]="okta.token">
|
type="{{ showOktaKey ? 'text' : 'password' }}"
|
||||||
|
class="form-control"
|
||||||
|
id="oktaToken"
|
||||||
|
name="OktaToken"
|
||||||
|
[(ngModel)]="okta.token"
|
||||||
|
/>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button type="button" class="btn btn-outline-secondary" appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleOktaKey()">
|
<button
|
||||||
<i class="fa fa-lg" aria-hidden="true"[ngClass]="showOktaKey ? 'fa-eye-slash' : 'fa-eye'"></i>
|
type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||||
|
(click)="toggleOktaKey()"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="bwi bwi-lg"
|
||||||
|
aria-hidden="true"
|
||||||
|
[ngClass]="showOktaKey ? 'bwi-eye-slash' : 'bwi-eye'"
|
||||||
|
></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -178,26 +350,49 @@
|
|||||||
</div>
|
</div>
|
||||||
<div [hidden]="directory != directoryType.OneLogin">
|
<div [hidden]="directory != directoryType.OneLogin">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="oneLoginClientId">{{'clientId' | i18n}}</label>
|
<label for="oneLoginClientId">{{ "clientId" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="oneLoginClientId" name="OneLoginClientId"
|
<input
|
||||||
[(ngModel)]="oneLogin.clientId">
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="oneLoginClientId"
|
||||||
|
name="OneLoginClientId"
|
||||||
|
[(ngModel)]="oneLogin.clientId"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="oneLoginClientSecret">{{'clientSecret' | i18n}}</label>
|
<label for="oneLoginClientSecret">{{ "clientSecret" | i18n }}</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="{{showOneLoginSecret ? 'text' : 'password'}}" class="form-control" id="oneLoginClientSecret" name="OneLoginClientSecret"
|
<input
|
||||||
[(ngModel)]="oneLogin.clientSecret">
|
type="{{ showOneLoginSecret ? 'text' : 'password' }}"
|
||||||
|
class="form-control"
|
||||||
|
id="oneLoginClientSecret"
|
||||||
|
name="OneLoginClientSecret"
|
||||||
|
[(ngModel)]="oneLogin.clientSecret"
|
||||||
|
/>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button type="button" class="btn btn-outline-secondary" appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleOneLoginSecret()">
|
<button
|
||||||
<i class="fa fa-lg" aria-hidden="true"[ngClass]="showOneLoginSecret ? 'fa-eye-slash' : 'fa-eye'"></i>
|
type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||||
|
(click)="toggleOneLoginSecret()"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="bwi bwi-lg"
|
||||||
|
aria-hidden="true"
|
||||||
|
[ngClass]="showOneLoginSecret ? 'bwi-eye-slash' : 'bwi-eye'"
|
||||||
|
></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="oneLoginRegion">{{'region' | i18n}}</label>
|
<label for="oneLoginRegion">{{ "region" | i18n }}</label>
|
||||||
<select class="form-control" id="oneLoginRegion" name="OneLoginRegion"
|
<select
|
||||||
[(ngModel)]="oneLogin.region">
|
class="form-control"
|
||||||
|
id="oneLoginRegion"
|
||||||
|
name="OneLoginRegion"
|
||||||
|
[(ngModel)]="oneLogin.region"
|
||||||
|
>
|
||||||
<option value="us">US</option>
|
<option value="us">US</option>
|
||||||
<option value="eu">EU</option>
|
<option value="eu">EU</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -205,209 +400,354 @@
|
|||||||
</div>
|
</div>
|
||||||
<div [hidden]="directory != directoryType.GSuite">
|
<div [hidden]="directory != directoryType.GSuite">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="domain">{{'domain' | i18n}}</label>
|
<label for="domain">{{ "domain" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="domain" name="Domain" [(ngModel)]="gsuite.domain">
|
<input
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} company.com</small>
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="domain"
|
||||||
|
name="Domain"
|
||||||
|
[(ngModel)]="gsuite.domain"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text">{{ "ex" | i18n }} company.com</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="adminUser">{{'adminUser' | i18n}}</label>
|
<label for="adminUser">{{ "adminUser" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="adminUser" name="AdminUser"
|
<input
|
||||||
[(ngModel)]="gsuite.adminUser">
|
type="text"
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} admin@company.com</small>
|
class="form-control"
|
||||||
|
id="adminUser"
|
||||||
|
name="AdminUser"
|
||||||
|
[(ngModel)]="gsuite.adminUser"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text">{{ "ex" | i18n }} admin@company.com</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="customerId">{{'customerId' | i18n}}</label>
|
<label for="customerId">{{ "customerId" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="customerId" name="CustomerId"
|
<input
|
||||||
[(ngModel)]="gsuite.customer">
|
type="text"
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} 39204722352</small>
|
class="form-control"
|
||||||
|
id="customerId"
|
||||||
|
name="CustomerId"
|
||||||
|
[(ngModel)]="gsuite.customer"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text">{{ "ex" | i18n }} 39204722352</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="keyFile">{{'jsonKeyFile' | i18n}}</label>
|
<label for="keyFile">{{ "jsonKeyFile" | i18n }}</label>
|
||||||
<input type="file" class="form-control-file" id="keyFile" accept=".json"
|
<input
|
||||||
(change)="parseKeyFile()">
|
type="file"
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} My Project-jksd3jd223.json</small>
|
class="form-control-file"
|
||||||
|
id="keyFile"
|
||||||
|
accept=".json"
|
||||||
|
(change)="parseKeyFile()"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text">{{ "ex" | i18n }} My Project-jksd3jd223.json</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" [hidden]="!gsuite.clientEmail">
|
<div class="form-group" [hidden]="!gsuite.clientEmail">
|
||||||
<label for="clientEmail">{{'clientEmail' | i18n}}</label>
|
<label for="clientEmail">{{ "clientEmail" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="clientEmail" name="ClientEmail"
|
<input
|
||||||
[(ngModel)]="gsuite.clientEmail">
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="clientEmail"
|
||||||
|
name="ClientEmail"
|
||||||
|
[(ngModel)]="gsuite.clientEmail"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" [hidden]="!gsuite.privateKey">
|
<div class="form-group" [hidden]="!gsuite.privateKey">
|
||||||
<label for="privateKey">{{'privateKey' | i18n}}</label>
|
<label for="privateKey">{{ "privateKey" | i18n }}</label>
|
||||||
<textarea class="form-control text-monospace" id="privateKey" name="PrivateKey"
|
<textarea
|
||||||
[(ngModel)]="gsuite.privateKey">
|
class="form-control text-monospace"
|
||||||
|
id="privateKey"
|
||||||
|
name="PrivateKey"
|
||||||
|
[(ngModel)]="gsuite.privateKey"
|
||||||
|
>
|
||||||
</textarea>
|
</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3 class="card-header">{{'sync' | i18n}}</h3>
|
<h3 class="card-header">{{ "sync" | i18n }}</h3>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="interval">{{'interval' | i18n}}</label>
|
<label for="interval">{{ "interval" | i18n }}</label>
|
||||||
<input type="number" min="5" class="form-control" id="interval" name="Interval"
|
<input
|
||||||
[(ngModel)]="sync.interval">
|
type="number"
|
||||||
<small class="text-muted form-text">{{'intervalMin' | i18n}}</small>
|
min="5"
|
||||||
|
class="form-control"
|
||||||
|
id="interval"
|
||||||
|
name="Interval"
|
||||||
|
[(ngModel)]="sync.interval"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text">{{ "intervalMin" | i18n }}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="removeDisabled"
|
<input
|
||||||
[(ngModel)]="sync.removeDisabled" name="RemoveDisabled">
|
class="form-check-input"
|
||||||
<label class="form-check-label" for="removeDisabled">{{'removeDisabled' | i18n}}</label>
|
type="checkbox"
|
||||||
|
id="removeDisabled"
|
||||||
|
[(ngModel)]="sync.removeDisabled"
|
||||||
|
name="RemoveDisabled"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="removeDisabled">{{
|
||||||
|
"removeDisabled" | i18n
|
||||||
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="overwriteExisting"
|
<input
|
||||||
[(ngModel)]="sync.overwriteExisting" name="OverwriteExisting">
|
class="form-check-input"
|
||||||
<label class="form-check-label" for="overwriteExisting">{{'overwriteExisting' | i18n}}</label>
|
type="checkbox"
|
||||||
|
id="overwriteExisting"
|
||||||
|
[(ngModel)]="sync.overwriteExisting"
|
||||||
|
name="OverwriteExisting"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="overwriteExisting">{{
|
||||||
|
"overwriteExisting" | i18n
|
||||||
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="largeImport" [(ngModel)]="sync.largeImport"
|
<input
|
||||||
name="LargeImport">
|
class="form-check-input"
|
||||||
<label class="form-check-label" for="largeImport">{{'largeImport' | i18n}}</label>
|
type="checkbox"
|
||||||
|
id="largeImport"
|
||||||
|
[(ngModel)]="sync.largeImport"
|
||||||
|
name="LargeImport"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="largeImport">{{ "largeImport" | i18n }}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="directory != directoryType.Ldap">
|
<div [hidden]="directory != directoryType.Ldap">
|
||||||
<div [hidden]="ldap.ad">
|
<div [hidden]="ldap.ad">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="memberAttribute">{{'memberAttribute' | i18n}}</label>
|
<label for="memberAttribute">{{ "memberAttribute" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="memberAttribute" name="MemberAttribute"
|
<input
|
||||||
[(ngModel)]="sync.memberAttribute">
|
type="text"
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} uniqueMember</small>
|
class="form-control"
|
||||||
|
id="memberAttribute"
|
||||||
|
name="MemberAttribute"
|
||||||
|
[(ngModel)]="sync.memberAttribute"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text">{{ "ex" | i18n }} uniqueMember</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="creationDateAttribute">{{'creationDateAttribute' | i18n}}</label>
|
<label for="creationDateAttribute">{{ "creationDateAttribute" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="creationDateAttribute"
|
<input
|
||||||
name="CreationDateAttribute" [(ngModel)]="sync.creationDateAttribute">
|
type="text"
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} whenCreated</small>
|
class="form-control"
|
||||||
|
id="creationDateAttribute"
|
||||||
|
name="CreationDateAttribute"
|
||||||
|
[(ngModel)]="sync.creationDateAttribute"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text">{{ "ex" | i18n }} whenCreated</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="revisionDateAttribute">{{'revisionDateAttribute' | i18n}}</label>
|
<label for="revisionDateAttribute">{{ "revisionDateAttribute" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="revisionDateAttribute"
|
<input
|
||||||
name="RevisionDateAttribute" [(ngModel)]="sync.revisionDateAttribute">
|
type="text"
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} whenChanged</small>
|
class="form-control"
|
||||||
|
id="revisionDateAttribute"
|
||||||
|
name="RevisionDateAttribute"
|
||||||
|
[(ngModel)]="sync.revisionDateAttribute"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text">{{ "ex" | i18n }} whenChanged</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="directory != directoryType.Ldap && directory != directoryType.OneLogin">
|
<div [hidden]="directory != directoryType.Ldap && directory != directoryType.OneLogin">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="useEmailPrefixSuffix"
|
<input
|
||||||
[(ngModel)]="sync.useEmailPrefixSuffix" name="UseEmailPrefixSuffix">
|
class="form-check-input"
|
||||||
<label class="form-check-label"
|
type="checkbox"
|
||||||
for="useEmailPrefixSuffix">{{'useEmailPrefixSuffix' | i18n}}</label>
|
id="useEmailPrefixSuffix"
|
||||||
|
[(ngModel)]="sync.useEmailPrefixSuffix"
|
||||||
|
name="UseEmailPrefixSuffix"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="useEmailPrefixSuffix">{{
|
||||||
|
"useEmailPrefixSuffix" | i18n
|
||||||
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="!sync.useEmailPrefixSuffix">
|
<div [hidden]="!sync.useEmailPrefixSuffix">
|
||||||
<div class="form-group" [hidden]="ldap.ad || directory != directoryType.Ldap">
|
<div class="form-group" [hidden]="ldap.ad || directory != directoryType.Ldap">
|
||||||
<label for="emailPrefixAttribute">{{'emailPrefixAttribute' | i18n}}</label>
|
<label for="emailPrefixAttribute">{{ "emailPrefixAttribute" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="emailPrefixAttribute"
|
<input
|
||||||
name="EmailPrefixAttribute" [(ngModel)]="sync.emailPrefixAttribute">
|
type="text"
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} accountName</small>
|
class="form-control"
|
||||||
|
id="emailPrefixAttribute"
|
||||||
|
name="EmailPrefixAttribute"
|
||||||
|
[(ngModel)]="sync.emailPrefixAttribute"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text">{{ "ex" | i18n }} accountName</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="emailSuffix">{{'emailSuffix' | i18n}}</label>
|
<label for="emailSuffix">{{ "emailSuffix" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="emailSuffix" name="EmailSuffix"
|
<input
|
||||||
[(ngModel)]="sync.emailSuffix">
|
type="text"
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} @company.com</small>
|
class="form-control"
|
||||||
|
id="emailSuffix"
|
||||||
|
name="EmailSuffix"
|
||||||
|
[(ngModel)]="sync.emailSuffix"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text">{{ "ex" | i18n }} @company.com</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="syncUsers" [(ngModel)]="sync.users"
|
<input
|
||||||
name="SyncUsers">
|
class="form-check-input"
|
||||||
<label class="form-check-label" for="syncUsers">{{'syncUsers' | i18n}}</label>
|
type="checkbox"
|
||||||
|
id="syncUsers"
|
||||||
|
[(ngModel)]="sync.users"
|
||||||
|
name="SyncUsers"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="syncUsers">{{ "syncUsers" | i18n }}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="!sync.users">
|
<div [hidden]="!sync.users">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="userFilter">{{'userFilter' | i18n}}</label>
|
<label for="userFilter">{{ "userFilter" | i18n }}</label>
|
||||||
<textarea class="form-control" id="userFilter" name="UserFilter"
|
<textarea
|
||||||
[(ngModel)]="sync.userFilter"></textarea>
|
class="form-control"
|
||||||
<small class="text-muted form-text" *ngIf="directory === directoryType.Ldap">{{'ex' | i18n}}
|
id="userFilter"
|
||||||
(&(givenName=John)(|(l=Dallas)(l=Austin)))</small>
|
name="UserFilter"
|
||||||
<small class="text-muted form-text"
|
[(ngModel)]="sync.userFilter"
|
||||||
*ngIf="directory === directoryType.AzureActiveDirectory">{{'ex' | i18n}}
|
></textarea>
|
||||||
exclude:joe@company.com</small>
|
<small class="text-muted form-text" *ngIf="directory === directoryType.Ldap"
|
||||||
<small class="text-muted form-text" *ngIf="directory === directoryType.Okta">{{'ex' | i18n}}
|
>{{ "ex" | i18n }} (&(givenName=John)(|(l=Dallas)(l=Austin)))</small
|
||||||
exclude:joe@company.com | profile.firstName eq "John"</small>
|
>
|
||||||
<small class="text-muted form-text" *ngIf="directory === directoryType.GSuite">{{'ex' | i18n}}
|
<small
|
||||||
exclude:joe@company.com | orgName=Engineering</small>
|
class="text-muted form-text"
|
||||||
|
*ngIf="directory === directoryType.AzureActiveDirectory"
|
||||||
|
>{{ "ex" | i18n }} exclude:joe@company.com</small
|
||||||
|
>
|
||||||
|
<small class="text-muted form-text" *ngIf="directory === directoryType.Okta"
|
||||||
|
>{{ "ex" | i18n }} exclude:joe@company.com | profile.firstName eq "John"</small
|
||||||
|
>
|
||||||
|
<small class="text-muted form-text" *ngIf="directory === directoryType.GSuite"
|
||||||
|
>{{ "ex" | i18n }} exclude:joe@company.com | orgName=Engineering</small
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" [hidden]="directory != directoryType.Ldap">
|
<div class="form-group" [hidden]="directory != directoryType.Ldap">
|
||||||
<label for="userPath">{{'userPath' | i18n}}</label>
|
<label for="userPath">{{ "userPath" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="userPath" name="UserPath"
|
<input
|
||||||
[(ngModel)]="sync.userPath">
|
type="text"
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} CN=Users</small>
|
class="form-control"
|
||||||
|
id="userPath"
|
||||||
|
name="UserPath"
|
||||||
|
[(ngModel)]="sync.userPath"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text">{{ "ex" | i18n }} CN=Users</small>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="directory != directoryType.Ldap || ldap.ad">
|
<div [hidden]="directory != directoryType.Ldap || ldap.ad">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="userObjectClass">{{'userObjectClass' | i18n}}</label>
|
<label for="userObjectClass">{{ "userObjectClass" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="userObjectClass" name="UserObjectClass"
|
<input
|
||||||
[(ngModel)]="sync.userObjectClass">
|
type="text"
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} inetOrgPerson</small>
|
class="form-control"
|
||||||
|
id="userObjectClass"
|
||||||
|
name="UserObjectClass"
|
||||||
|
[(ngModel)]="sync.userObjectClass"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text">{{ "ex" | i18n }} inetOrgPerson</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="userEmailAttribute">{{'userEmailAttribute' | i18n}}</label>
|
<label for="userEmailAttribute">{{ "userEmailAttribute" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="userEmailAttribute" name="UserEmailAttribute"
|
<input
|
||||||
[(ngModel)]="sync.userEmailAttribute">
|
type="text"
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} mail</small>
|
class="form-control"
|
||||||
|
id="userEmailAttribute"
|
||||||
|
name="UserEmailAttribute"
|
||||||
|
[(ngModel)]="sync.userEmailAttribute"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text">{{ "ex" | i18n }} mail</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="syncGroups" [(ngModel)]="sync.groups"
|
<input
|
||||||
name="SyncGroups">
|
class="form-check-input"
|
||||||
<label class="form-check-label" for="syncGroups">{{(directory !== directoryType.OneLogin ?
|
type="checkbox"
|
||||||
'syncGroups' : 'syncGroupsOneLogin') | i18n}}</label>
|
id="syncGroups"
|
||||||
|
[(ngModel)]="sync.groups"
|
||||||
|
name="SyncGroups"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="syncGroups">{{
|
||||||
|
(directory !== directoryType.OneLogin ? "syncGroups" : "syncGroupsOneLogin") | i18n
|
||||||
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="!sync.groups">
|
<div [hidden]="!sync.groups">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="groupFilter">{{(directory !== directoryType.OneLogin ? 'groupFilter' :
|
<label for="groupFilter">{{
|
||||||
'groupFilterOneLogin') | i18n}}</label>
|
(directory !== directoryType.OneLogin ? "groupFilter" : "groupFilterOneLogin") | i18n
|
||||||
<textarea class="form-control" id="groupFilter" name="GroupFilter"
|
}}</label>
|
||||||
[(ngModel)]="sync.groupFilter"></textarea>
|
<textarea
|
||||||
<small class="text-muted form-text" *ngIf="directory === directoryType.Ldap">{{'ex' | i18n}}
|
class="form-control"
|
||||||
(&!(name=Sales*)!(name=IT*))</small>
|
id="groupFilter"
|
||||||
<small class="text-muted form-text"
|
name="GroupFilter"
|
||||||
*ngIf="directory === directoryType.AzureActiveDirectory">{{'ex' | i18n}}
|
[(ngModel)]="sync.groupFilter"
|
||||||
include:Sales,IT</small>
|
></textarea>
|
||||||
<small class="text-muted form-text" *ngIf="directory === directoryType.Okta">{{'ex' | i18n}}
|
<small class="text-muted form-text" *ngIf="directory === directoryType.Ldap"
|
||||||
include:Sales,IT | type eq "APP_GROUP"</small>
|
>{{ "ex" | i18n }} (&!(name=Sales*)!(name=IT*))</small
|
||||||
<small class="text-muted form-text" *ngIf="directory === directoryType.GSuite">{{'ex' | i18n}}
|
>
|
||||||
include:Sales,IT</small>
|
<small
|
||||||
|
class="text-muted form-text"
|
||||||
|
*ngIf="directory === directoryType.AzureActiveDirectory"
|
||||||
|
>{{ "ex" | i18n }} include:Sales,IT</small
|
||||||
|
>
|
||||||
|
<small class="text-muted form-text" *ngIf="directory === directoryType.Okta"
|
||||||
|
>{{ "ex" | i18n }} include:Sales,IT | type eq "APP_GROUP"</small
|
||||||
|
>
|
||||||
|
<small class="text-muted form-text" *ngIf="directory === directoryType.GSuite"
|
||||||
|
>{{ "ex" | i18n }} include:Sales,IT</small
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" [hidden]="directory != directoryType.Ldap">
|
<div class="form-group" [hidden]="directory != directoryType.Ldap">
|
||||||
<label for="groupPath">{{'groupPath' | i18n}}</label>
|
<label for="groupPath">{{ "groupPath" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="groupPath" name="GroupPath"
|
<input
|
||||||
[(ngModel)]="sync.groupPath">
|
type="text"
|
||||||
<small class="text-muted form-text" *ngIf="!ldap.ad">{{'ex' | i18n}} CN=Groups</small>
|
class="form-control"
|
||||||
<small class="text-muted form-text" *ngIf="ldap.ad">{{'ex' | i18n}} CN=Users</small>
|
id="groupPath"
|
||||||
|
name="GroupPath"
|
||||||
|
[(ngModel)]="sync.groupPath"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text" *ngIf="!ldap.ad">{{ "ex" | i18n }} CN=Groups</small>
|
||||||
|
<small class="text-muted form-text" *ngIf="ldap.ad">{{ "ex" | i18n }} CN=Users</small>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="directory != directoryType.Ldap || ldap.ad">
|
<div [hidden]="directory != directoryType.Ldap || ldap.ad">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="groupObjectClass">{{'groupObjectClass' | i18n}}</label>
|
<label for="groupObjectClass">{{ "groupObjectClass" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="groupObjectClass" name="GroupObjectClass"
|
<input
|
||||||
[(ngModel)]="sync.groupObjectClass">
|
type="text"
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} groupOfUniqueNames</small>
|
class="form-control"
|
||||||
|
id="groupObjectClass"
|
||||||
|
name="GroupObjectClass"
|
||||||
|
[(ngModel)]="sync.groupObjectClass"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text">{{ "ex" | i18n }} groupOfUniqueNames</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="groupNameAttribute">{{'groupNameAttribute' | i18n}}</label>
|
<label for="groupNameAttribute">{{ "groupNameAttribute" | i18n }}</label>
|
||||||
<input type="text" class="form-control" id="groupNameAttribute" name="GroupNameAttribute"
|
<input
|
||||||
[(ngModel)]="sync.groupNameAttribute">
|
type="text"
|
||||||
<small class="text-muted form-text">{{'ex' | i18n}} name</small>
|
class="form-control"
|
||||||
|
id="groupNameAttribute"
|
||||||
|
name="GroupNameAttribute"
|
||||||
|
[(ngModel)]="sync.groupNameAttribute"
|
||||||
|
/>
|
||||||
|
<small class="text-muted form-text">{{ "ex" | i18n }} name</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,32 +1,21 @@
|
|||||||
import {
|
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||||
ChangeDetectorRef,
|
|
||||||
Component,
|
|
||||||
NgZone,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
|
|
||||||
import { ProfileOrganizationResponse } from 'jslib-common/models/response/profileOrganizationResponse';
|
import { StateService } from "../../abstractions/state.service";
|
||||||
|
import { DirectoryType } from "../../enums/directoryType";
|
||||||
import { ConfigurationService } from '../../services/configuration.service';
|
import { AzureConfiguration } from "../../models/azureConfiguration";
|
||||||
|
import { GSuiteConfiguration } from "../../models/gsuiteConfiguration";
|
||||||
import { DirectoryType } from '../../enums/directoryType';
|
import { LdapConfiguration } from "../../models/ldapConfiguration";
|
||||||
|
import { OktaConfiguration } from "../../models/oktaConfiguration";
|
||||||
import { AzureConfiguration } from '../../models/azureConfiguration';
|
import { OneLoginConfiguration } from "../../models/oneLoginConfiguration";
|
||||||
import { GSuiteConfiguration } from '../../models/gsuiteConfiguration';
|
import { SyncConfiguration } from "../../models/syncConfiguration";
|
||||||
import { LdapConfiguration } from '../../models/ldapConfiguration';
|
import { ConnectorUtils } from "../../utils";
|
||||||
import { OktaConfiguration } from '../../models/oktaConfiguration';
|
|
||||||
import { OneLoginConfiguration } from '../../models/oneLoginConfiguration';
|
|
||||||
import { SyncConfiguration } from '../../models/syncConfiguration';
|
|
||||||
|
|
||||||
import { ConnectorUtils } from '../../utils';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-settings',
|
selector: "app-settings",
|
||||||
templateUrl: 'settings.component.html',
|
templateUrl: "settings.component.html",
|
||||||
})
|
})
|
||||||
export class SettingsComponent implements OnInit, OnDestroy {
|
export class SettingsComponent implements OnInit, OnDestroy {
|
||||||
directory: DirectoryType;
|
directory: DirectoryType;
|
||||||
@@ -38,37 +27,45 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
oneLogin = new OneLoginConfiguration();
|
oneLogin = new OneLoginConfiguration();
|
||||||
sync = new SyncConfiguration();
|
sync = new SyncConfiguration();
|
||||||
directoryOptions: any[];
|
directoryOptions: any[];
|
||||||
showLdapPassword: boolean = false;
|
showLdapPassword = false;
|
||||||
showAzureKey: boolean = false;
|
showAzureKey = false;
|
||||||
showOktaKey: boolean = false;
|
showOktaKey = false;
|
||||||
showOneLoginSecret: boolean = false;
|
showOneLoginSecret = false;
|
||||||
|
|
||||||
constructor(private i18nService: I18nService, private configurationService: ConfigurationService,
|
constructor(
|
||||||
private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone,
|
private i18nService: I18nService,
|
||||||
private stateService: StateService) {
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private ngZone: NgZone,
|
||||||
|
private logService: LogService,
|
||||||
|
private stateService: StateService
|
||||||
|
) {
|
||||||
this.directoryOptions = [
|
this.directoryOptions = [
|
||||||
{ name: i18nService.t('select'), value: null },
|
{ name: this.i18nService.t("select"), value: null },
|
||||||
{ name: 'Active Directory / LDAP', value: DirectoryType.Ldap },
|
{ name: "Active Directory / LDAP", value: DirectoryType.Ldap },
|
||||||
{ name: 'Azure Active Directory', value: DirectoryType.AzureActiveDirectory },
|
{ name: "Azure Active Directory", value: DirectoryType.AzureActiveDirectory },
|
||||||
{ name: 'G Suite (Google)', value: DirectoryType.GSuite },
|
{ name: "G Suite (Google)", value: DirectoryType.GSuite },
|
||||||
{ name: 'Okta', value: DirectoryType.Okta },
|
{ name: "Okta", value: DirectoryType.Okta },
|
||||||
{ name: 'OneLogin', value: DirectoryType.OneLogin },
|
{ name: "OneLogin", value: DirectoryType.OneLogin },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.directory = await this.configurationService.getDirectoryType();
|
this.directory = await this.stateService.getDirectoryType();
|
||||||
this.ldap = (await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) ||
|
this.ldap =
|
||||||
this.ldap;
|
(await this.stateService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) || this.ldap;
|
||||||
this.gsuite = (await this.configurationService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite)) ||
|
this.gsuite =
|
||||||
|
(await this.stateService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite)) ||
|
||||||
this.gsuite;
|
this.gsuite;
|
||||||
this.azure = (await this.configurationService.getDirectory<AzureConfiguration>(
|
this.azure =
|
||||||
DirectoryType.AzureActiveDirectory)) || this.azure;
|
(await this.stateService.getDirectory<AzureConfiguration>(
|
||||||
this.okta = (await this.configurationService.getDirectory<OktaConfiguration>(
|
DirectoryType.AzureActiveDirectory
|
||||||
DirectoryType.Okta)) || this.okta;
|
)) || this.azure;
|
||||||
this.oneLogin = (await this.configurationService.getDirectory<OneLoginConfiguration>(
|
this.okta =
|
||||||
DirectoryType.OneLogin)) || this.oneLogin;
|
(await this.stateService.getDirectory<OktaConfiguration>(DirectoryType.Okta)) || this.okta;
|
||||||
this.sync = (await this.configurationService.getSync()) || this.sync;
|
this.oneLogin =
|
||||||
|
(await this.stateService.getDirectory<OneLoginConfiguration>(DirectoryType.OneLogin)) ||
|
||||||
|
this.oneLogin;
|
||||||
|
this.sync = (await this.stateService.getSync()) || this.sync;
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnDestroy() {
|
async ngOnDestroy() {
|
||||||
@@ -80,24 +77,24 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
if (this.ldap != null && this.ldap.ad) {
|
if (this.ldap != null && this.ldap.ad) {
|
||||||
this.ldap.pagedSearch = true;
|
this.ldap.pagedSearch = true;
|
||||||
}
|
}
|
||||||
await this.configurationService.saveDirectoryType(this.directory);
|
await this.stateService.setDirectoryType(this.directory);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.Ldap, this.ldap);
|
await this.stateService.setDirectory(DirectoryType.Ldap, this.ldap);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite);
|
await this.stateService.setDirectory(DirectoryType.GSuite, this.gsuite);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.AzureActiveDirectory, this.azure);
|
await this.stateService.setDirectory(DirectoryType.AzureActiveDirectory, this.azure);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.Okta, this.okta);
|
await this.stateService.setDirectory(DirectoryType.Okta, this.okta);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.OneLogin, this.oneLogin);
|
await this.stateService.setDirectory(DirectoryType.OneLogin, this.oneLogin);
|
||||||
await this.configurationService.saveSync(this.sync);
|
await this.stateService.setSync(this.sync);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseKeyFile() {
|
parseKeyFile() {
|
||||||
const filePicker = (document.getElementById('keyFile') as HTMLInputElement);
|
const filePicker = document.getElementById("keyFile") as HTMLInputElement;
|
||||||
if (filePicker.files == null || filePicker.files.length < 0) {
|
if (filePicker.files == null || filePicker.files.length < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.readAsText(filePicker.files[0], 'utf-8');
|
reader.readAsText(filePicker.files[0], "utf-8");
|
||||||
reader.onload = evt => {
|
reader.onload = (evt) => {
|
||||||
this.ngZone.run(async () => {
|
this.ngZone.run(async () => {
|
||||||
try {
|
try {
|
||||||
const result = JSON.parse((evt.target as FileReader).result as string);
|
const result = JSON.parse((evt.target as FileReader).result as string);
|
||||||
@@ -105,20 +102,22 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
this.gsuite.clientEmail = result.client_email;
|
this.gsuite.clientEmail = result.client_email;
|
||||||
this.gsuite.privateKey = result.private_key;
|
this.gsuite.privateKey = result.private_key;
|
||||||
}
|
}
|
||||||
} catch { }
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
|
}
|
||||||
this.changeDetectorRef.detectChanges();
|
this.changeDetectorRef.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
// reset file input
|
// reset file input
|
||||||
// ref: https://stackoverflow.com/a/20552042
|
// ref: https://stackoverflow.com/a/20552042
|
||||||
filePicker.type = '';
|
filePicker.type = "";
|
||||||
filePicker.type = 'file';
|
filePicker.type = "file";
|
||||||
filePicker.value = '';
|
filePicker.value = "";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setSslPath(id: string) {
|
setSslPath(id: string) {
|
||||||
const filePicker = (document.getElementById(id + '_file') as HTMLInputElement);
|
const filePicker = document.getElementById(id + "_file") as HTMLInputElement;
|
||||||
if (filePicker.files == null || filePicker.files.length < 0) {
|
if (filePicker.files == null || filePicker.files.length < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -126,28 +125,28 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
|||||||
(this.ldap as any)[id] = filePicker.files[0].path;
|
(this.ldap as any)[id] = filePicker.files[0].path;
|
||||||
// reset file input
|
// reset file input
|
||||||
// ref: https://stackoverflow.com/a/20552042
|
// ref: https://stackoverflow.com/a/20552042
|
||||||
filePicker.type = '';
|
filePicker.type = "";
|
||||||
filePicker.type = 'file';
|
filePicker.type = "file";
|
||||||
filePicker.value = '';
|
filePicker.value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleLdapPassword() {
|
toggleLdapPassword() {
|
||||||
this.showLdapPassword = !this.showLdapPassword;
|
this.showLdapPassword = !this.showLdapPassword;
|
||||||
document.getElementById('password').focus();
|
document.getElementById("password").focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleAzureKey() {
|
toggleAzureKey() {
|
||||||
this.showAzureKey = !this.showAzureKey;
|
this.showAzureKey = !this.showAzureKey;
|
||||||
document.getElementById('secretKey').focus();
|
document.getElementById("secretKey").focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleOktaKey() {
|
toggleOktaKey() {
|
||||||
this.showOktaKey = !this.showOktaKey;
|
this.showOktaKey = !this.showOktaKey;
|
||||||
document.getElementById('oktaToken').focus();
|
document.getElementById("oktaToken").focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleOneLoginSecret() {
|
toggleOneLoginSecret() {
|
||||||
this.showOneLoginSecret = !this.showOneLoginSecret;
|
this.showOneLoginSecret = !this.showOneLoginSecret;
|
||||||
document.getElementById('oneLoginClientSecret').focus();
|
document.getElementById("oneLoginClientSecret").focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,20 @@
|
|||||||
<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="fa fa-dashboard"></i>
|
<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="fa fa-cogs"></i>
|
<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="fa fa-sliders"></i>
|
<i class="bwi bwi-sliders"></i>
|
||||||
{{'more' | i18n}}
|
{{ "more" | i18n }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tabs',
|
selector: "app-tabs",
|
||||||
templateUrl: 'tabs.component.html',
|
templateUrl: "tabs.component.html",
|
||||||
})
|
})
|
||||||
export class TabsComponent {}
|
export class TabsComponent {}
|
||||||
|
|||||||
302
src/bwdc.ts
302
src/bwdc.ts
@@ -1,40 +1,51 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from "fs";
|
||||||
import * as path from 'path';
|
import * as path from "path";
|
||||||
|
|
||||||
import { LogLevelType } from 'jslib-common/enums/logLevelType';
|
import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
|
||||||
|
import { TwoFactorService as TwoFactorServiceAbstraction } from "jslib-common/abstractions/twoFactor.service";
|
||||||
|
import { ClientType } from "jslib-common/enums/clientType";
|
||||||
|
import { LogLevelType } from "jslib-common/enums/logLevelType";
|
||||||
|
import { StateFactory } from "jslib-common/factories/stateFactory";
|
||||||
|
import { GlobalState } from "jslib-common/models/domain/globalState";
|
||||||
|
import { ApiLogInCredentials } from "jslib-common/models/domain/logInCredentials";
|
||||||
|
import { AppIdService } from "jslib-common/services/appId.service";
|
||||||
|
import { CipherService } from "jslib-common/services/cipher.service";
|
||||||
|
import { CollectionService } from "jslib-common/services/collection.service";
|
||||||
|
import { ContainerService } from "jslib-common/services/container.service";
|
||||||
|
import { CryptoService } from "jslib-common/services/crypto.service";
|
||||||
|
import { EnvironmentService } from "jslib-common/services/environment.service";
|
||||||
|
import { FileUploadService } from "jslib-common/services/fileUpload.service";
|
||||||
|
import { FolderService } from "jslib-common/services/folder.service";
|
||||||
|
import { KeyConnectorService } from "jslib-common/services/keyConnector.service";
|
||||||
|
import { NoopMessagingService } from "jslib-common/services/noopMessaging.service";
|
||||||
|
import { OrganizationService } from "jslib-common/services/organization.service";
|
||||||
|
import { PasswordGenerationService } from "jslib-common/services/passwordGeneration.service";
|
||||||
|
import { PolicyService } from "jslib-common/services/policy.service";
|
||||||
|
import { ProviderService } from "jslib-common/services/provider.service";
|
||||||
|
import { SearchService } from "jslib-common/services/search.service";
|
||||||
|
import { SendService } from "jslib-common/services/send.service";
|
||||||
|
import { SettingsService } from "jslib-common/services/settings.service";
|
||||||
|
import { TokenService } from "jslib-common/services/token.service";
|
||||||
|
import { CliPlatformUtilsService } from "jslib-node/cli/services/cliPlatformUtils.service";
|
||||||
|
import { ConsoleLogService } from "jslib-node/cli/services/consoleLog.service";
|
||||||
|
import { NodeApiService } from "jslib-node/services/nodeApi.service";
|
||||||
|
import { NodeCryptoFunctionService } from "jslib-node/services/nodeCryptoFunction.service";
|
||||||
|
|
||||||
import { AuthService } from './services/auth.service';
|
import { Account } from "./models/account";
|
||||||
|
import { Program } from "./program";
|
||||||
|
import { AuthService } from "./services/auth.service";
|
||||||
|
import { I18nService } from "./services/i18n.service";
|
||||||
|
import { KeytarSecureStorageService } from "./services/keytarSecureStorage.service";
|
||||||
|
import { LowdbStorageService } from "./services/lowdbStorage.service";
|
||||||
|
import { NoopTwoFactorService } from "./services/noop/noopTwoFactor.service";
|
||||||
|
import { StateService } from "./services/state.service";
|
||||||
|
import { StateMigrationService } from "./services/stateMigration.service";
|
||||||
|
import { SyncService } from "./services/sync.service";
|
||||||
|
|
||||||
import { ConfigurationService } from './services/configuration.service';
|
// eslint-disable-next-line
|
||||||
import { I18nService } from './services/i18n.service';
|
const packageJson = require("./package.json");
|
||||||
import { KeytarSecureStorageService } from './services/keytarSecureStorage.service';
|
|
||||||
import { LowdbStorageService } from './services/lowdbStorage.service';
|
|
||||||
import { NodeApiService } from './services/nodeApi.service';
|
|
||||||
import { SyncService } from './services/sync.service';
|
|
||||||
|
|
||||||
import { CliPlatformUtilsService } from 'jslib-node/cli/services/cliPlatformUtils.service';
|
|
||||||
import { ConsoleLogService } from 'jslib-node/cli/services/consoleLog.service';
|
|
||||||
import { NodeCryptoFunctionService } from 'jslib-node/services/nodeCryptoFunction.service';
|
|
||||||
|
|
||||||
import { ApiKeyService } from 'jslib-common/services/apiKey.service';
|
|
||||||
import { AppIdService } from 'jslib-common/services/appId.service';
|
|
||||||
import { ConstantsService } from 'jslib-common/services/constants.service';
|
|
||||||
import { ContainerService } from 'jslib-common/services/container.service';
|
|
||||||
import { CryptoService } from 'jslib-common/services/crypto.service';
|
|
||||||
import { EnvironmentService } from 'jslib-common/services/environment.service';
|
|
||||||
import { NoopMessagingService } from 'jslib-common/services/noopMessaging.service';
|
|
||||||
import { PasswordGenerationService } from 'jslib-common/services/passwordGeneration.service';
|
|
||||||
import { TokenService } from 'jslib-common/services/token.service';
|
|
||||||
import { UserService } from 'jslib-common/services/user.service';
|
|
||||||
|
|
||||||
import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service';
|
|
||||||
|
|
||||||
import { Program } from './program';
|
|
||||||
import { refreshToken } from './services/api.service';
|
|
||||||
|
|
||||||
// tslint:disable-next-line
|
|
||||||
const packageJson = require('./package.json');
|
|
||||||
|
|
||||||
|
export const searchService: SearchService = null;
|
||||||
export class Main {
|
export class Main {
|
||||||
dataFilePath: string;
|
dataFilePath: string;
|
||||||
logService: ConsoleLogService;
|
logService: ConsoleLogService;
|
||||||
@@ -43,71 +54,209 @@ export class Main {
|
|||||||
secureStorageService: StorageServiceAbstraction;
|
secureStorageService: StorageServiceAbstraction;
|
||||||
i18nService: I18nService;
|
i18nService: I18nService;
|
||||||
platformUtilsService: CliPlatformUtilsService;
|
platformUtilsService: CliPlatformUtilsService;
|
||||||
constantsService: ConstantsService;
|
|
||||||
cryptoService: CryptoService;
|
cryptoService: CryptoService;
|
||||||
tokenService: TokenService;
|
tokenService: TokenService;
|
||||||
appIdService: AppIdService;
|
appIdService: AppIdService;
|
||||||
apiService: NodeApiService;
|
apiService: NodeApiService;
|
||||||
environmentService: EnvironmentService;
|
environmentService: EnvironmentService;
|
||||||
apiKeyService: ApiKeyService;
|
|
||||||
userService: UserService;
|
|
||||||
containerService: ContainerService;
|
containerService: ContainerService;
|
||||||
cryptoFunctionService: NodeCryptoFunctionService;
|
cryptoFunctionService: NodeCryptoFunctionService;
|
||||||
authService: AuthService;
|
authService: AuthService;
|
||||||
configurationService: ConfigurationService;
|
collectionService: CollectionService;
|
||||||
|
cipherService: CipherService;
|
||||||
|
fileUploadService: FileUploadService;
|
||||||
|
folderService: FolderService;
|
||||||
|
searchService: SearchService;
|
||||||
|
sendService: SendService;
|
||||||
|
settingsService: SettingsService;
|
||||||
syncService: SyncService;
|
syncService: SyncService;
|
||||||
passwordGenerationService: PasswordGenerationService;
|
passwordGenerationService: PasswordGenerationService;
|
||||||
|
policyService: PolicyService;
|
||||||
|
keyConnectorService: KeyConnectorService;
|
||||||
program: Program;
|
program: Program;
|
||||||
|
stateService: StateService;
|
||||||
|
stateMigrationService: StateMigrationService;
|
||||||
|
organizationService: OrganizationService;
|
||||||
|
providerService: ProviderService;
|
||||||
|
twoFactorService: TwoFactorServiceAbstraction;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const applicationName = 'Bitwarden Directory Connector';
|
const applicationName = "Bitwarden Directory Connector";
|
||||||
if (process.env.BITWARDENCLI_CONNECTOR_APPDATA_DIR) {
|
if (process.env.BITWARDENCLI_CONNECTOR_APPDATA_DIR) {
|
||||||
this.dataFilePath = path.resolve(process.env.BITWARDENCLI_CONNECTOR_APPDATA_DIR);
|
this.dataFilePath = path.resolve(process.env.BITWARDENCLI_CONNECTOR_APPDATA_DIR);
|
||||||
} else if (process.env.BITWARDEN_CONNECTOR_APPDATA_DIR) {
|
} else if (process.env.BITWARDEN_CONNECTOR_APPDATA_DIR) {
|
||||||
this.dataFilePath = path.resolve(process.env.BITWARDEN_CONNECTOR_APPDATA_DIR);
|
this.dataFilePath = path.resolve(process.env.BITWARDEN_CONNECTOR_APPDATA_DIR);
|
||||||
} else if (fs.existsSync(path.join(__dirname, 'bitwarden-connector-appdata'))) {
|
} else if (fs.existsSync(path.join(__dirname, "bitwarden-connector-appdata"))) {
|
||||||
this.dataFilePath = path.join(__dirname, 'bitwarden-connector-appdata');
|
this.dataFilePath = path.join(__dirname, "bitwarden-connector-appdata");
|
||||||
} else if (process.platform === 'darwin') {
|
} else if (process.platform === "darwin") {
|
||||||
this.dataFilePath = path.join(process.env.HOME, 'Library/Application Support/' + applicationName);
|
this.dataFilePath = path.join(
|
||||||
} else if (process.platform === 'win32') {
|
process.env.HOME,
|
||||||
|
"Library/Application Support/" + applicationName
|
||||||
|
);
|
||||||
|
} else if (process.platform === "win32") {
|
||||||
this.dataFilePath = path.join(process.env.APPDATA, applicationName);
|
this.dataFilePath = path.join(process.env.APPDATA, applicationName);
|
||||||
} else if (process.env.XDG_CONFIG_HOME) {
|
} else if (process.env.XDG_CONFIG_HOME) {
|
||||||
this.dataFilePath = path.join(process.env.XDG_CONFIG_HOME, applicationName);
|
this.dataFilePath = path.join(process.env.XDG_CONFIG_HOME, applicationName);
|
||||||
} else {
|
} else {
|
||||||
this.dataFilePath = path.join(process.env.HOME, '.config/' + applicationName);
|
this.dataFilePath = path.join(process.env.HOME, ".config/" + applicationName);
|
||||||
}
|
}
|
||||||
|
|
||||||
const plaintextSecrets = process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS === 'true';
|
const plaintextSecrets = process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS === "true";
|
||||||
this.i18nService = new I18nService('en', './locales');
|
this.i18nService = new I18nService("en", "./locales");
|
||||||
this.platformUtilsService = new CliPlatformUtilsService('connector', packageJson);
|
this.platformUtilsService = new CliPlatformUtilsService(
|
||||||
this.logService = new ConsoleLogService(this.platformUtilsService.isDev(),
|
ClientType.DirectoryConnector,
|
||||||
level => process.env.BITWARDENCLI_CONNECTOR_DEBUG !== 'true' && level <= LogLevelType.Info);
|
packageJson
|
||||||
|
);
|
||||||
|
this.logService = new ConsoleLogService(
|
||||||
|
this.platformUtilsService.isDev(),
|
||||||
|
(level) => process.env.BITWARDENCLI_CONNECTOR_DEBUG !== "true" && level <= LogLevelType.Info
|
||||||
|
);
|
||||||
this.cryptoFunctionService = new NodeCryptoFunctionService();
|
this.cryptoFunctionService = new NodeCryptoFunctionService();
|
||||||
this.storageService = new LowdbStorageService(this.logService, null, this.dataFilePath, false, true);
|
this.storageService = new LowdbStorageService(
|
||||||
this.secureStorageService = plaintextSecrets ?
|
this.logService,
|
||||||
this.storageService : new KeytarSecureStorageService(applicationName);
|
null,
|
||||||
this.cryptoService = new CryptoService(this.storageService, this.secureStorageService,
|
this.dataFilePath,
|
||||||
this.cryptoFunctionService, this.platformUtilsService, this.logService);
|
false,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
this.secureStorageService = plaintextSecrets
|
||||||
|
? this.storageService
|
||||||
|
: new KeytarSecureStorageService(applicationName);
|
||||||
|
|
||||||
|
this.stateMigrationService = new StateMigrationService(
|
||||||
|
this.storageService,
|
||||||
|
this.secureStorageService,
|
||||||
|
new StateFactory(GlobalState, Account)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.stateService = new StateService(
|
||||||
|
this.storageService,
|
||||||
|
this.secureStorageService,
|
||||||
|
this.logService,
|
||||||
|
this.stateMigrationService,
|
||||||
|
process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS !== "true",
|
||||||
|
new StateFactory(GlobalState, Account)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.cryptoService = new CryptoService(
|
||||||
|
this.cryptoFunctionService,
|
||||||
|
this.platformUtilsService,
|
||||||
|
this.logService,
|
||||||
|
this.stateService
|
||||||
|
);
|
||||||
|
|
||||||
this.appIdService = new AppIdService(this.storageService);
|
this.appIdService = new AppIdService(this.storageService);
|
||||||
this.tokenService = new TokenService(this.storageService);
|
this.tokenService = new TokenService(this.stateService);
|
||||||
this.messagingService = new NoopMessagingService();
|
this.messagingService = new NoopMessagingService();
|
||||||
this.environmentService = new EnvironmentService(this.storageService);
|
this.environmentService = new EnvironmentService(this.stateService);
|
||||||
this.apiService = new NodeApiService(this.tokenService, this.platformUtilsService, this.environmentService,
|
this.apiService = new NodeApiService(
|
||||||
() => refreshToken(this.apiKeyService, this.authService), async (expired: boolean) => await this.logout(),
|
this.tokenService,
|
||||||
'Bitwarden_DC/' + this.platformUtilsService.getApplicationVersion() +
|
this.platformUtilsService,
|
||||||
' (' + this.platformUtilsService.getDeviceString().toUpperCase() + ')', (clientId, clientSecret) =>
|
this.environmentService,
|
||||||
this.authService.logInApiKey(clientId, clientSecret));
|
async (expired: boolean) => await this.logout(),
|
||||||
this.apiKeyService = new ApiKeyService(this.tokenService, this.storageService);
|
"Bitwarden_DC/" +
|
||||||
this.userService = new UserService(this.tokenService, this.storageService);
|
this.platformUtilsService.getApplicationVersion() +
|
||||||
|
" (" +
|
||||||
|
this.platformUtilsService.getDeviceString().toUpperCase() +
|
||||||
|
")",
|
||||||
|
(clientId, clientSecret) =>
|
||||||
|
this.authService.logIn(new ApiLogInCredentials(clientId, clientSecret))
|
||||||
|
);
|
||||||
this.containerService = new ContainerService(this.cryptoService);
|
this.containerService = new ContainerService(this.cryptoService);
|
||||||
this.authService = new AuthService(this.cryptoService, this.apiService, this.userService, this.tokenService,
|
|
||||||
this.appIdService, this.i18nService, this.platformUtilsService, this.messagingService, null,
|
this.organizationService = new OrganizationService(this.stateService);
|
||||||
this.logService, this.apiKeyService, false);
|
|
||||||
this.configurationService = new ConfigurationService(this.storageService, this.secureStorageService,
|
this.keyConnectorService = new KeyConnectorService(
|
||||||
process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS !== 'true');
|
this.stateService,
|
||||||
this.syncService = new SyncService(this.configurationService, this.logService, this.cryptoFunctionService,
|
this.cryptoService,
|
||||||
this.apiService, this.messagingService, this.i18nService, this.environmentService);
|
this.apiService,
|
||||||
this.passwordGenerationService = new PasswordGenerationService(this.cryptoService, this.storageService, null);
|
this.tokenService,
|
||||||
|
this.logService,
|
||||||
|
this.organizationService,
|
||||||
|
this.cryptoFunctionService
|
||||||
|
);
|
||||||
|
|
||||||
|
this.twoFactorService = new NoopTwoFactorService();
|
||||||
|
|
||||||
|
this.authService = new AuthService(
|
||||||
|
this.cryptoService,
|
||||||
|
this.apiService,
|
||||||
|
this.tokenService,
|
||||||
|
this.appIdService,
|
||||||
|
this.platformUtilsService,
|
||||||
|
this.messagingService,
|
||||||
|
this.logService,
|
||||||
|
this.keyConnectorService,
|
||||||
|
this.environmentService,
|
||||||
|
this.stateService,
|
||||||
|
this.twoFactorService,
|
||||||
|
this.i18nService
|
||||||
|
);
|
||||||
|
|
||||||
|
this.syncService = new SyncService(
|
||||||
|
this.logService,
|
||||||
|
this.cryptoFunctionService,
|
||||||
|
this.apiService,
|
||||||
|
this.messagingService,
|
||||||
|
this.i18nService,
|
||||||
|
this.environmentService,
|
||||||
|
this.stateService
|
||||||
|
);
|
||||||
|
|
||||||
|
this.policyService = new PolicyService(
|
||||||
|
this.stateService,
|
||||||
|
this.organizationService,
|
||||||
|
this.apiService
|
||||||
|
);
|
||||||
|
|
||||||
|
this.passwordGenerationService = new PasswordGenerationService(
|
||||||
|
this.cryptoService,
|
||||||
|
this.policyService,
|
||||||
|
this.stateService
|
||||||
|
);
|
||||||
|
|
||||||
|
this.settingsService = new SettingsService(this.stateService);
|
||||||
|
|
||||||
|
this.fileUploadService = new FileUploadService(this.logService, this.apiService);
|
||||||
|
|
||||||
|
this.cipherService = new CipherService(
|
||||||
|
this.cryptoService,
|
||||||
|
this.settingsService,
|
||||||
|
this.apiService,
|
||||||
|
this.fileUploadService,
|
||||||
|
this.i18nService,
|
||||||
|
() => searchService,
|
||||||
|
this.logService,
|
||||||
|
this.stateService
|
||||||
|
);
|
||||||
|
|
||||||
|
this.searchService = new SearchService(this.cipherService, this.logService, this.i18nService);
|
||||||
|
|
||||||
|
this.folderService = new FolderService(
|
||||||
|
this.cryptoService,
|
||||||
|
this.apiService,
|
||||||
|
this.i18nService,
|
||||||
|
this.cipherService,
|
||||||
|
this.stateService
|
||||||
|
);
|
||||||
|
|
||||||
|
this.collectionService = new CollectionService(
|
||||||
|
this.cryptoService,
|
||||||
|
this.i18nService,
|
||||||
|
this.stateService
|
||||||
|
);
|
||||||
|
|
||||||
|
this.sendService = new SendService(
|
||||||
|
this.cryptoService,
|
||||||
|
this.apiService,
|
||||||
|
this.fileUploadService,
|
||||||
|
this.i18nService,
|
||||||
|
this.cryptoFunctionService,
|
||||||
|
this.stateService
|
||||||
|
);
|
||||||
|
|
||||||
|
this.providerService = new ProviderService(this.stateService);
|
||||||
|
|
||||||
this.program = new Program(this);
|
this.program = new Program(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,12 +267,12 @@ export class Main {
|
|||||||
|
|
||||||
async logout() {
|
async logout() {
|
||||||
await this.tokenService.clearToken();
|
await this.tokenService.clearToken();
|
||||||
await this.apiKeyService.clear();
|
await this.stateService.clean();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async init() {
|
private async init() {
|
||||||
await this.storageService.init();
|
await this.storageService.init();
|
||||||
await this.apiKeyService.migrateApiKeyStorage();
|
await this.stateService.init();
|
||||||
this.containerService.attachToWindow(global);
|
this.containerService.attachToWindow(global);
|
||||||
await this.environmentService.setUrlsFromStorage();
|
await this.environmentService.setUrlsFromStorage();
|
||||||
// Dev Server URLs. Comment out the line above.
|
// Dev Server URLs. Comment out the line above.
|
||||||
@@ -132,14 +281,13 @@ export class Main {
|
|||||||
// api: 'http://localhost:4000',
|
// api: 'http://localhost:4000',
|
||||||
// identity: 'http://localhost:33656',
|
// identity: 'http://localhost:33656',
|
||||||
// });
|
// });
|
||||||
const locale = await this.storageService.get<string>(ConstantsService.localeKey);
|
const locale = await this.stateService.getLocale();
|
||||||
await this.i18nService.init(locale);
|
await this.i18nService.init(locale);
|
||||||
this.authService.init();
|
|
||||||
|
|
||||||
const installedVersion = await this.storageService.get<string>(ConstantsService.installedVersionKey);
|
const installedVersion = await this.stateService.getInstalledVersion();
|
||||||
const currentVersion = await this.platformUtilsService.getApplicationVersion();
|
const currentVersion = await this.platformUtilsService.getApplicationVersion();
|
||||||
if (installedVersion == null || installedVersion !== currentVersion) {
|
if (installedVersion == null || installedVersion !== currentVersion) {
|
||||||
await this.storageService.save(ConstantsService.installedVersionKey, currentVersion);
|
await this.stateService.setInstalledVersion(currentVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import * as program from 'commander';
|
import * as program from "commander";
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
|
import { Response } from "jslib-node/cli/models/response";
|
||||||
|
import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
|
||||||
|
|
||||||
import { ConfigurationService } from '../services/configuration.service';
|
import { StateService } from "../abstractions/state.service";
|
||||||
|
|
||||||
import { Response } from 'jslib-node/cli/models/response';
|
|
||||||
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse';
|
|
||||||
|
|
||||||
export class ClearCacheCommand {
|
export class ClearCacheCommand {
|
||||||
constructor(private configurationService: ConfigurationService, private i18nService: I18nService) { }
|
constructor(private i18nService: I18nService, private stateService: StateService) {}
|
||||||
|
|
||||||
async run(cmd: program.OptionValues): Promise<Response> {
|
async run(cmd: program.OptionValues): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
await this.configurationService.clearStatefulSettings(true);
|
await this.stateService.clearSyncSettings(true);
|
||||||
const res = new MessageResponse(this.i18nService.t('syncCacheCleared'), null);
|
const res = new MessageResponse(this.i18nService.t("syncCacheCleared"), null);
|
||||||
return Response.success(res);
|
return Response.success(res);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Response.error(e);
|
return Response.error(e);
|
||||||
|
|||||||
@@ -1,25 +1,20 @@
|
|||||||
import * as program from 'commander';
|
import * as program from "commander";
|
||||||
|
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
|
import { NodeUtils } from "jslib-common/misc/nodeUtils";
|
||||||
|
import { Response } from "jslib-node/cli/models/response";
|
||||||
|
import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
|
||||||
|
|
||||||
import { ConfigurationService } from '../services/configuration.service';
|
import { StateService } from "../abstractions/state.service";
|
||||||
|
import { DirectoryType } from "../enums/directoryType";
|
||||||
import { DirectoryType } from '../enums/directoryType';
|
import { AzureConfiguration } from "../models/azureConfiguration";
|
||||||
|
import { GSuiteConfiguration } from "../models/gsuiteConfiguration";
|
||||||
import { Response } from 'jslib-node/cli/models/response';
|
import { LdapConfiguration } from "../models/ldapConfiguration";
|
||||||
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse';
|
import { OktaConfiguration } from "../models/oktaConfiguration";
|
||||||
|
import { OneLoginConfiguration } from "../models/oneLoginConfiguration";
|
||||||
import { AzureConfiguration } from '../models/azureConfiguration';
|
import { SyncConfiguration } from "../models/syncConfiguration";
|
||||||
import { GSuiteConfiguration } from '../models/gsuiteConfiguration';
|
import { ConnectorUtils } from "../utils";
|
||||||
import { LdapConfiguration } from '../models/ldapConfiguration';
|
|
||||||
import { OktaConfiguration } from '../models/oktaConfiguration';
|
|
||||||
import { OneLoginConfiguration } from '../models/oneLoginConfiguration';
|
|
||||||
import { SyncConfiguration } from '../models/syncConfiguration';
|
|
||||||
|
|
||||||
import { ConnectorUtils } from '../utils';
|
|
||||||
|
|
||||||
import { NodeUtils } from 'jslib-common/misc/nodeUtils';
|
|
||||||
|
|
||||||
export class ConfigCommand {
|
export class ConfigCommand {
|
||||||
private directory: DirectoryType;
|
private directory: DirectoryType;
|
||||||
@@ -30,12 +25,15 @@ export class ConfigCommand {
|
|||||||
private oneLogin = new OneLoginConfiguration();
|
private oneLogin = new OneLoginConfiguration();
|
||||||
private sync = new SyncConfiguration();
|
private sync = new SyncConfiguration();
|
||||||
|
|
||||||
constructor(private environmentService: EnvironmentService, private i18nService: I18nService,
|
constructor(
|
||||||
private configurationService: ConfigurationService) { }
|
private environmentService: EnvironmentService,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private stateService: StateService
|
||||||
|
) {}
|
||||||
|
|
||||||
async run(setting: string, value: string, options: program.OptionValues): Promise<Response> {
|
async run(setting: string, value: string, options: program.OptionValues): Promise<Response> {
|
||||||
setting = setting.toLowerCase();
|
setting = setting.toLowerCase();
|
||||||
if (value == null || value === '') {
|
if (value == null || value === "") {
|
||||||
if (options.secretfile) {
|
if (options.secretfile) {
|
||||||
value = await NodeUtils.readFirstLine(options.secretfile);
|
value = await NodeUtils.readFirstLine(options.secretfile);
|
||||||
} else if (options.secretenv && process.env[options.secretenv]) {
|
} else if (options.secretenv && process.env[options.secretenv]) {
|
||||||
@@ -44,39 +42,39 @@ export class ConfigCommand {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
switch (setting) {
|
switch (setting) {
|
||||||
case 'server':
|
case "server":
|
||||||
await this.setServer(value);
|
await this.setServer(value);
|
||||||
break;
|
break;
|
||||||
case 'directory':
|
case "directory":
|
||||||
await this.setDirectory(value);
|
await this.setDirectory(value);
|
||||||
break;
|
break;
|
||||||
case 'ldap.password':
|
case "ldap.password":
|
||||||
await this.setLdapPassword(value);
|
await this.setLdapPassword(value);
|
||||||
break;
|
break;
|
||||||
case 'gsuite.key':
|
case "gsuite.key":
|
||||||
await this.setGSuiteKey(value);
|
await this.setGSuiteKey(value);
|
||||||
break;
|
break;
|
||||||
case 'azure.key':
|
case "azure.key":
|
||||||
await this.setAzureKey(value);
|
await this.setAzureKey(value);
|
||||||
break;
|
break;
|
||||||
case 'okta.token':
|
case "okta.token":
|
||||||
await this.setOktaToken(value);
|
await this.setOktaToken(value);
|
||||||
break;
|
break;
|
||||||
case 'onelogin.secret':
|
case "onelogin.secret":
|
||||||
await this.setOneLoginSecret(value);
|
await this.setOneLoginSecret(value);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return Response.badRequest('Unknown setting.');
|
return Response.badRequest("Unknown setting.");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Response.error(e);
|
return Response.error(e);
|
||||||
}
|
}
|
||||||
const res = new MessageResponse(this.i18nService.t('savedSetting', setting), null);
|
const res = new MessageResponse(this.i18nService.t("savedSetting", setting), null);
|
||||||
return Response.success(res);
|
return Response.success(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setServer(url: string) {
|
private async setServer(url: string) {
|
||||||
url = (url === 'null' || url === 'bitwarden.com' || url === 'https://bitwarden.com' ? null : url);
|
url = url === "null" || url === "bitwarden.com" || url === "https://bitwarden.com" ? null : url;
|
||||||
await this.environmentService.setUrls({
|
await this.environmentService.setUrls({
|
||||||
base: url,
|
base: url,
|
||||||
});
|
});
|
||||||
@@ -85,7 +83,7 @@ export class ConfigCommand {
|
|||||||
private async setDirectory(type: string) {
|
private async setDirectory(type: string) {
|
||||||
const dir = parseInt(type, null);
|
const dir = parseInt(type, null);
|
||||||
if (dir < DirectoryType.Ldap || dir > DirectoryType.OneLogin) {
|
if (dir < DirectoryType.Ldap || dir > DirectoryType.OneLogin) {
|
||||||
throw new Error('Invalid directory type value.');
|
throw new Error("Invalid directory type value.");
|
||||||
}
|
}
|
||||||
await this.loadConfig();
|
await this.loadConfig();
|
||||||
this.directory = dir;
|
this.directory = dir;
|
||||||
@@ -123,28 +121,32 @@ export class ConfigCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async loadConfig() {
|
private async loadConfig() {
|
||||||
this.directory = await this.configurationService.getDirectoryType();
|
this.directory = await this.stateService.getDirectoryType();
|
||||||
this.ldap = (await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) ||
|
this.ldap =
|
||||||
this.ldap;
|
(await this.stateService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) || this.ldap;
|
||||||
this.gsuite = (await this.configurationService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite)) ||
|
this.gsuite =
|
||||||
|
(await this.stateService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite)) ||
|
||||||
this.gsuite;
|
this.gsuite;
|
||||||
this.azure = (await this.configurationService.getDirectory<AzureConfiguration>(
|
this.azure =
|
||||||
DirectoryType.AzureActiveDirectory)) || this.azure;
|
(await this.stateService.getDirectory<AzureConfiguration>(
|
||||||
this.okta = (await this.configurationService.getDirectory<OktaConfiguration>(
|
DirectoryType.AzureActiveDirectory
|
||||||
DirectoryType.Okta)) || this.okta;
|
)) || this.azure;
|
||||||
this.oneLogin = (await this.configurationService.getDirectory<OneLoginConfiguration>(
|
this.okta =
|
||||||
DirectoryType.OneLogin)) || this.oneLogin;
|
(await this.stateService.getDirectory<OktaConfiguration>(DirectoryType.Okta)) || this.okta;
|
||||||
this.sync = (await this.configurationService.getSync()) || this.sync;
|
this.oneLogin =
|
||||||
|
(await this.stateService.getDirectory<OneLoginConfiguration>(DirectoryType.OneLogin)) ||
|
||||||
|
this.oneLogin;
|
||||||
|
this.sync = (await this.stateService.getSync()) || this.sync;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async saveConfig() {
|
private async saveConfig() {
|
||||||
ConnectorUtils.adjustConfigForSave(this.ldap, this.sync);
|
ConnectorUtils.adjustConfigForSave(this.ldap, this.sync);
|
||||||
await this.configurationService.saveDirectoryType(this.directory);
|
await this.stateService.setDirectoryType(this.directory);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.Ldap, this.ldap);
|
await this.stateService.setDirectory(DirectoryType.Ldap, this.ldap);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite);
|
await this.stateService.setDirectory(DirectoryType.GSuite, this.gsuite);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.AzureActiveDirectory, this.azure);
|
await this.stateService.setDirectory(DirectoryType.AzureActiveDirectory, this.azure);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.Okta, this.okta);
|
await this.stateService.setDirectory(DirectoryType.Okta, this.okta);
|
||||||
await this.configurationService.saveDirectory(DirectoryType.OneLogin, this.oneLogin);
|
await this.stateService.setDirectory(DirectoryType.OneLogin, this.oneLogin);
|
||||||
await this.configurationService.saveSync(this.sync);
|
await this.stateService.setSync(this.sync);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,28 @@
|
|||||||
import * as program from 'commander';
|
import { Response } from "jslib-node/cli/models/response";
|
||||||
|
import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
|
||||||
|
|
||||||
import { ConfigurationService } from '../services/configuration.service';
|
import { StateService } from "../abstractions/state.service";
|
||||||
|
|
||||||
import { Response } from 'jslib-node/cli/models/response';
|
|
||||||
import { StringResponse } from 'jslib-node/cli/models/response/stringResponse';
|
|
||||||
|
|
||||||
export class LastSyncCommand {
|
export class LastSyncCommand {
|
||||||
constructor(private configurationService: ConfigurationService) { }
|
constructor(private stateService: StateService) {}
|
||||||
|
|
||||||
async run(object: string): Promise<Response> {
|
async run(object: string): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
switch (object.toLowerCase()) {
|
switch (object.toLowerCase()) {
|
||||||
case 'groups':
|
case "groups": {
|
||||||
const groupsDate = await this.configurationService.getLastGroupSyncDate();
|
const groupsDate = await this.stateService.getLastGroupSync();
|
||||||
const groupsRes = new StringResponse(groupsDate == null ? null : groupsDate.toISOString());
|
const groupsRes = new StringResponse(
|
||||||
|
groupsDate == null ? null : groupsDate.toISOString()
|
||||||
|
);
|
||||||
return Response.success(groupsRes);
|
return Response.success(groupsRes);
|
||||||
case 'users':
|
}
|
||||||
const usersDate = await this.configurationService.getLastUserSyncDate();
|
case "users": {
|
||||||
|
const usersDate = await this.stateService.getLastUserSync();
|
||||||
const usersRes = new StringResponse(usersDate == null ? null : usersDate.toISOString());
|
const usersRes = new StringResponse(usersDate == null ? null : usersDate.toISOString());
|
||||||
return Response.success(usersRes);
|
return Response.success(usersRes);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return Response.badRequest('Unknown object.');
|
return Response.badRequest("Unknown object.");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Response.error(e);
|
return Response.error(e);
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
|
import { Response } from "jslib-node/cli/models/response";
|
||||||
|
import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
|
||||||
|
|
||||||
import { SyncService } from '../services/sync.service';
|
import { SyncService } from "../services/sync.service";
|
||||||
|
|
||||||
import { Response } from 'jslib-node/cli/models/response';
|
|
||||||
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse';
|
|
||||||
|
|
||||||
export class SyncCommand {
|
export class SyncCommand {
|
||||||
constructor(private syncService: SyncService, private i18nService: I18nService) {}
|
constructor(private syncService: SyncService, private i18nService: I18nService) {}
|
||||||
@@ -13,8 +12,10 @@ export class SyncCommand {
|
|||||||
const result = await this.syncService.sync(false, false);
|
const result = await this.syncService.sync(false, false);
|
||||||
const groupCount = result[0] != null ? result[0].length : 0;
|
const groupCount = result[0] != null ? result[0].length : 0;
|
||||||
const userCount = result[1] != null ? result[1].length : 0;
|
const userCount = result[1] != null ? result[1].length : 0;
|
||||||
const res = new MessageResponse(this.i18nService.t('syncingComplete'),
|
const res = new MessageResponse(
|
||||||
this.i18nService.t('syncCounts', groupCount.toString(), userCount.toString()));
|
this.i18nService.t("syncingComplete"),
|
||||||
|
this.i18nService.t("syncCounts", groupCount.toString(), userCount.toString())
|
||||||
|
);
|
||||||
return Response.success(res);
|
return Response.success(res);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Response.error(e);
|
return Response.error(e);
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
import * as program from 'commander';
|
import * as program from "commander";
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
|
import { Response } from "jslib-node/cli/models/response";
|
||||||
|
|
||||||
import { SyncService } from '../services/sync.service';
|
import { TestResponse } from "../models/response/testResponse";
|
||||||
|
import { SyncService } from "../services/sync.service";
|
||||||
import { ConnectorUtils } from '../utils';
|
import { ConnectorUtils } from "../utils";
|
||||||
|
|
||||||
import { Response } from 'jslib-node/cli/models/response';
|
|
||||||
import { TestResponse } from '../models/response/testResponse';
|
|
||||||
|
|
||||||
export class TestCommand {
|
export class TestCommand {
|
||||||
constructor(private syncService: SyncService, private i18nService: I18nService) {}
|
constructor(private syncService: SyncService, private i18nService: I18nService) {}
|
||||||
|
|
||||||
async run(cmd: program.OptionValues): Promise<Response> {
|
async run(cmd: program.OptionValues): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
const result = await ConnectorUtils.simulate(this.syncService, this.i18nService, cmd.last || false);
|
const result = await ConnectorUtils.simulate(
|
||||||
|
this.syncService,
|
||||||
|
this.i18nService,
|
||||||
|
cmd.last || false
|
||||||
|
);
|
||||||
const res = new TestResponse(result);
|
const res = new TestResponse(result);
|
||||||
return Response.success(res);
|
return Response.success(res);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
2
src/global.d.ts
vendored
2
src/global.d.ts
vendored
@@ -1,3 +1,3 @@
|
|||||||
declare function escape(s: string): string;
|
declare function escape(s: string): string;
|
||||||
declare function unescape(s: string): string;
|
declare function unescape(s: string): string;
|
||||||
declare module 'duo_web_sdk';
|
declare module "duo_web_sdk";
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline';
|
<meta
|
||||||
img-src 'self' data: *; child-src *; frame-src *; connect-src *;">
|
http-equiv="Content-Security-Policy"
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
content="default-src 'self'; style-src 'self' 'unsafe-inline';
|
||||||
|
img-src 'self' data: *; child-src *; frame-src *; connect-src *;"
|
||||||
|
/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Bitwarden Directory Connector</title>
|
<title>Bitwarden Directory Connector</title>
|
||||||
<base href="">
|
<base href="" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app-root>
|
<app-root>
|
||||||
<div id="loading"><i class="fa fa-spinner fa-spin fa-3x"></i></div>
|
<div id="loading"><i class="bwi bwi-spinner bwi-spin bwi-3x"></i></div>
|
||||||
</app-root>
|
</app-root>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -354,6 +354,9 @@
|
|||||||
"rootPath": {
|
"rootPath": {
|
||||||
"message": "Root Path"
|
"message": "Root Path"
|
||||||
},
|
},
|
||||||
|
"identityAuthority": {
|
||||||
|
"message": "Identity Authority"
|
||||||
|
},
|
||||||
"tenant": {
|
"tenant": {
|
||||||
"message": "Tenant"
|
"message": "Tenant"
|
||||||
},
|
},
|
||||||
|
|||||||
135
src/main.ts
135
src/main.ts
@@ -1,17 +1,22 @@
|
|||||||
import { app } from 'electron';
|
import * as path from "path";
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
import { MenuMain } from './main/menu.main';
|
import { app } from "electron";
|
||||||
import { MessagingMain } from './main/messaging.main';
|
|
||||||
import { I18nService } from './services/i18n.service';
|
|
||||||
|
|
||||||
import { KeytarStorageListener } from 'jslib-electron/keytarStorageListener';
|
import { StateFactory } from "jslib-common/factories/stateFactory";
|
||||||
import { ElectronLogService } from 'jslib-electron/services/electronLog.service';
|
import { GlobalState } from "jslib-common/models/domain/globalState";
|
||||||
import { ElectronMainMessagingService } from 'jslib-electron/services/electronMainMessaging.service';
|
import { KeytarStorageListener } from "jslib-electron/keytarStorageListener";
|
||||||
import { ElectronStorageService } from 'jslib-electron/services/electronStorage.service';
|
import { ElectronLogService } from "jslib-electron/services/electronLog.service";
|
||||||
import { TrayMain } from 'jslib-electron/tray.main';
|
import { ElectronMainMessagingService } from "jslib-electron/services/electronMainMessaging.service";
|
||||||
import { UpdaterMain } from 'jslib-electron/updater.main';
|
import { ElectronStorageService } from "jslib-electron/services/electronStorage.service";
|
||||||
import { WindowMain } from 'jslib-electron/window.main';
|
import { TrayMain } from "jslib-electron/tray.main";
|
||||||
|
import { UpdaterMain } from "jslib-electron/updater.main";
|
||||||
|
import { WindowMain } from "jslib-electron/window.main";
|
||||||
|
|
||||||
|
import { MenuMain } from "./main/menu.main";
|
||||||
|
import { MessagingMain } from "./main/messaging.main";
|
||||||
|
import { Account } from "./models/account";
|
||||||
|
import { I18nService } from "./services/i18n.service";
|
||||||
|
import { StateService } from "./services/state.service";
|
||||||
|
|
||||||
export class Main {
|
export class Main {
|
||||||
logService: ElectronLogService;
|
logService: ElectronLogService;
|
||||||
@@ -19,6 +24,7 @@ export class Main {
|
|||||||
storageService: ElectronStorageService;
|
storageService: ElectronStorageService;
|
||||||
messagingService: ElectronMainMessagingService;
|
messagingService: ElectronMainMessagingService;
|
||||||
keytarStorageListener: KeytarStorageListener;
|
keytarStorageListener: KeytarStorageListener;
|
||||||
|
stateService: StateService;
|
||||||
|
|
||||||
windowMain: WindowMain;
|
windowMain: WindowMain;
|
||||||
messagingMain: MessagingMain;
|
messagingMain: MessagingMain;
|
||||||
@@ -31,76 +37,113 @@ export class Main {
|
|||||||
let appDataPath = null;
|
let appDataPath = null;
|
||||||
if (process.env.BITWARDEN_CONNECTOR_APPDATA_DIR != null) {
|
if (process.env.BITWARDEN_CONNECTOR_APPDATA_DIR != null) {
|
||||||
appDataPath = process.env.BITWARDEN_CONNECTOR_APPDATA_DIR;
|
appDataPath = process.env.BITWARDEN_CONNECTOR_APPDATA_DIR;
|
||||||
} else if (process.platform === 'win32' && process.env.PORTABLE_EXECUTABLE_DIR != null) {
|
} else if (process.platform === "win32" && process.env.PORTABLE_EXECUTABLE_DIR != null) {
|
||||||
appDataPath = path.join(process.env.PORTABLE_EXECUTABLE_DIR, 'bitwarden-connector-appdata');
|
appDataPath = path.join(process.env.PORTABLE_EXECUTABLE_DIR, "bitwarden-connector-appdata");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appDataPath != null) {
|
if (appDataPath != null) {
|
||||||
app.setPath('userData', appDataPath);
|
app.setPath("userData", appDataPath);
|
||||||
}
|
}
|
||||||
app.setPath('logs', path.join(app.getPath('userData'), 'logs'));
|
app.setPath("logs", path.join(app.getPath("userData"), "logs"));
|
||||||
|
|
||||||
const args = process.argv.slice(1);
|
const args = process.argv.slice(1);
|
||||||
const watch = args.some(val => val === '--watch');
|
const watch = args.some((val) => val === "--watch");
|
||||||
|
|
||||||
if (watch) {
|
if (watch) {
|
||||||
// tslint:disable-next-line
|
// eslint-disable-next-line
|
||||||
require('electron-reload')(__dirname, {});
|
require("electron-reload")(__dirname, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logService = new ElectronLogService(null, app.getPath('userData'));
|
this.logService = new ElectronLogService(null, app.getPath("userData"));
|
||||||
this.i18nService = new I18nService('en', './locales/');
|
this.i18nService = new I18nService("en", "./locales/");
|
||||||
this.storageService = new ElectronStorageService(app.getPath('userData'));
|
this.storageService = new ElectronStorageService(app.getPath("userData"));
|
||||||
|
this.stateService = new StateService(
|
||||||
|
this.storageService,
|
||||||
|
null,
|
||||||
|
this.logService,
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
new StateFactory(GlobalState, Account)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.windowMain = new WindowMain(
|
||||||
|
this.stateService,
|
||||||
|
this.logService,
|
||||||
|
false,
|
||||||
|
800,
|
||||||
|
600,
|
||||||
|
(arg) => this.processDeepLink(arg),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
this.windowMain = new WindowMain(this.storageService, false, 800, 600, arg => this.processDeepLink(arg), null);
|
|
||||||
this.menuMain = new MenuMain(this);
|
this.menuMain = new MenuMain(this);
|
||||||
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain, 'directory-connector', () => {
|
this.updaterMain = new UpdaterMain(
|
||||||
this.messagingService.send('checkingForUpdate');
|
this.i18nService,
|
||||||
}, () => {
|
this.windowMain,
|
||||||
this.messagingService.send('doneCheckingForUpdate');
|
"directory-connector",
|
||||||
}, () => {
|
() => {
|
||||||
this.messagingService.send('doneCheckingForUpdate');
|
this.messagingService.send("checkingForUpdate");
|
||||||
}, 'bitwardenDirectoryConnector');
|
},
|
||||||
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.storageService);
|
() => {
|
||||||
this.messagingMain = new MessagingMain(this.windowMain, this.menuMain, this.updaterMain, this.trayMain);
|
this.messagingService.send("doneCheckingForUpdate");
|
||||||
this.messagingService = new ElectronMainMessagingService(this.windowMain, message => {
|
},
|
||||||
|
() => {
|
||||||
|
this.messagingService.send("doneCheckingForUpdate");
|
||||||
|
},
|
||||||
|
"bitwardenDirectoryConnector"
|
||||||
|
);
|
||||||
|
|
||||||
|
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.stateService);
|
||||||
|
|
||||||
|
this.messagingMain = new MessagingMain(
|
||||||
|
this.windowMain,
|
||||||
|
this.menuMain,
|
||||||
|
this.updaterMain,
|
||||||
|
this.trayMain
|
||||||
|
);
|
||||||
|
this.messagingService = new ElectronMainMessagingService(this.windowMain, (message) => {
|
||||||
this.messagingMain.onMessage(message);
|
this.messagingMain.onMessage(message);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.keytarStorageListener = new KeytarStorageListener('Bitwarden Directory Connector', null);
|
this.keytarStorageListener = new KeytarStorageListener("Bitwarden Directory Connector", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
bootstrap() {
|
bootstrap() {
|
||||||
this.keytarStorageListener.init();
|
this.keytarStorageListener.init();
|
||||||
this.windowMain.init().then(async () => {
|
this.windowMain.init().then(
|
||||||
|
async () => {
|
||||||
await this.i18nService.init(app.getLocale());
|
await this.i18nService.init(app.getLocale());
|
||||||
this.menuMain.init();
|
this.menuMain.init();
|
||||||
this.messagingMain.init();
|
this.messagingMain.init();
|
||||||
await this.updaterMain.init();
|
await this.updaterMain.init();
|
||||||
await this.trayMain.init(this.i18nService.t('bitwardenDirectoryConnector'));
|
await this.trayMain.init(this.i18nService.t("bitwardenDirectoryConnector"));
|
||||||
|
|
||||||
if (!app.isDefaultProtocolClient('bwdc')) {
|
if (!app.isDefaultProtocolClient("bwdc")) {
|
||||||
app.setAsDefaultProtocolClient('bwdc');
|
app.setAsDefaultProtocolClient("bwdc");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process protocol for macOS
|
// Process protocol for macOS
|
||||||
app.on('open-url', (event, url) => {
|
app.on("open-url", (event, url) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.processDeepLink([url]);
|
this.processDeepLink([url]);
|
||||||
});
|
});
|
||||||
}, (e: any) => {
|
},
|
||||||
// tslint:disable-next-line
|
(e: any) => {
|
||||||
|
// eslint-disable-next-line
|
||||||
console.error(e);
|
console.error(e);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private processDeepLink(argv: string[]): void {
|
private processDeepLink(argv: string[]): void {
|
||||||
argv.filter(s => s.indexOf('bwdc://') === 0).forEach(s => {
|
argv
|
||||||
|
.filter((s) => s.indexOf("bwdc://") === 0)
|
||||||
|
.forEach((s) => {
|
||||||
const url = new URL(s);
|
const url = new URL(s);
|
||||||
const code = url.searchParams.get('code');
|
const code = url.searchParams.get("code");
|
||||||
const receivedState = url.searchParams.get('state');
|
const receivedState = url.searchParams.get("state");
|
||||||
if (code != null && receivedState != null) {
|
if (code != null && receivedState != null) {
|
||||||
this.messagingService.send('ssoCallback', { code: code, state: receivedState });
|
this.messagingService.send("ssoCallback", { code: code, state: receivedState });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import {
|
import { Menu, MenuItemConstructorOptions } from "electron";
|
||||||
Menu,
|
|
||||||
MenuItem,
|
|
||||||
MenuItemConstructorOptions,
|
|
||||||
} from 'electron';
|
|
||||||
|
|
||||||
import { Main } from '../main';
|
import { BaseMenu } from "jslib-electron/baseMenu";
|
||||||
|
|
||||||
import { BaseMenu } from 'jslib-electron/baseMenu';
|
import { Main } from "../main";
|
||||||
|
|
||||||
export class MenuMain extends BaseMenu {
|
export class MenuMain extends BaseMenu {
|
||||||
menu: Menu;
|
menu: Menu;
|
||||||
@@ -25,22 +21,22 @@ export class MenuMain extends BaseMenu {
|
|||||||
const template: MenuItemConstructorOptions[] = [
|
const template: MenuItemConstructorOptions[] = [
|
||||||
this.editMenuItemOptions,
|
this.editMenuItemOptions,
|
||||||
{
|
{
|
||||||
label: this.i18nService.t('view'),
|
label: this.i18nService.t("view"),
|
||||||
submenu: this.viewSubMenuItemOptions,
|
submenu: this.viewSubMenuItemOptions,
|
||||||
},
|
},
|
||||||
this.windowMenuItemOptions,
|
this.windowMenuItemOptions,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === "darwin") {
|
||||||
const firstMenuPart: MenuItemConstructorOptions[] = [
|
const firstMenuPart: MenuItemConstructorOptions[] = [
|
||||||
{
|
{
|
||||||
label: this.i18nService.t('aboutBitwarden'),
|
label: this.i18nService.t("aboutBitwarden"),
|
||||||
role: 'about',
|
role: "about",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
template.unshift({
|
template.unshift({
|
||||||
label: this.main.i18nService.t('bitwardenDirectoryConnector'),
|
label: this.main.i18nService.t("bitwardenDirectoryConnector"),
|
||||||
submenu: firstMenuPart.concat(this.macAppMenuItemOptions),
|
submenu: firstMenuPart.concat(this.macAppMenuItemOptions),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -48,19 +44,24 @@ export class MenuMain extends BaseMenu {
|
|||||||
template[template.length - 1].submenu = this.macWindowSubmenuOptions;
|
template[template.length - 1].submenu = this.macWindowSubmenuOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
(template[template.length - 1].submenu as MenuItemConstructorOptions[]).splice(1, 0,
|
(template[template.length - 1].submenu as MenuItemConstructorOptions[]).splice(
|
||||||
|
1,
|
||||||
|
0,
|
||||||
{
|
{
|
||||||
label: this.main.i18nService.t(process.platform === 'darwin' ? 'hideToMenuBar' : 'hideToTray'),
|
label: this.main.i18nService.t(
|
||||||
click: () => this.main.messagingService.send('hideToTray'),
|
process.platform === "darwin" ? "hideToMenuBar" : "hideToTray"
|
||||||
accelerator: 'CmdOrCtrl+Shift+M',
|
),
|
||||||
|
click: () => this.main.messagingService.send("hideToTray"),
|
||||||
|
accelerator: "CmdOrCtrl+Shift+M",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'checkbox',
|
type: "checkbox",
|
||||||
label: this.main.i18nService.t('alwaysOnTop'),
|
label: this.main.i18nService.t("alwaysOnTop"),
|
||||||
checked: this.windowMain.win.isAlwaysOnTop(),
|
checked: this.windowMain.win.isAlwaysOnTop(),
|
||||||
click: () => this.main.windowMain.toggleAlwaysOnTop(),
|
click: () => this.main.windowMain.toggleAlwaysOnTop(),
|
||||||
accelerator: 'CmdOrCtrl+Shift+T',
|
accelerator: "CmdOrCtrl+Shift+T",
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
this.menu = Menu.buildFromTemplate(template);
|
this.menu = Menu.buildFromTemplate(template);
|
||||||
Menu.setApplicationMenu(this.menu);
|
Menu.setApplicationMenu(this.menu);
|
||||||
|
|||||||
@@ -1,43 +1,44 @@
|
|||||||
import {
|
import { ipcMain } from "electron";
|
||||||
app,
|
|
||||||
ipcMain,
|
|
||||||
} from 'electron';
|
|
||||||
|
|
||||||
import { TrayMain } from 'jslib-electron/tray.main';
|
import { TrayMain } from "jslib-electron/tray.main";
|
||||||
import { UpdaterMain } from 'jslib-electron/updater.main';
|
import { UpdaterMain } from "jslib-electron/updater.main";
|
||||||
import { WindowMain } from 'jslib-electron/window.main';
|
import { WindowMain } from "jslib-electron/window.main";
|
||||||
|
|
||||||
import { MenuMain } from './menu.main';
|
import { MenuMain } from "./menu.main";
|
||||||
|
|
||||||
const SyncCheckInterval = 60 * 1000; // 1 minute
|
const SyncCheckInterval = 60 * 1000; // 1 minute
|
||||||
|
|
||||||
export class MessagingMain {
|
export class MessagingMain {
|
||||||
private syncTimeout: NodeJS.Timer;
|
private syncTimeout: NodeJS.Timer;
|
||||||
|
|
||||||
constructor(private windowMain: WindowMain, private menuMain: MenuMain,
|
constructor(
|
||||||
private updaterMain: UpdaterMain, private trayMain: TrayMain) { }
|
private windowMain: WindowMain,
|
||||||
|
private menuMain: MenuMain,
|
||||||
|
private updaterMain: UpdaterMain,
|
||||||
|
private trayMain: TrayMain
|
||||||
|
) {}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
ipcMain.on('messagingService', async (event: any, message: any) => this.onMessage(message));
|
ipcMain.on("messagingService", async (event: any, message: any) => this.onMessage(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessage(message: any) {
|
onMessage(message: any) {
|
||||||
switch (message.command) {
|
switch (message.command) {
|
||||||
case 'checkForUpdate':
|
case "checkForUpdate":
|
||||||
this.updaterMain.checkForUpdate(true);
|
this.updaterMain.checkForUpdate(true);
|
||||||
break;
|
break;
|
||||||
case 'scheduleNextDirSync':
|
case "scheduleNextDirSync":
|
||||||
this.scheduleNextSync();
|
this.scheduleNextSync();
|
||||||
break;
|
break;
|
||||||
case 'cancelDirSync':
|
case "cancelDirSync":
|
||||||
this.windowMain.win.webContents.send('messagingService', {
|
this.windowMain.win.webContents.send("messagingService", {
|
||||||
command: 'syncScheduleStopped',
|
command: "syncScheduleStopped",
|
||||||
});
|
});
|
||||||
if (this.syncTimeout) {
|
if (this.syncTimeout) {
|
||||||
global.clearTimeout(this.syncTimeout);
|
global.clearTimeout(this.syncTimeout);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'hideToTray':
|
case "hideToTray":
|
||||||
this.trayMain.hideToTray();
|
this.trayMain.hideToTray();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -46,8 +47,8 @@ export class MessagingMain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private scheduleNextSync() {
|
private scheduleNextSync() {
|
||||||
this.windowMain.win.webContents.send('messagingService', {
|
this.windowMain.win.webContents.send("messagingService", {
|
||||||
command: 'syncScheduleStarted',
|
command: "syncScheduleStarted",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.syncTimeout) {
|
if (this.syncTimeout) {
|
||||||
@@ -59,8 +60,8 @@ export class MessagingMain {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.windowMain.win.webContents.send('messagingService', {
|
this.windowMain.win.webContents.send("messagingService", {
|
||||||
command: 'checkDirSync',
|
command: "checkDirSync",
|
||||||
});
|
});
|
||||||
}, SyncCheckInterval);
|
}, SyncCheckInterval);
|
||||||
}
|
}
|
||||||
|
|||||||
62
src/misc/logInStrategies/organizationLogIn.strategy.ts
Normal file
62
src/misc/logInStrategies/organizationLogIn.strategy.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { LogInStrategy } from "jslib-common/misc/logInStrategies/logIn.strategy";
|
||||||
|
import { AccountKeys, AccountProfile, AccountTokens } from "jslib-common/models/domain/account";
|
||||||
|
import { AuthResult } from "jslib-common/models/domain/authResult";
|
||||||
|
import { ApiLogInCredentials } from "jslib-common/models/domain/logInCredentials";
|
||||||
|
import { ApiTokenRequest } from "jslib-common/models/request/identityToken/apiTokenRequest";
|
||||||
|
import { IdentityTokenResponse } from "jslib-common/models/response/identityTokenResponse";
|
||||||
|
|
||||||
|
import { Account, DirectoryConfigurations, DirectorySettings } from "src/models/account";
|
||||||
|
|
||||||
|
export class OrganizationLogInStrategy extends LogInStrategy {
|
||||||
|
tokenRequest: ApiTokenRequest;
|
||||||
|
|
||||||
|
async logIn(credentials: ApiLogInCredentials) {
|
||||||
|
this.tokenRequest = new ApiTokenRequest(
|
||||||
|
credentials.clientId,
|
||||||
|
credentials.clientSecret,
|
||||||
|
await this.buildTwoFactor(),
|
||||||
|
await this.buildDeviceRequest()
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.startLogIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async processTokenResponse(response: IdentityTokenResponse): Promise<AuthResult> {
|
||||||
|
await this.saveAccountInformation(response);
|
||||||
|
return new AuthResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async saveAccountInformation(tokenResponse: IdentityTokenResponse) {
|
||||||
|
const clientId = this.tokenRequest.clientId;
|
||||||
|
const entityId = clientId.split("organization.")[1];
|
||||||
|
const clientSecret = this.tokenRequest.clientSecret;
|
||||||
|
|
||||||
|
await this.stateService.addAccount(
|
||||||
|
new Account({
|
||||||
|
profile: {
|
||||||
|
...new AccountProfile(),
|
||||||
|
...{
|
||||||
|
userId: entityId,
|
||||||
|
apiKeyClientId: clientId,
|
||||||
|
entityId: entityId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tokens: {
|
||||||
|
...new AccountTokens(),
|
||||||
|
...{
|
||||||
|
accessToken: tokenResponse.accessToken,
|
||||||
|
refreshToken: tokenResponse.refreshToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
keys: {
|
||||||
|
...new AccountKeys(),
|
||||||
|
...{
|
||||||
|
apiKeyClientSecret: clientSecret,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
directorySettings: new DirectorySettings(),
|
||||||
|
directoryConfigurations: new DirectoryConfigurations(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/models/IConfiguration.ts
Normal file
2
src/models/IConfiguration.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// eslint-disable-next-line
|
||||||
|
export interface IConfiguration {}
|
||||||
47
src/models/account.ts
Normal file
47
src/models/account.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { Account as BaseAccount } from "jslib-common/models/domain/account";
|
||||||
|
|
||||||
|
import { DirectoryType } from "src/enums/directoryType";
|
||||||
|
|
||||||
|
import { AzureConfiguration } from "./azureConfiguration";
|
||||||
|
import { GSuiteConfiguration } from "./gsuiteConfiguration";
|
||||||
|
import { LdapConfiguration } from "./ldapConfiguration";
|
||||||
|
import { OktaConfiguration } from "./oktaConfiguration";
|
||||||
|
import { OneLoginConfiguration } from "./oneLoginConfiguration";
|
||||||
|
import { SyncConfiguration } from "./syncConfiguration";
|
||||||
|
|
||||||
|
export class Account extends BaseAccount {
|
||||||
|
directoryConfigurations?: DirectoryConfigurations = new DirectoryConfigurations();
|
||||||
|
directorySettings: DirectorySettings = new DirectorySettings();
|
||||||
|
clientKeys: ClientKeys = new ClientKeys();
|
||||||
|
|
||||||
|
constructor(init: Partial<Account>) {
|
||||||
|
super(init);
|
||||||
|
this.directoryConfigurations = init?.directoryConfigurations ?? new DirectoryConfigurations();
|
||||||
|
this.directorySettings = init?.directorySettings ?? new DirectorySettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ClientKeys {
|
||||||
|
clientId: string;
|
||||||
|
clientSecret: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DirectoryConfigurations {
|
||||||
|
ldap: LdapConfiguration;
|
||||||
|
gsuite: GSuiteConfiguration;
|
||||||
|
azure: AzureConfiguration;
|
||||||
|
okta: OktaConfiguration;
|
||||||
|
oneLogin: OneLoginConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DirectorySettings {
|
||||||
|
organizationId?: string;
|
||||||
|
sync?: SyncConfiguration;
|
||||||
|
directoryType?: DirectoryType;
|
||||||
|
userDelta?: string;
|
||||||
|
groupDelta?: string;
|
||||||
|
lastUserSync?: Date;
|
||||||
|
lastGroupSync?: Date;
|
||||||
|
lastSyncHash?: string;
|
||||||
|
syncingDir?: boolean;
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
export class AzureConfiguration {
|
import { IConfiguration } from "./IConfiguration";
|
||||||
|
|
||||||
|
export class AzureConfiguration implements IConfiguration {
|
||||||
|
identityAuthority: string;
|
||||||
tenant: string;
|
tenant: string;
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
key: string;
|
key: string;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Entry } from './entry';
|
import { Entry } from "./entry";
|
||||||
|
|
||||||
export class GroupEntry extends Entry {
|
export class GroupEntry extends Entry {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export class GSuiteConfiguration {
|
import { IConfiguration } from "./IConfiguration";
|
||||||
|
|
||||||
|
export class GSuiteConfiguration implements IConfiguration {
|
||||||
clientEmail: string;
|
clientEmail: string;
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
domain: string;
|
domain: string;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export class LdapConfiguration {
|
import { IConfiguration } from "./IConfiguration";
|
||||||
|
|
||||||
|
export class LdapConfiguration implements IConfiguration {
|
||||||
ssl = false;
|
ssl = false;
|
||||||
startTls = false;
|
startTls = false;
|
||||||
tlsCaPath: string;
|
tlsCaPath: string;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export class OktaConfiguration {
|
import { IConfiguration } from "./IConfiguration";
|
||||||
|
|
||||||
|
export class OktaConfiguration implements IConfiguration {
|
||||||
orgUrl: string;
|
orgUrl: string;
|
||||||
token: string;
|
token: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
export class OneLoginConfiguration {
|
import { IConfiguration } from "./IConfiguration";
|
||||||
|
|
||||||
|
export class OneLoginConfiguration implements IConfiguration {
|
||||||
clientId: string;
|
clientId: string;
|
||||||
clientSecret: string;
|
clientSecret: string;
|
||||||
region = 'us';
|
region = "us";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { GroupEntry } from '../groupEntry';
|
import { GroupEntry } from "../groupEntry";
|
||||||
|
|
||||||
export class GroupResponse {
|
export class GroupResponse {
|
||||||
externalId: string;
|
externalId: string;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { GroupResponse } from './groupResponse';
|
import { BaseResponse } from "jslib-node/cli/models/response/baseResponse";
|
||||||
import { UserResponse } from './userResponse';
|
|
||||||
|
|
||||||
import { SimResult } from '../simResult';
|
import { SimResult } from "../simResult";
|
||||||
|
|
||||||
import { BaseResponse } from 'jslib-node/cli/models/response/baseResponse';
|
import { GroupResponse } from "./groupResponse";
|
||||||
|
import { UserResponse } from "./userResponse";
|
||||||
|
|
||||||
export class TestResponse implements BaseResponse {
|
export class TestResponse implements BaseResponse {
|
||||||
object: string;
|
object: string;
|
||||||
@@ -13,10 +13,13 @@ export class TestResponse implements BaseResponse {
|
|||||||
deletedUsers: UserResponse[] = [];
|
deletedUsers: UserResponse[] = [];
|
||||||
|
|
||||||
constructor(result: SimResult) {
|
constructor(result: SimResult) {
|
||||||
this.object = 'test';
|
this.object = "test";
|
||||||
this.groups = result.groups != null ? result.groups.map(g => new GroupResponse(g)) : [];
|
this.groups = result.groups != null ? result.groups.map((g) => new GroupResponse(g)) : [];
|
||||||
this.enabledUsers = result.enabledUsers != null ? result.enabledUsers.map(u => new UserResponse(u)) : [];
|
this.enabledUsers =
|
||||||
this.disabledUsers = result.disabledUsers != null ? result.disabledUsers.map(u => new UserResponse(u)) : [];
|
result.enabledUsers != null ? result.enabledUsers.map((u) => new UserResponse(u)) : [];
|
||||||
this.deletedUsers = result.deletedUsers != null ? result.deletedUsers.map(u => new UserResponse(u)) : [];
|
this.disabledUsers =
|
||||||
|
result.disabledUsers != null ? result.disabledUsers.map((u) => new UserResponse(u)) : [];
|
||||||
|
this.deletedUsers =
|
||||||
|
result.deletedUsers != null ? result.deletedUsers.map((u) => new UserResponse(u)) : [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { UserEntry } from '../userEntry';
|
import { UserEntry } from "../userEntry";
|
||||||
|
|
||||||
export class UserResponse {
|
export class UserResponse {
|
||||||
externalId: string;
|
externalId: string;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { GroupEntry } from './groupEntry';
|
import { GroupEntry } from "./groupEntry";
|
||||||
import { UserEntry } from './userEntry';
|
import { UserEntry } from "./userEntry";
|
||||||
|
|
||||||
export class SimResult {
|
export class SimResult {
|
||||||
groups: GroupEntry[] = [];
|
groups: GroupEntry[] = [];
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Entry } from './entry';
|
import { Entry } from "./entry";
|
||||||
|
|
||||||
export class UserEntry extends Entry {
|
export class UserEntry extends Entry {
|
||||||
email: string;
|
email: string;
|
||||||
|
|||||||
1784
src/package-lock.json
generated
Normal file
1784
src/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"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": "2.9.9-alpha",
|
"version": "2.10.0",
|
||||||
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
||||||
"homepage": "https://bitwarden.com",
|
"homepage": "https://bitwarden.com",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
@@ -13,9 +13,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"browser-hrtime": "^1.1.8",
|
"browser-hrtime": "^1.1.8",
|
||||||
"electron-log": "4.3.5",
|
"electron-log": "4.4.1",
|
||||||
"electron-store": "8.0.0",
|
"electron-store": "8.0.1",
|
||||||
"electron-updater": "4.3.9",
|
"electron-updater": "4.6.1",
|
||||||
"keytar": "7.6.0"
|
"keytar": "7.7.0",
|
||||||
|
"rxjs": "^7.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
364
src/program.ts
364
src/program.ts
@@ -1,107 +1,111 @@
|
|||||||
import * as chalk from 'chalk';
|
import * as path from "path";
|
||||||
import * as program from 'commander';
|
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
import { Main } from './bwdc';
|
import * as chalk from "chalk";
|
||||||
|
import * as program from "commander";
|
||||||
|
|
||||||
import { ClearCacheCommand } from './commands/clearCache.command';
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
import { ConfigCommand } from './commands/config.command';
|
import { BaseProgram } from "jslib-node/cli/baseProgram";
|
||||||
import { LastSyncCommand } from './commands/lastSync.command';
|
import { LoginCommand } from "jslib-node/cli/commands/login.command";
|
||||||
import { SyncCommand } from './commands/sync.command';
|
import { LogoutCommand } from "jslib-node/cli/commands/logout.command";
|
||||||
import { TestCommand } from './commands/test.command';
|
import { UpdateCommand } from "jslib-node/cli/commands/update.command";
|
||||||
|
import { Response } from "jslib-node/cli/models/response";
|
||||||
|
import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
|
||||||
|
|
||||||
import { LoginCommand } from 'jslib-node/cli/commands/login.command';
|
import { Main } from "./bwdc";
|
||||||
import { LogoutCommand } from 'jslib-node/cli/commands/logout.command';
|
import { ClearCacheCommand } from "./commands/clearCache.command";
|
||||||
import { UpdateCommand } from 'jslib-node/cli/commands/update.command';
|
import { ConfigCommand } from "./commands/config.command";
|
||||||
|
import { LastSyncCommand } from "./commands/lastSync.command";
|
||||||
|
import { SyncCommand } from "./commands/sync.command";
|
||||||
|
import { TestCommand } from "./commands/test.command";
|
||||||
|
|
||||||
import { BaseProgram } from 'jslib-node/cli/baseProgram';
|
const writeLn = (s: string, finalLine = false, error = false) => {
|
||||||
|
|
||||||
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service';
|
|
||||||
import { Response } from 'jslib-node/cli/models/response';
|
|
||||||
import { StringResponse } from 'jslib-node/cli/models/response/stringResponse';
|
|
||||||
|
|
||||||
import { Utils } from 'jslib-common/misc/utils';
|
|
||||||
|
|
||||||
const writeLn = (s: string, finalLine: boolean = false, error: boolean = false) => {
|
|
||||||
const stream = error ? process.stderr : process.stdout;
|
const stream = error ? process.stderr : process.stdout;
|
||||||
if (finalLine && process.platform === 'win32') {
|
if (finalLine && process.platform === "win32") {
|
||||||
stream.write(s);
|
stream.write(s);
|
||||||
} else {
|
} else {
|
||||||
stream.write(s + '\n');
|
stream.write(s + "\n");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Program extends BaseProgram {
|
export class Program extends BaseProgram {
|
||||||
private apiKeyService: ApiKeyService;
|
|
||||||
|
|
||||||
constructor(private main: Main) {
|
constructor(private main: Main) {
|
||||||
super(main.userService, writeLn);
|
super(main.stateService, writeLn);
|
||||||
this.apiKeyService = main.apiKeyService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
program
|
program
|
||||||
.option('--pretty', 'Format output. JSON is tabbed with two spaces.')
|
.option("--pretty", "Format output. JSON is tabbed with two spaces.")
|
||||||
.option('--raw', 'Return raw output instead of a descriptive message.')
|
.option("--raw", "Return raw output instead of a descriptive message.")
|
||||||
.option('--response', 'Return a JSON formatted version of response output.')
|
.option("--response", "Return a JSON formatted version of response output.")
|
||||||
.option('--cleanexit', 'Exit with a success exit code (0) unless an error is thrown.')
|
.option("--cleanexit", "Exit with a success exit code (0) unless an error is thrown.")
|
||||||
.option('--quiet', 'Don\'t return anything to stdout.')
|
.option("--quiet", "Don't return anything to stdout.")
|
||||||
.option('--nointeraction', 'Do not prompt for interactive user input.')
|
.option("--nointeraction", "Do not prompt for interactive user input.")
|
||||||
.version(await this.main.platformUtilsService.getApplicationVersion(), '-v, --version');
|
.version(await this.main.platformUtilsService.getApplicationVersion(), "-v, --version");
|
||||||
|
|
||||||
program.on('option:pretty', () => {
|
program.on("option:pretty", () => {
|
||||||
process.env.BW_PRETTY = 'true';
|
process.env.BW_PRETTY = "true";
|
||||||
});
|
});
|
||||||
|
|
||||||
program.on('option:raw', () => {
|
program.on("option:raw", () => {
|
||||||
process.env.BW_RAW = 'true';
|
process.env.BW_RAW = "true";
|
||||||
});
|
});
|
||||||
|
|
||||||
program.on('option:quiet', () => {
|
program.on("option:quiet", () => {
|
||||||
process.env.BW_QUIET = 'true';
|
process.env.BW_QUIET = "true";
|
||||||
});
|
});
|
||||||
|
|
||||||
program.on('option:response', () => {
|
program.on("option:response", () => {
|
||||||
process.env.BW_RESPONSE = 'true';
|
process.env.BW_RESPONSE = "true";
|
||||||
});
|
});
|
||||||
|
|
||||||
program.on('option:cleanexit', () => {
|
program.on("option:cleanexit", () => {
|
||||||
process.env.BW_CLEANEXIT = 'true';
|
process.env.BW_CLEANEXIT = "true";
|
||||||
});
|
});
|
||||||
|
|
||||||
program.on('option:nointeraction', () => {
|
program.on("option:nointeraction", () => {
|
||||||
process.env.BW_NOINTERACTION = 'true';
|
process.env.BW_NOINTERACTION = "true";
|
||||||
});
|
});
|
||||||
|
|
||||||
program.on('command:*', () => {
|
program.on("command:*", () => {
|
||||||
writeLn(chalk.redBright('Invalid command: ' + program.args.join(' ')), false, true);
|
writeLn(chalk.redBright("Invalid command: " + program.args.join(" ")), false, true);
|
||||||
writeLn('See --help for a list of available commands.', true, true);
|
writeLn("See --help for a list of available commands.", true, true);
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
program.on('--help', () => {
|
program.on("--help", () => {
|
||||||
writeLn('\n Examples:');
|
writeLn("\n Examples:");
|
||||||
writeLn('');
|
writeLn("");
|
||||||
writeLn(' bwdc login');
|
writeLn(" bwdc login");
|
||||||
writeLn(' bwdc test');
|
writeLn(" bwdc test");
|
||||||
writeLn(' bwdc sync');
|
writeLn(" bwdc sync");
|
||||||
writeLn(' bwdc last-sync');
|
writeLn(" bwdc last-sync");
|
||||||
writeLn(' bwdc config server https://bw.company.com');
|
writeLn(" bwdc config server https://bw.company.com");
|
||||||
writeLn(' bwdc update');
|
writeLn(" bwdc update");
|
||||||
writeLn('', true);
|
writeLn("", true);
|
||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('login [clientId] [clientSecret]')
|
.command("login [clientId] [clientSecret]")
|
||||||
.description('Log into an organization account.', {
|
.description("Log into an organization account.", {
|
||||||
clientId: 'Client_id part of your organization\'s API key',
|
clientId: "Client_id part of your organization's API key",
|
||||||
clientSecret: 'Client_secret part of your organization\'s API key',
|
clientSecret: "Client_secret part of your organization's API key",
|
||||||
})
|
})
|
||||||
.action(async (clientId: string, clientSecret: string, options: program.OptionValues) => {
|
.action(async (clientId: string, clientSecret: string, options: program.OptionValues) => {
|
||||||
await this.exitIfAuthed();
|
await this.exitIfAuthed();
|
||||||
const command = new LoginCommand(this.main.authService, this.main.apiService, this.main.i18nService,
|
const command = new LoginCommand(
|
||||||
this.main.environmentService, this.main.passwordGenerationService, this.main.cryptoFunctionService,
|
this.main.authService,
|
||||||
this.main.platformUtilsService, 'connector');
|
this.main.apiService,
|
||||||
|
this.main.i18nService,
|
||||||
|
this.main.environmentService,
|
||||||
|
this.main.passwordGenerationService,
|
||||||
|
this.main.cryptoFunctionService,
|
||||||
|
this.main.platformUtilsService,
|
||||||
|
this.main.stateService,
|
||||||
|
this.main.cryptoService,
|
||||||
|
this.main.policyService,
|
||||||
|
this.main.twoFactorService,
|
||||||
|
"connector"
|
||||||
|
);
|
||||||
|
|
||||||
if (!Utils.isNullOrWhitespace(clientId)) {
|
if (!Utils.isNullOrWhitespace(clientId)) {
|
||||||
process.env.BW_CLIENTID = clientId;
|
process.env.BW_CLIENTID = clientId;
|
||||||
@@ -116,32 +120,35 @@ export class Program extends BaseProgram {
|
|||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('logout')
|
.command("logout")
|
||||||
.description('Log out of the current user account.')
|
.description("Log out of the current user account.")
|
||||||
.on('--help', () => {
|
.on("--help", () => {
|
||||||
writeLn('\n Examples:');
|
writeLn("\n Examples:");
|
||||||
writeLn('');
|
writeLn("");
|
||||||
writeLn(' bwdc logout');
|
writeLn(" bwdc logout");
|
||||||
writeLn('', true);
|
writeLn("", true);
|
||||||
})
|
})
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
await this.exitIfNotAuthed();
|
await this.exitIfNotAuthed();
|
||||||
const command = new LogoutCommand(this.main.authService, this.main.i18nService,
|
const command = new LogoutCommand(
|
||||||
async () => await this.main.logout());
|
this.main.authService,
|
||||||
|
this.main.i18nService,
|
||||||
|
async () => await this.main.logout()
|
||||||
|
);
|
||||||
const response = await command.run();
|
const response = await command.run();
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('test')
|
.command("test")
|
||||||
.description('Test a simulated sync.')
|
.description("Test a simulated sync.")
|
||||||
.option('-l, --last', 'Since the last successful sync.')
|
.option("-l, --last", "Since the last successful sync.")
|
||||||
.on('--help', () => {
|
.on("--help", () => {
|
||||||
writeLn('\n Examples:');
|
writeLn("\n Examples:");
|
||||||
writeLn('');
|
writeLn("");
|
||||||
writeLn(' bwdc test');
|
writeLn(" bwdc test");
|
||||||
writeLn(' bwdc test --last');
|
writeLn(" bwdc test --last");
|
||||||
writeLn('', true);
|
writeLn("", true);
|
||||||
})
|
})
|
||||||
.action(async (options: program.OptionValues) => {
|
.action(async (options: program.OptionValues) => {
|
||||||
await this.exitIfNotAuthed();
|
await this.exitIfNotAuthed();
|
||||||
@@ -151,13 +158,13 @@ export class Program extends BaseProgram {
|
|||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('sync')
|
.command("sync")
|
||||||
.description('Sync the directory.')
|
.description("Sync the directory.")
|
||||||
.on('--help', () => {
|
.on("--help", () => {
|
||||||
writeLn('\n Examples:');
|
writeLn("\n Examples:");
|
||||||
writeLn('');
|
writeLn("");
|
||||||
writeLn(' bwdc sync');
|
writeLn(" bwdc sync");
|
||||||
writeLn('', true);
|
writeLn("", true);
|
||||||
})
|
})
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
await this.exitIfNotAuthed();
|
await this.exitIfNotAuthed();
|
||||||
@@ -167,116 +174,124 @@ export class Program extends BaseProgram {
|
|||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('last-sync <object>')
|
.command("last-sync <object>")
|
||||||
.description('Get the last successful sync date.')
|
.description("Get the last successful sync date.")
|
||||||
.on('--help', () => {
|
.on("--help", () => {
|
||||||
writeLn('\n Notes:');
|
writeLn("\n Notes:");
|
||||||
writeLn('');
|
writeLn("");
|
||||||
writeLn(' Returns empty response if no sync has been performed for the given object.');
|
writeLn(" Returns empty response if no sync has been performed for the given object.");
|
||||||
writeLn('');
|
writeLn("");
|
||||||
writeLn(' Examples:');
|
writeLn(" Examples:");
|
||||||
writeLn('');
|
writeLn("");
|
||||||
writeLn(' bwdc last-sync groups');
|
writeLn(" bwdc last-sync groups");
|
||||||
writeLn(' bwdc last-sync users');
|
writeLn(" bwdc last-sync users");
|
||||||
writeLn('', true);
|
writeLn("", true);
|
||||||
})
|
})
|
||||||
.action(async (object: string) => {
|
.action(async (object: string) => {
|
||||||
await this.exitIfNotAuthed();
|
await this.exitIfNotAuthed();
|
||||||
const command = new LastSyncCommand(this.main.configurationService);
|
const command = new LastSyncCommand(this.main.stateService);
|
||||||
const response = await command.run(object);
|
const response = await command.run(object);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('config <setting> [value]')
|
.command("config <setting> [value]")
|
||||||
.description('Configure settings.')
|
.description("Configure settings.")
|
||||||
.option('--secretenv <variable-name>', 'Read secret from the named environment variable.')
|
.option("--secretenv <variable-name>", "Read secret from the named environment variable.")
|
||||||
.option('--secretfile <filename>', 'Read secret from first line of the named file.')
|
.option("--secretfile <filename>", "Read secret from first line of the named file.")
|
||||||
.on('--help', () => {
|
.on("--help", () => {
|
||||||
writeLn('\n Settings:');
|
writeLn("\n Settings:");
|
||||||
writeLn('');
|
writeLn("");
|
||||||
writeLn(' server - On-premise hosted installation URL.');
|
writeLn(" server - On-premise hosted installation URL.");
|
||||||
writeLn(' directory - The type of directory to use.');
|
writeLn(" directory - The type of directory to use.");
|
||||||
writeLn(' ldap.password - The password for connection to this LDAP server.');
|
writeLn(" ldap.password - The password for connection to this LDAP server.");
|
||||||
writeLn(' azure.key - The Azure AD secret key.');
|
writeLn(" azure.key - The Azure AD secret key.");
|
||||||
writeLn(' gsuite.key - The G Suite private key.');
|
writeLn(" gsuite.key - The G Suite private key.");
|
||||||
writeLn(' okta.token - The Okta token.');
|
writeLn(" okta.token - The Okta token.");
|
||||||
writeLn(' onelogin.secret - The OneLogin client secret.');
|
writeLn(" onelogin.secret - The OneLogin client secret.");
|
||||||
writeLn('');
|
writeLn("");
|
||||||
writeLn(' Examples:');
|
writeLn(" Examples:");
|
||||||
writeLn('');
|
writeLn("");
|
||||||
writeLn(' bwdc config server https://bw.company.com');
|
writeLn(" bwdc config server https://bw.company.com");
|
||||||
writeLn(' bwdc config server bitwarden.com');
|
writeLn(" bwdc config server bitwarden.com");
|
||||||
writeLn(' bwdc config directory 1');
|
writeLn(" bwdc config directory 1");
|
||||||
writeLn(' bwdc config ldap.password <password>');
|
writeLn(" bwdc config ldap.password <password>");
|
||||||
writeLn(' bwdc config ldap.password --secretenv LDAP_PWD');
|
writeLn(" bwdc config ldap.password --secretenv LDAP_PWD");
|
||||||
writeLn(' bwdc config azure.key <key>');
|
writeLn(" bwdc config azure.key <key>");
|
||||||
writeLn(' bwdc config gsuite.key <key>');
|
writeLn(" bwdc config gsuite.key <key>");
|
||||||
writeLn(' bwdc config okta.token <token>');
|
writeLn(" bwdc config okta.token <token>");
|
||||||
writeLn(' bwdc config onelogin.secret <secret>');
|
writeLn(" bwdc config onelogin.secret <secret>");
|
||||||
writeLn('', true);
|
writeLn("", true);
|
||||||
})
|
})
|
||||||
.action(async (setting: string, value: string, options: program.OptionValues) => {
|
.action(async (setting: string, value: string, options: program.OptionValues) => {
|
||||||
const command = new ConfigCommand(this.main.environmentService, this.main.i18nService,
|
const command = new ConfigCommand(
|
||||||
this.main.configurationService);
|
this.main.environmentService,
|
||||||
|
this.main.i18nService,
|
||||||
|
this.main.stateService
|
||||||
|
);
|
||||||
const response = await command.run(setting, value, options);
|
const response = await command.run(setting, value, options);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('data-file')
|
.command("data-file")
|
||||||
.description('Path to data.json database file.')
|
.description("Path to data.json database file.")
|
||||||
.on('--help', () => {
|
.on("--help", () => {
|
||||||
writeLn('\n Examples:');
|
writeLn("\n Examples:");
|
||||||
writeLn('');
|
writeLn("");
|
||||||
writeLn(' bwdc data-file');
|
writeLn(" bwdc data-file");
|
||||||
writeLn('', true);
|
writeLn("", true);
|
||||||
})
|
})
|
||||||
.action(() => {
|
.action(() => {
|
||||||
this.processResponse(
|
this.processResponse(
|
||||||
Response.success(new StringResponse(path.join(this.main.dataFilePath, 'data.json'))));
|
Response.success(new StringResponse(path.join(this.main.dataFilePath, "data.json")))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('clear-cache')
|
.command("clear-cache")
|
||||||
.description('Clear the sync cache.')
|
.description("Clear the sync cache.")
|
||||||
.on('--help', () => {
|
.on("--help", () => {
|
||||||
writeLn('\n Examples:');
|
writeLn("\n Examples:");
|
||||||
writeLn('');
|
writeLn("");
|
||||||
writeLn(' bwdc clear-cache');
|
writeLn(" bwdc clear-cache");
|
||||||
writeLn('', true);
|
writeLn("", true);
|
||||||
})
|
})
|
||||||
.action(async (options: program.OptionValues) => {
|
.action(async (options: program.OptionValues) => {
|
||||||
const command = new ClearCacheCommand(this.main.configurationService, this.main.i18nService);
|
const command = new ClearCacheCommand(this.main.i18nService, this.main.stateService);
|
||||||
const response = await command.run(options);
|
const response = await command.run(options);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('update')
|
.command("update")
|
||||||
.description('Check for updates.')
|
.description("Check for updates.")
|
||||||
.on('--help', () => {
|
.on("--help", () => {
|
||||||
writeLn('\n Notes:');
|
writeLn("\n Notes:");
|
||||||
writeLn('');
|
writeLn("");
|
||||||
writeLn(' Returns the URL to download the newest version of this CLI tool.');
|
writeLn(" Returns the URL to download the newest version of this CLI tool.");
|
||||||
writeLn('');
|
writeLn("");
|
||||||
writeLn(' Use the `--raw` option to return only the download URL for the update.');
|
writeLn(" Use the `--raw` option to return only the download URL for the update.");
|
||||||
writeLn('');
|
writeLn("");
|
||||||
writeLn(' Examples:');
|
writeLn(" Examples:");
|
||||||
writeLn('');
|
writeLn("");
|
||||||
writeLn(' bwdc update');
|
writeLn(" bwdc update");
|
||||||
writeLn(' bwdc update --raw');
|
writeLn(" bwdc update --raw");
|
||||||
writeLn('', true);
|
writeLn("", true);
|
||||||
})
|
})
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
const command = new UpdateCommand(this.main.platformUtilsService, this.main.i18nService,
|
const command = new UpdateCommand(
|
||||||
'directory-connector', 'bwdc', false);
|
this.main.platformUtilsService,
|
||||||
|
this.main.i18nService,
|
||||||
|
"directory-connector",
|
||||||
|
"bwdc",
|
||||||
|
false
|
||||||
|
);
|
||||||
const response = await command.run();
|
const response = await command.run();
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
program
|
program.parse(process.argv);
|
||||||
.parse(process.argv);
|
|
||||||
|
|
||||||
if (process.argv.slice(2).length === 0) {
|
if (process.argv.slice(2).length === 0) {
|
||||||
program.outputHelp();
|
program.outputHelp();
|
||||||
@@ -284,18 +299,21 @@ export class Program extends BaseProgram {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async exitIfAuthed() {
|
async exitIfAuthed() {
|
||||||
const authed = await this.apiKeyService.isAuthenticated();
|
const authed = await this.stateService.getIsAuthenticated();
|
||||||
if (authed) {
|
if (authed) {
|
||||||
const type = await this.apiKeyService.getEntityType();
|
const type = await this.stateService.getEntityType();
|
||||||
const id = await this.apiKeyService.getEntityId();
|
const id = await this.stateService.getEntityId();
|
||||||
this.processResponse(Response.error('You are already logged in as ' + type + '.' + id + '.'), true);
|
this.processResponse(
|
||||||
|
Response.error("You are already logged in as " + type + "." + id + "."),
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async exitIfNotAuthed() {
|
async exitIfNotAuthed() {
|
||||||
const authed = await this.apiKeyService.isAuthenticated();
|
const authed = await this.stateService.getIsAuthenticated();
|
||||||
if (!authed) {
|
if (!authed) {
|
||||||
this.processResponse(Response.error('You are not logged in.'), true);
|
this.processResponse(Response.error("You are not logged in."), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
src/scss/bootstrap.scss
vendored
30
src/scss/bootstrap.scss
vendored
@@ -1,5 +1,15 @@
|
|||||||
$theme-colors: ( "primary": #175DDC, "primary-accent": #1252A3, "danger": #dd4b39, "success": #00a65a, "info": #555555, "warning": #bf7e16, "secondary": #ced4da, "secondary-alt": #1A3B66);
|
$theme-colors: (
|
||||||
$font-family-sans-serif: 'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
"primary": #175ddc,
|
||||||
|
"primary-accent": #1252a3,
|
||||||
|
"danger": #dd4b39,
|
||||||
|
"success": #00a65a,
|
||||||
|
"info": #555555,
|
||||||
|
"warning": #bf7e16,
|
||||||
|
"secondary": #ced4da,
|
||||||
|
"secondary-alt": #1a3b66,
|
||||||
|
);
|
||||||
|
$font-family-sans-serif: "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;
|
||||||
@@ -8,13 +18,13 @@ $h4-font-size: 1rem;
|
|||||||
$h5-font-size: 1rem;
|
$h5-font-size: 1rem;
|
||||||
$h6-font-size: 1rem;
|
$h6-font-size: 1rem;
|
||||||
|
|
||||||
$primary: map_get($theme-colors, 'primary');
|
$primary: map_get($theme-colors, "primary");
|
||||||
$primary-accent: map_get($theme-colors, 'primary-accent');
|
$primary-accent: map_get($theme-colors, "primary-accent");
|
||||||
$success: map_get($theme-colors, 'success');
|
$success: map_get($theme-colors, "success");
|
||||||
$info: map_get($theme-colors, 'info');
|
$info: map_get($theme-colors, "info");
|
||||||
$warning: map_get($theme-colors, 'warning');
|
$warning: map_get($theme-colors, "warning");
|
||||||
$danger: map_get($theme-colors, 'danger');
|
$danger: map_get($theme-colors, "danger");
|
||||||
$secondary: map_get($theme-colors, 'secondary');
|
$secondary: map_get($theme-colors, "secondary");
|
||||||
$secondary-alt: map_get($theme-colors, 'secondary-alt');
|
$secondary-alt: map_get($theme-colors, "secondary-alt");
|
||||||
|
|
||||||
@import "~bootstrap/scss/bootstrap.scss";
|
@import "~bootstrap/scss/bootstrap.scss";
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ h1 {
|
|||||||
|
|
||||||
small {
|
small {
|
||||||
color: $text-muted;
|
color: $text-muted;
|
||||||
font-size: $h1-font-size * .5;
|
font-size: $h1-font-size * 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ h4 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#duo-frame {
|
#duo-frame {
|
||||||
background: url('../images/loading.svg') 0 0 no-repeat;
|
background: url("../images/loading.svg") 0 0 no-repeat;
|
||||||
height: 380px;
|
height: 380px;
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
@@ -138,7 +138,6 @@ ul.testing-list {
|
|||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&.focus {
|
&.focus {
|
||||||
box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq($primary), $primary, 15%), .5);
|
box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq($primary), $primary, 15%), 0.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1,38 @@
|
|||||||
$fa-font-path: "~font-awesome/fonts";
|
@import "~ngx-toastr/toastr";
|
||||||
@import "~font-awesome/scss/font-awesome.scss";
|
|
||||||
@import "~angular2-toaster/toaster";
|
|
||||||
|
|
||||||
@import "~bootstrap/scss/_variables.scss";
|
@import "~bootstrap/scss/_variables.scss";
|
||||||
|
|
||||||
#toast-container {
|
.toast-container {
|
||||||
.toast-close-button {
|
.toast-close-button {
|
||||||
right: -0.15em;
|
font-size: 18px;
|
||||||
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast {
|
.ngx-toastr {
|
||||||
opacity: 1 !important;
|
align-items: center;
|
||||||
background-image: none !important;
|
background-image: none !important;
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.35);
|
box-shadow: 0 0 8px rgba(0, 0, 0, 0.35);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
padding: 15px;
|
||||||
|
|
||||||
|
.toast-close-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 5px;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.6);
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:before {
|
.icon i::before {
|
||||||
font-family: FontAwesome;
|
float: left;
|
||||||
|
font-style: normal;
|
||||||
|
font-family: $icomoon-font-family;
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
float: left;
|
padding-right: 15px;
|
||||||
color: #ffffff;
|
|
||||||
padding-right: 10px;
|
|
||||||
margin: auto 0 auto -36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toaster-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast-message {
|
.toast-message {
|
||||||
@@ -45,40 +45,36 @@ $fa-font-path: "~font-awesome/fonts";
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.toast-danger, &.toast-error {
|
&.toast-danger,
|
||||||
background-image: none !important;
|
&.toast-error {
|
||||||
background-color: $danger;
|
background-color: $danger;
|
||||||
|
|
||||||
&:before {
|
.icon i::before {
|
||||||
content: "\f0e7";
|
content: map_get($icons, "error");
|
||||||
margin-left: -30px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.toast-warning {
|
&.toast-warning {
|
||||||
background-image: none !important;
|
|
||||||
background-color: $warning;
|
background-color: $warning;
|
||||||
|
|
||||||
&:before {
|
.icon i::before {
|
||||||
content: "\f071";
|
content: map_get($icons, "exclamation-triangle");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.toast-info {
|
&.toast-info {
|
||||||
background-image: none !important;
|
|
||||||
background-color: $info;
|
background-color: $info;
|
||||||
|
|
||||||
&:before {
|
.icon i:before {
|
||||||
content: "\f05a";
|
content: map_get($icons, "info-circle");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.toast-success {
|
&.toast-success {
|
||||||
background-image: none !important;
|
|
||||||
background-color: $success;
|
background-color: $success;
|
||||||
|
|
||||||
&:before {
|
.icon i:before {
|
||||||
content: "\f00C";
|
content: map_get($icons, "check");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@import "../../jslib/angular/src/scss/webfonts.css";
|
@import "../../jslib/angular/src/scss/webfonts.css";
|
||||||
|
@import "../../jslib/angular/src/scss/bwicons/styles/style.scss";
|
||||||
@import "bootstrap.scss";
|
@import "bootstrap.scss";
|
||||||
@import "pages.scss";
|
@import "pages.scss";
|
||||||
@import "misc.scss";
|
@import "misc.scss";
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service';
|
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { TokenService } from "jslib-common/abstractions/token.service";
|
||||||
import { TokenService } from 'jslib-common/abstractions/token.service';
|
import { ApiLogInCredentials } from "jslib-common/models/domain/logInCredentials";
|
||||||
|
import { ApiService as ApiServiceBase } from "jslib-common/services/api.service";
|
||||||
|
|
||||||
import { ApiService as ApiServiceBase } from 'jslib-common/services/api.service';
|
import { StateService } from "../abstractions/state.service";
|
||||||
|
|
||||||
export async function refreshToken(apiKeyService: ApiKeyService, authService: AuthService) {
|
export async function refreshToken(stateService: StateService, authService: AuthService) {
|
||||||
try {
|
try {
|
||||||
const clientId = await apiKeyService.getClientId();
|
const clientId = await stateService.getApiKeyClientId();
|
||||||
const clientSecret = await apiKeyService.getClientSecret();
|
const clientSecret = await stateService.getApiKeyClientSecret();
|
||||||
if (clientId != null && clientSecret != null) {
|
if (clientId != null && clientSecret != null) {
|
||||||
await authService.logInApiKey(clientId, clientSecret);
|
await authService.logIn(new ApiLogInCredentials(clientId, clientSecret));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Promise.reject(e);
|
return Promise.reject(e);
|
||||||
@@ -19,11 +20,15 @@ export async function refreshToken(apiKeyService: ApiKeyService, authService: Au
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ApiService extends ApiServiceBase {
|
export class ApiService extends ApiServiceBase {
|
||||||
constructor(tokenService: TokenService, platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
constructor(
|
||||||
private refreshTokenCallback: () => Promise<void>, logoutCallback: (expired: boolean) => Promise<void>,
|
tokenService: TokenService,
|
||||||
customUserAgent: string = null) {
|
platformUtilsService: PlatformUtilsService,
|
||||||
|
environmentService: EnvironmentService,
|
||||||
|
private refreshTokenCallback: () => Promise<void>,
|
||||||
|
logoutCallback: (expired: boolean) => Promise<void>,
|
||||||
|
customUserAgent: string = null
|
||||||
|
) {
|
||||||
super(tokenService, platformUtilsService, environmentService, logoutCallback, customUserAgent);
|
super(tokenService, platformUtilsService, environmentService, logoutCallback, customUserAgent);
|
||||||
this.apiKeyRefresh = (clientId: string, clientSecret: string) => this.doRefreshToken();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
doRefreshToken(): Promise<void> {
|
doRefreshToken(): Promise<void> {
|
||||||
|
|||||||
@@ -1,61 +1,68 @@
|
|||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { Injectable } from "@angular/core";
|
||||||
import { ApiKeyService } from 'jslib-common/abstractions/apiKey.service';
|
|
||||||
import { AppIdService } from 'jslib-common/abstractions/appId.service';
|
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
|
||||||
import { TokenService } from 'jslib-common/abstractions/token.service';
|
|
||||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
|
||||||
|
|
||||||
import { AuthService as AuthServiceBase } from 'jslib-common/services/auth.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
|
import { AppIdService } from "jslib-common/abstractions/appId.service";
|
||||||
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
|
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||||
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
import { TokenService } from "jslib-common/abstractions/token.service";
|
||||||
|
import { TwoFactorService } from "jslib-common/abstractions/twoFactor.service";
|
||||||
|
import { AuthResult } from "jslib-common/models/domain/authResult";
|
||||||
|
import { ApiLogInCredentials } from "jslib-common/models/domain/logInCredentials";
|
||||||
|
import { AuthService as AuthServiceBase } from "jslib-common/services/auth.service";
|
||||||
|
|
||||||
import { AuthResult } from 'jslib-common/models/domain/authResult';
|
import { StateService } from "../abstractions/state.service";
|
||||||
import { DeviceRequest } from 'jslib-common/models/request/deviceRequest';
|
import { OrganizationLogInStrategy } from "../misc/logInStrategies/organizationLogIn.strategy";
|
||||||
import { TokenRequest } from 'jslib-common/models/request/tokenRequest';
|
|
||||||
import { IdentityTokenResponse } from 'jslib-common/models/response/identityTokenResponse';
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
export class AuthService extends AuthServiceBase {
|
export class AuthService extends AuthServiceBase {
|
||||||
|
constructor(
|
||||||
constructor(cryptoService: CryptoService, apiService: ApiService, userService: UserService,
|
cryptoService: CryptoService,
|
||||||
tokenService: TokenService, appIdService: AppIdService, i18nService: I18nService,
|
apiService: ApiService,
|
||||||
platformUtilsService: PlatformUtilsService, messagingService: MessagingService,
|
tokenService: TokenService,
|
||||||
vaultTimeoutService: VaultTimeoutService, logService: LogService, private apiKeyService: ApiKeyService,
|
appIdService: AppIdService,
|
||||||
setCryptoKeys = true) {
|
platformUtilsService: PlatformUtilsService,
|
||||||
super(cryptoService, apiService, userService, tokenService, appIdService, i18nService, platformUtilsService,
|
messagingService: MessagingService,
|
||||||
messagingService, vaultTimeoutService, logService, setCryptoKeys);
|
logService: LogService,
|
||||||
|
keyConnectorService: KeyConnectorService,
|
||||||
|
environmentService: EnvironmentService,
|
||||||
|
stateService: StateService,
|
||||||
|
twoFactorService: TwoFactorService,
|
||||||
|
i18nService: I18nService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
cryptoService,
|
||||||
|
apiService,
|
||||||
|
tokenService,
|
||||||
|
appIdService,
|
||||||
|
platformUtilsService,
|
||||||
|
messagingService,
|
||||||
|
logService,
|
||||||
|
keyConnectorService,
|
||||||
|
environmentService,
|
||||||
|
stateService,
|
||||||
|
twoFactorService,
|
||||||
|
i18nService
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async logInApiKey(clientId: string, clientSecret: string): Promise<AuthResult> {
|
async logIn(credentials: ApiLogInCredentials): Promise<AuthResult> {
|
||||||
this.selectedTwoFactorProviderType = null;
|
const strategy = new OrganizationLogInStrategy(
|
||||||
if (clientId.startsWith('organization')) {
|
this.cryptoService,
|
||||||
return await this.organizationLogInHelper(clientId, clientSecret);
|
this.apiService,
|
||||||
}
|
this.tokenService,
|
||||||
return await super.logInApiKey(clientId, clientSecret);
|
this.appIdService,
|
||||||
}
|
this.platformUtilsService,
|
||||||
|
this.messagingService,
|
||||||
|
this.logService,
|
||||||
|
this.stateService,
|
||||||
|
this.twoFactorService
|
||||||
|
);
|
||||||
|
|
||||||
async logOut(callback: Function) {
|
return strategy.logIn(credentials);
|
||||||
this.apiKeyService.clear();
|
|
||||||
super.logOut(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async organizationLogInHelper(clientId: string, clientSecret: string) {
|
|
||||||
const appId = await this.appIdService.getAppId();
|
|
||||||
const deviceRequest = new DeviceRequest(appId, this.platformUtilsService);
|
|
||||||
const request = new TokenRequest(null, null, [clientId, clientSecret], null,
|
|
||||||
null, false, null, deviceRequest);
|
|
||||||
|
|
||||||
const response = await this.apiService.postIdentityToken(request);
|
|
||||||
const result = new AuthResult();
|
|
||||||
result.twoFactor = !(response as any).accessToken;
|
|
||||||
|
|
||||||
const tokenResponse = response as IdentityTokenResponse;
|
|
||||||
result.resetMasterPassword = tokenResponse.resetMasterPassword;
|
|
||||||
await this.tokenService.setToken(tokenResponse.accessToken);
|
|
||||||
await this.apiKeyService.setInformation(clientId, clientSecret);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,29 @@
|
|||||||
import * as graph from '@microsoft/microsoft-graph-client';
|
import * as https from "https";
|
||||||
import * as graphType from '@microsoft/microsoft-graph-types';
|
import * as querystring from "querystring";
|
||||||
import * as https from 'https';
|
|
||||||
import * as querystring from 'querystring';
|
|
||||||
|
|
||||||
import { DirectoryType } from '../enums/directoryType';
|
import * as graph from "@microsoft/microsoft-graph-client";
|
||||||
|
import * as graphType from "@microsoft/microsoft-graph-types";
|
||||||
|
|
||||||
import { AzureConfiguration } from '../models/azureConfiguration';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { GroupEntry } from '../models/groupEntry';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { SyncConfiguration } from '../models/syncConfiguration';
|
|
||||||
import { UserEntry } from '../models/userEntry';
|
|
||||||
|
|
||||||
import { BaseDirectoryService } from './baseDirectory.service';
|
import { StateService } from "../abstractions/state.service";
|
||||||
import { ConfigurationService } from './configuration.service';
|
import { DirectoryType } from "../enums/directoryType";
|
||||||
import { IDirectoryService } from './directory.service';
|
import { AzureConfiguration } from "../models/azureConfiguration";
|
||||||
|
import { GroupEntry } from "../models/groupEntry";
|
||||||
|
import { SyncConfiguration } from "../models/syncConfiguration";
|
||||||
|
import { UserEntry } from "../models/userEntry";
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { BaseDirectoryService } from "./baseDirectory.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { IDirectoryService } from "./directory.service";
|
||||||
|
|
||||||
const NextLink = '@odata.nextLink';
|
const AzurePublicIdentityAuhtority = "login.microsoftonline.com";
|
||||||
const DeltaLink = '@odata.deltaLink';
|
const AzureGovermentIdentityAuhtority = "login.microsoftonline.us";
|
||||||
const ObjectType = '@odata.type';
|
|
||||||
const UserSelectParams = '?$select=id,mail,userPrincipalName,displayName,accountEnabled';
|
const NextLink = "@odata.nextLink";
|
||||||
|
const DeltaLink = "@odata.deltaLink";
|
||||||
|
const ObjectType = "@odata.type";
|
||||||
|
const UserSelectParams = "?$select=id,mail,userPrincipalName,displayName,accountEnabled";
|
||||||
|
|
||||||
enum UserSetType {
|
enum UserSetType {
|
||||||
IncludeUser,
|
IncludeUser,
|
||||||
@@ -36,25 +39,29 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
|
|||||||
private accessToken: string;
|
private accessToken: string;
|
||||||
private accessTokenExpiration: Date;
|
private accessTokenExpiration: Date;
|
||||||
|
|
||||||
constructor(private configurationService: ConfigurationService, private logService: LogService,
|
constructor(
|
||||||
private i18nService: I18nService) {
|
private logService: LogService,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private stateService: StateService
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
||||||
const type = await this.configurationService.getDirectoryType();
|
const type = await this.stateService.getDirectoryType();
|
||||||
if (type !== DirectoryType.AzureActiveDirectory) {
|
if (type !== DirectoryType.AzureActiveDirectory) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dirConfig = await this.configurationService.getDirectory<AzureConfiguration>(
|
this.dirConfig = await this.stateService.getDirectory<AzureConfiguration>(
|
||||||
DirectoryType.AzureActiveDirectory);
|
DirectoryType.AzureActiveDirectory
|
||||||
|
);
|
||||||
if (this.dirConfig == null) {
|
if (this.dirConfig == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.syncConfig = await this.configurationService.getSync();
|
this.syncConfig = await this.stateService.getSync();
|
||||||
if (this.syncConfig == null) {
|
if (this.syncConfig == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -77,41 +84,28 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getCurrentUsers(): Promise<UserEntry[]> {
|
private async getCurrentUsers(): Promise<UserEntry[]> {
|
||||||
const entryIds = new Set<string>();
|
let entries: UserEntry[] = [];
|
||||||
const entries: UserEntry[] = [];
|
let users: graphType.User[];
|
||||||
const userReq = this.client.api('/users' + UserSelectParams);
|
|
||||||
let res = await userReq.get();
|
|
||||||
const setFilter = this.createCustomUserSet(this.syncConfig.userFilter);
|
const setFilter = this.createCustomUserSet(this.syncConfig.userFilter);
|
||||||
while (true) {
|
const userIdsToExclude = new Set<string>();
|
||||||
const users: graphType.User[] = res.value;
|
|
||||||
if (users != null) {
|
|
||||||
for (const user of users) {
|
|
||||||
if (user.id == null || entryIds.has(user.id)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const entry = this.buildUser(user);
|
|
||||||
if (await this.filterOutUserResult(setFilter, entry, true)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!entry.disabled && !entry.deleted &&
|
// Only get users for the groups provided in includeGroup filter
|
||||||
(entry.email == null || entry.email.indexOf('#') > -1)) {
|
if (setFilter != null && setFilter[0] === UserSetType.IncludeGroup) {
|
||||||
continue;
|
users = await this.getUsersByGroups(setFilter);
|
||||||
}
|
// Get the users in the excludedGroups and filter them out from all users
|
||||||
|
} else if (setFilter != null && setFilter[0] === UserSetType.ExcludeGroup) {
|
||||||
entries.push(entry);
|
(await this.getUsersByGroups(setFilter)).forEach((user: graphType.User) =>
|
||||||
entryIds.add(user.id);
|
userIdsToExclude.add(user.id)
|
||||||
}
|
);
|
||||||
}
|
const userReq = this.client.api("/users" + UserSelectParams);
|
||||||
|
users = await this.getUsersByResource(userReq);
|
||||||
if (res[NextLink] == null) {
|
|
||||||
break;
|
|
||||||
} else {
|
} else {
|
||||||
const nextReq = this.client.api(res[NextLink]);
|
const userReq = this.client.api("/users" + UserSelectParams);
|
||||||
res = await nextReq.get();
|
users = await this.getUsersByResource(userReq);
|
||||||
}
|
}
|
||||||
|
if (users != null) {
|
||||||
|
entries = await this.buildUserEntries(users, userIdsToExclude, setFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +114,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
|
|||||||
const entries: UserEntry[] = [];
|
const entries: UserEntry[] = [];
|
||||||
|
|
||||||
let res: any = null;
|
let res: any = null;
|
||||||
const token = await this.configurationService.getUserDeltaToken();
|
const token = await this.stateService.getUserDelta();
|
||||||
if (!force && token != null) {
|
if (!force && token != null) {
|
||||||
try {
|
try {
|
||||||
const deltaReq = this.client.api(token);
|
const deltaReq = this.client.api(token);
|
||||||
@@ -131,11 +125,12 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (res == null) {
|
if (res == null) {
|
||||||
const userReq = this.client.api('/users/delta' + UserSelectParams);
|
const userReq = this.client.api("/users/delta" + UserSelectParams);
|
||||||
res = await userReq.get();
|
res = await userReq.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
const setFilter = this.createCustomUserSet(this.syncConfig.userFilter);
|
const setFilter = this.createCustomUserSet(this.syncConfig.userFilter);
|
||||||
|
// eslint-disable-next-line
|
||||||
while (true) {
|
while (true) {
|
||||||
const users: graphType.User[] = res.value;
|
const users: graphType.User[] = res.value;
|
||||||
if (users != null) {
|
if (users != null) {
|
||||||
@@ -147,7 +142,13 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
|
|||||||
if (!entry.deleted) {
|
if (!entry.deleted) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (await this.filterOutUserResult(setFilter, entry, false)) {
|
|
||||||
|
if (
|
||||||
|
setFilter != null &&
|
||||||
|
(setFilter[0] === UserSetType.IncludeUser ||
|
||||||
|
setFilter[0] === UserSetType.ExcludeUser) &&
|
||||||
|
(await this.filterOutUserResult(setFilter, entry))
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +159,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
|
|||||||
|
|
||||||
if (res[NextLink] == null) {
|
if (res[NextLink] == null) {
|
||||||
if (res[DeltaLink] != null && saveDelta) {
|
if (res[DeltaLink] != null && saveDelta) {
|
||||||
await this.configurationService.saveUserDeltaToken(res[DeltaLink]);
|
await this.stateService.setUserDelta(res[DeltaLink]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
@@ -171,42 +172,43 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async createAadCustomSet(filter: string): Promise<[boolean, Set<string>]> {
|
private async createAadCustomSet(filter: string): Promise<[boolean, Set<string>]> {
|
||||||
if (filter == null || filter === '') {
|
if (filter == null || filter === "") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainParts = filter.split('|');
|
const mainParts = filter.split("|");
|
||||||
if (mainParts.length < 1 || mainParts[0] == null || mainParts[0].trim() === '') {
|
if (mainParts.length < 1 || mainParts[0] == null || mainParts[0].trim() === "") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts = mainParts[0].split(':');
|
const parts = mainParts[0].split(":");
|
||||||
if (parts.length !== 2) {
|
if (parts.length !== 2) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyword = parts[0].trim().toLowerCase();
|
const keyword = parts[0].trim().toLowerCase();
|
||||||
let exclude = true;
|
let exclude = true;
|
||||||
if (keyword === 'include') {
|
if (keyword === "include") {
|
||||||
exclude = false;
|
exclude = false;
|
||||||
} else if (keyword === 'exclude') {
|
} else if (keyword === "exclude") {
|
||||||
exclude = true;
|
exclude = true;
|
||||||
} else if (keyword === 'excludeadministrativeunit') {
|
} else if (keyword === "excludeadministrativeunit") {
|
||||||
exclude = true;
|
exclude = true;
|
||||||
} else if (keyword === 'includeadministrativeunit') {
|
} else if (keyword === "includeadministrativeunit") {
|
||||||
exclude = false;
|
exclude = false;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const set = new Set<string>();
|
const set = new Set<string>();
|
||||||
const pieces = parts[1].split(',');
|
const pieces = parts[1].split(",");
|
||||||
if (keyword === 'excludeadministrativeunit' || keyword === 'includeadministrativeunit') {
|
if (keyword === "excludeadministrativeunit" || keyword === "includeadministrativeunit") {
|
||||||
for (const p of pieces) {
|
for (const p of pieces) {
|
||||||
const auMembers = await this.client
|
const auMembers = await this.client
|
||||||
.api(`https://graph.microsoft.com/v1.0/directory/administrativeUnits/${p}/members`).get();
|
.api(`https://graph.microsoft.com/v1.0/directory/administrativeUnits/${p}/members`)
|
||||||
|
.get();
|
||||||
for (const auMember of auMembers.value) {
|
for (const auMember of auMembers.value) {
|
||||||
if (auMember['@odata.type'] === '#microsoft.graph.group') {
|
if (auMember["@odata.type"] === "#microsoft.graph.group") {
|
||||||
set.add(auMember.displayName.toLowerCase());
|
set.add(auMember.displayName.toLowerCase());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,36 +222,36 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createCustomUserSet(filter: string): [UserSetType, Set<string>] {
|
private createCustomUserSet(filter: string): [UserSetType, Set<string>] {
|
||||||
if (filter == null || filter === '') {
|
if (filter == null || filter === "") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainParts = filter.split('|');
|
const mainParts = filter.split("|");
|
||||||
if (mainParts.length < 1 || mainParts[0] == null || mainParts[0].trim() === '') {
|
if (mainParts.length < 1 || mainParts[0] == null || mainParts[0].trim() === "") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts = mainParts[0].split(':');
|
const parts = mainParts[0].split(":");
|
||||||
if (parts.length !== 2) {
|
if (parts.length !== 2) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyword = parts[0].trim().toLowerCase();
|
const keyword = parts[0].trim().toLowerCase();
|
||||||
let userSetType = UserSetType.IncludeUser;
|
let userSetType = UserSetType.IncludeUser;
|
||||||
if (keyword === 'include') {
|
if (keyword === "include") {
|
||||||
userSetType = UserSetType.IncludeUser;
|
userSetType = UserSetType.IncludeUser;
|
||||||
} else if (keyword === 'exclude') {
|
} else if (keyword === "exclude") {
|
||||||
userSetType = UserSetType.ExcludeUser;
|
userSetType = UserSetType.ExcludeUser;
|
||||||
} else if (keyword === 'includegroup') {
|
} else if (keyword === "includegroup") {
|
||||||
userSetType = UserSetType.IncludeGroup;
|
userSetType = UserSetType.IncludeGroup;
|
||||||
} else if (keyword === 'excludegroup') {
|
} else if (keyword === "excludegroup") {
|
||||||
userSetType = UserSetType.ExcludeGroup;
|
userSetType = UserSetType.ExcludeGroup;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const set = new Set<string>();
|
const set = new Set<string>();
|
||||||
const pieces = parts[1].split(',');
|
const pieces = parts[1].split(",");
|
||||||
for (const p of pieces) {
|
for (const p of pieces) {
|
||||||
set.add(p.trim().toLowerCase());
|
set.add(p.trim().toLowerCase());
|
||||||
}
|
}
|
||||||
@@ -257,8 +259,10 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
|
|||||||
return [userSetType, set];
|
return [userSetType, set];
|
||||||
}
|
}
|
||||||
|
|
||||||
private async filterOutUserResult(setFilter: [UserSetType, Set<string>], user: UserEntry,
|
private async filterOutUserResult(
|
||||||
checkGroupsFilter: boolean): Promise<boolean> {
|
setFilter: [UserSetType, Set<string>],
|
||||||
|
user: UserEntry
|
||||||
|
): Promise<boolean> {
|
||||||
if (setFilter == null) {
|
if (setFilter == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -274,23 +278,6 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
|
|||||||
return this.filterOutResult([userSetTypeExclude, setFilter[1]], user.email);
|
return this.filterOutResult([userSetTypeExclude, setFilter[1]], user.email);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to *not* call the /checkMemberGroups method for deleted users, it will always fail
|
|
||||||
if (!checkGroupsFilter) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const memberGroups = await this.client.api(`/users/${user.externalId}/checkMemberGroups`).post({
|
|
||||||
groupIds: Array.from(setFilter[1]),
|
|
||||||
});
|
|
||||||
if (memberGroups.value.length > 0 && setFilter[0] === UserSetType.IncludeGroup) {
|
|
||||||
return false;
|
|
||||||
} else if (memberGroups.value.length > 0 && setFilter[0] === UserSetType.ExcludeGroup) {
|
|
||||||
return true;
|
|
||||||
} else if (memberGroups.value.length === 0 && setFilter[0] === UserSetType.IncludeGroup) {
|
|
||||||
return true;
|
|
||||||
} else if (memberGroups.value.length === 0 && setFilter[0] === UserSetType.ExcludeGroup) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,8 +287,10 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
|
|||||||
entry.externalId = user.id;
|
entry.externalId = user.id;
|
||||||
entry.email = user.mail;
|
entry.email = user.mail;
|
||||||
|
|
||||||
if (user.userPrincipalName && (entry.email == null || entry.email === '' ||
|
if (
|
||||||
entry.email.indexOf('onmicrosoft.com') > -1)) {
|
user.userPrincipalName &&
|
||||||
|
(entry.email == null || entry.email === "" || entry.email.indexOf("onmicrosoft.com") > -1)
|
||||||
|
) {
|
||||||
entry.email = user.userPrincipalName;
|
entry.email = user.userPrincipalName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,7 +300,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
|
|||||||
|
|
||||||
entry.disabled = user.accountEnabled == null ? false : !user.accountEnabled;
|
entry.disabled = user.accountEnabled == null ? false : !user.accountEnabled;
|
||||||
|
|
||||||
if ((user as any)['@removed'] != null && (user as any)['@removed'].reason === 'changed') {
|
if ((user as any)["@removed"] != null && (user as any)["@removed"].reason === "changed") {
|
||||||
entry.deleted = true;
|
entry.deleted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,8 +310,9 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
|
|||||||
private async getGroups(setFilter: [boolean, Set<string>]): Promise<GroupEntry[]> {
|
private async getGroups(setFilter: [boolean, Set<string>]): Promise<GroupEntry[]> {
|
||||||
const entryIds = new Set<string>();
|
const entryIds = new Set<string>();
|
||||||
const entries: GroupEntry[] = [];
|
const entries: GroupEntry[] = [];
|
||||||
const groupsReq = this.client.api('/groups');
|
const groupsReq = this.client.api("/groups");
|
||||||
let res = await groupsReq.get();
|
let res = await groupsReq.get();
|
||||||
|
// eslint-disable-next-line
|
||||||
while (true) {
|
while (true) {
|
||||||
const groups: graphType.Group[] = res.value;
|
const groups: graphType.Group[] = res.value;
|
||||||
if (groups != null) {
|
if (groups != null) {
|
||||||
@@ -351,21 +341,78 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getUsersByResource(usersRequest: graph.GraphRequest) {
|
||||||
|
const users: graphType.User[] = [];
|
||||||
|
let res = await usersRequest.get();
|
||||||
|
res.value.forEach((user: graphType.User) => users.push(user));
|
||||||
|
while (res[NextLink] != null) {
|
||||||
|
const nextReq = this.client.api(res[NextLink]);
|
||||||
|
res = await nextReq.get();
|
||||||
|
res.value.forEach((user: graphType.User) => users.push(user));
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getUsersByGroups(setFilter: [UserSetType, Set<string>]): Promise<graphType.User[]> {
|
||||||
|
const users: graphType.User[] = [];
|
||||||
|
for (const group of setFilter[1]) {
|
||||||
|
const groupUsersReq = this.client.api(
|
||||||
|
`/groups/${group}/transitiveMembers` + UserSelectParams
|
||||||
|
);
|
||||||
|
users.push(...(await this.getUsersByResource(groupUsersReq)));
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async buildUserEntries(
|
||||||
|
users: graphType.User[],
|
||||||
|
userIdsToExclude: Set<string>,
|
||||||
|
setFilter: [UserSetType, Set<string>]
|
||||||
|
) {
|
||||||
|
const entryIds = new Set<string>();
|
||||||
|
const entries: UserEntry[] = [];
|
||||||
|
|
||||||
|
for (const user of users) {
|
||||||
|
if (user.id == null || entryIds.has(user.id) || userIdsToExclude.has(user.id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const entry = this.buildUser(user);
|
||||||
|
|
||||||
|
if (
|
||||||
|
setFilter != null &&
|
||||||
|
(setFilter[0] === UserSetType.IncludeUser || setFilter[0] === UserSetType.ExcludeUser) &&
|
||||||
|
(await this.filterOutUserResult(setFilter, entry))
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!this.isInvalidUser(entry)) {
|
||||||
|
entries.push(entry);
|
||||||
|
entryIds.add(user.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isInvalidUser(user: UserEntry): boolean {
|
||||||
|
return !user.disabled && !user.deleted && (user.email == null || user.email.indexOf("#") > -1);
|
||||||
|
}
|
||||||
|
|
||||||
private async buildGroup(group: graphType.Group): Promise<GroupEntry> {
|
private async buildGroup(group: graphType.Group): Promise<GroupEntry> {
|
||||||
const entry = new GroupEntry();
|
const entry = new GroupEntry();
|
||||||
entry.referenceId = group.id;
|
entry.referenceId = group.id;
|
||||||
entry.externalId = group.id;
|
entry.externalId = group.id;
|
||||||
entry.name = group.displayName;
|
entry.name = group.displayName;
|
||||||
|
|
||||||
const memReq = this.client.api('/groups/' + group.id + '/members');
|
const memReq = this.client.api("/groups/" + group.id + "/members");
|
||||||
let memRes = await memReq.get();
|
let memRes = await memReq.get();
|
||||||
|
// eslint-disable-next-line
|
||||||
while (true) {
|
while (true) {
|
||||||
const members: any = memRes.value;
|
const members: any = memRes.value;
|
||||||
if (members != null) {
|
if (members != null) {
|
||||||
for (const member of members) {
|
for (const member of members) {
|
||||||
if (member[ObjectType] === '#microsoft.graph.group') {
|
if (member[ObjectType] === "#microsoft.graph.group") {
|
||||||
entry.groupMemberReferenceIds.add((member as graphType.Group).id);
|
entry.groupMemberReferenceIds.add((member as graphType.Group).id);
|
||||||
} else if (member[ObjectType] === '#microsoft.graph.user') {
|
} else if (member[ObjectType] === "#microsoft.graph.user") {
|
||||||
entry.userMemberExternalIds.add((member as graphType.User).id);
|
entry.userMemberExternalIds.add((member as graphType.User).id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -383,10 +430,25 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
|
|||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
this.client = graph.Client.init({
|
this.client = graph.Client.init({
|
||||||
authProvider: done => {
|
authProvider: (done) => {
|
||||||
if (this.dirConfig.applicationId == null || this.dirConfig.key == null ||
|
if (
|
||||||
this.dirConfig.tenant == null) {
|
this.dirConfig.applicationId == null ||
|
||||||
done(new Error(this.i18nService.t('dirConfigIncomplete')), null);
|
this.dirConfig.key == null ||
|
||||||
|
this.dirConfig.tenant == null
|
||||||
|
) {
|
||||||
|
done(new Error(this.i18nService.t("dirConfigIncomplete")), null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const identityAuthority =
|
||||||
|
this.dirConfig.identityAuthority != null
|
||||||
|
? this.dirConfig.identityAuthority
|
||||||
|
: AzurePublicIdentityAuhtority;
|
||||||
|
if (
|
||||||
|
identityAuthority !== AzurePublicIdentityAuhtority &&
|
||||||
|
identityAuthority !== AzureGovermentIdentityAuhtority
|
||||||
|
) {
|
||||||
|
done(new Error(this.i18nService.t("dirConfigIncomplete")), null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,37 +463,42 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
|
|||||||
const data = querystring.stringify({
|
const data = querystring.stringify({
|
||||||
client_id: this.dirConfig.applicationId,
|
client_id: this.dirConfig.applicationId,
|
||||||
client_secret: this.dirConfig.key,
|
client_secret: this.dirConfig.key,
|
||||||
grant_type: 'client_credentials',
|
grant_type: "client_credentials",
|
||||||
scope: 'https://graph.microsoft.com/.default',
|
scope: "https://graph.microsoft.com/.default",
|
||||||
});
|
});
|
||||||
|
|
||||||
const req = https.request({
|
const req = https
|
||||||
host: 'login.microsoftonline.com',
|
.request(
|
||||||
path: '/' + this.dirConfig.tenant + '/oauth2/v2.0/token',
|
{
|
||||||
method: 'POST',
|
host: identityAuthority,
|
||||||
|
path: "/" + this.dirConfig.tenant + "/oauth2/v2.0/token",
|
||||||
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
'Content-Length': Buffer.byteLength(data),
|
"Content-Length": Buffer.byteLength(data),
|
||||||
},
|
},
|
||||||
}, res => {
|
},
|
||||||
res.setEncoding('utf8');
|
(res) => {
|
||||||
res.on('data', (chunk: string) => {
|
res.setEncoding("utf8");
|
||||||
|
res.on("data", (chunk: string) => {
|
||||||
const d = JSON.parse(chunk);
|
const d = JSON.parse(chunk);
|
||||||
if (res.statusCode === 200 && d.access_token != null) {
|
if (res.statusCode === 200 && d.access_token != null) {
|
||||||
this.setAccessTokenExpiration(d.access_token, d.expires_in);
|
this.setAccessTokenExpiration(d.access_token, d.expires_in);
|
||||||
done(null, d.access_token);
|
done(null, d.access_token);
|
||||||
} else if (d.error != null && d.error_description != null) {
|
} else if (d.error != null && d.error_description != null) {
|
||||||
const shortError = d.error_description?.split('\n', 1)[0];
|
const shortError = d.error_description?.split("\n", 1)[0];
|
||||||
const err = new Error(d.error + ' (' + res.statusCode + '): ' + shortError);
|
const err = new Error(d.error + " (" + res.statusCode + "): " + shortError);
|
||||||
// tslint: disable-next-line
|
// eslint-disable-next-line
|
||||||
console.error(d.error_description);
|
console.error(d.error_description);
|
||||||
done(err, null);
|
done(err, null);
|
||||||
} else {
|
} else {
|
||||||
const err = new Error('Unknown error (' + res.statusCode + ').');
|
const err = new Error("Unknown error (" + res.statusCode + ").");
|
||||||
done(err, null);
|
done(err, null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).on('error', err => {
|
}
|
||||||
|
)
|
||||||
|
.on("error", (err) => {
|
||||||
done(err, null);
|
done(err, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { SyncConfiguration } from '../models/syncConfiguration';
|
import { GroupEntry } from "../models/groupEntry";
|
||||||
|
import { SyncConfiguration } from "../models/syncConfiguration";
|
||||||
import { GroupEntry } from '../models/groupEntry';
|
import { UserEntry } from "../models/userEntry";
|
||||||
import { UserEntry } from '../models/userEntry';
|
|
||||||
|
|
||||||
export abstract class BaseDirectoryService {
|
export abstract class BaseDirectoryService {
|
||||||
protected createDirectoryQuery(filter: string) {
|
protected createDirectoryQuery(filter: string) {
|
||||||
if (filter == null || filter === '') {
|
if (filter == null || filter === "") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainParts = filter.split('|');
|
const mainParts = filter.split("|");
|
||||||
if (mainParts.length < 2 || mainParts[1] == null || mainParts[1].trim() === '') {
|
if (mainParts.length < 2 || mainParts[1] == null || mainParts[1].trim() === "") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,32 +17,32 @@ export abstract class BaseDirectoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected createCustomSet(filter: string): [boolean, Set<string>] {
|
protected createCustomSet(filter: string): [boolean, Set<string>] {
|
||||||
if (filter == null || filter === '') {
|
if (filter == null || filter === "") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainParts = filter.split('|');
|
const mainParts = filter.split("|");
|
||||||
if (mainParts.length < 1 || mainParts[0] == null || mainParts[0].trim() === '') {
|
if (mainParts.length < 1 || mainParts[0] == null || mainParts[0].trim() === "") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts = mainParts[0].split(':');
|
const parts = mainParts[0].split(":");
|
||||||
if (parts.length !== 2) {
|
if (parts.length !== 2) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyword = parts[0].trim().toLowerCase();
|
const keyword = parts[0].trim().toLowerCase();
|
||||||
let exclude = true;
|
let exclude = true;
|
||||||
if (keyword === 'include') {
|
if (keyword === "include") {
|
||||||
exclude = false;
|
exclude = false;
|
||||||
} else if (keyword === 'exclude') {
|
} else if (keyword === "exclude") {
|
||||||
exclude = true;
|
exclude = true;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const set = new Set<string>();
|
const set = new Set<string>();
|
||||||
const pieces = parts[1].split(',');
|
const pieces = parts[1].split(",");
|
||||||
for (const p of pieces) {
|
for (const p of pieces) {
|
||||||
set.add(p.trim().toLowerCase());
|
set.add(p.trim().toLowerCase());
|
||||||
}
|
}
|
||||||
@@ -53,7 +52,7 @@ export abstract class BaseDirectoryService {
|
|||||||
|
|
||||||
protected filterOutResult(setFilter: [boolean, Set<string>], result: string) {
|
protected filterOutResult(setFilter: [boolean, Set<string>], result: string) {
|
||||||
if (setFilter != null) {
|
if (setFilter != null) {
|
||||||
const cleanResult = result != null ? result.trim().toLowerCase() : '--';
|
const cleanResult = result != null ? result.trim().toLowerCase() : "--";
|
||||||
const excluded = setFilter[0];
|
const excluded = setFilter[0];
|
||||||
const set = setFilter[1];
|
const set = setFilter[1];
|
||||||
|
|
||||||
@@ -67,13 +66,17 @@ export abstract class BaseDirectoryService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected filterUsersFromGroupsSet(users: UserEntry[], groups: GroupEntry[],
|
protected filterUsersFromGroupsSet(
|
||||||
setFilter: [boolean, Set<string>], syncConfig: SyncConfiguration): UserEntry[] {
|
users: UserEntry[],
|
||||||
|
groups: GroupEntry[],
|
||||||
|
setFilter: [boolean, Set<string>],
|
||||||
|
syncConfig: SyncConfiguration
|
||||||
|
): UserEntry[] {
|
||||||
if (setFilter == null || users == null) {
|
if (setFilter == null || users == null) {
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
return users.filter(u => {
|
return users.filter((u) => {
|
||||||
if (u.deleted) {
|
if (u.deleted) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -81,11 +84,11 @@ export abstract class BaseDirectoryService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return groups.filter(g => g.userMemberExternalIds.has(u.externalId)).length > 0;
|
return groups.filter((g) => g.userMemberExternalIds.has(u.externalId)).length > 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected forceGroup(force: boolean, users: UserEntry[]): boolean {
|
protected forceGroup(force: boolean, users: UserEntry[]): boolean {
|
||||||
return force || (users != null && users.filter(u => !u.deleted && !u.disabled).length > 0);
|
return force || (users != null && users.filter((u) => !u.deleted && !u.disabled).length > 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,229 +0,0 @@
|
|||||||
import { DirectoryType } from '../enums/directoryType';
|
|
||||||
|
|
||||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
|
||||||
import { AzureConfiguration } from '../models/azureConfiguration';
|
|
||||||
import { GSuiteConfiguration } from '../models/gsuiteConfiguration';
|
|
||||||
import { LdapConfiguration } from '../models/ldapConfiguration';
|
|
||||||
import { OktaConfiguration } from '../models/oktaConfiguration';
|
|
||||||
import { OneLoginConfiguration } from '../models/oneLoginConfiguration';
|
|
||||||
import { SyncConfiguration } from '../models/syncConfiguration';
|
|
||||||
|
|
||||||
const StoredSecurely = '[STORED SECURELY]';
|
|
||||||
const Keys = {
|
|
||||||
ldap: 'ldapPassword',
|
|
||||||
gsuite: 'gsuitePrivateKey',
|
|
||||||
azure: 'azureKey',
|
|
||||||
okta: 'oktaToken',
|
|
||||||
oneLogin: 'oneLoginClientSecret',
|
|
||||||
directoryConfigPrefix: 'directoryConfig_',
|
|
||||||
sync: 'syncConfig',
|
|
||||||
directoryType: 'directoryType',
|
|
||||||
userDelta: 'userDeltaToken',
|
|
||||||
groupDelta: 'groupDeltaToken',
|
|
||||||
lastUserSync: 'lastUserSync',
|
|
||||||
lastGroupSync: 'lastGroupSync',
|
|
||||||
lastSyncHash: 'lastSyncHash',
|
|
||||||
organizationId: 'organizationId',
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ConfigurationService {
|
|
||||||
constructor(private storageService: StorageService, private secureStorageService: StorageService,
|
|
||||||
private useSecureStorageForSecrets = true) { }
|
|
||||||
|
|
||||||
async getDirectory<T>(type: DirectoryType): Promise<T> {
|
|
||||||
const config = await this.storageService.get<T>(Keys.directoryConfigPrefix + type);
|
|
||||||
if (config == null) {
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.useSecureStorageForSecrets) {
|
|
||||||
switch (type) {
|
|
||||||
case DirectoryType.Ldap:
|
|
||||||
(config as any).password = await this.secureStorageService.get<string>(Keys.ldap);
|
|
||||||
break;
|
|
||||||
case DirectoryType.AzureActiveDirectory:
|
|
||||||
(config as any).key = await this.secureStorageService.get<string>(Keys.azure);
|
|
||||||
break;
|
|
||||||
case DirectoryType.Okta:
|
|
||||||
(config as any).token = await this.secureStorageService.get<string>(Keys.okta);
|
|
||||||
break;
|
|
||||||
case DirectoryType.GSuite:
|
|
||||||
(config as any).privateKey = await this.secureStorageService.get<string>(Keys.gsuite);
|
|
||||||
break;
|
|
||||||
case DirectoryType.OneLogin:
|
|
||||||
(config as any).clientSecret = await this.secureStorageService.get<string>(Keys.oneLogin);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveDirectory(type: DirectoryType,
|
|
||||||
config: LdapConfiguration | GSuiteConfiguration | AzureConfiguration | OktaConfiguration |
|
|
||||||
OneLoginConfiguration): Promise<any> {
|
|
||||||
const savedConfig: any = Object.assign({}, config);
|
|
||||||
if (this.useSecureStorageForSecrets) {
|
|
||||||
switch (type) {
|
|
||||||
case DirectoryType.Ldap:
|
|
||||||
if (savedConfig.password == null) {
|
|
||||||
await this.secureStorageService.remove(Keys.ldap);
|
|
||||||
} else {
|
|
||||||
await this.secureStorageService.save(Keys.ldap, savedConfig.password);
|
|
||||||
savedConfig.password = StoredSecurely;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DirectoryType.AzureActiveDirectory:
|
|
||||||
if (savedConfig.key == null) {
|
|
||||||
await this.secureStorageService.remove(Keys.azure);
|
|
||||||
} else {
|
|
||||||
await this.secureStorageService.save(Keys.azure, savedConfig.key);
|
|
||||||
savedConfig.key = StoredSecurely;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DirectoryType.Okta:
|
|
||||||
if (savedConfig.token == null) {
|
|
||||||
await this.secureStorageService.remove(Keys.okta);
|
|
||||||
} else {
|
|
||||||
await this.secureStorageService.save(Keys.okta, savedConfig.token);
|
|
||||||
savedConfig.token = StoredSecurely;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DirectoryType.GSuite:
|
|
||||||
if (savedConfig.privateKey == null) {
|
|
||||||
await this.secureStorageService.remove(Keys.gsuite);
|
|
||||||
} else {
|
|
||||||
(config as GSuiteConfiguration).privateKey = savedConfig.privateKey =
|
|
||||||
savedConfig.privateKey.replace(/\\n/g, '\n');
|
|
||||||
await this.secureStorageService.save(Keys.gsuite, savedConfig.privateKey);
|
|
||||||
savedConfig.privateKey = StoredSecurely;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DirectoryType.OneLogin:
|
|
||||||
if (savedConfig.clientSecret == null) {
|
|
||||||
await this.secureStorageService.remove(Keys.oneLogin);
|
|
||||||
} else {
|
|
||||||
await this.secureStorageService.save(Keys.oneLogin, savedConfig.clientSecret);
|
|
||||||
savedConfig.clientSecret = StoredSecurely;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await this.storageService.save(Keys.directoryConfigPrefix + type, savedConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
getSync(): Promise<SyncConfiguration> {
|
|
||||||
return this.storageService.get<SyncConfiguration>(Keys.sync);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveSync(config: SyncConfiguration) {
|
|
||||||
return this.storageService.save(Keys.sync, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
getDirectoryType(): Promise<DirectoryType> {
|
|
||||||
return this.storageService.get<DirectoryType>(Keys.directoryType);
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveDirectoryType(type: DirectoryType) {
|
|
||||||
const currentType = await this.getDirectoryType();
|
|
||||||
if (type !== currentType) {
|
|
||||||
await this.clearStatefulSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.storageService.save(Keys.directoryType, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
getUserDeltaToken(): Promise<string> {
|
|
||||||
return this.storageService.get<string>(Keys.userDelta);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveUserDeltaToken(token: string) {
|
|
||||||
if (token == null) {
|
|
||||||
return this.storageService.remove(Keys.userDelta);
|
|
||||||
} else {
|
|
||||||
return this.storageService.save(Keys.userDelta, token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getGroupDeltaToken(): Promise<string> {
|
|
||||||
return this.storageService.get<string>(Keys.groupDelta);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveGroupDeltaToken(token: string) {
|
|
||||||
if (token == null) {
|
|
||||||
return this.storageService.remove(Keys.groupDelta);
|
|
||||||
} else {
|
|
||||||
return this.storageService.save(Keys.groupDelta, token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getLastUserSyncDate(): Promise<Date> {
|
|
||||||
const dateString = await this.storageService.get<string>(Keys.lastUserSync);
|
|
||||||
if (dateString == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new Date(dateString);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveLastUserSyncDate(date: Date) {
|
|
||||||
if (date == null) {
|
|
||||||
return this.storageService.remove(Keys.lastUserSync);
|
|
||||||
} else {
|
|
||||||
return this.storageService.save(Keys.lastUserSync, date);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getLastGroupSyncDate(): Promise<Date> {
|
|
||||||
const dateString = await this.storageService.get<string>(Keys.lastGroupSync);
|
|
||||||
if (dateString == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new Date(dateString);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveLastGroupSyncDate(date: Date) {
|
|
||||||
if (date == null) {
|
|
||||||
return this.storageService.remove(Keys.lastGroupSync);
|
|
||||||
} else {
|
|
||||||
return this.storageService.save(Keys.lastGroupSync, date);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getLastSyncHash(): Promise<string> {
|
|
||||||
return this.storageService.get<string>(Keys.lastSyncHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveLastSyncHash(hash: string) {
|
|
||||||
if (hash == null) {
|
|
||||||
return this.storageService.remove(Keys.lastSyncHash);
|
|
||||||
} else {
|
|
||||||
return this.storageService.save(Keys.lastSyncHash, hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getOrganizationId(): Promise<string> {
|
|
||||||
return this.storageService.get<string>(Keys.organizationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveOrganizationId(id: string) {
|
|
||||||
const currentId = await this.getOrganizationId();
|
|
||||||
if (currentId !== id) {
|
|
||||||
await this.clearStatefulSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id == null) {
|
|
||||||
return this.storageService.remove(Keys.organizationId);
|
|
||||||
} else {
|
|
||||||
return this.storageService.save(Keys.organizationId, id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async clearStatefulSettings(hashToo = false) {
|
|
||||||
await this.saveUserDeltaToken(null);
|
|
||||||
await this.saveGroupDeltaToken(null);
|
|
||||||
await this.saveLastGroupSyncDate(null);
|
|
||||||
await this.saveLastUserSyncDate(null);
|
|
||||||
if (hashToo) {
|
|
||||||
await this.saveLastSyncHash(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { GroupEntry } from '../models/groupEntry';
|
import { GroupEntry } from "../models/groupEntry";
|
||||||
import { UserEntry } from '../models/userEntry';
|
import { UserEntry } from "../models/userEntry";
|
||||||
|
|
||||||
export interface IDirectoryService {
|
export interface IDirectoryService {
|
||||||
getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]>;
|
getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]>;
|
||||||
|
|||||||
@@ -1,22 +1,18 @@
|
|||||||
import { JWT } from 'google-auth-library';
|
import { JWT } from "google-auth-library";
|
||||||
import {
|
import { admin_directory_v1, google } from "googleapis";
|
||||||
admin_directory_v1,
|
|
||||||
google,
|
|
||||||
} from 'googleapis';
|
|
||||||
|
|
||||||
import { DirectoryType } from '../enums/directoryType';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
|
|
||||||
import { GroupEntry } from '../models/groupEntry';
|
import { StateService } from "../abstractions/state.service";
|
||||||
import { GSuiteConfiguration } from '../models/gsuiteConfiguration';
|
import { DirectoryType } from "../enums/directoryType";
|
||||||
import { SyncConfiguration } from '../models/syncConfiguration';
|
import { GroupEntry } from "../models/groupEntry";
|
||||||
import { UserEntry } from '../models/userEntry';
|
import { GSuiteConfiguration } from "../models/gsuiteConfiguration";
|
||||||
|
import { SyncConfiguration } from "../models/syncConfiguration";
|
||||||
|
import { UserEntry } from "../models/userEntry";
|
||||||
|
|
||||||
import { BaseDirectoryService } from './baseDirectory.service';
|
import { BaseDirectoryService } from "./baseDirectory.service";
|
||||||
import { ConfigurationService } from './configuration.service';
|
import { IDirectoryService } from "./directory.service";
|
||||||
import { IDirectoryService } from './directory.service';
|
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
|
||||||
|
|
||||||
export class GSuiteDirectoryService extends BaseDirectoryService implements IDirectoryService {
|
export class GSuiteDirectoryService extends BaseDirectoryService implements IDirectoryService {
|
||||||
private client: JWT;
|
private client: JWT;
|
||||||
@@ -25,24 +21,29 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
|
|||||||
private dirConfig: GSuiteConfiguration;
|
private dirConfig: GSuiteConfiguration;
|
||||||
private syncConfig: SyncConfiguration;
|
private syncConfig: SyncConfiguration;
|
||||||
|
|
||||||
constructor(private configurationService: ConfigurationService, private logService: LogService,
|
constructor(
|
||||||
private i18nService: I18nService) {
|
private logService: LogService,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private stateService: StateService
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
this.service = google.admin('directory_v1');
|
this.service = google.admin("directory_v1");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
||||||
const type = await this.configurationService.getDirectoryType();
|
const type = await this.stateService.getDirectoryType();
|
||||||
if (type !== DirectoryType.GSuite) {
|
if (type !== DirectoryType.GSuite) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dirConfig = await this.configurationService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite);
|
this.dirConfig = await this.stateService.getDirectory<GSuiteConfiguration>(
|
||||||
|
DirectoryType.GSuite
|
||||||
|
);
|
||||||
if (this.dirConfig == null) {
|
if (this.dirConfig == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.syncConfig = await this.configurationService.getSync();
|
this.syncConfig = await this.stateService.getSync();
|
||||||
if (this.syncConfig == null) {
|
if (this.syncConfig == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -70,12 +71,13 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
|
|||||||
let nextPageToken: string = null;
|
let nextPageToken: string = null;
|
||||||
|
|
||||||
const filter = this.createCustomSet(this.syncConfig.userFilter);
|
const filter = this.createCustomSet(this.syncConfig.userFilter);
|
||||||
|
// eslint-disable-next-line
|
||||||
while (true) {
|
while (true) {
|
||||||
this.logService.info('Querying users - nextPageToken:' + nextPageToken);
|
this.logService.info("Querying users - nextPageToken:" + nextPageToken);
|
||||||
const p = Object.assign({ query: query, pageToken: nextPageToken }, this.authParams);
|
const p = Object.assign({ query: query, pageToken: nextPageToken }, this.authParams);
|
||||||
const res = await this.service.users.list(p);
|
const res = await this.service.users.list(p);
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw new Error('User list API failed: ' + res.statusText);
|
throw new Error("User list API failed: " + res.statusText);
|
||||||
}
|
}
|
||||||
|
|
||||||
nextPageToken = res.data.nextPageToken;
|
nextPageToken = res.data.nextPageToken;
|
||||||
@@ -97,12 +99,16 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
|
|||||||
}
|
}
|
||||||
|
|
||||||
nextPageToken = null;
|
nextPageToken = null;
|
||||||
|
// eslint-disable-next-line
|
||||||
while (true) {
|
while (true) {
|
||||||
this.logService.info('Querying deleted users - nextPageToken:' + nextPageToken);
|
this.logService.info("Querying deleted users - nextPageToken:" + nextPageToken);
|
||||||
const p = Object.assign({ showDeleted: true, query: query, pageToken: nextPageToken }, this.authParams);
|
const p = Object.assign(
|
||||||
|
{ showDeleted: true, query: query, pageToken: nextPageToken },
|
||||||
|
this.authParams
|
||||||
|
);
|
||||||
const delRes = await this.service.users.list(p);
|
const delRes = await this.service.users.list(p);
|
||||||
if (delRes.status !== 200) {
|
if (delRes.status !== 200) {
|
||||||
throw new Error('Deleted user list API failed: ' + delRes.statusText);
|
throw new Error("Deleted user list API failed: " + delRes.statusText);
|
||||||
}
|
}
|
||||||
|
|
||||||
nextPageToken = delRes.data.nextPageToken;
|
nextPageToken = delRes.data.nextPageToken;
|
||||||
@@ -127,7 +133,7 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
|
|||||||
}
|
}
|
||||||
|
|
||||||
private buildUser(user: admin_directory_v1.Schema$User, deleted: boolean) {
|
private buildUser(user: admin_directory_v1.Schema$User, deleted: boolean) {
|
||||||
if ((user.emails == null || user.emails === '') && !deleted) {
|
if ((user.emails == null || user.emails === "") && !deleted) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,16 +146,20 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getGroups(setFilter: [boolean, Set<string>], users: UserEntry[]): Promise<GroupEntry[]> {
|
private async getGroups(
|
||||||
|
setFilter: [boolean, Set<string>],
|
||||||
|
users: UserEntry[]
|
||||||
|
): Promise<GroupEntry[]> {
|
||||||
const entries: GroupEntry[] = [];
|
const entries: GroupEntry[] = [];
|
||||||
let nextPageToken: string = null;
|
let nextPageToken: string = null;
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
while (true) {
|
while (true) {
|
||||||
this.logService.info('Querying groups - nextPageToken:' + nextPageToken);
|
this.logService.info("Querying groups - nextPageToken:" + nextPageToken);
|
||||||
const p = Object.assign({ pageToken: nextPageToken }, this.authParams);
|
const p = Object.assign({ pageToken: nextPageToken }, this.authParams);
|
||||||
const res = await this.service.groups.list(p);
|
const res = await this.service.groups.list(p);
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw new Error('Group list API failed: ' + res.statusText);
|
throw new Error("Group list API failed: " + res.statusText);
|
||||||
}
|
}
|
||||||
|
|
||||||
nextPageToken = res.data.nextPageToken;
|
nextPageToken = res.data.nextPageToken;
|
||||||
@@ -178,11 +188,12 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
|
|||||||
entry.externalId = group.id;
|
entry.externalId = group.id;
|
||||||
entry.name = group.name;
|
entry.name = group.name;
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
while (true) {
|
while (true) {
|
||||||
const p = Object.assign({ groupKey: group.id, pageToken: nextPageToken }, this.authParams);
|
const p = Object.assign({ groupKey: group.id, pageToken: nextPageToken }, this.authParams);
|
||||||
const memRes = await this.service.members.list(p);
|
const memRes = await this.service.members.list(p);
|
||||||
if (memRes.status !== 200) {
|
if (memRes.status !== 200) {
|
||||||
this.logService.warning('Group member list API failed: ' + memRes.statusText);
|
this.logService.warning("Group member list API failed: " + memRes.statusText);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,14 +204,14 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const type = member.type.toLowerCase();
|
const type = member.type.toLowerCase();
|
||||||
if (type === 'user') {
|
if (type === "user") {
|
||||||
if (member.status == null || member.status.toLowerCase() !== 'active') {
|
if (member.status == null || member.status.toLowerCase() !== "active") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
entry.userMemberExternalIds.add(member.id);
|
entry.userMemberExternalIds.add(member.id);
|
||||||
} else if (type === 'group') {
|
} else if (type === "group") {
|
||||||
entry.groupMemberReferenceIds.add(member.id);
|
entry.groupMemberReferenceIds.add(member.id);
|
||||||
} else if (type === 'customer') {
|
} else if (type === "customer") {
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
entry.userMemberExternalIds.add(user.externalId);
|
entry.userMemberExternalIds.add(user.externalId);
|
||||||
}
|
}
|
||||||
@@ -217,9 +228,13 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async auth() {
|
private async auth() {
|
||||||
if (this.dirConfig.clientEmail == null || this.dirConfig.privateKey == null ||
|
if (
|
||||||
this.dirConfig.adminUser == null || this.dirConfig.domain == null) {
|
this.dirConfig.clientEmail == null ||
|
||||||
throw new Error(this.i18nService.t('dirConfigIncomplete'));
|
this.dirConfig.privateKey == null ||
|
||||||
|
this.dirConfig.adminUser == null ||
|
||||||
|
this.dirConfig.domain == null
|
||||||
|
) {
|
||||||
|
throw new Error(this.i18nService.t("dirConfigIncomplete"));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.client = new google.auth.JWT({
|
this.client = new google.auth.JWT({
|
||||||
@@ -227,9 +242,9 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
|
|||||||
key: this.dirConfig.privateKey != null ? this.dirConfig.privateKey.trimLeft() : null,
|
key: this.dirConfig.privateKey != null ? this.dirConfig.privateKey.trimLeft() : null,
|
||||||
subject: this.dirConfig.adminUser,
|
subject: this.dirConfig.adminUser,
|
||||||
scopes: [
|
scopes: [
|
||||||
'https://www.googleapis.com/auth/admin.directory.user.readonly',
|
"https://www.googleapis.com/auth/admin.directory.user.readonly",
|
||||||
'https://www.googleapis.com/auth/admin.directory.group.readonly',
|
"https://www.googleapis.com/auth/admin.directory.group.readonly",
|
||||||
'https://www.googleapis.com/auth/admin.directory.group.member.readonly',
|
"https://www.googleapis.com/auth/admin.directory.group.member.readonly",
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -238,10 +253,10 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
|
|||||||
this.authParams = {
|
this.authParams = {
|
||||||
auth: this.client,
|
auth: this.client,
|
||||||
};
|
};
|
||||||
if (this.dirConfig.domain != null && this.dirConfig.domain.trim() !== '') {
|
if (this.dirConfig.domain != null && this.dirConfig.domain.trim() !== "") {
|
||||||
this.authParams.domain = this.dirConfig.domain;
|
this.authParams.domain = this.dirConfig.domain;
|
||||||
}
|
}
|
||||||
if (this.dirConfig.customer != null && this.dirConfig.customer.trim() !== '') {
|
if (this.dirConfig.customer != null && this.dirConfig.customer.trim() !== "") {
|
||||||
this.authParams.customer = this.dirConfig.customer;
|
this.authParams.customer = this.dirConfig.customer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from "fs";
|
||||||
import * as path from 'path';
|
import * as path from "path";
|
||||||
|
|
||||||
import { I18nService as BaseI18nService } from 'jslib-common/services/i18n.service';
|
import { I18nService as BaseI18nService } from "jslib-common/services/i18n.service";
|
||||||
|
|
||||||
export class I18nService extends BaseI18nService {
|
export class I18nService extends BaseI18nService {
|
||||||
constructor(systemLanguage: string, localesDirectory: string) {
|
constructor(systemLanguage: string, localesDirectory: string) {
|
||||||
super(systemLanguage, localesDirectory, (formattedLocale: string) => {
|
super(systemLanguage, localesDirectory, (formattedLocale: string) => {
|
||||||
const filePath = path.join(__dirname, this.localesDirectory + '/' + formattedLocale + '/messages.json');
|
const filePath = path.join(
|
||||||
const localesJson = fs.readFileSync(filePath, 'utf8');
|
__dirname,
|
||||||
const locales = JSON.parse(localesJson.replace(/^\uFEFF/, '')); // strip the BOM
|
this.localesDirectory + "/" + formattedLocale + "/messages.json"
|
||||||
|
);
|
||||||
|
const localesJson = fs.readFileSync(filePath, "utf8");
|
||||||
|
const locales = JSON.parse(localesJson.replace(/^\uFEFF/, "")); // strip the BOM
|
||||||
return Promise.resolve(locales);
|
return Promise.resolve(locales);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
import {
|
import { deletePassword, getPassword, setPassword } from "keytar";
|
||||||
deletePassword,
|
|
||||||
getPassword,
|
|
||||||
setPassword,
|
|
||||||
} from 'keytar';
|
|
||||||
|
|
||||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||||
|
|
||||||
export class KeytarSecureStorageService implements StorageService {
|
export class KeytarSecureStorageService implements StorageService {
|
||||||
constructor(private serviceName: string) {}
|
constructor(private serviceName: string) {}
|
||||||
|
|
||||||
get<T>(key: string): Promise<T> {
|
get<T>(key: string): Promise<T> {
|
||||||
return getPassword(this.serviceName, key).then(val => {
|
return getPassword(this.serviceName, key).then((val) => {
|
||||||
return JSON.parse(val) as T;
|
return JSON.parse(val) as T;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from "fs";
|
||||||
import * as ldap from 'ldapjs';
|
import { checkServerIdentity, PeerCertificate } from "tls";
|
||||||
|
|
||||||
import { checkServerIdentity, PeerCertificate } from 'tls';
|
import * as ldap from "ldapjs";
|
||||||
|
|
||||||
import { DirectoryType } from '../enums/directoryType';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
|
|
||||||
import { GroupEntry } from '../models/groupEntry';
|
import { StateService } from "../abstractions/state.service";
|
||||||
import { LdapConfiguration } from '../models/ldapConfiguration';
|
import { DirectoryType } from "../enums/directoryType";
|
||||||
import { SyncConfiguration } from '../models/syncConfiguration';
|
import { GroupEntry } from "../models/groupEntry";
|
||||||
import { UserEntry } from '../models/userEntry';
|
import { LdapConfiguration } from "../models/ldapConfiguration";
|
||||||
|
import { SyncConfiguration } from "../models/syncConfiguration";
|
||||||
|
import { UserEntry } from "../models/userEntry";
|
||||||
|
|
||||||
import { ConfigurationService } from './configuration.service';
|
import { IDirectoryService } from "./directory.service";
|
||||||
import { IDirectoryService } from './directory.service';
|
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
|
||||||
|
|
||||||
import { Utils } from 'jslib-common/misc/utils';
|
|
||||||
|
|
||||||
const UserControlAccountDisabled = 2;
|
const UserControlAccountDisabled = 2;
|
||||||
|
|
||||||
@@ -25,21 +23,24 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
private dirConfig: LdapConfiguration;
|
private dirConfig: LdapConfiguration;
|
||||||
private syncConfig: SyncConfiguration;
|
private syncConfig: SyncConfiguration;
|
||||||
|
|
||||||
constructor(private configurationService: ConfigurationService, private logService: LogService,
|
constructor(
|
||||||
private i18nService: I18nService) { }
|
private logService: LogService,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private stateService: StateService
|
||||||
|
) {}
|
||||||
|
|
||||||
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
||||||
const type = await this.configurationService.getDirectoryType();
|
const type = await this.stateService.getDirectoryType();
|
||||||
if (type !== DirectoryType.Ldap) {
|
if (type !== DirectoryType.Ldap) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dirConfig = await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap);
|
this.dirConfig = await this.stateService.getDirectory<LdapConfiguration>(DirectoryType.Ldap);
|
||||||
if (this.dirConfig == null) {
|
if (this.dirConfig == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.syncConfig = await this.configurationService.getSync();
|
this.syncConfig = await this.stateService.getSync();
|
||||||
if (this.syncConfig == null) {
|
if (this.syncConfig == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -55,7 +56,7 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
if (this.syncConfig.groups) {
|
if (this.syncConfig.groups) {
|
||||||
let groupForce = force;
|
let groupForce = force;
|
||||||
if (!groupForce && users != null) {
|
if (!groupForce && users != null) {
|
||||||
const activeUsers = users.filter(u => !u.deleted && !u.disabled);
|
const activeUsers = users.filter((u) => !u.deleted && !u.disabled);
|
||||||
groupForce = activeUsers.length > 0;
|
groupForce = activeUsers.length > 0;
|
||||||
}
|
}
|
||||||
groups = await this.getGroups(groupForce);
|
groups = await this.getGroups(groupForce);
|
||||||
@@ -66,31 +67,40 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getUsers(force: boolean): Promise<UserEntry[]> {
|
private async getUsers(force: boolean): Promise<UserEntry[]> {
|
||||||
const lastSync = await this.configurationService.getLastUserSyncDate();
|
const lastSync = await this.stateService.getLastUserSync();
|
||||||
let filter = this.buildBaseFilter(this.syncConfig.userObjectClass, this.syncConfig.userFilter);
|
let filter = this.buildBaseFilter(this.syncConfig.userObjectClass, this.syncConfig.userFilter);
|
||||||
filter = this.buildRevisionFilter(filter, force, lastSync);
|
filter = this.buildRevisionFilter(filter, force, lastSync);
|
||||||
|
|
||||||
const path = this.makeSearchPath(this.syncConfig.userPath);
|
const path = this.makeSearchPath(this.syncConfig.userPath);
|
||||||
this.logService.info('User search: ' + path + ' => ' + filter);
|
this.logService.info("User search: " + path + " => " + filter);
|
||||||
|
|
||||||
const regularUsers = await this.search<UserEntry>(path, filter, (se: any) => this.buildUser(se, false));
|
const regularUsers = await this.search<UserEntry>(path, filter, (se: any) =>
|
||||||
|
this.buildUser(se, false)
|
||||||
|
);
|
||||||
if (!this.dirConfig.ad) {
|
if (!this.dirConfig.ad) {
|
||||||
return regularUsers;
|
return regularUsers;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let deletedFilter = this.buildBaseFilter(this.syncConfig.userObjectClass, '(isDeleted=TRUE)');
|
let deletedFilter = this.buildBaseFilter(this.syncConfig.userObjectClass, "(isDeleted=TRUE)");
|
||||||
deletedFilter = this.buildRevisionFilter(deletedFilter, force, lastSync);
|
deletedFilter = this.buildRevisionFilter(deletedFilter, force, lastSync);
|
||||||
|
|
||||||
const deletedPath = this.makeSearchPath('CN=Deleted Objects');
|
const deletedPath = this.makeSearchPath("CN=Deleted Objects");
|
||||||
this.logService.info('Deleted user search: ' + deletedPath + ' => ' + deletedFilter);
|
this.logService.info("Deleted user search: " + deletedPath + " => " + deletedFilter);
|
||||||
|
|
||||||
const delControl = new (ldap as any).Control({ type: '1.2.840.113556.1.4.417', criticality: true });
|
const delControl = new (ldap as any).Control({
|
||||||
const deletedUsers = await this.search<UserEntry>(deletedPath, deletedFilter,
|
type: "1.2.840.113556.1.4.417",
|
||||||
(se: any) => this.buildUser(se, true), [delControl]);
|
criticality: true,
|
||||||
|
});
|
||||||
|
const deletedUsers = await this.search<UserEntry>(
|
||||||
|
deletedPath,
|
||||||
|
deletedFilter,
|
||||||
|
(se: any) => this.buildUser(se, true),
|
||||||
|
[delControl]
|
||||||
|
);
|
||||||
return regularUsers.concat(deletedUsers);
|
return regularUsers.concat(deletedUsers);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logService.warning('Cannot query deleted users.');
|
this.logService.warning("Cannot query deleted users.");
|
||||||
return regularUsers;
|
return regularUsers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,8 +117,12 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
user.externalId = this.getExternalId(searchEntry, user.referenceId);
|
user.externalId = this.getExternalId(searchEntry, user.referenceId);
|
||||||
user.disabled = this.entryDisabled(searchEntry);
|
user.disabled = this.entryDisabled(searchEntry);
|
||||||
user.email = this.getAttr(searchEntry, this.syncConfig.userEmailAttribute);
|
user.email = this.getAttr(searchEntry, this.syncConfig.userEmailAttribute);
|
||||||
if (user.email == null && this.syncConfig.useEmailPrefixSuffix &&
|
if (
|
||||||
this.syncConfig.emailPrefixAttribute != null && this.syncConfig.emailSuffix != null) {
|
user.email == null &&
|
||||||
|
this.syncConfig.useEmailPrefixSuffix &&
|
||||||
|
this.syncConfig.emailPrefixAttribute != null &&
|
||||||
|
this.syncConfig.emailSuffix != null
|
||||||
|
) {
|
||||||
const prefixAttr = this.getAttr(searchEntry, this.syncConfig.emailPrefixAttribute);
|
const prefixAttr = this.getAttr(searchEntry, this.syncConfig.emailPrefixAttribute);
|
||||||
if (prefixAttr != null) {
|
if (prefixAttr != null) {
|
||||||
user.email = prefixAttr + this.syncConfig.emailSuffix;
|
user.email = prefixAttr + this.syncConfig.emailSuffix;
|
||||||
@@ -119,7 +133,7 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
user.email = user.email.trim().toLowerCase();
|
user.email = user.email.trim().toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.deleted && (user.email == null || user.email.trim() === '')) {
|
if (!user.deleted && (user.email == null || user.email.trim() === "")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,15 +143,18 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
private async getGroups(force: boolean): Promise<GroupEntry[]> {
|
private async getGroups(force: boolean): Promise<GroupEntry[]> {
|
||||||
const entries: GroupEntry[] = [];
|
const entries: GroupEntry[] = [];
|
||||||
|
|
||||||
const lastSync = await this.configurationService.getLastUserSyncDate();
|
const lastSync = await this.stateService.getLastUserSync();
|
||||||
const originalFilter = this.buildBaseFilter(this.syncConfig.groupObjectClass, this.syncConfig.groupFilter);
|
const originalFilter = this.buildBaseFilter(
|
||||||
|
this.syncConfig.groupObjectClass,
|
||||||
|
this.syncConfig.groupFilter
|
||||||
|
);
|
||||||
let filter = originalFilter;
|
let filter = originalFilter;
|
||||||
const revisionFilter = this.buildRevisionFilter(filter, force, lastSync);
|
const revisionFilter = this.buildRevisionFilter(filter, force, lastSync);
|
||||||
const searchSinceRevision = filter !== revisionFilter;
|
const searchSinceRevision = filter !== revisionFilter;
|
||||||
filter = revisionFilter;
|
filter = revisionFilter;
|
||||||
|
|
||||||
const path = this.makeSearchPath(this.syncConfig.groupPath);
|
const path = this.makeSearchPath(this.syncConfig.groupPath);
|
||||||
this.logService.info('Group search: ' + path + ' => ' + filter);
|
this.logService.info("Group search: " + path + " => " + filter);
|
||||||
|
|
||||||
let groupSearchEntries: any[] = [];
|
let groupSearchEntries: any[] = [];
|
||||||
const initialSearchGroupIds = await this.search<string>(path, filter, (se: any) => {
|
const initialSearchGroupIds = await this.search<string>(path, filter, (se: any) => {
|
||||||
@@ -151,7 +168,10 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
groupSearchEntries = await this.search<string>(path, originalFilter, (se: any) => se);
|
groupSearchEntries = await this.search<string>(path, originalFilter, (se: any) => se);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userFilter = this.buildBaseFilter(this.syncConfig.userObjectClass, this.syncConfig.userFilter);
|
const userFilter = this.buildBaseFilter(
|
||||||
|
this.syncConfig.userObjectClass,
|
||||||
|
this.syncConfig.userFilter
|
||||||
|
);
|
||||||
const userPath = this.makeSearchPath(this.syncConfig.userPath);
|
const userPath = this.makeSearchPath(this.syncConfig.userPath);
|
||||||
const userIdMap = new Map<string, string>();
|
const userIdMap = new Map<string, string>();
|
||||||
await this.search<string>(userPath, userFilter, (se: any) => {
|
await this.search<string>(userPath, userFilter, (se: any) => {
|
||||||
@@ -180,7 +200,7 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
|
|
||||||
group.name = this.getAttr(searchEntry, this.syncConfig.groupNameAttribute);
|
group.name = this.getAttr(searchEntry, this.syncConfig.groupNameAttribute);
|
||||||
if (group.name == null) {
|
if (group.name == null) {
|
||||||
group.name = this.getAttr(searchEntry, 'cn');
|
group.name = this.getAttr(searchEntry, "cn");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (group.name == null) {
|
if (group.name == null) {
|
||||||
@@ -202,7 +222,7 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getExternalId(searchEntry: any, referenceId: string) {
|
private getExternalId(searchEntry: any, referenceId: string) {
|
||||||
const attrObj = this.getAttrObj(searchEntry, 'objectGUID');
|
const attrObj = this.getAttrObj(searchEntry, "objectGUID");
|
||||||
if (attrObj != null && attrObj._vals != null && attrObj._vals.length > 0) {
|
if (attrObj != null && attrObj._vals != null && attrObj._vals.length > 0) {
|
||||||
return this.bufToGuid(attrObj._vals[0]);
|
return this.bufToGuid(attrObj._vals[0]);
|
||||||
} else {
|
} else {
|
||||||
@@ -212,35 +232,35 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
|
|
||||||
private buildBaseFilter(objectClass: string, subFilter: string): string {
|
private buildBaseFilter(objectClass: string, subFilter: string): string {
|
||||||
let filter = this.buildObjectClassFilter(objectClass);
|
let filter = this.buildObjectClassFilter(objectClass);
|
||||||
if (subFilter != null && subFilter.trim() !== '') {
|
if (subFilter != null && subFilter.trim() !== "") {
|
||||||
filter = '(&' + filter + subFilter + ')';
|
filter = "(&" + filter + subFilter + ")";
|
||||||
}
|
}
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildObjectClassFilter(objectClass: string): string {
|
private buildObjectClassFilter(objectClass: string): string {
|
||||||
return '(&(objectClass=' + objectClass + '))';
|
return "(&(objectClass=" + objectClass + "))";
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildRevisionFilter(baseFilter: string, force: boolean, lastRevisionDate: Date) {
|
private buildRevisionFilter(baseFilter: string, force: boolean, lastRevisionDate: Date) {
|
||||||
const revisionAttr = this.syncConfig.revisionDateAttribute;
|
const revisionAttr = this.syncConfig.revisionDateAttribute;
|
||||||
if (!force && lastRevisionDate != null && revisionAttr != null && revisionAttr.trim() !== '') {
|
if (!force && lastRevisionDate != null && revisionAttr != null && revisionAttr.trim() !== "") {
|
||||||
const dateString = lastRevisionDate.toISOString().replace(/[-:T]/g, '').substr(0, 16) + 'Z';
|
const dateString = lastRevisionDate.toISOString().replace(/[-:T]/g, "").substr(0, 16) + "Z";
|
||||||
baseFilter = '(&' + baseFilter + '(' + revisionAttr + '>=' + dateString + '))';
|
baseFilter = "(&" + baseFilter + "(" + revisionAttr + ">=" + dateString + "))";
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseFilter;
|
return baseFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
private makeSearchPath(pathPrefix: string) {
|
private makeSearchPath(pathPrefix: string) {
|
||||||
if (this.dirConfig.rootPath.toLowerCase().indexOf('dc=') === -1) {
|
if (this.dirConfig.rootPath.toLowerCase().indexOf("dc=") === -1) {
|
||||||
return pathPrefix;
|
return pathPrefix;
|
||||||
}
|
}
|
||||||
if (this.dirConfig.rootPath != null && this.dirConfig.rootPath.trim() !== '') {
|
if (this.dirConfig.rootPath != null && this.dirConfig.rootPath.trim() !== "") {
|
||||||
const trimmedRootPath = this.dirConfig.rootPath.trim().toLowerCase();
|
const trimmedRootPath = this.dirConfig.rootPath.trim().toLowerCase();
|
||||||
let path = trimmedRootPath.substr(trimmedRootPath.indexOf('dc='));
|
let path = trimmedRootPath.substr(trimmedRootPath.indexOf("dc="));
|
||||||
if (pathPrefix != null && pathPrefix.trim() !== '') {
|
if (pathPrefix != null && pathPrefix.trim() !== "") {
|
||||||
path = pathPrefix.trim() + ',' + path;
|
path = pathPrefix.trim() + "," + path;
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
@@ -254,7 +274,12 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const attrs = searchEntry.attributes.filter((a: any) => a.type === attr);
|
const attrs = searchEntry.attributes.filter((a: any) => a.type === attr);
|
||||||
if (attrs == null || attrs.length === 0 || attrs[0].vals == null || attrs[0].vals.length === 0) {
|
if (
|
||||||
|
attrs == null ||
|
||||||
|
attrs.length === 0 ||
|
||||||
|
attrs[0].vals == null ||
|
||||||
|
attrs[0].vals.length === 0
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,23 +303,29 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private entryDisabled(searchEntry: any): boolean {
|
private entryDisabled(searchEntry: any): boolean {
|
||||||
const c = this.getAttr(searchEntry, 'userAccountControl');
|
const c = this.getAttr(searchEntry, "userAccountControl");
|
||||||
if (c != null) {
|
if (c != null) {
|
||||||
try {
|
try {
|
||||||
const control = parseInt(c, null);
|
const control = parseInt(c, null);
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
return (control & UserControlAccountDisabled) === UserControlAccountDisabled;
|
return (control & UserControlAccountDisabled) === UserControlAccountDisabled;
|
||||||
} catch { }
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async search<T>(path: string, filter: string, processEntry: (searchEntry: any) => T,
|
private async search<T>(
|
||||||
controls: ldap.Control[] = []): Promise<T[]> {
|
path: string,
|
||||||
|
filter: string,
|
||||||
|
processEntry: (searchEntry: any) => T,
|
||||||
|
controls: ldap.Control[] = []
|
||||||
|
): Promise<T[]> {
|
||||||
const options: ldap.SearchOptions = {
|
const options: ldap.SearchOptions = {
|
||||||
filter: filter,
|
filter: filter,
|
||||||
scope: 'sub',
|
scope: "sub",
|
||||||
paged: this.dirConfig.pagedSearch,
|
paged: this.dirConfig.pagedSearch,
|
||||||
};
|
};
|
||||||
const entries: T[] = [];
|
const entries: T[] = [];
|
||||||
@@ -305,18 +336,18 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.on('error', resErr => {
|
res.on("error", (resErr) => {
|
||||||
reject(resErr);
|
reject(resErr);
|
||||||
});
|
});
|
||||||
|
|
||||||
res.on('searchEntry', entry => {
|
res.on("searchEntry", (entry) => {
|
||||||
const e = processEntry(entry);
|
const e = processEntry(entry);
|
||||||
if (e != null) {
|
if (e != null) {
|
||||||
entries.push(e);
|
entries.push(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
res.on('end', result => {
|
res.on("end", (result) => {
|
||||||
resolve(entries);
|
resolve(entries);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -326,12 +357,11 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
private async bind(): Promise<any> {
|
private async bind(): Promise<any> {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
if (this.dirConfig.hostname == null || this.dirConfig.port == null) {
|
if (this.dirConfig.hostname == null || this.dirConfig.port == null) {
|
||||||
reject(this.i18nService.t('dirConfigIncomplete'));
|
reject(this.i18nService.t("dirConfigIncomplete"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const protocol = 'ldap' + (this.dirConfig.ssl && !this.dirConfig.startTls ? 's' : '');
|
const protocol = "ldap" + (this.dirConfig.ssl && !this.dirConfig.startTls ? "s" : "");
|
||||||
const url = protocol + '://' + this.dirConfig.hostname +
|
const url = protocol + "://" + this.dirConfig.hostname + ":" + this.dirConfig.port;
|
||||||
':' + this.dirConfig.port;
|
|
||||||
const options: ldap.ClientOptions = {
|
const options: ldap.ClientOptions = {
|
||||||
url: url.trim().toLowerCase(),
|
url: url.trim().toLowerCase(),
|
||||||
};
|
};
|
||||||
@@ -342,21 +372,33 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
tlsOptions.rejectUnauthorized = !this.dirConfig.sslAllowUnauthorized;
|
tlsOptions.rejectUnauthorized = !this.dirConfig.sslAllowUnauthorized;
|
||||||
}
|
}
|
||||||
if (!this.dirConfig.startTls) {
|
if (!this.dirConfig.startTls) {
|
||||||
if (this.dirConfig.sslCaPath != null && this.dirConfig.sslCaPath !== '' &&
|
if (
|
||||||
fs.existsSync(this.dirConfig.sslCaPath)) {
|
this.dirConfig.sslCaPath != null &&
|
||||||
|
this.dirConfig.sslCaPath !== "" &&
|
||||||
|
fs.existsSync(this.dirConfig.sslCaPath)
|
||||||
|
) {
|
||||||
tlsOptions.ca = [fs.readFileSync(this.dirConfig.sslCaPath)];
|
tlsOptions.ca = [fs.readFileSync(this.dirConfig.sslCaPath)];
|
||||||
}
|
}
|
||||||
if (this.dirConfig.sslCertPath != null && this.dirConfig.sslCertPath !== '' &&
|
if (
|
||||||
fs.existsSync(this.dirConfig.sslCertPath)) {
|
this.dirConfig.sslCertPath != null &&
|
||||||
|
this.dirConfig.sslCertPath !== "" &&
|
||||||
|
fs.existsSync(this.dirConfig.sslCertPath)
|
||||||
|
) {
|
||||||
tlsOptions.cert = fs.readFileSync(this.dirConfig.sslCertPath);
|
tlsOptions.cert = fs.readFileSync(this.dirConfig.sslCertPath);
|
||||||
}
|
}
|
||||||
if (this.dirConfig.sslKeyPath != null && this.dirConfig.sslKeyPath !== '' &&
|
if (
|
||||||
fs.existsSync(this.dirConfig.sslKeyPath)) {
|
this.dirConfig.sslKeyPath != null &&
|
||||||
|
this.dirConfig.sslKeyPath !== "" &&
|
||||||
|
fs.existsSync(this.dirConfig.sslKeyPath)
|
||||||
|
) {
|
||||||
tlsOptions.key = fs.readFileSync(this.dirConfig.sslKeyPath);
|
tlsOptions.key = fs.readFileSync(this.dirConfig.sslKeyPath);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.dirConfig.tlsCaPath != null && this.dirConfig.tlsCaPath !== '' &&
|
if (
|
||||||
fs.existsSync(this.dirConfig.tlsCaPath)) {
|
this.dirConfig.tlsCaPath != null &&
|
||||||
|
this.dirConfig.tlsCaPath !== "" &&
|
||||||
|
fs.existsSync(this.dirConfig.tlsCaPath)
|
||||||
|
) {
|
||||||
tlsOptions.ca = [fs.readFileSync(this.dirConfig.tlsCaPath)];
|
tlsOptions.ca = [fs.readFileSync(this.dirConfig.tlsCaPath)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,13 +409,17 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
|
|
||||||
this.client = ldap.createClient(options);
|
this.client = ldap.createClient(options);
|
||||||
|
|
||||||
const user = this.dirConfig.username == null || this.dirConfig.username.trim() === '' ? null :
|
const user =
|
||||||
this.dirConfig.username;
|
this.dirConfig.username == null || this.dirConfig.username.trim() === ""
|
||||||
const pass = this.dirConfig.password == null || this.dirConfig.password.trim() === '' ? null :
|
? null
|
||||||
this.dirConfig.password;
|
: this.dirConfig.username;
|
||||||
|
const pass =
|
||||||
|
this.dirConfig.password == null || this.dirConfig.password.trim() === ""
|
||||||
|
? null
|
||||||
|
: this.dirConfig.password;
|
||||||
|
|
||||||
if (user == null || pass == null) {
|
if (user == null || pass == null) {
|
||||||
reject(this.i18nService.t('usernamePasswordNotConfigured'));
|
reject(this.i18nService.t("usernamePasswordNotConfigured"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +428,7 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
if (err != null) {
|
if (err != null) {
|
||||||
reject(err.message);
|
reject(err.message);
|
||||||
} else {
|
} else {
|
||||||
this.client.bind(user, pass, err2 => {
|
this.client.bind(user, pass, (err2) => {
|
||||||
if (err2 != null) {
|
if (err2 != null) {
|
||||||
reject(err2.message);
|
reject(err2.message);
|
||||||
} else {
|
} else {
|
||||||
@@ -392,7 +438,7 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.client.bind(user, pass, err => {
|
this.client.bind(user, pass, (err) => {
|
||||||
if (err != null) {
|
if (err != null) {
|
||||||
reject(err.message);
|
reject(err.message);
|
||||||
} else {
|
} else {
|
||||||
@@ -405,7 +451,7 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
|
|
||||||
private async unbind(): Promise<void> {
|
private async unbind(): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.client.unbind(err => {
|
this.client.unbind((err) => {
|
||||||
if (err != null) {
|
if (err != null) {
|
||||||
reject(err);
|
reject(err);
|
||||||
} else {
|
} else {
|
||||||
@@ -422,8 +468,16 @@ export class LdapDirectoryService implements IDirectoryService {
|
|||||||
const p3 = arr.slice(6, 8).reverse().buffer;
|
const p3 = arr.slice(6, 8).reverse().buffer;
|
||||||
const p4 = arr.slice(8, 10).buffer;
|
const p4 = arr.slice(8, 10).buffer;
|
||||||
const p5 = arr.slice(10).buffer;
|
const p5 = arr.slice(10).buffer;
|
||||||
const guid = Utils.fromBufferToHex(p1) + '-' + Utils.fromBufferToHex(p2) + '-' + Utils.fromBufferToHex(p3) +
|
const guid =
|
||||||
'-' + Utils.fromBufferToHex(p4) + '-' + Utils.fromBufferToHex(p5);
|
Utils.fromBufferToHex(p1) +
|
||||||
|
"-" +
|
||||||
|
Utils.fromBufferToHex(p2) +
|
||||||
|
"-" +
|
||||||
|
Utils.fromBufferToHex(p3) +
|
||||||
|
"-" +
|
||||||
|
Utils.fromBufferToHex(p4) +
|
||||||
|
"-" +
|
||||||
|
Utils.fromBufferToHex(p5);
|
||||||
return guid.toLowerCase();
|
return guid.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
import * as lock from 'proper-lockfile';
|
import * as lock from "proper-lockfile";
|
||||||
|
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
import { LowdbStorageService as LowdbStorageServiceBase } from 'jslib-node/services/lowdbStorage.service';
|
import { LowdbStorageService as LowdbStorageServiceBase } from "jslib-node/services/lowdbStorage.service";
|
||||||
|
|
||||||
import { Utils } from 'jslib-common/misc/utils';
|
|
||||||
|
|
||||||
export class LowdbStorageService extends LowdbStorageServiceBase {
|
export class LowdbStorageService extends LowdbStorageServiceBase {
|
||||||
constructor(logService: LogService, defaults?: any, dir?: string, allowCache = false, private requireLock = false) {
|
constructor(
|
||||||
|
logService: LogService,
|
||||||
|
defaults?: any,
|
||||||
|
dir?: string,
|
||||||
|
allowCache = false,
|
||||||
|
private requireLock = false
|
||||||
|
) {
|
||||||
super(logService, defaults, dir, allowCache);
|
super(logService, defaults, dir, allowCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async lockDbFile<T>(action: () => T): Promise<T> {
|
protected async lockDbFile<T>(action: () => T): Promise<T> {
|
||||||
if (this.requireLock && !Utils.isNullOrWhitespace(this.dataFilePath)) {
|
if (this.requireLock && !Utils.isNullOrWhitespace(this.dataFilePath)) {
|
||||||
this.logService.info('acquiring db file lock');
|
this.logService.info("acquiring db file lock");
|
||||||
return await lock.lock(this.dataFilePath, { retries: 3 }).then(release => {
|
return await lock.lock(this.dataFilePath, { retries: 3 }).then((release) => {
|
||||||
try {
|
try {
|
||||||
return action();
|
return action();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
|
||||||
import { TokenService } from 'jslib-common/abstractions/token.service';
|
|
||||||
|
|
||||||
import { NodeApiService as NodeApiServiceBase } from 'jslib-node/services/nodeApi.service';
|
|
||||||
|
|
||||||
export class NodeApiService extends NodeApiServiceBase {
|
|
||||||
constructor(tokenService: TokenService, platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
|
||||||
private refreshTokenCallback: () => Promise<void>, logoutCallback: (expired: boolean) => Promise<void>,
|
|
||||||
customUserAgent: string = null, apiKeyRefresh: (clientId: string, clientSecret: string) => Promise<any>) {
|
|
||||||
super(tokenService, platformUtilsService, environmentService, logoutCallback, customUserAgent, apiKeyRefresh);
|
|
||||||
}
|
|
||||||
|
|
||||||
doRefreshToken(): Promise<void> {
|
|
||||||
return this.refreshTokenCallback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
40
src/services/noop/noopTwoFactor.service.ts
Normal file
40
src/services/noop/noopTwoFactor.service.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import {
|
||||||
|
TwoFactorProviderDetails,
|
||||||
|
TwoFactorService,
|
||||||
|
} from "jslib-common/abstractions/twoFactor.service";
|
||||||
|
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
|
||||||
|
import { IdentityTwoFactorResponse } from "jslib-common/models/response/identityTwoFactorResponse";
|
||||||
|
|
||||||
|
export class NoopTwoFactorService implements TwoFactorService {
|
||||||
|
init() {
|
||||||
|
// Noop
|
||||||
|
}
|
||||||
|
|
||||||
|
getSupportedProviders(win: Window): TwoFactorProviderDetails[] {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultProvider(webAuthnSupported: boolean): TwoFactorProviderType {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedProvider(type: TwoFactorProviderType) {
|
||||||
|
// Noop
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSelectedProvider() {
|
||||||
|
// Noop
|
||||||
|
}
|
||||||
|
|
||||||
|
setProviders(response: IdentityTwoFactorResponse) {
|
||||||
|
// Noop
|
||||||
|
}
|
||||||
|
|
||||||
|
clearProviders() {
|
||||||
|
// Noop
|
||||||
|
}
|
||||||
|
|
||||||
|
getProviders(): Map<TwoFactorProviderType, { [key: string]: string }> {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
import { DirectoryType } from '../enums/directoryType';
|
import * as https from "https";
|
||||||
|
|
||||||
import { GroupEntry } from '../models/groupEntry';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { OktaConfiguration } from '../models/oktaConfiguration';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { SyncConfiguration } from '../models/syncConfiguration';
|
|
||||||
import { UserEntry } from '../models/userEntry';
|
|
||||||
|
|
||||||
import { BaseDirectoryService } from './baseDirectory.service';
|
import { StateService } from "../abstractions/state.service";
|
||||||
import { ConfigurationService } from './configuration.service';
|
import { DirectoryType } from "../enums/directoryType";
|
||||||
import { IDirectoryService } from './directory.service';
|
import { GroupEntry } from "../models/groupEntry";
|
||||||
|
import { OktaConfiguration } from "../models/oktaConfiguration";
|
||||||
|
import { SyncConfiguration } from "../models/syncConfiguration";
|
||||||
|
import { UserEntry } from "../models/userEntry";
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { BaseDirectoryService } from "./baseDirectory.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { IDirectoryService } from "./directory.service";
|
||||||
|
|
||||||
import * as https from 'https';
|
|
||||||
|
|
||||||
const DelayBetweenBuildGroupCallsInMilliseconds = 500;
|
const DelayBetweenBuildGroupCallsInMilliseconds = 500;
|
||||||
|
|
||||||
@@ -21,29 +20,32 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
|
|||||||
private syncConfig: SyncConfiguration;
|
private syncConfig: SyncConfiguration;
|
||||||
private lastBuildGroupCall: number;
|
private lastBuildGroupCall: number;
|
||||||
|
|
||||||
constructor(private configurationService: ConfigurationService, private logService: LogService,
|
constructor(
|
||||||
private i18nService: I18nService) {
|
private logService: LogService,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private stateService: StateService
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
||||||
const type = await this.configurationService.getDirectoryType();
|
const type = await this.stateService.getDirectoryType();
|
||||||
if (type !== DirectoryType.Okta) {
|
if (type !== DirectoryType.Okta) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dirConfig = await this.configurationService.getDirectory<OktaConfiguration>(DirectoryType.Okta);
|
this.dirConfig = await this.stateService.getDirectory<OktaConfiguration>(DirectoryType.Okta);
|
||||||
if (this.dirConfig == null) {
|
if (this.dirConfig == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.syncConfig = await this.configurationService.getSync();
|
this.syncConfig = await this.stateService.getSync();
|
||||||
if (this.syncConfig == null) {
|
if (this.syncConfig == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.dirConfig.orgUrl == null || this.dirConfig.token == null) {
|
if (this.dirConfig.orgUrl == null || this.dirConfig.token == null) {
|
||||||
throw new Error(this.i18nService.t('dirConfigIncomplete'));
|
throw new Error(this.i18nService.t("dirConfigIncomplete"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let users: UserEntry[];
|
let users: UserEntry[];
|
||||||
@@ -63,13 +65,14 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
|
|||||||
|
|
||||||
private async getUsers(force: boolean): Promise<UserEntry[]> {
|
private async getUsers(force: boolean): Promise<UserEntry[]> {
|
||||||
const entries: UserEntry[] = [];
|
const entries: UserEntry[] = [];
|
||||||
const lastSync = await this.configurationService.getLastUserSyncDate();
|
const lastSync = await this.stateService.getLastUserSync();
|
||||||
const oktaFilter = this.buildOktaFilter(this.syncConfig.userFilter, force, lastSync);
|
const oktaFilter = this.buildOktaFilter(this.syncConfig.userFilter, force, lastSync);
|
||||||
const setFilter = this.createCustomSet(this.syncConfig.userFilter);
|
const setFilter = this.createCustomSet(this.syncConfig.userFilter);
|
||||||
|
|
||||||
this.logService.info('Querying users.');
|
this.logService.info("Querying users.");
|
||||||
const usersPromise = this.apiGetMany('users?filter=' + this.encodeUrlParameter(oktaFilter))
|
const usersPromise = this.apiGetMany(
|
||||||
.then((users: any[]) => {
|
"users?filter=" + this.encodeUrlParameter(oktaFilter)
|
||||||
|
).then((users: any[]) => {
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
const entry = this.buildUser(user);
|
const entry = this.buildUser(user);
|
||||||
if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
|
if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
|
||||||
@@ -80,13 +83,14 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
|
|||||||
|
|
||||||
// Deactivated users have to be queried for separately, only when no filter is provided in the first query
|
// Deactivated users have to be queried for separately, only when no filter is provided in the first query
|
||||||
let deactUsersPromise: any;
|
let deactUsersPromise: any;
|
||||||
if (oktaFilter == null || oktaFilter.indexOf('lastUpdated ') === -1) {
|
if (oktaFilter == null || oktaFilter.indexOf("lastUpdated ") === -1) {
|
||||||
let deactOktaFilter = 'status eq "DEPROVISIONED"';
|
let deactOktaFilter = 'status eq "DEPROVISIONED"';
|
||||||
if (oktaFilter != null) {
|
if (oktaFilter != null) {
|
||||||
deactOktaFilter = '(' + oktaFilter + ') and ' + deactOktaFilter;
|
deactOktaFilter = "(" + oktaFilter + ") and " + deactOktaFilter;
|
||||||
}
|
}
|
||||||
deactUsersPromise = this.apiGetMany('users?filter=' + this.encodeUrlParameter(deactOktaFilter))
|
deactUsersPromise = this.apiGetMany(
|
||||||
.then((users: any[]) => {
|
"users?filter=" + this.encodeUrlParameter(deactOktaFilter)
|
||||||
|
).then((users: any[]) => {
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
const entry = this.buildUser(user);
|
const entry = this.buildUser(user);
|
||||||
if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
|
if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
|
||||||
@@ -107,25 +111,32 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
|
|||||||
entry.externalId = user.id;
|
entry.externalId = user.id;
|
||||||
entry.referenceId = user.id;
|
entry.referenceId = user.id;
|
||||||
entry.email = user.profile.email != null ? user.profile.email.trim().toLowerCase() : null;
|
entry.email = user.profile.email != null ? user.profile.email.trim().toLowerCase() : null;
|
||||||
entry.deleted = user.status === 'DEPROVISIONED';
|
entry.deleted = user.status === "DEPROVISIONED";
|
||||||
entry.disabled = user.status === 'SUSPENDED';
|
entry.disabled = user.status === "SUSPENDED";
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getGroups(force: boolean, setFilter: [boolean, Set<string>]): Promise<GroupEntry[]> {
|
private async getGroups(
|
||||||
|
force: boolean,
|
||||||
|
setFilter: [boolean, Set<string>]
|
||||||
|
): Promise<GroupEntry[]> {
|
||||||
const entries: GroupEntry[] = [];
|
const entries: GroupEntry[] = [];
|
||||||
const lastSync = await this.configurationService.getLastGroupSyncDate();
|
const lastSync = await this.stateService.getLastGroupSync();
|
||||||
const oktaFilter = this.buildOktaFilter(this.syncConfig.groupFilter, force, lastSync);
|
const oktaFilter = this.buildOktaFilter(this.syncConfig.groupFilter, force, lastSync);
|
||||||
|
|
||||||
this.logService.info('Querying groups.');
|
this.logService.info("Querying groups.");
|
||||||
await this.apiGetMany('groups?filter=' + this.encodeUrlParameter(oktaFilter)).then(async (groups: any[]) => {
|
await this.apiGetMany("groups?filter=" + this.encodeUrlParameter(oktaFilter)).then(
|
||||||
for (const group of groups.filter(g => !this.filterOutResult(setFilter, g.profile.name))) {
|
async (groups: any[]) => {
|
||||||
|
for (const group of groups.filter(
|
||||||
|
(g) => !this.filterOutResult(setFilter, g.profile.name)
|
||||||
|
)) {
|
||||||
const entry = await this.buildGroup(group);
|
const entry = await this.buildGroup(group);
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
entries.push(entry);
|
entries.push(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,14 +147,14 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
|
|||||||
entry.name = group.profile.name;
|
entry.name = group.profile.name;
|
||||||
|
|
||||||
// throttle some to avoid rate limiting
|
// throttle some to avoid rate limiting
|
||||||
const neededDelay = DelayBetweenBuildGroupCallsInMilliseconds - (Date.now() - this.lastBuildGroupCall);
|
const neededDelay =
|
||||||
|
DelayBetweenBuildGroupCallsInMilliseconds - (Date.now() - this.lastBuildGroupCall);
|
||||||
if (neededDelay > 0) {
|
if (neededDelay > 0) {
|
||||||
await new Promise(resolve =>
|
await new Promise((resolve) => setTimeout(resolve, neededDelay));
|
||||||
setTimeout(resolve, neededDelay));
|
|
||||||
}
|
}
|
||||||
this.lastBuildGroupCall = Date.now();
|
this.lastBuildGroupCall = Date.now();
|
||||||
|
|
||||||
await this.apiGetMany('groups/' + group.id + '/users').then((users: any[]) => {
|
await this.apiGetMany("groups/" + group.id + "/users").then((users: any[]) => {
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
entry.userMemberExternalIds.add(user.id);
|
entry.userMemberExternalIds.add(user.id);
|
||||||
}
|
}
|
||||||
@@ -154,7 +165,7 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
|
|||||||
|
|
||||||
private buildOktaFilter(baseFilter: string, force: boolean, lastSync: Date) {
|
private buildOktaFilter(baseFilter: string, force: boolean, lastSync: Date) {
|
||||||
baseFilter = this.createDirectoryQuery(baseFilter);
|
baseFilter = this.createDirectoryQuery(baseFilter);
|
||||||
baseFilter = baseFilter == null || baseFilter.trim() === '' ? null : baseFilter;
|
baseFilter = baseFilter == null || baseFilter.trim() === "" ? null : baseFilter;
|
||||||
if (force || lastSync == null) {
|
if (force || lastSync == null) {
|
||||||
return baseFilter;
|
return baseFilter;
|
||||||
}
|
}
|
||||||
@@ -164,32 +175,34 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
|
|||||||
return updatedFilter;
|
return updatedFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
return '(' + baseFilter + ') and ' + updatedFilter;
|
return "(" + baseFilter + ") and " + updatedFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
private encodeUrlParameter(filter: string): string {
|
private encodeUrlParameter(filter: string): string {
|
||||||
return filter == null ? '' : encodeURIComponent(filter);
|
return filter == null ? "" : encodeURIComponent(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async apiGetCall(url: string): Promise<[any, Map<string, string | string[]>]> {
|
private async apiGetCall(url: string): Promise<[any, Map<string, string | string[]>]> {
|
||||||
const u = new URL(url);
|
const u = new URL(url);
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve) => {
|
||||||
https.get({
|
https.get(
|
||||||
|
{
|
||||||
hostname: u.hostname,
|
hostname: u.hostname,
|
||||||
path: u.pathname + u.search,
|
path: u.pathname + u.search,
|
||||||
port: 443,
|
port: 443,
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: 'SSWS ' + this.dirConfig.token,
|
Authorization: "SSWS " + this.dirConfig.token,
|
||||||
Accept: 'application/json',
|
Accept: "application/json",
|
||||||
},
|
},
|
||||||
}, res => {
|
},
|
||||||
let body = '';
|
(res) => {
|
||||||
|
let body = "";
|
||||||
|
|
||||||
res.on('data', chunk => {
|
res.on("data", (chunk) => {
|
||||||
body += chunk;
|
body += chunk;
|
||||||
});
|
});
|
||||||
|
|
||||||
res.on('end', () => {
|
res.on("end", () => {
|
||||||
if (res.statusCode !== 200) {
|
if (res.statusCode !== 200) {
|
||||||
resolve(null);
|
resolve(null);
|
||||||
return;
|
return;
|
||||||
@@ -199,6 +212,7 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
|
|||||||
if (res.headers != null) {
|
if (res.headers != null) {
|
||||||
const headersMap = new Map<string, string | string[]>();
|
const headersMap = new Map<string, string | string[]>();
|
||||||
for (const key in res.headers) {
|
for (const key in res.headers) {
|
||||||
|
// eslint-disable-next-line
|
||||||
if (res.headers.hasOwnProperty(key)) {
|
if (res.headers.hasOwnProperty(key)) {
|
||||||
const val = res.headers[key];
|
const val = res.headers[key];
|
||||||
headersMap.set(key.toLowerCase(), val);
|
headersMap.set(key.toLowerCase(), val);
|
||||||
@@ -210,18 +224,20 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
|
|||||||
resolve([responseJson, null]);
|
resolve([responseJson, null]);
|
||||||
});
|
});
|
||||||
|
|
||||||
res.on('error', () => {
|
res.on("error", () => {
|
||||||
resolve(null);
|
resolve(null);
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async apiGetMany(endpoint: string, currentData: any[] = []): Promise<any[]> {
|
private async apiGetMany(endpoint: string, currentData: any[] = []): Promise<any[]> {
|
||||||
const url = endpoint.indexOf('https://') === 0 ? endpoint : `${this.dirConfig.orgUrl}/api/v1/${endpoint}`;
|
const url =
|
||||||
|
endpoint.indexOf("https://") === 0 ? endpoint : `${this.dirConfig.orgUrl}/api/v1/${endpoint}`;
|
||||||
const response = await this.apiGetCall(url);
|
const response = await this.apiGetCall(url);
|
||||||
if (response == null || response[0] == null || !Array.isArray(response[0])) {
|
if (response == null || response[0] == null || !Array.isArray(response[0])) {
|
||||||
throw new Error('API call failed.');
|
throw new Error("API call failed.");
|
||||||
}
|
}
|
||||||
if (response[0].length === 0) {
|
if (response[0].length === 0) {
|
||||||
return currentData;
|
return currentData;
|
||||||
@@ -230,17 +246,17 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
|
|||||||
if (response[1] == null) {
|
if (response[1] == null) {
|
||||||
return currentData;
|
return currentData;
|
||||||
}
|
}
|
||||||
const linkHeader = response[1].get('link');
|
const linkHeader = response[1].get("link");
|
||||||
if (linkHeader == null || Array.isArray(linkHeader)) {
|
if (linkHeader == null || Array.isArray(linkHeader)) {
|
||||||
return currentData;
|
return currentData;
|
||||||
}
|
}
|
||||||
let nextLink: string = null;
|
let nextLink: string = null;
|
||||||
const linkHeaderParts = linkHeader.split(',');
|
const linkHeaderParts = linkHeader.split(",");
|
||||||
for (const part of linkHeaderParts) {
|
for (const part of linkHeaderParts) {
|
||||||
if (part.indexOf('; rel="next"') > -1) {
|
if (part.indexOf('; rel="next"') > -1) {
|
||||||
const subParts = part.split(';');
|
const subParts = part.split(";");
|
||||||
if (subParts.length > 0 && subParts[0].indexOf('https://') > -1) {
|
if (subParts.length > 0 && subParts[0].indexOf("https://") > -1) {
|
||||||
nextLink = subParts[0].replace('>', '').replace('<', '').trim();
|
nextLink = subParts[0].replace(">", "").replace("<", "").trim();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { DirectoryType } from '../enums/directoryType';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
|
|
||||||
import { GroupEntry } from '../models/groupEntry';
|
import { StateService } from "../abstractions/state.service";
|
||||||
import { OneLoginConfiguration } from '../models/oneLoginConfiguration';
|
import { DirectoryType } from "../enums/directoryType";
|
||||||
import { SyncConfiguration } from '../models/syncConfiguration';
|
import { GroupEntry } from "../models/groupEntry";
|
||||||
import { UserEntry } from '../models/userEntry';
|
import { OneLoginConfiguration } from "../models/oneLoginConfiguration";
|
||||||
|
import { SyncConfiguration } from "../models/syncConfiguration";
|
||||||
|
import { UserEntry } from "../models/userEntry";
|
||||||
|
|
||||||
import { BaseDirectoryService } from './baseDirectory.service';
|
import { BaseDirectoryService } from "./baseDirectory.service";
|
||||||
import { ConfigurationService } from './configuration.service';
|
import { IDirectoryService } from "./directory.service";
|
||||||
import { IDirectoryService } from './directory.service';
|
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
|
||||||
|
|
||||||
// Basic email validation: something@something.something
|
// Basic email validation: something@something.something
|
||||||
const ValidEmailRegex = /^\S+@\S+\.\S+$/;
|
const ValidEmailRegex = /^\S+@\S+\.\S+$/;
|
||||||
@@ -21,34 +20,39 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
|
|||||||
private accessToken: string;
|
private accessToken: string;
|
||||||
private allUsers: any[] = [];
|
private allUsers: any[] = [];
|
||||||
|
|
||||||
constructor(private configurationService: ConfigurationService, private logService: LogService,
|
constructor(
|
||||||
private i18nService: I18nService) {
|
private logService: LogService,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private stateService: StateService
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
||||||
const type = await this.configurationService.getDirectoryType();
|
const type = await this.stateService.getDirectoryType();
|
||||||
if (type !== DirectoryType.OneLogin) {
|
if (type !== DirectoryType.OneLogin) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dirConfig = await this.configurationService.getDirectory<OneLoginConfiguration>(DirectoryType.OneLogin);
|
this.dirConfig = await this.stateService.getDirectory<OneLoginConfiguration>(
|
||||||
|
DirectoryType.OneLogin
|
||||||
|
);
|
||||||
if (this.dirConfig == null) {
|
if (this.dirConfig == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.syncConfig = await this.configurationService.getSync();
|
this.syncConfig = await this.stateService.getSync();
|
||||||
if (this.syncConfig == null) {
|
if (this.syncConfig == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.dirConfig.clientId == null || this.dirConfig.clientSecret == null) {
|
if (this.dirConfig.clientId == null || this.dirConfig.clientSecret == null) {
|
||||||
throw new Error(this.i18nService.t('dirConfigIncomplete'));
|
throw new Error(this.i18nService.t("dirConfigIncomplete"));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.accessToken = await this.getAccessToken();
|
this.accessToken = await this.getAccessToken();
|
||||||
if (this.accessToken == null) {
|
if (this.accessToken == null) {
|
||||||
throw new Error('Could not get access token');
|
throw new Error("Could not get access token");
|
||||||
}
|
}
|
||||||
|
|
||||||
let users: UserEntry[];
|
let users: UserEntry[];
|
||||||
@@ -70,9 +74,9 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
|
|||||||
const entries: UserEntry[] = [];
|
const entries: UserEntry[] = [];
|
||||||
const query = this.createDirectoryQuery(this.syncConfig.userFilter);
|
const query = this.createDirectoryQuery(this.syncConfig.userFilter);
|
||||||
const setFilter = this.createCustomSet(this.syncConfig.userFilter);
|
const setFilter = this.createCustomSet(this.syncConfig.userFilter);
|
||||||
this.logService.info('Querying users.');
|
this.logService.info("Querying users.");
|
||||||
this.allUsers = await this.apiGetMany('users' + (query != null ? '?' + query : ''));
|
this.allUsers = await this.apiGetMany("users" + (query != null ? "?" + query : ""));
|
||||||
this.allUsers.forEach(user => {
|
this.allUsers.forEach((user) => {
|
||||||
const entry = this.buildUser(user);
|
const entry = this.buildUser(user);
|
||||||
if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
|
if (entry != null && !this.filterOutResult(setFilter, entry.email)) {
|
||||||
entries.push(entry);
|
entries.push(entry);
|
||||||
@@ -88,7 +92,7 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
|
|||||||
entry.deleted = false;
|
entry.deleted = false;
|
||||||
entry.disabled = user.status === 2;
|
entry.disabled = user.status === 2;
|
||||||
entry.email = user.email;
|
entry.email = user.email;
|
||||||
if (!this.validEmailAddress(entry.email) && user.username != null && user.username !== '') {
|
if (!this.validEmailAddress(entry.email) && user.username != null && user.username !== "") {
|
||||||
if (this.validEmailAddress(user.username)) {
|
if (this.validEmailAddress(user.username)) {
|
||||||
entry.email = user.username;
|
entry.email = user.username;
|
||||||
} else if (this.syncConfig.useEmailPrefixSuffix && this.syncConfig.emailSuffix != null) {
|
} else if (this.syncConfig.useEmailPrefixSuffix && this.syncConfig.emailSuffix != null) {
|
||||||
@@ -104,12 +108,15 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getGroups(force: boolean, setFilter: [boolean, Set<string>]): Promise<GroupEntry[]> {
|
private async getGroups(
|
||||||
|
force: boolean,
|
||||||
|
setFilter: [boolean, Set<string>]
|
||||||
|
): Promise<GroupEntry[]> {
|
||||||
const entries: GroupEntry[] = [];
|
const entries: GroupEntry[] = [];
|
||||||
const query = this.createDirectoryQuery(this.syncConfig.groupFilter);
|
const query = this.createDirectoryQuery(this.syncConfig.groupFilter);
|
||||||
this.logService.info('Querying groups.');
|
this.logService.info("Querying groups.");
|
||||||
const roles = await this.apiGetMany('roles' + (query != null ? '?' + query : ''));
|
const roles = await this.apiGetMany("roles" + (query != null ? "?" + query : ""));
|
||||||
roles.forEach(role => {
|
roles.forEach((role) => {
|
||||||
const entry = this.buildGroup(role);
|
const entry = this.buildGroup(role);
|
||||||
if (entry != null && !this.filterOutResult(setFilter, entry.name)) {
|
if (entry != null && !this.filterOutResult(setFilter, entry.name)) {
|
||||||
entries.push(entry);
|
entries.push(entry);
|
||||||
@@ -125,7 +132,7 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
|
|||||||
entry.name = group.name;
|
entry.name = group.name;
|
||||||
|
|
||||||
if (this.allUsers != null) {
|
if (this.allUsers != null) {
|
||||||
this.allUsers.forEach(user => {
|
this.allUsers.forEach((user) => {
|
||||||
if (user.role_id != null && user.role_id.indexOf(entry.referenceId) > -1) {
|
if (user.role_id != null && user.role_id.indexOf(entry.referenceId) > -1) {
|
||||||
entry.userMemberExternalIds.add(user.id);
|
entry.userMemberExternalIds.add(user.id);
|
||||||
}
|
}
|
||||||
@@ -136,17 +143,21 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getAccessToken() {
|
private async getAccessToken() {
|
||||||
const response = await fetch(`https://api.${this.dirConfig.region}.onelogin.com/auth/oauth2/v2/token`, {
|
const response = await fetch(
|
||||||
method: 'POST',
|
`https://api.${this.dirConfig.region}.onelogin.com/auth/oauth2/v2/token`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
'Authorization': 'Basic ' + btoa(this.dirConfig.clientId + ':' + this.dirConfig.clientSecret),
|
Authorization:
|
||||||
'Content-Type': 'application/json; charset=utf-8',
|
"Basic " + btoa(this.dirConfig.clientId + ":" + this.dirConfig.clientSecret),
|
||||||
'Accept': 'application/json',
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
Accept: "application/json",
|
||||||
}),
|
}),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
grant_type: 'client_credentials',
|
grant_type: "client_credentials",
|
||||||
}),
|
}),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
const responseJson = await response.json();
|
const responseJson = await response.json();
|
||||||
if (responseJson.access_token != null) {
|
if (responseJson.access_token != null) {
|
||||||
@@ -158,10 +169,10 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
|
|||||||
|
|
||||||
private async apiGetCall(url: string): Promise<any> {
|
private async apiGetCall(url: string): Promise<any> {
|
||||||
const req: RequestInit = {
|
const req: RequestInit = {
|
||||||
method: 'GET',
|
method: "GET",
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
Authorization: 'bearer:' + this.accessToken,
|
Authorization: "bearer:" + this.accessToken,
|
||||||
Accept: 'application/json',
|
Accept: "application/json",
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
const response = await fetch(new Request(url, req));
|
const response = await fetch(new Request(url, req));
|
||||||
@@ -173,14 +184,16 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async apiGetMany(endpoint: string, currentData: any[] = []): Promise<any[]> {
|
private async apiGetMany(endpoint: string, currentData: any[] = []): Promise<any[]> {
|
||||||
const url = endpoint.indexOf('https://') === 0 ? endpoint :
|
const url =
|
||||||
`https://api.${this.dirConfig.region}.onelogin.com/api/1/${endpoint}`;
|
endpoint.indexOf("https://") === 0
|
||||||
|
? endpoint
|
||||||
|
: `https://api.${this.dirConfig.region}.onelogin.com/api/1/${endpoint}`;
|
||||||
const response = await this.apiGetCall(url);
|
const response = await this.apiGetCall(url);
|
||||||
if (response == null || response.status == null || response.data == null) {
|
if (response == null || response.status == null || response.data == null) {
|
||||||
return currentData;
|
return currentData;
|
||||||
}
|
}
|
||||||
if (response.status.code !== 200) {
|
if (response.status.code !== 200) {
|
||||||
throw new Error('API call failed.');
|
throw new Error("API call failed.");
|
||||||
}
|
}
|
||||||
currentData = currentData.concat(response.data);
|
currentData = currentData.concat(response.data);
|
||||||
if (response.pagination == null || response.pagination.next_link == null) {
|
if (response.pagination == null || response.pagination.next_link == null) {
|
||||||
@@ -190,6 +203,6 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
|
|||||||
}
|
}
|
||||||
|
|
||||||
private validEmailAddress(email: string) {
|
private validEmailAddress(email: string) {
|
||||||
return email != null && email !== '' && ValidEmailRegex.test(email);
|
return email != null && email !== "" && ValidEmailRegex.test(email);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
577
src/services/state.service.ts
Normal file
577
src/services/state.service.ts
Normal file
@@ -0,0 +1,577 @@
|
|||||||
|
import { Inject, Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { SECURE_STORAGE, STATE_FACTORY } from "jslib-common/abstractions/injectionTokens";
|
||||||
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
|
import { StateMigrationService } from "jslib-common/abstractions/stateMigration.service";
|
||||||
|
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||||
|
import { StateFactory } from "jslib-common/factories/stateFactory";
|
||||||
|
import { GlobalState } from "jslib-common/models/domain/globalState";
|
||||||
|
import { StorageOptions } from "jslib-common/models/domain/storageOptions";
|
||||||
|
import { StateService as BaseStateService } from "jslib-common/services/state.service";
|
||||||
|
|
||||||
|
import { StateService as StateServiceAbstraction } from "src/abstractions/state.service";
|
||||||
|
import { USE_SECURE_STORAGE_FOR_SECRETS } from "src/app/services/injectionTokens";
|
||||||
|
import { DirectoryType } from "src/enums/directoryType";
|
||||||
|
import { IConfiguration } from "src/models/IConfiguration";
|
||||||
|
import { Account } from "src/models/account";
|
||||||
|
import { AzureConfiguration } from "src/models/azureConfiguration";
|
||||||
|
import { GSuiteConfiguration } from "src/models/gsuiteConfiguration";
|
||||||
|
import { LdapConfiguration } from "src/models/ldapConfiguration";
|
||||||
|
import { OktaConfiguration } from "src/models/oktaConfiguration";
|
||||||
|
import { OneLoginConfiguration } from "src/models/oneLoginConfiguration";
|
||||||
|
import { SyncConfiguration } from "src/models/syncConfiguration";
|
||||||
|
|
||||||
|
|
||||||
|
const SecureStorageKeys = {
|
||||||
|
ldap: "ldapPassword",
|
||||||
|
gsuite: "gsuitePrivateKey",
|
||||||
|
azure: "azureKey",
|
||||||
|
okta: "oktaToken",
|
||||||
|
oneLogin: "oneLoginClientSecret",
|
||||||
|
userDelta: "userDeltaToken",
|
||||||
|
groupDelta: "groupDeltaToken",
|
||||||
|
lastUserSync: "lastUserSync",
|
||||||
|
lastGroupSync: "lastGroupSync",
|
||||||
|
lastSyncHash: "lastSyncHash",
|
||||||
|
};
|
||||||
|
|
||||||
|
const keys = {
|
||||||
|
tempAccountSettings: "tempAccountSettings",
|
||||||
|
tempDirectoryConfigs: "tempDirectoryConfigs",
|
||||||
|
tempDirectorySettings: "tempDirectorySettings",
|
||||||
|
};
|
||||||
|
|
||||||
|
const StoredSecurely = "[STORED SECURELY]";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class StateService
|
||||||
|
extends BaseStateService<GlobalState, Account>
|
||||||
|
implements StateServiceAbstraction
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
protected storageService: StorageService,
|
||||||
|
@Inject(SECURE_STORAGE) protected secureStorageService: StorageService,
|
||||||
|
protected logService: LogService,
|
||||||
|
protected stateMigrationService: StateMigrationService,
|
||||||
|
@Inject(USE_SECURE_STORAGE_FOR_SECRETS) private useSecureStorageForSecrets = true,
|
||||||
|
@Inject(STATE_FACTORY) protected stateFactory: StateFactory<GlobalState, Account>
|
||||||
|
) {
|
||||||
|
super(storageService, secureStorageService, logService, stateMigrationService, stateFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDirectory<T extends IConfiguration>(type: DirectoryType): Promise<T> {
|
||||||
|
const config = await this.getConfiguration(type);
|
||||||
|
if (config == null) {
|
||||||
|
return config as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.useSecureStorageForSecrets) {
|
||||||
|
switch (type) {
|
||||||
|
case DirectoryType.Ldap:
|
||||||
|
(config as any).password = await this.getLdapKey();
|
||||||
|
break;
|
||||||
|
case DirectoryType.AzureActiveDirectory:
|
||||||
|
(config as any).key = await this.getAzureKey();
|
||||||
|
break;
|
||||||
|
case DirectoryType.Okta:
|
||||||
|
(config as any).token = await this.getOktaKey();
|
||||||
|
break;
|
||||||
|
case DirectoryType.GSuite:
|
||||||
|
(config as any).privateKey = await this.getGsuiteKey();
|
||||||
|
break;
|
||||||
|
case DirectoryType.OneLogin:
|
||||||
|
(config as any).clientSecret = await this.getOneLoginKey();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setDirectory(
|
||||||
|
type: DirectoryType,
|
||||||
|
config:
|
||||||
|
| LdapConfiguration
|
||||||
|
| GSuiteConfiguration
|
||||||
|
| AzureConfiguration
|
||||||
|
| OktaConfiguration
|
||||||
|
| OneLoginConfiguration
|
||||||
|
): Promise<any> {
|
||||||
|
const savedConfig: any = Object.assign({}, config);
|
||||||
|
if (this.useSecureStorageForSecrets) {
|
||||||
|
switch (type) {
|
||||||
|
case DirectoryType.Ldap:
|
||||||
|
await this.setLdapKey(savedConfig.password);
|
||||||
|
savedConfig.password = StoredSecurely;
|
||||||
|
await this.setLdapConfiguration(savedConfig);
|
||||||
|
break;
|
||||||
|
case DirectoryType.AzureActiveDirectory:
|
||||||
|
await this.setAzureKey(savedConfig.key);
|
||||||
|
savedConfig.key = StoredSecurely;
|
||||||
|
await this.setAzureConfiguration(savedConfig);
|
||||||
|
break;
|
||||||
|
case DirectoryType.Okta:
|
||||||
|
await this.setOktaKey(savedConfig.token);
|
||||||
|
savedConfig.token = StoredSecurely;
|
||||||
|
await this.setOktaConfiguration(savedConfig);
|
||||||
|
break;
|
||||||
|
case DirectoryType.GSuite:
|
||||||
|
if (savedConfig.privateKey == null) {
|
||||||
|
await this.setGsuiteKey(null);
|
||||||
|
} else {
|
||||||
|
(config as GSuiteConfiguration).privateKey = savedConfig.privateKey =
|
||||||
|
savedConfig.privateKey.replace(/\\n/g, "\n");
|
||||||
|
await this.setGsuiteKey(savedConfig.privateKey);
|
||||||
|
savedConfig.privateKey = StoredSecurely;
|
||||||
|
}
|
||||||
|
await this.setGsuiteConfiguration(savedConfig);
|
||||||
|
break;
|
||||||
|
case DirectoryType.OneLogin:
|
||||||
|
await this.setOneLoginKey(savedConfig.clientSecret);
|
||||||
|
savedConfig.clientSecret = StoredSecurely;
|
||||||
|
await this.setOneLoginConfiguration(savedConfig);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLdapKey(options?: StorageOptions): Promise<string> {
|
||||||
|
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
|
||||||
|
if (options?.userId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await this.secureStorageService.get<string>(
|
||||||
|
`${options.userId}_${SecureStorageKeys.ldap}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setLdapKey(value: string, options?: StorageOptions): Promise<void> {
|
||||||
|
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
|
||||||
|
if (options?.userId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.secureStorageService.save(
|
||||||
|
`${options.userId}_${SecureStorageKeys.ldap}`,
|
||||||
|
value,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getGsuiteKey(options?: StorageOptions): Promise<string> {
|
||||||
|
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
|
||||||
|
if (options?.userId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await this.secureStorageService.get<string>(
|
||||||
|
`${options.userId}_${SecureStorageKeys.gsuite}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setGsuiteKey(value: string, options?: StorageOptions): Promise<void> {
|
||||||
|
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
|
||||||
|
if (options?.userId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.secureStorageService.save(
|
||||||
|
`${options.userId}_${SecureStorageKeys.gsuite}`,
|
||||||
|
value,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAzureKey(options?: StorageOptions): Promise<string> {
|
||||||
|
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
|
||||||
|
if (options?.userId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await this.secureStorageService.get<string>(
|
||||||
|
`${options.userId}_${SecureStorageKeys.azure}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setAzureKey(value: string, options?: StorageOptions): Promise<void> {
|
||||||
|
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
|
||||||
|
if (options?.userId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.secureStorageService.save(
|
||||||
|
`${options.userId}_${SecureStorageKeys.azure}`,
|
||||||
|
value,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOktaKey(options?: StorageOptions): Promise<string> {
|
||||||
|
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
|
||||||
|
if (options?.userId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await this.secureStorageService.get<string>(
|
||||||
|
`${options.userId}_${SecureStorageKeys.okta}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setOktaKey(value: string, options?: StorageOptions): Promise<void> {
|
||||||
|
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
|
||||||
|
if (options?.userId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.secureStorageService.save(
|
||||||
|
`${options.userId}_${SecureStorageKeys.okta}`,
|
||||||
|
value,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOneLoginKey(options?: StorageOptions): Promise<string> {
|
||||||
|
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
|
||||||
|
if (options?.userId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await this.secureStorageService.get<string>(
|
||||||
|
`${options.userId}_${SecureStorageKeys.oneLogin}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setOneLoginKey(value: string, options?: StorageOptions): Promise<void> {
|
||||||
|
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
|
||||||
|
if (options?.userId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.secureStorageService.save(
|
||||||
|
`${options.userId}_${SecureStorageKeys.oneLogin}`,
|
||||||
|
value,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getConfiguration(type: DirectoryType): Promise<IConfiguration> {
|
||||||
|
switch (type) {
|
||||||
|
case DirectoryType.Ldap:
|
||||||
|
return await this.getLdapConfiguration();
|
||||||
|
case DirectoryType.GSuite:
|
||||||
|
return await this.getGsuiteConfiguration();
|
||||||
|
case DirectoryType.AzureActiveDirectory:
|
||||||
|
return await this.getAzureConfiguration();
|
||||||
|
case DirectoryType.Okta:
|
||||||
|
return await this.getOktaConfiguration();
|
||||||
|
case DirectoryType.OneLogin:
|
||||||
|
return await this.getOneLoginConfiguration();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLdapConfiguration(options?: StorageOptions): Promise<LdapConfiguration> {
|
||||||
|
return (
|
||||||
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
|
)?.directoryConfigurations?.ldap;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setLdapConfiguration(value: LdapConfiguration, options?: StorageOptions): Promise<void> {
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
account.directoryConfigurations.ldap = value;
|
||||||
|
await this.saveAccount(
|
||||||
|
account,
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getGsuiteConfiguration(options?: StorageOptions): Promise<GSuiteConfiguration> {
|
||||||
|
return (
|
||||||
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
|
)?.directoryConfigurations?.gsuite;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setGsuiteConfiguration(
|
||||||
|
value: GSuiteConfiguration,
|
||||||
|
options?: StorageOptions
|
||||||
|
): Promise<void> {
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
account.directoryConfigurations.gsuite = value;
|
||||||
|
await this.saveAccount(
|
||||||
|
account,
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAzureConfiguration(options?: StorageOptions): Promise<AzureConfiguration> {
|
||||||
|
return (
|
||||||
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
|
)?.directoryConfigurations?.azure;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setAzureConfiguration(value: AzureConfiguration, options?: StorageOptions): Promise<void> {
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
account.directoryConfigurations.azure = value;
|
||||||
|
await this.saveAccount(
|
||||||
|
account,
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOktaConfiguration(options?: StorageOptions): Promise<OktaConfiguration> {
|
||||||
|
return (
|
||||||
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
|
)?.directoryConfigurations?.okta;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setOktaConfiguration(value: OktaConfiguration, options?: StorageOptions): Promise<void> {
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
account.directoryConfigurations.okta = value;
|
||||||
|
await this.saveAccount(
|
||||||
|
account,
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOneLoginConfiguration(options?: StorageOptions): Promise<OneLoginConfiguration> {
|
||||||
|
return (
|
||||||
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
|
)?.directoryConfigurations?.oneLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setOneLoginConfiguration(
|
||||||
|
value: OneLoginConfiguration,
|
||||||
|
options?: StorageOptions
|
||||||
|
): Promise<void> {
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
account.directoryConfigurations.oneLogin = value;
|
||||||
|
await this.saveAccount(
|
||||||
|
account,
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOrganizationId(options?: StorageOptions): Promise<string> {
|
||||||
|
return (
|
||||||
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
|
)?.directorySettings?.organizationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setOrganizationId(value: string, options?: StorageOptions): Promise<void> {
|
||||||
|
const currentId = await this.getOrganizationId();
|
||||||
|
if (currentId !== value) {
|
||||||
|
await this.clearSyncSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
account.directorySettings.organizationId = value;
|
||||||
|
await this.saveAccount(
|
||||||
|
account,
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSync(options?: StorageOptions): Promise<SyncConfiguration> {
|
||||||
|
return (
|
||||||
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
|
)?.directorySettings?.sync;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setSync(value: SyncConfiguration, options?: StorageOptions): Promise<void> {
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
account.directorySettings.sync = value;
|
||||||
|
await this.saveAccount(
|
||||||
|
account,
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDirectoryType(options?: StorageOptions): Promise<DirectoryType> {
|
||||||
|
return (
|
||||||
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
|
)?.directorySettings?.directoryType;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setDirectoryType(value: DirectoryType, options?: StorageOptions): Promise<void> {
|
||||||
|
const currentType = await this.getDirectoryType();
|
||||||
|
if (value !== currentType) {
|
||||||
|
await this.clearSyncSettings();
|
||||||
|
}
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
account.directorySettings.directoryType = value;
|
||||||
|
await this.saveAccount(
|
||||||
|
account,
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLastUserSync(options?: StorageOptions): Promise<Date> {
|
||||||
|
const userSyncDate = (
|
||||||
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
|
)?.directorySettings?.lastUserSync;
|
||||||
|
return userSyncDate ? new Date(userSyncDate) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setLastUserSync(value: Date, options?: StorageOptions): Promise<void> {
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
account.directorySettings.lastUserSync = value;
|
||||||
|
await this.saveAccount(
|
||||||
|
account,
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLastGroupSync(options?: StorageOptions): Promise<Date> {
|
||||||
|
const groupSyncDate = (
|
||||||
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
|
)?.directorySettings?.lastGroupSync;
|
||||||
|
return groupSyncDate ? new Date(groupSyncDate) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setLastGroupSync(value: Date, options?: StorageOptions): Promise<void> {
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
account.directorySettings.lastGroupSync = value;
|
||||||
|
await this.saveAccount(
|
||||||
|
account,
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLastSyncHash(options?: StorageOptions): Promise<string> {
|
||||||
|
return (
|
||||||
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
|
)?.directorySettings?.lastSyncHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setLastSyncHash(value: string, options?: StorageOptions): Promise<void> {
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
account.directorySettings.lastSyncHash = value;
|
||||||
|
await this.saveAccount(
|
||||||
|
account,
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSyncingDir(options?: StorageOptions): Promise<boolean> {
|
||||||
|
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))
|
||||||
|
?.directorySettings?.syncingDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setSyncingDir(value: boolean, options?: StorageOptions): Promise<void> {
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, this.defaultInMemoryOptions)
|
||||||
|
);
|
||||||
|
account.directorySettings.syncingDir = value;
|
||||||
|
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUserDelta(options?: StorageOptions): Promise<string> {
|
||||||
|
return (
|
||||||
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
|
)?.directorySettings?.userDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setUserDelta(value: string, options?: StorageOptions): Promise<void> {
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
account.directorySettings.userDelta = value;
|
||||||
|
await this.saveAccount(
|
||||||
|
account,
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getGroupDelta(options?: StorageOptions): Promise<string> {
|
||||||
|
return (
|
||||||
|
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||||
|
)?.directorySettings?.groupDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setGroupDelta(value: string, options?: StorageOptions): Promise<void> {
|
||||||
|
const account = await this.getAccount(
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
account.directorySettings.groupDelta = value;
|
||||||
|
await this.saveAccount(
|
||||||
|
account,
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearSyncSettings(hashToo = false) {
|
||||||
|
await this.setUserDelta(null);
|
||||||
|
await this.setGroupDelta(null);
|
||||||
|
await this.setLastGroupSync(null);
|
||||||
|
await this.setLastUserSync(null);
|
||||||
|
if (hashToo) {
|
||||||
|
await this.setLastSyncHash(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async scaffoldNewAccountStorage(account: Account): Promise<void> {
|
||||||
|
await this.scaffoldNewAccountDiskStorage(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async scaffoldNewAccountDiskStorage(account: Account): Promise<void> {
|
||||||
|
const storageOptions = this.reconcileOptions(
|
||||||
|
{ userId: account.profile.userId },
|
||||||
|
await this.defaultOnDiskLocalOptions()
|
||||||
|
);
|
||||||
|
|
||||||
|
const storedAccount = await this.getAccount(storageOptions);
|
||||||
|
if (storedAccount != null) {
|
||||||
|
account.settings = storedAccount.settings;
|
||||||
|
account.directorySettings = storedAccount.directorySettings;
|
||||||
|
account.directoryConfigurations = storedAccount.directoryConfigurations;
|
||||||
|
} else if (await this.hasTemporaryStorage()) {
|
||||||
|
// If migrating to state V2 with an no actively authed account we store temporary data to be copied on auth - this will only be run once.
|
||||||
|
account.settings = await this.storageService.get<any>(keys.tempAccountSettings);
|
||||||
|
account.directorySettings = await this.storageService.get<any>(keys.tempDirectorySettings);
|
||||||
|
account.directoryConfigurations = await this.storageService.get<any>(
|
||||||
|
keys.tempDirectoryConfigs
|
||||||
|
);
|
||||||
|
await this.storageService.remove(keys.tempAccountSettings);
|
||||||
|
await this.storageService.remove(keys.tempDirectorySettings);
|
||||||
|
await this.storageService.remove(keys.tempDirectoryConfigs);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.saveAccount(account, storageOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async pushAccounts(): Promise<void> {
|
||||||
|
if (this.state?.accounts == null || Object.keys(this.state.accounts).length < 1) {
|
||||||
|
this.accounts.next(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.accounts.next(this.state.accounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async hasTemporaryStorage(): Promise<boolean> {
|
||||||
|
return (
|
||||||
|
(await this.storageService.has(keys.tempAccountSettings)) ||
|
||||||
|
(await this.storageService.has(keys.tempDirectorySettings)) ||
|
||||||
|
(await this.storageService.has(keys.tempDirectoryConfigs))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected resetAccount(account: Account) {
|
||||||
|
const persistentAccountInformation = {
|
||||||
|
settings: account.settings,
|
||||||
|
directorySettings: account.directorySettings,
|
||||||
|
directoryConfigurations: account.directoryConfigurations,
|
||||||
|
};
|
||||||
|
return Object.assign(this.createAccount(), persistentAccountInformation);
|
||||||
|
}
|
||||||
|
}
|
||||||
200
src/services/stateMigration.service.ts
Normal file
200
src/services/stateMigration.service.ts
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { StateVersion } from "jslib-common/enums/stateVersion";
|
||||||
|
import { StateMigrationService as BaseStateMigrationService } from "jslib-common/services/stateMigration.service";
|
||||||
|
|
||||||
|
import { DirectoryType } from "src/enums/directoryType";
|
||||||
|
import { Account, DirectoryConfigurations, DirectorySettings } from "src/models/account";
|
||||||
|
import { AzureConfiguration } from "src/models/azureConfiguration";
|
||||||
|
import { GSuiteConfiguration } from "src/models/gsuiteConfiguration";
|
||||||
|
import { LdapConfiguration } from "src/models/ldapConfiguration";
|
||||||
|
import { OktaConfiguration } from "src/models/oktaConfiguration";
|
||||||
|
import { OneLoginConfiguration } from "src/models/oneLoginConfiguration";
|
||||||
|
import { SyncConfiguration } from "src/models/syncConfiguration";
|
||||||
|
|
||||||
|
const SecureStorageKeys: { [key: string]: any } = {
|
||||||
|
ldap: "ldapPassword",
|
||||||
|
gsuite: "gsuitePrivateKey",
|
||||||
|
azure: "azureKey",
|
||||||
|
okta: "oktaToken",
|
||||||
|
oneLogin: "oneLoginClientSecret",
|
||||||
|
directoryConfigPrefix: "directoryConfig_",
|
||||||
|
sync: "syncConfig",
|
||||||
|
directoryType: "directoryType",
|
||||||
|
organizationId: "organizationId",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Keys: { [key: string]: any } = {
|
||||||
|
entityId: "entityId",
|
||||||
|
directoryType: "directoryType",
|
||||||
|
organizationId: "organizationId",
|
||||||
|
lastUserSync: "lastUserSync",
|
||||||
|
lastGroupSync: "lastGroupSync",
|
||||||
|
lastSyncHash: "lastSyncHash",
|
||||||
|
syncingDir: "syncingDir",
|
||||||
|
syncConfig: "syncConfig",
|
||||||
|
userDelta: "userDeltaToken",
|
||||||
|
groupDelta: "groupDeltaToken",
|
||||||
|
tempDirectoryConfigs: "tempDirectoryConfigs",
|
||||||
|
tempDirectorySettings: "tempDirectorySettings",
|
||||||
|
};
|
||||||
|
|
||||||
|
const StateKeys = {
|
||||||
|
global: "global",
|
||||||
|
authenticatedAccounts: "authenticatedAccounts",
|
||||||
|
};
|
||||||
|
|
||||||
|
const ClientKeys: { [key: string]: any } = {
|
||||||
|
clientIdOld: "clientId",
|
||||||
|
clientId: "apikey_clientId",
|
||||||
|
clientSecretOld: "clientSecret",
|
||||||
|
clientSecret: "apikey_clientSecret",
|
||||||
|
};
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class StateMigrationService extends BaseStateMigrationService {
|
||||||
|
async migrate(): Promise<void> {
|
||||||
|
let currentStateVersion = await this.getCurrentStateVersion();
|
||||||
|
while (currentStateVersion < StateVersion.Latest) {
|
||||||
|
switch (currentStateVersion) {
|
||||||
|
case StateVersion.One:
|
||||||
|
await this.migrateClientKeys();
|
||||||
|
await this.migrateStateFrom1To2();
|
||||||
|
break;
|
||||||
|
case StateVersion.Two:
|
||||||
|
await this.migrateStateFrom2To3();
|
||||||
|
}
|
||||||
|
currentStateVersion += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove this migration when we are confident existing api keys are all migrated. Probably 1-2 releases.
|
||||||
|
protected async migrateClientKeys() {
|
||||||
|
const oldClientId = await this.storageService.get<string>(ClientKeys.clientIdOld);
|
||||||
|
const oldClientSecret = await this.storageService.get<string>(ClientKeys.clientSecretOld);
|
||||||
|
|
||||||
|
if (oldClientId != null) {
|
||||||
|
await this.storageService.save(ClientKeys.clientId, oldClientId);
|
||||||
|
await this.storageService.remove(ClientKeys.clientIdOld);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldClientSecret != null) {
|
||||||
|
await this.storageService.save(ClientKeys.clientSecret, oldClientSecret);
|
||||||
|
await this.storageService.remove(ClientKeys.clientSecretOld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async migrateStateFrom1To2(useSecureStorageForSecrets = true): Promise<void> {
|
||||||
|
// Grabbing a couple of key settings before they get cleared by the base migration
|
||||||
|
const userId = await this.get<string>(Keys.entityId);
|
||||||
|
const clientId = await this.get<string>(ClientKeys.clientId);
|
||||||
|
const clientSecret = await this.get<string>(ClientKeys.clientSecret);
|
||||||
|
|
||||||
|
await super.migrateStateFrom1To2();
|
||||||
|
|
||||||
|
// Setup reusable method for clearing keys since we will want to do that regardless of if there is an active authenticated session
|
||||||
|
const clearDirectoryConnectorV1Keys = async () => {
|
||||||
|
for (const key in Keys) {
|
||||||
|
if (key == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const directoryType in DirectoryType) {
|
||||||
|
if (directoryType == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await this.set(SecureStorageKeys.directoryConfigPrefix + directoryType, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initilize typed objects from key/value pairs in storage to either be saved temporarily until an account is authed or applied to the active account
|
||||||
|
const getDirectoryConfig = async <T>(type: DirectoryType) =>
|
||||||
|
await this.get<T>(SecureStorageKeys.directoryConfigPrefix + type);
|
||||||
|
const directoryConfigs: DirectoryConfigurations = {
|
||||||
|
ldap: await getDirectoryConfig<LdapConfiguration>(DirectoryType.Ldap),
|
||||||
|
gsuite: await getDirectoryConfig<GSuiteConfiguration>(DirectoryType.GSuite),
|
||||||
|
azure: await getDirectoryConfig<AzureConfiguration>(DirectoryType.AzureActiveDirectory),
|
||||||
|
okta: await getDirectoryConfig<OktaConfiguration>(DirectoryType.Okta),
|
||||||
|
oneLogin: await getDirectoryConfig<OneLoginConfiguration>(DirectoryType.OneLogin),
|
||||||
|
};
|
||||||
|
|
||||||
|
const directorySettings: DirectorySettings = {
|
||||||
|
directoryType: await this.get<DirectoryType>(Keys.directoryType),
|
||||||
|
organizationId: await this.get<string>(Keys.organizationId),
|
||||||
|
lastUserSync: await this.get<Date>(Keys.lastUserSync),
|
||||||
|
lastGroupSync: await this.get<Date>(Keys.lastGroupSync),
|
||||||
|
lastSyncHash: await this.get<string>(Keys.lastSyncHash),
|
||||||
|
syncingDir: await this.get<boolean>(Keys.syncingDir),
|
||||||
|
sync: await this.get<SyncConfiguration>(Keys.syncConfig),
|
||||||
|
userDelta: await this.get<string>(Keys.userDelta),
|
||||||
|
groupDelta: await this.get<string>(Keys.groupDelta),
|
||||||
|
};
|
||||||
|
|
||||||
|
// (userId == null) = no authed account, stored data temporarily to be applied and cleared on next auth
|
||||||
|
// (userId != null) = authed account known, applied stored data to it and do not save temp data
|
||||||
|
if (userId == null) {
|
||||||
|
await this.set(Keys.tempDirectoryConfigs, directoryConfigs);
|
||||||
|
await this.set(Keys.tempDirectorySettings, directorySettings);
|
||||||
|
await clearDirectoryConnectorV1Keys();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = await this.get<Account>(userId);
|
||||||
|
account.directoryConfigurations = directoryConfigs;
|
||||||
|
account.directorySettings = directorySettings;
|
||||||
|
account.profile = {
|
||||||
|
userId: userId,
|
||||||
|
entityId: userId,
|
||||||
|
apiKeyClientId: clientId,
|
||||||
|
};
|
||||||
|
account.clientKeys = {
|
||||||
|
clientId: clientId,
|
||||||
|
clientSecret: clientSecret,
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.set(userId, account);
|
||||||
|
await clearDirectoryConnectorV1Keys();
|
||||||
|
|
||||||
|
if (useSecureStorageForSecrets) {
|
||||||
|
for (const key in SecureStorageKeys) {
|
||||||
|
if (await this.secureStorageService.has(SecureStorageKeys[key])) {
|
||||||
|
await this.secureStorageService.save(
|
||||||
|
`${userId}_${SecureStorageKeys[key]}`,
|
||||||
|
await this.secureStorageService.get(SecureStorageKeys[key])
|
||||||
|
);
|
||||||
|
await this.secureStorageService.remove(SecureStorageKeys[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected async migrateStateFrom2To3(useSecureStorageForSecrets = true): Promise<void> {
|
||||||
|
if (useSecureStorageForSecrets) {
|
||||||
|
const authenticatedUserIds = await this.get<string[]>(StateKeys.authenticatedAccounts);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
authenticatedUserIds.map(async (userId) => {
|
||||||
|
const account = await this.get<Account>(userId);
|
||||||
|
|
||||||
|
// Fix for userDelta and groupDelta being put into secure storage when they should not have
|
||||||
|
if (await this.secureStorageService.has(`${userId}_${Keys.userDelta}`)) {
|
||||||
|
account.directorySettings.userDelta = await this.secureStorageService.get(
|
||||||
|
`${userId}_${Keys.userDelta}`
|
||||||
|
);
|
||||||
|
await this.secureStorageService.remove(`${userId}_${Keys.userDelta}`);
|
||||||
|
}
|
||||||
|
if (await this.secureStorageService.has(`${userId}_${Keys.groupDelta}`)) {
|
||||||
|
account.directorySettings.groupDelta = await this.secureStorageService.get(
|
||||||
|
`${userId}_${Keys.groupDelta}`
|
||||||
|
);
|
||||||
|
await this.secureStorageService.remove(`${userId}_${Keys.groupDelta}`);
|
||||||
|
}
|
||||||
|
await this.set(userId, account);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const globals = await this.getGlobals();
|
||||||
|
globals.stateVersion = StateVersion.Three;
|
||||||
|
await this.set(StateKeys.global, globals);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,55 +1,63 @@
|
|||||||
import { DirectoryType } from '../enums/directoryType';
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
import { GroupEntry } from '../models/groupEntry';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { SyncConfiguration } from '../models/syncConfiguration';
|
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
|
||||||
import { UserEntry } from '../models/userEntry';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
|
import { OrganizationImportRequest } from "jslib-common/models/request/organizationImportRequest";
|
||||||
|
|
||||||
import { OrganizationImportRequest } from 'jslib-common/models/request/organizationImportRequest';
|
import { StateService } from "../abstractions/state.service";
|
||||||
|
import { DirectoryType } from "../enums/directoryType";
|
||||||
|
import { GroupEntry } from "../models/groupEntry";
|
||||||
|
import { SyncConfiguration } from "../models/syncConfiguration";
|
||||||
|
import { UserEntry } from "../models/userEntry";
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { AzureDirectoryService } from "./azure-directory.service";
|
||||||
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
|
import { IDirectoryService } from "./directory.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { GSuiteDirectoryService } from "./gsuite-directory.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { LdapDirectoryService } from "./ldap-directory.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { OktaDirectoryService } from "./okta-directory.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { OneLoginDirectoryService } from "./onelogin-directory.service";
|
||||||
|
|
||||||
import { Utils } from 'jslib-common/misc/utils';
|
|
||||||
|
|
||||||
import { AzureDirectoryService } from './azure-directory.service';
|
|
||||||
import { ConfigurationService } from './configuration.service';
|
|
||||||
import { IDirectoryService } from './directory.service';
|
|
||||||
import { GSuiteDirectoryService } from './gsuite-directory.service';
|
|
||||||
import { LdapDirectoryService } from './ldap-directory.service';
|
|
||||||
import { OktaDirectoryService } from './okta-directory.service';
|
|
||||||
import { OneLoginDirectoryService } from './onelogin-directory.service';
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
export class SyncService {
|
export class SyncService {
|
||||||
private dirType: DirectoryType;
|
private dirType: DirectoryType;
|
||||||
|
|
||||||
constructor(private configurationService: ConfigurationService, private logService: LogService,
|
constructor(
|
||||||
private cryptoFunctionService: CryptoFunctionService, private apiService: ApiService,
|
private logService: LogService,
|
||||||
private messagingService: MessagingService, private i18nService: I18nService,
|
private cryptoFunctionService: CryptoFunctionService,
|
||||||
private environmentService: EnvironmentService) { }
|
private apiService: ApiService,
|
||||||
|
private messagingService: MessagingService,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private environmentService: EnvironmentService,
|
||||||
|
private stateService: StateService
|
||||||
|
) {}
|
||||||
|
|
||||||
async sync(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
async sync(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
||||||
this.dirType = await this.configurationService.getDirectoryType();
|
this.dirType = await this.stateService.getDirectoryType();
|
||||||
if (this.dirType == null) {
|
if (this.dirType == null) {
|
||||||
throw new Error('No directory configured.');
|
throw new Error("No directory configured.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const directoryService = this.getDirectoryService();
|
const directoryService = this.getDirectoryService();
|
||||||
if (directoryService == null) {
|
if (directoryService == null) {
|
||||||
throw new Error('Cannot load directory service.');
|
throw new Error("Cannot load directory service.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncConfig = await this.configurationService.getSync();
|
const syncConfig = await this.stateService.getSync();
|
||||||
const startingGroupDelta = await this.configurationService.getGroupDeltaToken();
|
const startingGroupDelta = await this.stateService.getGroupDelta();
|
||||||
const startingUserDelta = await this.configurationService.getUserDeltaToken();
|
const startingUserDelta = await this.stateService.getUserDelta();
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
this.messagingService.send('dirSyncStarted');
|
this.messagingService.send("dirSyncStarted");
|
||||||
try {
|
try {
|
||||||
const entries = await directoryService.getEntries(force || syncConfig.overwriteExisting, test);
|
const entries = await directoryService.getEntries(
|
||||||
|
force || syncConfig.overwriteExisting,
|
||||||
|
test
|
||||||
|
);
|
||||||
let groups = entries[0];
|
let groups = entries[0];
|
||||||
let users = this.filterUnsupportedUsers(entries[1]);
|
let users = this.filterUnsupportedUsers(entries[1]);
|
||||||
|
|
||||||
@@ -59,89 +67,124 @@ export class SyncService {
|
|||||||
|
|
||||||
users = this.removeDuplicateUsers(users);
|
users = this.removeDuplicateUsers(users);
|
||||||
|
|
||||||
if (test || (!syncConfig.overwriteExisting &&
|
if (
|
||||||
(groups == null || groups.length === 0) && (users == null || users.length === 0))) {
|
test ||
|
||||||
|
(!syncConfig.overwriteExisting &&
|
||||||
|
(groups == null || groups.length === 0) &&
|
||||||
|
(users == null || users.length === 0))
|
||||||
|
) {
|
||||||
if (!test) {
|
if (!test) {
|
||||||
await this.saveSyncTimes(syncConfig, now);
|
await this.saveSyncTimes(syncConfig, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.messagingService.send('dirSyncCompleted', { successfully: true });
|
this.messagingService.send("dirSyncCompleted", { successfully: true });
|
||||||
return [groups, users];
|
return [groups, users];
|
||||||
}
|
}
|
||||||
|
|
||||||
const req = this.buildRequest(groups, users, syncConfig.removeDisabled, syncConfig.overwriteExisting, syncConfig.largeImport);
|
const req = this.buildRequest(
|
||||||
|
groups,
|
||||||
|
users,
|
||||||
|
syncConfig.removeDisabled,
|
||||||
|
syncConfig.overwriteExisting,
|
||||||
|
syncConfig.largeImport
|
||||||
|
);
|
||||||
const reqJson = JSON.stringify(req);
|
const reqJson = JSON.stringify(req);
|
||||||
|
|
||||||
const orgId = await this.configurationService.getOrganizationId();
|
const orgId = await this.stateService.getOrganizationId();
|
||||||
if (orgId == null) {
|
if (orgId == null) {
|
||||||
throw new Error('Organization not set.');
|
throw new Error("Organization not set.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove hashLegacy once we're sure clients have had time to sync new hashes
|
// TODO: Remove hashLegacy once we're sure clients have had time to sync new hashes
|
||||||
let hashLegacy: string = null;
|
let hashLegacy: string = null;
|
||||||
const hashBuffLegacy = await this.cryptoFunctionService.hash(this.environmentService.getApiUrl() + reqJson, 'sha256');
|
const hashBuffLegacy = await this.cryptoFunctionService.hash(
|
||||||
|
this.environmentService.getApiUrl() + reqJson,
|
||||||
|
"sha256"
|
||||||
|
);
|
||||||
if (hashBuffLegacy != null) {
|
if (hashBuffLegacy != null) {
|
||||||
hashLegacy = Utils.fromBufferToB64(hashBuffLegacy);
|
hashLegacy = Utils.fromBufferToB64(hashBuffLegacy);
|
||||||
}
|
}
|
||||||
let hash: string = null;
|
let hash: string = null;
|
||||||
const hashBuff = await this.cryptoFunctionService.hash(this.environmentService.getApiUrl() + orgId + reqJson, 'sha256');
|
const hashBuff = await this.cryptoFunctionService.hash(
|
||||||
|
this.environmentService.getApiUrl() + orgId + reqJson,
|
||||||
|
"sha256"
|
||||||
|
);
|
||||||
if (hashBuff != null) {
|
if (hashBuff != null) {
|
||||||
hash = Utils.fromBufferToB64(hashBuff);
|
hash = Utils.fromBufferToB64(hashBuff);
|
||||||
}
|
}
|
||||||
const lastHash = await this.configurationService.getLastSyncHash();
|
const lastHash = await this.stateService.getLastSyncHash();
|
||||||
|
|
||||||
if (lastHash == null || (hash !== lastHash && hashLegacy !== lastHash)) {
|
if (lastHash == null || (hash !== lastHash && hashLegacy !== lastHash)) {
|
||||||
await this.apiService.postPublicImportDirectory(req);
|
await this.apiService.postPublicImportDirectory(req);
|
||||||
await this.configurationService.saveLastSyncHash(hash);
|
await this.stateService.setLastSyncHash(hash);
|
||||||
} else {
|
} else {
|
||||||
groups = null;
|
groups = null;
|
||||||
users = null;
|
users = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.saveSyncTimes(syncConfig, now);
|
await this.saveSyncTimes(syncConfig, now);
|
||||||
this.messagingService.send('dirSyncCompleted', { successfully: true });
|
this.messagingService.send("dirSyncCompleted", { successfully: true });
|
||||||
return [groups, users];
|
return [groups, users];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!test) {
|
if (!test) {
|
||||||
await this.configurationService.saveGroupDeltaToken(startingGroupDelta);
|
await this.stateService.setGroupDelta(startingGroupDelta);
|
||||||
await this.configurationService.saveUserDeltaToken(startingUserDelta);
|
await this.stateService.setUserDelta(startingUserDelta);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.messagingService.send('dirSyncCompleted', { successfully: false });
|
this.messagingService.send("dirSyncCompleted", { successfully: false });
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeDuplicateUsers(users: UserEntry[]) {
|
private removeDuplicateUsers(users: UserEntry[]) {
|
||||||
|
if (users == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const uniqueUsers = new Array<UserEntry>();
|
const uniqueUsers = new Array<UserEntry>();
|
||||||
const processedUsers = new Map<string, string>();
|
const processedActiveUsers = new Map<string, string>();
|
||||||
|
const processedDeletedUsers = new Map<string, string>();
|
||||||
const duplicateEmails = new Array<string>();
|
const duplicateEmails = new Array<string>();
|
||||||
|
|
||||||
// UserEntrys with the same email are ignored if their properties are the same
|
// UserEntrys with the same email are ignored if their properties are the same
|
||||||
// UserEntrys with the same email but different properties will throw an error
|
// UserEntrys with the same email but different properties will throw an error, unless they are all in a deleted state.
|
||||||
users.forEach(u => {
|
users.forEach((u) => {
|
||||||
if (processedUsers.has(u.email)) {
|
if (processedActiveUsers.has(u.email)) {
|
||||||
if (processedUsers.get(u.email) != JSON.stringify(u)) {
|
if (processedActiveUsers.get(u.email) !== JSON.stringify(u)) {
|
||||||
duplicateEmails.push(u.email);
|
duplicateEmails.push(u.email);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (!u.deleted) {
|
||||||
|
// Check that active UserEntry does not conflict with a deleted UserEntry
|
||||||
|
if (processedDeletedUsers.has(u.email)) {
|
||||||
|
duplicateEmails.push(u.email);
|
||||||
|
} else {
|
||||||
|
processedActiveUsers.set(u.email, JSON.stringify(u));
|
||||||
uniqueUsers.push(u);
|
uniqueUsers.push(u);
|
||||||
processedUsers.set(u.email, JSON.stringify(u));
|
}
|
||||||
|
} else {
|
||||||
|
// UserEntrys with duplicate email will not throw an error if they are all deleted. They will be synced.
|
||||||
|
processedDeletedUsers.set(u.email, JSON.stringify(u));
|
||||||
|
uniqueUsers.push(u);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (duplicateEmails.length > 0) {
|
if (duplicateEmails.length > 0) {
|
||||||
const emailsMessage = duplicateEmails.length < 4 ?
|
const emailsMessage =
|
||||||
duplicateEmails.join('\n') :
|
duplicateEmails.length < 4
|
||||||
duplicateEmails.slice(0, 3).join('\n') + '\n' + this.i18nService.t('andMore', `${duplicateEmails.length - 3}`);
|
? duplicateEmails.join("\n")
|
||||||
throw new Error(this.i18nService.t('duplicateEmails') + '\n' + emailsMessage);
|
: duplicateEmails.slice(0, 3).join("\n") +
|
||||||
|
"\n" +
|
||||||
|
this.i18nService.t("andMore", `${duplicateEmails.length - 3}`);
|
||||||
|
throw new Error(this.i18nService.t("duplicateEmails") + "\n" + emailsMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
return uniqueUsers;
|
return uniqueUsers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private filterUnsupportedUsers(users: UserEntry[]): UserEntry[] {
|
private filterUnsupportedUsers(users: UserEntry[]): UserEntry[] {
|
||||||
return users == null ? null : users.filter(u => u.email?.length <= 256);
|
return users == null ? null : users.filter((u) => u.email?.length <= 256);
|
||||||
}
|
}
|
||||||
|
|
||||||
private flattenUsersToGroups(levelGroups: GroupEntry[], allGroups: GroupEntry[]): Set<string> {
|
private flattenUsersToGroups(levelGroups: GroupEntry[], allGroups: GroupEntry[]): Set<string> {
|
||||||
@@ -150,9 +193,9 @@ export class SyncService {
|
|||||||
return allUsers;
|
return allUsers;
|
||||||
}
|
}
|
||||||
for (const group of levelGroups) {
|
for (const group of levelGroups) {
|
||||||
const childGroups = allGroups.filter(g => group.groupMemberReferenceIds.has(g.referenceId));
|
const childGroups = allGroups.filter((g) => group.groupMemberReferenceIds.has(g.referenceId));
|
||||||
const childUsers = this.flattenUsersToGroups(childGroups, allGroups);
|
const childUsers = this.flattenUsersToGroups(childGroups, allGroups);
|
||||||
childUsers.forEach(id => group.userMemberExternalIds.add(id));
|
childUsers.forEach((id) => group.userMemberExternalIds.add(id));
|
||||||
allUsers = new Set([...allUsers, ...group.userMemberExternalIds]);
|
allUsers = new Set([...allUsers, ...group.userMemberExternalIds]);
|
||||||
}
|
}
|
||||||
return allUsers;
|
return allUsers;
|
||||||
@@ -161,31 +204,36 @@ export class SyncService {
|
|||||||
private getDirectoryService(): IDirectoryService {
|
private getDirectoryService(): IDirectoryService {
|
||||||
switch (this.dirType) {
|
switch (this.dirType) {
|
||||||
case DirectoryType.GSuite:
|
case DirectoryType.GSuite:
|
||||||
return new GSuiteDirectoryService(this.configurationService, this.logService, this.i18nService);
|
return new GSuiteDirectoryService(this.logService, this.i18nService, this.stateService);
|
||||||
case DirectoryType.AzureActiveDirectory:
|
case DirectoryType.AzureActiveDirectory:
|
||||||
return new AzureDirectoryService(this.configurationService, this.logService, this.i18nService);
|
return new AzureDirectoryService(this.logService, this.i18nService, this.stateService);
|
||||||
case DirectoryType.Ldap:
|
case DirectoryType.Ldap:
|
||||||
return new LdapDirectoryService(this.configurationService, this.logService, this.i18nService);
|
return new LdapDirectoryService(this.logService, this.i18nService, this.stateService);
|
||||||
case DirectoryType.Okta:
|
case DirectoryType.Okta:
|
||||||
return new OktaDirectoryService(this.configurationService, this.logService, this.i18nService);
|
return new OktaDirectoryService(this.logService, this.i18nService, this.stateService);
|
||||||
case DirectoryType.OneLogin:
|
case DirectoryType.OneLogin:
|
||||||
return new OneLoginDirectoryService(this.configurationService, this.logService, this.i18nService);
|
return new OneLoginDirectoryService(this.logService, this.i18nService, this.stateService);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildRequest(groups: GroupEntry[], users: UserEntry[], removeDisabled: boolean, overwriteExisting: boolean,
|
private buildRequest(
|
||||||
largeImport: boolean = false) {
|
groups: GroupEntry[],
|
||||||
|
users: UserEntry[],
|
||||||
|
removeDisabled: boolean,
|
||||||
|
overwriteExisting: boolean,
|
||||||
|
largeImport = false
|
||||||
|
) {
|
||||||
return new OrganizationImportRequest({
|
return new OrganizationImportRequest({
|
||||||
groups: (groups ?? []).map(g => {
|
groups: (groups ?? []).map((g) => {
|
||||||
return {
|
return {
|
||||||
name: g.name,
|
name: g.name,
|
||||||
externalId: g.externalId,
|
externalId: g.externalId,
|
||||||
memberExternalIds: Array.from(g.userMemberExternalIds),
|
memberExternalIds: Array.from(g.userMemberExternalIds),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
users: (users ?? []).map(u => {
|
users: (users ?? []).map((u) => {
|
||||||
return {
|
return {
|
||||||
email: u.email,
|
email: u.email,
|
||||||
externalId: u.externalId,
|
externalId: u.externalId,
|
||||||
@@ -199,10 +247,10 @@ export class SyncService {
|
|||||||
|
|
||||||
private async saveSyncTimes(syncConfig: SyncConfiguration, time: Date) {
|
private async saveSyncTimes(syncConfig: SyncConfiguration, time: Date) {
|
||||||
if (syncConfig.groups) {
|
if (syncConfig.groups) {
|
||||||
await this.configurationService.saveLastGroupSyncDate(time);
|
await this.stateService.setLastGroupSync(time);
|
||||||
}
|
}
|
||||||
if (syncConfig.users) {
|
if (syncConfig.users) {
|
||||||
await this.configurationService.saveLastUserSyncDate(time);
|
await this.stateService.setLastUserSync(time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
51
src/utils.ts
51
src/utils.ts
@@ -1,15 +1,19 @@
|
|||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
|
|
||||||
import { SyncService } from './services/sync.service';
|
import { Entry } from "./models/entry";
|
||||||
|
import { LdapConfiguration } from "./models/ldapConfiguration";
|
||||||
import { Entry } from './models/entry';
|
import { SimResult } from "./models/simResult";
|
||||||
import { LdapConfiguration } from './models/ldapConfiguration';
|
import { SyncConfiguration } from "./models/syncConfiguration";
|
||||||
import { SimResult } from './models/simResult';
|
import { UserEntry } from "./models/userEntry";
|
||||||
import { SyncConfiguration } from './models/syncConfiguration';
|
import { SyncService } from "./services/sync.service";
|
||||||
import { UserEntry } from './models/userEntry';
|
|
||||||
|
|
||||||
export class ConnectorUtils {
|
export class ConnectorUtils {
|
||||||
static async simulate(syncService: SyncService, i18nService: I18nService, sinceLast: boolean): Promise<SimResult> {
|
static async simulate(
|
||||||
|
syncService: SyncService,
|
||||||
|
i18nService: I18nService,
|
||||||
|
sinceLast: boolean
|
||||||
|
): Promise<SimResult> {
|
||||||
|
// eslint-disable-next-line
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
const simResult = new SimResult();
|
const simResult = new SimResult();
|
||||||
try {
|
try {
|
||||||
@@ -23,7 +27,7 @@ export class ConnectorUtils {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
simResult.groups = null;
|
simResult.groups = null;
|
||||||
simResult.users = null;
|
simResult.users = null;
|
||||||
reject(e || i18nService.t('syncError'));
|
reject(e || i18nService.t("syncError"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +50,7 @@ export class ConnectorUtils {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const anyG = (g as any);
|
const anyG = g as any;
|
||||||
anyG.users = [];
|
anyG.users = [];
|
||||||
for (const uid of g.userMemberExternalIds) {
|
for (const uid of g.userMemberExternalIds) {
|
||||||
if (userMap.has(uid)) {
|
if (userMap.has(uid)) {
|
||||||
@@ -65,20 +69,20 @@ export class ConnectorUtils {
|
|||||||
|
|
||||||
static adjustConfigForSave(ldap: LdapConfiguration, sync: SyncConfiguration) {
|
static adjustConfigForSave(ldap: LdapConfiguration, sync: SyncConfiguration) {
|
||||||
if (ldap.ad) {
|
if (ldap.ad) {
|
||||||
sync.creationDateAttribute = 'whenCreated';
|
sync.creationDateAttribute = "whenCreated";
|
||||||
sync.revisionDateAttribute = 'whenChanged';
|
sync.revisionDateAttribute = "whenChanged";
|
||||||
sync.emailPrefixAttribute = 'sAMAccountName';
|
sync.emailPrefixAttribute = "sAMAccountName";
|
||||||
sync.memberAttribute = 'member';
|
sync.memberAttribute = "member";
|
||||||
sync.userObjectClass = 'person';
|
sync.userObjectClass = "person";
|
||||||
sync.groupObjectClass = 'group';
|
sync.groupObjectClass = "group";
|
||||||
sync.userEmailAttribute = 'mail';
|
sync.userEmailAttribute = "mail";
|
||||||
sync.groupNameAttribute = 'name';
|
sync.groupNameAttribute = "name";
|
||||||
|
|
||||||
if (sync.groupPath == null) {
|
if (sync.groupPath == null) {
|
||||||
sync.groupPath = 'CN=Users';
|
sync.groupPath = "CN=Users";
|
||||||
}
|
}
|
||||||
if (sync.userPath == null) {
|
if (sync.userPath == null) {
|
||||||
sync.userPath = 'CN=Users';
|
sync.userPath = "CN=Users";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,8 +97,9 @@ export class ConnectorUtils {
|
|||||||
|
|
||||||
private static sortEntries(arr: Entry[], i18nService: I18nService) {
|
private static sortEntries(arr: Entry[], i18nService: I18nService) {
|
||||||
arr.sort((a, b) => {
|
arr.sort((a, b) => {
|
||||||
return i18nService.collator ? i18nService.collator.compare(a.displayName, b.displayName) :
|
return i18nService.collator
|
||||||
a.displayName.localeCompare(b.displayName);
|
? i18nService.collator.compare(a.displayName, b.displayName)
|
||||||
|
: a.displayName.localeCompare(b.displayName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,28 +12,15 @@
|
|||||||
"types": [],
|
"types": [],
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"tldjs": [
|
"tldjs": ["jslib/src/misc/tldjs.noop"],
|
||||||
"jslib/src/misc/tldjs.noop"
|
"jslib-common/*": ["jslib/common/src/*"],
|
||||||
],
|
"jslib-angular/*": ["jslib/angular/src/*"],
|
||||||
"jslib-common/*": [
|
"jslib-electron/*": ["jslib/electron/src/*"],
|
||||||
"jslib/common/src/*"
|
"jslib-node/*": ["jslib/node/src/*"]
|
||||||
],
|
|
||||||
"jslib-angular/*": [
|
|
||||||
"jslib/angular/src/*"
|
|
||||||
],
|
|
||||||
"jslib-electron/*": [
|
|
||||||
"jslib/electron/src/*"
|
|
||||||
],
|
|
||||||
"jslib-node/*": [
|
|
||||||
"jslib/node/src/*"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
"preserveWhitespaces": true
|
"preserveWhitespaces": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src", "src-cli"]
|
||||||
"src",
|
|
||||||
"src-cli"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
75
tslint.json
75
tslint.json
@@ -1,75 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "tslint:recommended",
|
|
||||||
"rules": {
|
|
||||||
"align": [ true, "statements", "members" ],
|
|
||||||
"ban-types": {
|
|
||||||
"options": [
|
|
||||||
[ "Object", "Avoid using the `Object` type. Did you mean `object`?" ],
|
|
||||||
[ "Boolean", "Avoid using the `Boolean` type. Did you mean `boolean`?" ],
|
|
||||||
[ "Number", "Avoid using the `Number` type. Did you mean `number`?" ],
|
|
||||||
[ "String", "Avoid using the `String` type. Did you mean `string`?" ],
|
|
||||||
[ "Symbol", "Avoid using the `Symbol` type. Did you mean `symbol`?" ]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"member-access": [ true, "no-public" ],
|
|
||||||
"member-ordering": [
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
"order": [
|
|
||||||
"public-static-field",
|
|
||||||
"public-static-method",
|
|
||||||
"protected-static-field",
|
|
||||||
"protected-static-method",
|
|
||||||
"private-static-field",
|
|
||||||
"private-static-method",
|
|
||||||
"public-instance-field",
|
|
||||||
"protected-instance-field",
|
|
||||||
"private-instance-field",
|
|
||||||
"public-constructor",
|
|
||||||
"protected-constructor",
|
|
||||||
"private-constructor",
|
|
||||||
"public-instance-method",
|
|
||||||
"protected-instance-method",
|
|
||||||
"private-instance-method"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"no-empty": [ true, "allow-empty-catch" ],
|
|
||||||
"object-literal-sort-keys": false,
|
|
||||||
"object-literal-shorthand": [ true, "never" ],
|
|
||||||
"prefer-for-of": false,
|
|
||||||
"quotemark": [ true, "single" ],
|
|
||||||
"whitespace": [
|
|
||||||
true,
|
|
||||||
"check-branch",
|
|
||||||
"check-decl",
|
|
||||||
"check-module",
|
|
||||||
"check-operator",
|
|
||||||
"check-preblock",
|
|
||||||
"check-separator",
|
|
||||||
"check-type"
|
|
||||||
],
|
|
||||||
"max-classes-per-file": false,
|
|
||||||
"ordered-imports": true,
|
|
||||||
"arrow-parens": [
|
|
||||||
true,
|
|
||||||
"ban-single-arg-parens"
|
|
||||||
],
|
|
||||||
"semicolon": [
|
|
||||||
true,
|
|
||||||
"always"
|
|
||||||
],
|
|
||||||
"trailing-comma": [
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
"multiline": {
|
|
||||||
"objects": "always",
|
|
||||||
"arrays": "always",
|
|
||||||
"functions": "ignore",
|
|
||||||
"typeLiterals": "ignore"
|
|
||||||
},
|
|
||||||
"singleline": "never"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +1,70 @@
|
|||||||
const path = require('path');
|
const path = require("path");
|
||||||
const webpack = require('webpack');
|
|
||||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||||
const nodeExternals = require('webpack-node-externals');
|
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
|
||||||
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
const webpack = require("webpack");
|
||||||
|
const nodeExternals = require("webpack-node-externals");
|
||||||
|
|
||||||
if (process.env.NODE_ENV == null) {
|
if (process.env.NODE_ENV == null) {
|
||||||
process.env.NODE_ENV = 'development';
|
process.env.NODE_ENV = "development";
|
||||||
}
|
}
|
||||||
const ENV = process.env.ENV = process.env.NODE_ENV;
|
const ENV = (process.env.ENV = process.env.NODE_ENV);
|
||||||
|
|
||||||
const moduleRules = [
|
const moduleRules = [
|
||||||
{
|
{
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
enforce: 'pre',
|
use: "ts-loader",
|
||||||
loader: 'tslint-loader',
|
exclude: path.resolve(__dirname, "node_modules"),
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.ts$/,
|
|
||||||
loaders: ['ts-loader'],
|
|
||||||
exclude: path.resolve(__dirname, 'node_modules'),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.node$/,
|
test: /\.node$/,
|
||||||
loader: 'node-loader',
|
loader: "node-loader",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const plugins = [
|
const plugins = [
|
||||||
new CleanWebpackPlugin(),
|
new CleanWebpackPlugin(),
|
||||||
new CopyWebpackPlugin({
|
new CopyWebpackPlugin({
|
||||||
patterns: [
|
patterns: [{ from: "./src/locales", to: "locales" }],
|
||||||
{ from: './src/locales', to: 'locales' },
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env.BWCLI_ENV': JSON.stringify(ENV),
|
"process.env.BWCLI_ENV": JSON.stringify(ENV),
|
||||||
}),
|
}),
|
||||||
new webpack.BannerPlugin({
|
new webpack.BannerPlugin({
|
||||||
banner: '#!/usr/bin/env node',
|
banner: "#!/usr/bin/env node",
|
||||||
raw: true
|
raw: true,
|
||||||
}),
|
}),
|
||||||
new webpack.IgnorePlugin(/^encoding$/, /node-fetch/),
|
new webpack.IgnorePlugin({
|
||||||
|
resourceRegExp: /^encoding$/,
|
||||||
|
contextRegExp: /node-fetch/,
|
||||||
|
}),
|
||||||
|
new webpack.NormalModuleReplacementPlugin(/@angular\/core/, "jslib-node/angular-core.noop"),
|
||||||
];
|
];
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
mode: ENV,
|
mode: ENV,
|
||||||
target: 'node',
|
target: "node",
|
||||||
devtool: ENV === 'development' ? 'eval-source-map' : 'source-map',
|
devtool: ENV === "development" ? "eval-source-map" : "source-map",
|
||||||
node: {
|
node: {
|
||||||
__dirname: false,
|
__dirname: false,
|
||||||
__filename: false,
|
__filename: false,
|
||||||
},
|
},
|
||||||
entry: {
|
entry: {
|
||||||
'bwdc': './src/bwdc.ts',
|
bwdc: "./src/bwdc.ts",
|
||||||
},
|
},
|
||||||
optimization: {
|
optimization: {
|
||||||
minimize: false,
|
minimize: false,
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.ts', '.js', '.json'],
|
extensions: [".ts", ".js", ".json"],
|
||||||
plugins: [new TsconfigPathsPlugin({ configFile: './tsconfig.json' })],
|
plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })],
|
||||||
symlinks: false,
|
symlinks: false,
|
||||||
modules: [path.resolve('node_modules')],
|
modules: [path.resolve("node_modules")],
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
filename: '[name].js',
|
filename: "[name].js",
|
||||||
path: path.resolve(__dirname, 'build-cli'),
|
path: path.resolve(__dirname, "build-cli"),
|
||||||
},
|
},
|
||||||
module: { rules: moduleRules },
|
module: { rules: moduleRules },
|
||||||
plugins: plugins,
|
plugins: plugins,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user