Compare commits
141 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6e92bf597 | ||
|
|
af4a8d5ae8 | ||
|
|
c3704bbb67 | ||
|
|
8620f2f80d | ||
|
|
e0124474cf | ||
|
|
ac21b78225 | ||
|
|
dca69a5428 | ||
|
|
6b1fa0f50c | ||
|
|
3ab35dc4e0 | ||
|
|
2aa1fc1acb | ||
|
|
e6643fa4cb | ||
|
|
4848dd8502 | ||
|
|
4b08d7b4b2 | ||
|
|
839189c772 | ||
|
|
a06ff45c21 | ||
|
|
c598efcb9f | ||
|
|
878476d195 | ||
|
|
82e599fc13 | ||
|
|
79b76687a0 | ||
|
|
af5349c4cb | ||
|
|
3de35b481e | ||
|
|
008bb308e7 | ||
|
|
9b44fc4a35 | ||
|
|
3d374fc792 | ||
|
|
b51c842d18 | ||
|
|
95b595d5bf | ||
|
|
445c3f220a | ||
|
|
9644d95b9b | ||
|
|
eef9f151ef | ||
|
|
6c05a4ec25 | ||
|
|
2ea71e1feb | ||
|
|
294404750d | ||
|
|
ada957c7a3 | ||
|
|
cda747a1f5 | ||
|
|
3f9db95eba | ||
|
|
18d77f5ff5 | ||
|
|
e7e8cfe8da | ||
|
|
7abc791050 | ||
|
|
913098916e | ||
|
|
cbf1f3bd9f | ||
|
|
a0a1fe6737 | ||
|
|
cb2a702829 | ||
|
|
65f171bde0 | ||
|
|
85531e2133 | ||
|
|
17e058dce2 | ||
|
|
566f187a3d | ||
|
|
85c528ce27 | ||
|
|
9a87c8bffb | ||
|
|
ae931b2dde | ||
|
|
183be70d44 | ||
|
|
eb6e5d7a75 | ||
|
|
e1101bdd62 | ||
|
|
954cf2c0ff | ||
|
|
17c7806850 | ||
|
|
2594773a12 | ||
|
|
a0025e933a | ||
|
|
aea11afb5a | ||
|
|
e40ee1e3a8 | ||
|
|
1632af3e22 | ||
|
|
fd3793d3d7 | ||
|
|
fef65c5209 | ||
|
|
173ab8cf91 | ||
|
|
9d6cb6e044 | ||
|
|
27c5909d7f | ||
|
|
91b0943a8b | ||
|
|
a8b741bb22 | ||
|
|
4cd1a8a6a0 | ||
|
|
db501f9f72 | ||
|
|
95d98181fc | ||
|
|
3981cb95fc | ||
|
|
c80fa93515 | ||
|
|
5e2895ce33 | ||
|
|
9c5e0df719 | ||
|
|
da73fcd973 | ||
|
|
f8ae050556 | ||
|
|
d53c1211cf | ||
|
|
905ebd76ae | ||
|
|
179687490c | ||
|
|
85aff3e75a | ||
|
|
388a69fe4e | ||
|
|
4e9086a042 | ||
|
|
d6707488c8 | ||
|
|
bbcc65f149 | ||
|
|
e74b99ec65 | ||
|
|
125de3e35e | ||
|
|
9689a63bdc | ||
|
|
89b468e443 | ||
|
|
db6b6fe8fa | ||
|
|
e5718dd742 | ||
|
|
39622fe263 | ||
|
|
1d723000a9 | ||
|
|
36101fd7dd | ||
|
|
b8bb6cb55c | ||
|
|
995249e421 | ||
|
|
4533dd72ae | ||
|
|
f5c12d9ea6 | ||
|
|
ce8d58199a | ||
|
|
5556a57685 | ||
|
|
39f760e135 | ||
|
|
e8351c3246 | ||
|
|
35bf1f0f77 | ||
|
|
87039fa784 | ||
|
|
dea8e48895 | ||
|
|
4145de7662 | ||
|
|
2800c2f077 | ||
|
|
e7787ea95c | ||
|
|
e7809b405d | ||
|
|
b04b3ef924 | ||
|
|
6876b905cd | ||
|
|
371101bf69 | ||
|
|
e17ecf967d | ||
|
|
b0705a911d | ||
|
|
99c7f619e0 | ||
|
|
c4a37b2a85 | ||
|
|
c12f40ecf7 | ||
|
|
fe6055c402 | ||
|
|
d2b36c4d23 | ||
|
|
fdd29190a4 | ||
|
|
6d31d0a60e | ||
|
|
a4cb908390 | ||
|
|
b8d3c35e34 | ||
|
|
48bde677bc | ||
|
|
cafb7da93e | ||
|
|
ad6c3cb132 | ||
|
|
e1e532ed91 | ||
|
|
2afbeb1c10 | ||
|
|
598db5c83b | ||
|
|
ceddb83d2d | ||
|
|
4e70378b46 | ||
|
|
5cd7f3568e | ||
|
|
2c2f1921c1 | ||
|
|
1e5e28e2b6 | ||
|
|
41f8263a7c | ||
|
|
818a4db96e | ||
|
|
51ab260fe6 | ||
|
|
be393f7a63 | ||
|
|
28c0509886 | ||
|
|
835c9f9cac | ||
|
|
6e4e78c30e | ||
|
|
7d3ea444f4 | ||
|
|
fef8bd1e00 |
16
.editorconfig
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# EditorConfig is awesome: http://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Unix-style newlines with a newline ending every file
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
quote_type = single
|
||||||
|
|
||||||
|
# Set default charset
|
||||||
|
[*.{js,ts,scss,html}]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
272
.gitignore
vendored
@@ -1,261 +1,13 @@
|
|||||||
## Ignore Visual Studio temporary files, build results, and
|
.vs
|
||||||
## files generated by popular Visual Studio add-ons.
|
.idea
|
||||||
|
node_modules
|
||||||
# User-specific files
|
npm-debug.log
|
||||||
*.suo
|
vwd.webinfo
|
||||||
*.user
|
dist/
|
||||||
*.userosscache
|
*.crx
|
||||||
*.sln.docstates
|
*.pem
|
||||||
|
build/
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
yarn-error.log
|
||||||
*.userprefs
|
.DS_Store
|
||||||
|
|
||||||
# Build results
|
|
||||||
[Dd]ebug/
|
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
bld/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
[Ll]og/
|
|
||||||
|
|
||||||
# Visual Studio 2015 cache/options directory
|
|
||||||
.vs/
|
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
|
||||||
#wwwroot/
|
|
||||||
|
|
||||||
# MSTest test Results
|
|
||||||
[Tt]est[Rr]esult*/
|
|
||||||
[Bb]uild[Ll]og.*
|
|
||||||
|
|
||||||
# NUNIT
|
|
||||||
*.VisualState.xml
|
|
||||||
TestResult.xml
|
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
|
||||||
[Dd]ebugPS/
|
|
||||||
[Rr]eleasePS/
|
|
||||||
dlldata.c
|
|
||||||
|
|
||||||
# DNX
|
|
||||||
project.lock.json
|
|
||||||
project.fragment.lock.json
|
|
||||||
artifacts/
|
|
||||||
|
|
||||||
*_i.c
|
|
||||||
*_p.c
|
|
||||||
*_i.h
|
|
||||||
*.ilk
|
|
||||||
*.meta
|
|
||||||
*.obj
|
|
||||||
*.pch
|
|
||||||
*.pdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.tmp_proj
|
|
||||||
*.log
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
*.pidb
|
|
||||||
*.svclog
|
|
||||||
*.scc
|
|
||||||
|
|
||||||
# Chutzpah Test files
|
|
||||||
_Chutzpah*
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opendb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.cachefile
|
|
||||||
*.VC.db
|
|
||||||
*.VC.VC.opendb
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
*.sap
|
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
|
||||||
$tf/
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*/
|
|
||||||
*.[Rr]e[Ss]harper
|
|
||||||
*.DotSettings.user
|
|
||||||
|
|
||||||
# JustCode is a .NET coding add-in
|
|
||||||
.JustCode
|
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
|
||||||
_TeamCity*
|
|
||||||
|
|
||||||
# DotCover is a Code Coverage Tool
|
|
||||||
*.dotCover
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
_NCrunch_*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
nCrunchTemp_*
|
|
||||||
|
|
||||||
# MightyMoose
|
|
||||||
*.mm.*
|
|
||||||
AutoTest.Net/
|
|
||||||
|
|
||||||
# Web workbench (sass)
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress/
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish/
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.[Pp]ublish.xml
|
|
||||||
*.azurePubxml
|
|
||||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
|
||||||
#*.pubxml
|
|
||||||
*.publishproj
|
|
||||||
|
|
||||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
|
||||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
|
||||||
# in these scripts will be unencrypted
|
|
||||||
PublishScripts/
|
|
||||||
|
|
||||||
# NuGet Packages
|
|
||||||
*.nupkg
|
*.nupkg
|
||||||
# The packages folder can be ignored because of Package Restore
|
*.provisionprofile
|
||||||
**/packages/*
|
|
||||||
# except build/, which is used as an MSBuild target.
|
|
||||||
!**/packages/build/
|
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
|
||||||
#!**/packages/repositories.config
|
|
||||||
# NuGet v3's project.json files produces more ignoreable files
|
|
||||||
*.nuget.props
|
|
||||||
*.nuget.targets
|
|
||||||
|
|
||||||
# Microsoft Azure Build Output
|
|
||||||
csx/
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Microsoft Azure Emulator
|
|
||||||
ecf/
|
|
||||||
rcf/
|
|
||||||
|
|
||||||
# Windows Store app package directories and files
|
|
||||||
AppPackages/
|
|
||||||
BundleArtifacts/
|
|
||||||
Package.StoreAssociation.xml
|
|
||||||
_pkginfo.txt
|
|
||||||
|
|
||||||
# Visual Studio cache files
|
|
||||||
# files ending in .cache can be ignored
|
|
||||||
*.[Cc]ache
|
|
||||||
# but keep track of directories ending in .cache
|
|
||||||
!*.[Cc]ache/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
ClientBin/
|
|
||||||
~$*
|
|
||||||
*~
|
|
||||||
*.dbmdl
|
|
||||||
*.dbproj.schemaview
|
|
||||||
*.jfm
|
|
||||||
*.pfx
|
|
||||||
*.publishsettings
|
|
||||||
node_modules/
|
|
||||||
orleans.codegen.cs
|
|
||||||
|
|
||||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
|
||||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
|
||||||
#bower_components/
|
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
|
||||||
Generated_Code/
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
|
||||||
# because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
UpgradeLog*.htm
|
|
||||||
|
|
||||||
# SQL Server files
|
|
||||||
*.mdf
|
|
||||||
*.ldf
|
|
||||||
|
|
||||||
# Business Intelligence projects
|
|
||||||
*.rdl.data
|
|
||||||
*.bim.layout
|
|
||||||
*.bim_*.settings
|
|
||||||
|
|
||||||
# Microsoft Fakes
|
|
||||||
FakesAssemblies/
|
|
||||||
|
|
||||||
# GhostDoc plugin setting file
|
|
||||||
*.GhostDoc.xml
|
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
|
||||||
.ntvs_analysis.dat
|
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
|
||||||
*.plg
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
|
||||||
*.opt
|
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/ModelManifest.xml
|
|
||||||
**/*.Server/GeneratedArtifacts
|
|
||||||
**/*.Server/ModelManifest.xml
|
|
||||||
_Pvt_Extensions
|
|
||||||
|
|
||||||
# Paket dependency manager
|
|
||||||
.paket/paket.exe
|
|
||||||
paket-files/
|
|
||||||
|
|
||||||
# FAKE - F# Make
|
|
||||||
.fake/
|
|
||||||
|
|
||||||
# JetBrains Rider
|
|
||||||
.idea/
|
|
||||||
*.sln.iml
|
|
||||||
|
|
||||||
# CodeRush
|
|
||||||
.cr/
|
|
||||||
|
|
||||||
# Python Tools for Visual Studio (PTVS)
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
|
|||||||
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[submodule "jslib"]
|
||||||
|
path = jslib
|
||||||
|
url = https://github.com/bitwarden/jslib.git
|
||||||
|
branch = master
|
||||||
37
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Electron: Main",
|
||||||
|
"protocol": "inspector",
|
||||||
|
"cwd": "${workspaceRoot}/build",
|
||||||
|
"runtimeArgs": [
|
||||||
|
"--remote-debugging-port=9223",
|
||||||
|
"."
|
||||||
|
],
|
||||||
|
"windows": {
|
||||||
|
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
|
||||||
|
},
|
||||||
|
"sourceMaps": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Electron: Renderer",
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "attach",
|
||||||
|
"port": 9223,
|
||||||
|
"webRoot": "${workspaceFolder}/build",
|
||||||
|
"sourceMaps": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compounds": [
|
||||||
|
{
|
||||||
|
"name": "Electron: All",
|
||||||
|
"configurations": [
|
||||||
|
"Electron: Main",
|
||||||
|
"Electron: Renderer"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
CONTRIBUTING.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
Code contributions are welcome! Please commit any pull requests against the `master` branch.
|
||||||
|
|
||||||
|
# Localization (l10n)
|
||||||
|
|
||||||
|
[](https://crowdin.com/project/bitwarden-desktop)
|
||||||
|
|
||||||
|
We use a translation tool called [Crowdin](https://crowdin.com) to help manage our localization efforts across many different languages.
|
||||||
|
|
||||||
|
If you are interested in helping translate the Bitwarden desktop app into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-desktop
|
||||||
|
|
||||||
|
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/mail/compose/kspearrin).
|
||||||
|
|
||||||
|
You can read Crowdin's getting started guide for translators here: https://support.crowdin.com/crowdin-intro/
|
||||||
5
ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<!--
|
||||||
|
Please do not submit feature requests. The [Community Forums][1] has a
|
||||||
|
section for submitting, voting for, and discussing product feature requests.
|
||||||
|
[1]: https://community.bitwarden.com
|
||||||
|
-->
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Version 3, 19 November 2007
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
@@ -7,15 +7,17 @@
|
|||||||
|
|
||||||
Preamble
|
Preamble
|
||||||
|
|
||||||
The GNU Affero General Public License is a free, copyleft license for
|
The GNU General Public License is a free, copyleft license for
|
||||||
software and other kinds of works, specifically designed to ensure
|
software and other kinds of works.
|
||||||
cooperation with the community in the case of network server software.
|
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
The licenses for most software and other practical works are designed
|
||||||
to take away your freedom to share and change the works. By contrast,
|
to take away your freedom to share and change the works. By contrast,
|
||||||
our General Public Licenses are intended to guarantee your freedom to
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
share and change all versions of a program--to make sure it remains free
|
share and change all versions of a program--to make sure it remains free
|
||||||
software for all its users.
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
When we speak of free software, we are referring to freedom, not
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
@@ -24,34 +26,44 @@ them if you wish), that you receive source code or can get it if you
|
|||||||
want it, that you can change the software or use pieces of it in new
|
want it, that you can change the software or use pieces of it in new
|
||||||
free programs, and that you know you can do these things.
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
Developers that use our General Public Licenses protect your rights
|
To protect your rights, we need to prevent others from denying you
|
||||||
with two steps: (1) assert copyright on the software, and (2) offer
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
you this License which gives you legal permission to copy, distribute
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
and/or modify the software.
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
A secondary benefit of defending all users' freedom is that
|
For example, if you distribute copies of such a program, whether
|
||||||
improvements made in alternate versions of the program, if they
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
receive widespread use, become available for other developers to
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
incorporate. Many developers of free software are heartened and
|
or can get the source code. And you must show them these terms so they
|
||||||
encouraged by the resulting cooperation. However, in the case of
|
know their rights.
|
||||||
software used on network servers, this result may fail to come about.
|
|
||||||
The GNU General Public License permits making a modified version and
|
|
||||||
letting the public access it on a server without ever releasing its
|
|
||||||
source code to the public.
|
|
||||||
|
|
||||||
The GNU Affero General Public License is designed specifically to
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
ensure that, in such cases, the modified source code becomes available
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
to the community. It requires the operator of a network server to
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
provide the source code of the modified version running there to the
|
|
||||||
users of that server. Therefore, public use of a modified version, on
|
|
||||||
a publicly accessible server, gives the public access to the source
|
|
||||||
code of the modified version.
|
|
||||||
|
|
||||||
An older license, called the Affero General Public License and
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
published by Affero, was designed to accomplish similar goals. This is
|
that there is no warranty for this free software. For both users' and
|
||||||
a different license, not a version of the Affero GPL, but Affero has
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
released a new version of the Affero GPL which permits relicensing under
|
changed, so that their problems will not be attributed erroneously to
|
||||||
this license.
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
The precise terms and conditions for copying, distribution and
|
||||||
modification follow.
|
modification follow.
|
||||||
@@ -60,7 +72,7 @@ modification follow.
|
|||||||
|
|
||||||
0. Definitions.
|
0. Definitions.
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
works, such as semiconductor masks.
|
works, such as semiconductor masks.
|
||||||
@@ -537,45 +549,35 @@ to collect a royalty for further conveying from those to whom you convey
|
|||||||
the Program, the only way you could satisfy both those terms and this
|
the Program, the only way you could satisfy both those terms and this
|
||||||
License would be to refrain entirely from conveying the Program.
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, if you modify the
|
|
||||||
Program, your modified version must prominently offer all users
|
|
||||||
interacting with it remotely through a computer network (if your version
|
|
||||||
supports such interaction) an opportunity to receive the Corresponding
|
|
||||||
Source of your version by providing access to the Corresponding Source
|
|
||||||
from a network server at no charge, through some standard or customary
|
|
||||||
means of facilitating copying of software. This Corresponding Source
|
|
||||||
shall include the Corresponding Source for any work covered by version 3
|
|
||||||
of the GNU General Public License that is incorporated pursuant to the
|
|
||||||
following paragraph.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
Notwithstanding any other provision of this License, you have
|
||||||
permission to link or combine any covered work with a work licensed
|
permission to link or combine any covered work with a work licensed
|
||||||
under version 3 of the GNU General Public License into a single
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
combined work, and to convey the resulting work. The terms of this
|
combined work, and to convey the resulting work. The terms of this
|
||||||
License will continue to apply to the part which is the covered work,
|
License will continue to apply to the part which is the covered work,
|
||||||
but the work with which it is combined will remain governed by version
|
but the special requirements of the GNU Affero General Public License,
|
||||||
3 of the GNU General Public License.
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
the GNU Affero General Public License from time to time. Such new versions
|
the GNU General Public License from time to time. Such new versions will
|
||||||
will be similar in spirit to the present version, but may differ in detail to
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
address new problems or concerns.
|
address new problems or concerns.
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
Each version is given a distinguishing version number. If the
|
||||||
Program specifies that a certain numbered version of the GNU Affero General
|
Program specifies that a certain numbered version of the GNU General
|
||||||
Public License "or any later version" applies to it, you have the
|
Public License "or any later version" applies to it, you have the
|
||||||
option of following the terms and conditions either of that numbered
|
option of following the terms and conditions either of that numbered
|
||||||
version or of any later version published by the Free Software
|
version or of any later version published by the Free Software
|
||||||
Foundation. If the Program does not specify a version number of the
|
Foundation. If the Program does not specify a version number of the
|
||||||
GNU Affero General Public License, you may choose any version ever published
|
GNU General Public License, you may choose any version ever published
|
||||||
by the Free Software Foundation.
|
by the Free Software Foundation.
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
If the Program specifies that a proxy can decide which future
|
||||||
versions of the GNU Affero General Public License can be used, that proxy's
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
public statement of acceptance of a version permanently authorizes you
|
public statement of acceptance of a version permanently authorizes you
|
||||||
to choose that version for the Program.
|
to choose that version for the Program.
|
||||||
|
|
||||||
@@ -629,33 +631,44 @@ to attach them to the start of each source file to most effectively
|
|||||||
state the exclusion of warranty; and each file should have at least
|
state the exclusion of warranty; and each file should have at least
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
<one line to give the program's name and a brief idea of what it does.>
|
{one line to give the program's name and a brief idea of what it does.}
|
||||||
Copyright (C) <year> <name of author>
|
Copyright (C) {year} {name of author}
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published
|
it under the terms of the GNU General Public License as published by
|
||||||
by the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
This program is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU Affero General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
If your software can interact with users remotely through a computer
|
If the program does terminal interaction, make it output a short
|
||||||
network, you should also make sure that it provides a way for users to
|
notice like this when it starts in an interactive mode:
|
||||||
get its source. For example, if your program is a web application, its
|
|
||||||
interface could display a "Source" link that leads users to an archive
|
{project} Copyright (C) {year} {fullname}
|
||||||
of the code. There are many ways you could offer source, and different
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
solutions will be better for different programs; see section 13 for the
|
This is free software, and you are welcome to redistribute it
|
||||||
specific requirements.
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
<http://www.gnu.org/licenses/>.
|
<http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||||
26
README.md
@@ -1,29 +1,39 @@
|
|||||||
[](https://ci.appveyor.com/project/bitwarden/directory-connector)
|
[](https://ci.appveyor.com/project/bitwarden/directory-connector)
|
||||||
[](https://gitter.im/bitwarden/Lobby)
|
[](https://gitter.im/bitwarden/Lobby)
|
||||||
|
|
||||||
# bitwarden Directory Connector
|
# Bitwarden Directory Connector
|
||||||
|
|
||||||
The bitwarden Directory Connector is a command line application used to connect 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.
|
||||||
It is written in C# with the .NET Framework. It consists of a console application and an optional windows service to run syncs in the background on a specified interval.
|
|
||||||
|
|
||||||
Supported directories:
|
Supported directories:
|
||||||
- Active Directory
|
- Active Directory
|
||||||
|
- Any other LDAP-based directory
|
||||||
- Azure Active Directory
|
- Azure Active Directory
|
||||||
- G Suite (Google)
|
- G Suite (Google)
|
||||||
- Any other LDAP-based directory
|
- Okta
|
||||||
|
|
||||||
<img src="https://i.imgur.com/IdqS0se.png" alt="" width="680" height="479" />
|
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)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
# Build/Run
|
# Build/Run
|
||||||
|
|
||||||
**Requirements**
|
**Requirements**
|
||||||
|
|
||||||
- [Visual Studio](https://www.visualstudio.com/)
|
- [Node.js](https://nodejs.org/)
|
||||||
|
- 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).
|
||||||
|
|
||||||
Open `bitwarden-directory-connector.sln` or `bitwarden-directory-connector-noinstaller.sln`. After restoring the nuget packages, you can build and run the application.
|
**Run the app**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run electron
|
||||||
|
```
|
||||||
|
|
||||||
# Contribute
|
# Contribute
|
||||||
|
|
||||||
Code contributions are welcome! Visual Studio is required to work on this project. Please commit any pull requests against the `master` branch.
|
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.
|
||||||
|
|||||||
16
SECURITY.md
@@ -1,4 +1,4 @@
|
|||||||
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
|
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!
|
notify us. We welcome working with you to resolve the issue promptly. Thanks in advance!
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
|
|||||||
|
|
||||||
# In-scope
|
# In-scope
|
||||||
|
|
||||||
- Security issues in any current release of bitwarden. This includes the web vault, browser extension,
|
- 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
|
and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source
|
||||||
code is available at https://github.com/bitwarden.
|
code is available at https://github.com/bitwarden.
|
||||||
|
|
||||||
@@ -24,14 +24,14 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
|
|||||||
|
|
||||||
The following bug classes are out-of scope:
|
The following bug classes are out-of scope:
|
||||||
|
|
||||||
- Bugs that are already reported on any of bitwarden's issue trackers (https://github.com/bitwarden),
|
- 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.
|
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
|
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
|
||||||
upstream maintainer.
|
upstream maintainer.
|
||||||
- Attacks requiring physical access to a user's device.
|
- Attacks requiring physical access to a user's device.
|
||||||
- Self-XSS
|
- Self-XSS
|
||||||
- Issues related to software or protocols not under bitwarden's control
|
- Issues related to software or protocols not under Bitwarden's control
|
||||||
- Vulnerabilities in outdated versions of bitwarden
|
- Vulnerabilities in outdated versions of Bitwarden
|
||||||
- Missing security best practices that do not directly lead to a vulnerability
|
- Missing security best practices that do not directly lead to a vulnerability
|
||||||
- Issues that do not have any impact on the general public
|
- Issues that do not have any impact on the general public
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ While researching, we'd like to ask you to refrain from:
|
|||||||
|
|
||||||
- Denial of service
|
- Denial of service
|
||||||
- Spamming
|
- Spamming
|
||||||
- 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
|
||||||
|
|
||||||
Thank you for helping keep bitwarden and our users safe!
|
Thank you for helping keep Bitwarden and our users safe!
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio 15
|
|
||||||
VisualStudioVersion = 15.0.26730.10
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Console", "src\Console\Console.csproj", "{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "src\Core\Core.csproj", "{AE082484-A34C-4B3A-A69F-49E5EF298B27}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Service", "src\Service\Service.csproj", "{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{AE082484-A34C-4B3A-A69F-49E5EF298B27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{AE082484-A34C-4B3A-A69F-49E5EF298B27}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{AE082484-A34C-4B3A-A69F-49E5EF298B27}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{AE082484-A34C-4B3A-A69F-49E5EF298B27}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {52CB25C1-56A6-4FBE-977C-E842EA0AFAD7}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio 15
|
|
||||||
VisualStudioVersion = 15.0.26730.10
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Console", "src\Console\Console.csproj", "{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "src\Core\Core.csproj", "{AE082484-A34C-4B3A-A69F-49E5EF298B27}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Service", "src\Service\Service.csproj", "{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}"
|
|
||||||
EndProject
|
|
||||||
Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "Setup", "src\Setup\Setup.vdproj", "{4D852DF8-9327-43D0-93AB-FA68D4F3414B}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{AE082484-A34C-4B3A-A69F-49E5EF298B27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{AE082484-A34C-4B3A-A69F-49E5EF298B27}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{AE082484-A34C-4B3A-A69F-49E5EF298B27}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{AE082484-A34C-4B3A-A69F-49E5EF298B27}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{4D852DF8-9327-43D0-93AB-FA68D4F3414B}.Debug|Any CPU.ActiveCfg = Debug
|
|
||||||
{4D852DF8-9327-43D0-93AB-FA68D4F3414B}.Release|Any CPU.ActiveCfg = Release
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {AB7EF6F7-C38B-4E66-BBF9-1F6915896CB0}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
1
jslib
Submodule
11186
package-lock.json
generated
Normal file
174
package.json
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
{
|
||||||
|
"name": "bitwarden-directory-connector",
|
||||||
|
"productName": "Bitwarden Directory Connector",
|
||||||
|
"description": "Sync your user directory to your Bitwarden organization.",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"keywords": [
|
||||||
|
"bitwarden",
|
||||||
|
"password",
|
||||||
|
"vault",
|
||||||
|
"password manager"
|
||||||
|
],
|
||||||
|
"author": "8bit Solutions LLC <hello@bitwarden.com> (https://bitwarden.com)",
|
||||||
|
"homepage": "https://bitwarden.com",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/bitwarden/directory-connector"
|
||||||
|
},
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"scripts": {
|
||||||
|
"sub:init": "git submodule update --init --recursive",
|
||||||
|
"sub:update": "git submodule update --remote",
|
||||||
|
"sub:pull": "git submodule foreach git pull origin master",
|
||||||
|
"sub:commit": "npm run sub:pull && git commit -am \"update submodule\"",
|
||||||
|
"postinstall": "./node_modules/.bin/electron-rebuild && npm run sub:init",
|
||||||
|
"lint": "tslint src/**/*.ts || true",
|
||||||
|
"lint:fix": "tslint src/**/*.ts --fix",
|
||||||
|
"build": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\"",
|
||||||
|
"build:main": "webpack --config webpack.main.js",
|
||||||
|
"build:renderer": "webpack --config webpack.renderer.js",
|
||||||
|
"build:renderer:watch": "webpack --config webpack.renderer.js --watch",
|
||||||
|
"electron": "npm run build:main && concurrently -k -n Main,Rend -c yellow,cyan \"electron --inspect=5858 ./build --watch\" \"npm run build:renderer:watch\"",
|
||||||
|
"clean:dist": "rimraf ./dist/*",
|
||||||
|
"pack:lin": "npm run clean:dist && build --linux --x64 -p never",
|
||||||
|
"pack:mac": "npm run clean:dist && build --mac -p never",
|
||||||
|
"pack:win": "npm run clean:dist && build --win --x64 --ia32 -p never -c.win.certificateSubjectName=\"8bit Solutions LLC\"",
|
||||||
|
"pack:win:ci": "npm run clean:dist && build --win --x64 --ia32 -p never",
|
||||||
|
"dist:lin": "npm run build && npm run pack:lin",
|
||||||
|
"dist:mac": "npm run build && npm run pack:mac",
|
||||||
|
"dist:win": "npm run build && npm run pack:win",
|
||||||
|
"dist:win:ci": "npm run build && npm run pack:win:ci",
|
||||||
|
"publish:lin": "npm run build && npm run clean:dist && build --linux --x64 -p always",
|
||||||
|
"publish:mac": "npm run build && npm run clean:dist && build --mac -p always",
|
||||||
|
"publish:win": "npm run build && npm run clean:dist && build --win --x64 --ia32 -p always -c.win.certificateSubjectName=\"8bit Solutions LLC\""
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"appId": "com.bitwarden.directory-connector",
|
||||||
|
"copyright": "Copyright © 2015-2018 8bit Solutions LLC",
|
||||||
|
"directories": {
|
||||||
|
"buildResources": "resources",
|
||||||
|
"output": "dist",
|
||||||
|
"app": "build"
|
||||||
|
},
|
||||||
|
"mac": {
|
||||||
|
"category": "public.app-category.productivity",
|
||||||
|
"target": [
|
||||||
|
"dmg",
|
||||||
|
"zip"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"win": {
|
||||||
|
"target": [
|
||||||
|
"portable",
|
||||||
|
"nsis"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"category": "Utility",
|
||||||
|
"synopsis": "Sync your user directory to your Bitwarden organization.",
|
||||||
|
"target": [
|
||||||
|
"AppImage"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dmg": {
|
||||||
|
"artifactName": "Bitwarden-Connector-${version}.${ext}",
|
||||||
|
"icon": "dmg.icns",
|
||||||
|
"contents": [
|
||||||
|
{
|
||||||
|
"x": 150,
|
||||||
|
"y": 185,
|
||||||
|
"type": "file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 390,
|
||||||
|
"y": 180,
|
||||||
|
"type": "link",
|
||||||
|
"path": "/Applications"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"window": {
|
||||||
|
"width": 540,
|
||||||
|
"height": 380
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nsis": {
|
||||||
|
"oneClick": false,
|
||||||
|
"perMachine": true,
|
||||||
|
"allowToChangeInstallationDirectory": true,
|
||||||
|
"artifactName": "Bitwarden-Connector-Installer-${version}.${ext}",
|
||||||
|
"uninstallDisplayName": "${productName}",
|
||||||
|
"deleteAppDataOnUninstall": true
|
||||||
|
},
|
||||||
|
"portable": {
|
||||||
|
"artifactName": "Bitwarden-Connector-Portable-${version}.${ext}"
|
||||||
|
},
|
||||||
|
"appImage": {
|
||||||
|
"artifactName": "Bitwarden-Connector-${version}-${arch}.${ext}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular/compiler-cli": "5.2.0",
|
||||||
|
"@microsoft/microsoft-graph-types": "^1.2.0",
|
||||||
|
"@ngtools/webpack": "1.10.2",
|
||||||
|
"@types/keytar": "^4.0.1",
|
||||||
|
"@types/ldapjs": "^1.0.3",
|
||||||
|
"@types/lowdb": "^1.0.1",
|
||||||
|
"@types/lunr": "2.1.5",
|
||||||
|
"@types/node": "8.0.19",
|
||||||
|
"@types/node-forge": "0.7.1",
|
||||||
|
"@types/webcrypto": "0.0.28",
|
||||||
|
"clean-webpack-plugin": "^0.1.17",
|
||||||
|
"concurrently": "3.5.1",
|
||||||
|
"copy-webpack-plugin": "^4.2.0",
|
||||||
|
"css-loader": "^0.28.7",
|
||||||
|
"electron": "2.0.2",
|
||||||
|
"electron-builder": "^20.8.1",
|
||||||
|
"electron-rebuild": "1.7.3",
|
||||||
|
"electron-reload": "1.2.2",
|
||||||
|
"extract-text-webpack-plugin": "^3.0.1",
|
||||||
|
"file-loader": "^1.1.5",
|
||||||
|
"font-awesome": "4.7.0",
|
||||||
|
"google-fonts-webpack-plugin": "^0.4.4",
|
||||||
|
"html-loader": "^0.5.1",
|
||||||
|
"html-webpack-plugin": "^2.30.1",
|
||||||
|
"node-loader": "^0.6.0",
|
||||||
|
"node-sass": "^4.7.2",
|
||||||
|
"rimraf": "^2.6.2",
|
||||||
|
"sass-loader": "^6.0.6",
|
||||||
|
"ts-loader": "^3.5.0",
|
||||||
|
"tslint": "^5.9.1",
|
||||||
|
"tslint-loader": "^3.5.3",
|
||||||
|
"typescript": "^2.7.1",
|
||||||
|
"webpack": "^3.10.0",
|
||||||
|
"webpack-merge": "^4.1.0",
|
||||||
|
"webpack-node-externals": "^1.6.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "5.2.0",
|
||||||
|
"@angular/common": "5.2.0",
|
||||||
|
"@angular/compiler": "5.2.0",
|
||||||
|
"@angular/core": "5.2.0",
|
||||||
|
"@angular/forms": "5.2.0",
|
||||||
|
"@angular/http": "5.2.0",
|
||||||
|
"@angular/platform-browser": "5.2.0",
|
||||||
|
"@angular/platform-browser-dynamic": "5.2.0",
|
||||||
|
"@angular/router": "5.2.0",
|
||||||
|
"@angular/upgrade": "5.2.0",
|
||||||
|
"@microsoft/microsoft-graph-client": "1.0.0",
|
||||||
|
"@okta/okta-sdk-nodejs": "1.1.0",
|
||||||
|
"angular2-toaster": "4.0.2",
|
||||||
|
"angulartics2": "5.0.1",
|
||||||
|
"bootstrap": "4.1.0",
|
||||||
|
"core-js": "2.4.1",
|
||||||
|
"electron-log": "2.2.14",
|
||||||
|
"electron-updater": "2.21.4",
|
||||||
|
"googleapis": "29.0.0",
|
||||||
|
"keytar": "4.1.0",
|
||||||
|
"ldapjs": "1.0.2",
|
||||||
|
"lowdb": "1.0.0",
|
||||||
|
"lunr": "2.1.6",
|
||||||
|
"node-forge": "0.7.1",
|
||||||
|
"rxjs": "5.5.6",
|
||||||
|
"zone.js": "0.8.19"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
resources/background.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
resources/dmg.icns
Normal file
BIN
resources/dmg.iconset/icon_128x128.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
resources/dmg.iconset/icon_128x128@2x.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
resources/dmg.iconset/icon_16x16.png
Normal file
|
After Width: | Height: | Size: 692 B |
BIN
resources/dmg.iconset/icon_16x16@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
resources/dmg.iconset/icon_256x256.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
resources/dmg.iconset/icon_256x256@2x.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
resources/dmg.iconset/icon_32x32.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
resources/dmg.iconset/icon_32x32@2x.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
resources/dmg.iconset/icon_512x512.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
resources/dmg.iconset/icon_512x512@2x.png
Normal file
|
After Width: | Height: | Size: 492 KiB |
BIN
resources/icon.icns
Normal file
BIN
resources/icon.ico
Normal file
|
After Width: | Height: | Size: 279 KiB |
BIN
resources/icon.iconset/icon_128x128.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
resources/icon.iconset/icon_128x128@2x.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
resources/icon.iconset/icon_16x16.png
Normal file
|
After Width: | Height: | Size: 330 B |
BIN
resources/icon.iconset/icon_16x16@2x.png
Normal file
|
After Width: | Height: | Size: 507 B |
BIN
resources/icon.iconset/icon_256x256.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
resources/icon.iconset/icon_256x256@2x.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
resources/icon.iconset/icon_32x32.png
Normal file
|
After Width: | Height: | Size: 507 B |
BIN
resources/icon.iconset/icon_32x32@2x.png
Normal file
|
After Width: | Height: | Size: 823 B |
BIN
resources/icon.iconset/icon_512x512.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
resources/icon.iconset/icon_512x512@2x.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
resources/icon.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
resources/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
resources/icons/16x16.png
Normal file
|
After Width: | Height: | Size: 330 B |
BIN
resources/icons/256x256.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
resources/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 507 B |
BIN
resources/icons/48x48.png
Normal file
|
After Width: | Height: | Size: 681 B |
BIN
resources/icons/512x512.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
resources/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 961 B |
BIN
resources/installerSidebar.bmp
Normal file
|
After Width: | Height: | Size: 151 KiB |
@@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<configuration>
|
|
||||||
<startup>
|
|
||||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
|
|
||||||
</startup>
|
|
||||||
<runtime>
|
|
||||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
|
||||||
<dependentAssembly>
|
|
||||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral"/>
|
|
||||||
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0"/>
|
|
||||||
</dependentAssembly>
|
|
||||||
</assemblyBinding>
|
|
||||||
</runtime>
|
|
||||||
</configuration>
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
|
||||||
<PropertyGroup>
|
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
|
||||||
<ProjectGuid>{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}</ProjectGuid>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<RootNamespace>Bit.Console</RootNamespace>
|
|
||||||
<AssemblyName>Console</AssemblyName>
|
|
||||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
|
||||||
<FileAlignment>512</FileAlignment>
|
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
|
||||||
<TargetFrameworkProfile />
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
<DebugType>full</DebugType>
|
|
||||||
<Optimize>false</Optimize>
|
|
||||||
<OutputPath>bin\Debug\</OutputPath>
|
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
|
||||||
<DebugType>pdbonly</DebugType>
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<OutputPath>bin\Release\</OutputPath>
|
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="System" />
|
|
||||||
<Reference Include="System.Core" />
|
|
||||||
<Reference Include="System.Xml.Linq" />
|
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
|
||||||
<Reference Include="Microsoft.CSharp" />
|
|
||||||
<Reference Include="System.Data" />
|
|
||||||
<Reference Include="System.Net.Http" />
|
|
||||||
<Reference Include="System.Xml" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="Program.cs" />
|
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="App.config" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Core\Core.csproj">
|
|
||||||
<Project>{ae082484-a34c-4b3a-a69f-49e5ef298b27}</Project>
|
|
||||||
<Name>Core</Name>
|
|
||||||
</ProjectReference>
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
|
||||||
</Project>
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
using System.Reflection;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
// General Information about an assembly is controlled through the following
|
|
||||||
// set of attributes. Change these attribute values to modify the information
|
|
||||||
// associated with an assembly.
|
|
||||||
[assembly: AssemblyTitle("Console")]
|
|
||||||
[assembly: AssemblyDescription("")]
|
|
||||||
[assembly: AssemblyConfiguration("")]
|
|
||||||
[assembly: AssemblyCompany("")]
|
|
||||||
[assembly: AssemblyProduct("Console")]
|
|
||||||
[assembly: AssemblyCopyright("Copyright © 2017")]
|
|
||||||
[assembly: AssemblyTrademark("")]
|
|
||||||
[assembly: AssemblyCulture("")]
|
|
||||||
|
|
||||||
// Setting ComVisible to false makes the types in this assembly not visible
|
|
||||||
// to COM components. If you need to access a type in this assembly from
|
|
||||||
// COM, set the ComVisible attribute to true on that type.
|
|
||||||
[assembly: ComVisible(false)]
|
|
||||||
|
|
||||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
|
||||||
[assembly: Guid("dd4e5cd2-c9dd-4912-9a25-1600a07bf8c2")]
|
|
||||||
|
|
||||||
// Version information for an assembly consists of the following four values:
|
|
||||||
//
|
|
||||||
// Major Version
|
|
||||||
// Minor Version
|
|
||||||
// Build Number
|
|
||||||
// Revision
|
|
||||||
//
|
|
||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
|
||||||
// by using the '*' as shown below:
|
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
|
||||||
[assembly: AssemblyVersion("1.0.0.0")]
|
|
||||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
|
||||||
<PropertyGroup>
|
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
|
||||||
<ProjectGuid>{AE082484-A34C-4B3A-A69F-49E5EF298B27}</ProjectGuid>
|
|
||||||
<OutputType>Library</OutputType>
|
|
||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
|
||||||
<RootNamespace>Bit.Core</RootNamespace>
|
|
||||||
<AssemblyName>Core</AssemblyName>
|
|
||||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
|
||||||
<FileAlignment>512</FileAlignment>
|
|
||||||
<TargetFrameworkProfile />
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
<DebugType>full</DebugType>
|
|
||||||
<Optimize>false</Optimize>
|
|
||||||
<OutputPath>bin\Debug\</OutputPath>
|
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
|
||||||
<DebugType>pdbonly</DebugType>
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<OutputPath>bin\Release\</OutputPath>
|
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="BouncyCastle.Crypto, Version=1.8.1.0, Culture=neutral, PublicKeyToken=0e99375e54769942">
|
|
||||||
<HintPath>..\..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Google.Apis, Version=1.28.0.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\..\packages\Google.Apis.1.28.0\lib\net45\Google.Apis.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Google.Apis.Admin.Directory.directory_v1, Version=1.28.0.934, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\..\packages\Google.Apis.Admin.Directory.directory_v1.1.28.0.934\lib\net45\Google.Apis.Admin.Directory.directory_v1.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Google.Apis.Auth, Version=1.28.0.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\..\packages\Google.Apis.Auth.1.28.0\lib\net45\Google.Apis.Auth.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Google.Apis.Auth.PlatformServices, Version=1.28.0.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\..\packages\Google.Apis.Auth.1.28.0\lib\net45\Google.Apis.Auth.PlatformServices.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Google.Apis.Core, Version=1.28.0.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\..\packages\Google.Apis.Core.1.28.0\lib\net45\Google.Apis.Core.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Google.Apis.PlatformServices, Version=1.28.0.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\..\packages\Google.Apis.1.28.0\lib\net45\Google.Apis.PlatformServices.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Microsoft.Graph, Version=1.5.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\..\packages\Microsoft.Graph.1.5.1\lib\net45\Microsoft.Graph.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Microsoft.Graph.Core, Version=1.6.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\..\packages\Microsoft.Graph.Core.1.6.1\lib\net45\Microsoft.Graph.Core.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Microsoft.IdentityModel.Clients.ActiveDirectory, Version=3.16.0.14, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.16.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Microsoft.IdentityModel.Clients.ActiveDirectory.Platform, Version=3.16.0.14, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.16.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="System" />
|
|
||||||
<Reference Include="System.Core" />
|
|
||||||
<Reference Include="System.DirectoryServices" />
|
|
||||||
<Reference Include="System.DirectoryServices.Protocols" />
|
|
||||||
<Reference Include="System.Security" />
|
|
||||||
<Reference Include="System.ServiceProcess" />
|
|
||||||
<Reference Include="System.Xml.Linq" />
|
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
|
||||||
<Reference Include="Microsoft.CSharp" />
|
|
||||||
<Reference Include="System.Data" />
|
|
||||||
<Reference Include="System.Net.Http" />
|
|
||||||
<Reference Include="System.Xml" />
|
|
||||||
<Reference Include="Zlib.Portable, Version=1.11.0.0, Culture=neutral, PublicKeyToken=431cba815f6a8b5b, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\..\packages\Zlib.Portable.Signed.1.11.0\lib\portable-net4+sl5+wp8+win8+wpa81+MonoTouch+MonoAndroid\Zlib.Portable.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="Enums\DirectoryType.cs" />
|
|
||||||
<Compile Include="Enums\OrganizationUserType.cs" />
|
|
||||||
<Compile Include="Enums\OrganizationUserStatusType.cs" />
|
|
||||||
<Compile Include="Enums\TwoFactorProviderType.cs" />
|
|
||||||
<Compile Include="Enums\UserAccountControl.cs" />
|
|
||||||
<Compile Include="Models\ApiError.cs" />
|
|
||||||
<Compile Include="Models\ApiResult.cs" />
|
|
||||||
<Compile Include="Models\Entry.cs" />
|
|
||||||
<Compile Include="Models\GSuiteConfiguration.cs" />
|
|
||||||
<Compile Include="Models\ImportRequest.cs" />
|
|
||||||
<Compile Include="Models\AzureConfiguration.cs" />
|
|
||||||
<Compile Include="Models\ServerConfiguration.cs" />
|
|
||||||
<Compile Include="Models\Organization.cs" />
|
|
||||||
<Compile Include="Models\ProfileOrganizationResponse.cs" />
|
|
||||||
<Compile Include="Models\SyncConfiguration.cs" />
|
|
||||||
<Compile Include="Models\LdapConfiguration.cs" />
|
|
||||||
<Compile Include="Models\LoginResult.cs" />
|
|
||||||
<Compile Include="Models\ErrorResponse.cs" />
|
|
||||||
<Compile Include="Models\EncryptedData.cs" />
|
|
||||||
<Compile Include="Models\SyncResult.cs" />
|
|
||||||
<Compile Include="Models\TokenRequest.cs" />
|
|
||||||
<Compile Include="Models\ProfileResponse.cs" />
|
|
||||||
<Compile Include="Models\TokenResponse.cs" />
|
|
||||||
<Compile Include="Models\TwoFactorEmailRequest.cs" />
|
|
||||||
<Compile Include="Services\ApiService.cs" />
|
|
||||||
<Compile Include="Services\GSuiteDirectoryService.cs" />
|
|
||||||
<Compile Include="Services\ControllerService.cs" />
|
|
||||||
<Compile Include="Services\AzureDirectoryService.cs" />
|
|
||||||
<Compile Include="Services\LdapDirectoryService.cs" />
|
|
||||||
<Compile Include="Services\IDirectoryService.cs" />
|
|
||||||
<Compile Include="Services\SettingsService.cs" />
|
|
||||||
<Compile Include="Utilities\AzureAuthenticationProvider.cs" />
|
|
||||||
<Compile Include="Utilities\Constants.cs" />
|
|
||||||
<Compile Include="Utilities\Crypto.cs" />
|
|
||||||
<Compile Include="Services\TokenService.cs" />
|
|
||||||
<Compile Include="Services\AuthService.cs" />
|
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
|
||||||
<Compile Include="Utilities\Extensions.cs" />
|
|
||||||
<Compile Include="Utilities\Helpers.cs" />
|
|
||||||
<Compile Include="Utilities\Sync.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="app.config" />
|
|
||||||
<None Include="packages.config">
|
|
||||||
<SubType>Designer</SubType>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup />
|
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
|
||||||
</Project>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Enums
|
|
||||||
{
|
|
||||||
public enum DirectoryType : byte
|
|
||||||
{
|
|
||||||
ActiveDirectory = 0,
|
|
||||||
AzureActiveDirectory = 1,
|
|
||||||
Other = 2,
|
|
||||||
GSuite = 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Enums
|
|
||||||
{
|
|
||||||
public enum OrganizationUserStatusType : byte
|
|
||||||
{
|
|
||||||
Invited = 0,
|
|
||||||
Accepted = 1,
|
|
||||||
Confirmed = 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Enums
|
|
||||||
{
|
|
||||||
public enum OrganizationUserType : byte
|
|
||||||
{
|
|
||||||
Owner = 0,
|
|
||||||
Admin = 1,
|
|
||||||
User = 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace Bit.Core.Enums
|
|
||||||
{
|
|
||||||
public enum TwoFactorProviderType : byte
|
|
||||||
{
|
|
||||||
Authenticator = 0,
|
|
||||||
Email = 1,
|
|
||||||
Duo = 2,
|
|
||||||
YubiKey = 3,
|
|
||||||
U2f = 4,
|
|
||||||
Remember = 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Enums
|
|
||||||
{
|
|
||||||
[Flags]
|
|
||||||
public enum UserAccountControl : int
|
|
||||||
{
|
|
||||||
AccountDisabled = 0x00000002,
|
|
||||||
LockOut = 0x00000010,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class ApiError
|
|
||||||
{
|
|
||||||
public string Message { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Net;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class ApiResult<T>
|
|
||||||
{
|
|
||||||
private List<ApiError> m_errors = new List<ApiError>();
|
|
||||||
|
|
||||||
public bool Succeeded { get; private set; }
|
|
||||||
public T Result { get; set; }
|
|
||||||
public IEnumerable<ApiError> Errors => m_errors;
|
|
||||||
public HttpStatusCode StatusCode { get; private set; }
|
|
||||||
|
|
||||||
public static ApiResult<T> Success(T result, HttpStatusCode statusCode)
|
|
||||||
{
|
|
||||||
return new ApiResult<T>
|
|
||||||
{
|
|
||||||
Succeeded = true,
|
|
||||||
Result = result,
|
|
||||||
StatusCode = statusCode
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ApiResult<T> Failed(HttpStatusCode statusCode, params ApiError[] errors)
|
|
||||||
{
|
|
||||||
var result = new ApiResult<T>
|
|
||||||
{
|
|
||||||
Succeeded = false,
|
|
||||||
StatusCode = statusCode
|
|
||||||
};
|
|
||||||
|
|
||||||
if(errors != null)
|
|
||||||
{
|
|
||||||
result.m_errors.AddRange(errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ApiResult
|
|
||||||
{
|
|
||||||
private List<ApiError> m_errors = new List<ApiError>();
|
|
||||||
|
|
||||||
public bool Succeeded { get; private set; }
|
|
||||||
public IEnumerable<ApiError> Errors => m_errors;
|
|
||||||
public HttpStatusCode StatusCode { get; private set; }
|
|
||||||
|
|
||||||
public static ApiResult Success(HttpStatusCode statusCode)
|
|
||||||
{
|
|
||||||
return new ApiResult
|
|
||||||
{
|
|
||||||
Succeeded = true,
|
|
||||||
StatusCode = statusCode
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ApiResult Failed(HttpStatusCode statusCode, params ApiError[] errors)
|
|
||||||
{
|
|
||||||
var result = new ApiResult
|
|
||||||
{
|
|
||||||
Succeeded = false,
|
|
||||||
StatusCode = statusCode
|
|
||||||
};
|
|
||||||
|
|
||||||
if(errors != null)
|
|
||||||
{
|
|
||||||
result.m_errors.AddRange(errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.DirectoryServices;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class AzureConfiguration
|
|
||||||
{
|
|
||||||
public string Tenant { get; set; } = "yourcompany.onmicrosoft.com";
|
|
||||||
public string Id { get; set; }
|
|
||||||
public EncryptedData Secret { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class EncryptedData
|
|
||||||
{
|
|
||||||
public EncryptedData() { }
|
|
||||||
|
|
||||||
public EncryptedData(byte[] plainValue)
|
|
||||||
{
|
|
||||||
IV = RandomBytes();
|
|
||||||
Value = ProtectedData.Protect(plainValue, IV, DataProtectionScope.LocalMachine);
|
|
||||||
}
|
|
||||||
|
|
||||||
public EncryptedData(string plainValue)
|
|
||||||
{
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(plainValue);
|
|
||||||
IV = RandomBytes();
|
|
||||||
Value = ProtectedData.Protect(bytes, IV, DataProtectionScope.LocalMachine);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] Value { get; set; }
|
|
||||||
public byte[] IV { get; set; }
|
|
||||||
|
|
||||||
public byte[] Decrypt()
|
|
||||||
{
|
|
||||||
return ProtectedData.Unprotect(Value, IV, DataProtectionScope.LocalMachine);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string DecryptToString()
|
|
||||||
{
|
|
||||||
var bytes = ProtectedData.Unprotect(Value, IV, DataProtectionScope.LocalMachine);
|
|
||||||
return Encoding.UTF8.GetString(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] RandomBytes()
|
|
||||||
{
|
|
||||||
var entropy = new byte[16];
|
|
||||||
new RNGCryptoServiceProvider().GetBytes(entropy);
|
|
||||||
return entropy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public abstract class Entry
|
|
||||||
{
|
|
||||||
public string ReferenceId { get; set; }
|
|
||||||
public string ExternalId { get; set; }
|
|
||||||
public DateTime? CreationDate { get; set; }
|
|
||||||
public DateTime? RevisionDate { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class GroupEntry : Entry
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public HashSet<string> UserMemberExternalIds { get; set; } = new HashSet<string>();
|
|
||||||
public HashSet<string> GroupMemberReferenceIds { get; set; } = new HashSet<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UserEntry : Entry
|
|
||||||
{
|
|
||||||
public string Email { get; set; }
|
|
||||||
public bool Disabled { get; set; }
|
|
||||||
public bool Deleted { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class ErrorResponse
|
|
||||||
{
|
|
||||||
public string Message { get; set; }
|
|
||||||
public Dictionary<string, IEnumerable<string>> ValidationErrors { get; set; }
|
|
||||||
// For use in development environments.
|
|
||||||
public string ExceptionMessage { get; set; }
|
|
||||||
public string ExceptionStackTrace { get; set; }
|
|
||||||
public string InnerExceptionMessage { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class GSuiteConfiguration
|
|
||||||
{
|
|
||||||
public string SecretFile { get; set; } = "client_secret.json";
|
|
||||||
public string Customer { get; set; }
|
|
||||||
public string Domain { get; set; } = "yourcompany.com";
|
|
||||||
public string AdminUser { get; set; } = "adminuser@yourcompany.com";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
using Bit.Core.Services;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class ImportRequest
|
|
||||||
{
|
|
||||||
public ImportRequest(List<GroupEntry> groups, List<UserEntry> users)
|
|
||||||
{
|
|
||||||
Groups = groups?.Select(g => new Group(g)).ToArray() ?? new Group[] { };
|
|
||||||
Users = users?.Select(u => new User(u)).ToArray() ?? new User[] { };
|
|
||||||
}
|
|
||||||
|
|
||||||
public Group[] Groups { get; set; }
|
|
||||||
public User[] Users { get; set; }
|
|
||||||
|
|
||||||
public class Group
|
|
||||||
{
|
|
||||||
public Group(GroupEntry entry)
|
|
||||||
{
|
|
||||||
Name = entry.Name;
|
|
||||||
ExternalId = entry.ExternalId;
|
|
||||||
Users = entry.UserMemberExternalIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string ExternalId { get; set; }
|
|
||||||
public IEnumerable<string> Users { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class User
|
|
||||||
{
|
|
||||||
public User(UserEntry entry)
|
|
||||||
{
|
|
||||||
Email = entry.Email;
|
|
||||||
Deleted = (SettingsService.Instance.Sync.RemoveDisabledUsers && entry.Disabled) || entry.Deleted;
|
|
||||||
ExternalId = entry.ExternalId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ExternalId { get; set; }
|
|
||||||
public string Email { get; set; }
|
|
||||||
public bool Deleted { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
using Bit.Core.Services;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.DirectoryServices;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class LdapConfiguration
|
|
||||||
{
|
|
||||||
public string Address { get; set; }
|
|
||||||
public string Port { get; set; } = "389";
|
|
||||||
public string Path { get; set; }
|
|
||||||
public string Username { get; set; }
|
|
||||||
public EncryptedData Password { get; set; }
|
|
||||||
public Enums.DirectoryType Type { get; set; } = Enums.DirectoryType.ActiveDirectory;
|
|
||||||
|
|
||||||
public DirectoryEntry GetUserDirectoryEntry()
|
|
||||||
{
|
|
||||||
return GetPathedDirectoryEntry(SettingsService.Instance.Sync.Ldap.UserPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DirectoryEntry GetGroupDirectoryEntry()
|
|
||||||
{
|
|
||||||
return GetPathedDirectoryEntry(SettingsService.Instance.Sync.Ldap.GroupPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DirectoryEntry GetPathedDirectoryEntry(string pathPrefix = null)
|
|
||||||
{
|
|
||||||
var path = Path;
|
|
||||||
if(!string.IsNullOrWhiteSpace(pathPrefix))
|
|
||||||
{
|
|
||||||
path = string.Concat(pathPrefix, ",", path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetDirectoryEntry(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DirectoryEntry GetBasePathDirectoryEntry()
|
|
||||||
{
|
|
||||||
var path = Path.Substring(Path.IndexOf("dc=", StringComparison.InvariantCultureIgnoreCase));
|
|
||||||
return GetDirectoryEntry(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DirectoryEntry GetDirectoryEntry(string path = null)
|
|
||||||
{
|
|
||||||
if(Password == null && string.IsNullOrWhiteSpace(Username))
|
|
||||||
{
|
|
||||||
return new DirectoryEntry(ServerPath(path));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new DirectoryEntry(ServerPath(path), Username, Password.DecryptToString(), AuthenticationTypes.None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string ServerPath(string path)
|
|
||||||
{
|
|
||||||
return $"LDAP://{Address}:{Port}/{path}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using Bit.Core.Enums;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class LoginResult
|
|
||||||
{
|
|
||||||
public bool Success { get; set; }
|
|
||||||
public string ErrorMessage { get; set; }
|
|
||||||
public bool TwoFactorRequired => TwoFactorProviders != null && TwoFactorProviders.Count > 0;
|
|
||||||
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; }
|
|
||||||
public string MasterPasswordHash { get; set; }
|
|
||||||
public List<Organization> Organizations { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class Organization
|
|
||||||
{
|
|
||||||
public Organization() { }
|
|
||||||
|
|
||||||
public Organization(ProfileOrganizationResponseModel org)
|
|
||||||
{
|
|
||||||
Name = org.Name;
|
|
||||||
Id = org.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using Bit.Core.Enums;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class ProfileOrganizationResponseModel
|
|
||||||
{
|
|
||||||
public string Id { get; set; }
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string Key { get; set; }
|
|
||||||
public OrganizationUserStatusType Status { get; set; }
|
|
||||||
public OrganizationUserType Type { get; set; }
|
|
||||||
public bool Enabled { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class ProfileResponse
|
|
||||||
{
|
|
||||||
public string Id { get; set; }
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string Email { get; set; }
|
|
||||||
public string MasterPasswordHint { get; set; }
|
|
||||||
public string Culture { get; set; }
|
|
||||||
public bool TwoFactorEnabled { get; set; }
|
|
||||||
public IEnumerable<ProfileOrganizationResponseModel> Organizations { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.DirectoryServices;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class ServerConfiguration
|
|
||||||
{
|
|
||||||
public Enums.DirectoryType Type { get; set; } = Enums.DirectoryType.ActiveDirectory;
|
|
||||||
public LdapConfiguration Ldap { get; set; }
|
|
||||||
public AzureConfiguration Azure { get; set; }
|
|
||||||
public GSuiteConfiguration GSuite { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
using Bit.Core.Enums;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.DirectoryServices;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class SyncConfiguration
|
|
||||||
{
|
|
||||||
public SyncConfiguration() { }
|
|
||||||
|
|
||||||
public SyncConfiguration(DirectoryType type)
|
|
||||||
{
|
|
||||||
Ldap = new LdapSyncConfiguration(type);
|
|
||||||
|
|
||||||
switch(type)
|
|
||||||
{
|
|
||||||
case DirectoryType.ActiveDirectory:
|
|
||||||
break;
|
|
||||||
case DirectoryType.AzureActiveDirectory:
|
|
||||||
break;
|
|
||||||
case DirectoryType.Other:
|
|
||||||
break;
|
|
||||||
case DirectoryType.GSuite:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Depending on what server type you are using, filters are be one of the following:
|
|
||||||
*
|
|
||||||
* 1. ActiveDirectory or Other
|
|
||||||
* - LDAP query/filter syntax
|
|
||||||
* - Read more at: http://bit.ly/2qyLpzW
|
|
||||||
* - ex. "(&(givenName=John)(|(l=Dallas)(l=Austin)))"
|
|
||||||
*
|
|
||||||
* 2. AzureActiveDirectory
|
|
||||||
* - OData syntax for a Microsoft Graph query parameter '$filter'
|
|
||||||
* - Read more at http://bit.ly/2q3FOOD
|
|
||||||
* - ex. "startswith(displayName,'J')"
|
|
||||||
*
|
|
||||||
* 3. G Suite
|
|
||||||
* - Group Filter
|
|
||||||
* - Custom filtering syntax that allows you to exclude or include a comma separated list of group names.
|
|
||||||
* - ex. "include:Group A,Sales People,My Other Group"
|
|
||||||
* or "exclude:Group C,Developers,Some Other Group"
|
|
||||||
* - User Filter
|
|
||||||
* - Custom filtering syntax that allows you to exclude or include a comma separated list of group names.
|
|
||||||
* - Allows you to concatenate a G Suite Admin API user search query to the end of the filter after delimiting
|
|
||||||
* the include/exclude filter with a pipe (|).
|
|
||||||
* - Read more at http://bit.ly/2rlTskX
|
|
||||||
* - ex.
|
|
||||||
* or "include:joe@company.com,bill@company.com,tom@company.com"
|
|
||||||
* or "exclude:john@company.com,bill@company.com|orgName=Engineering orgTitle:Manager"
|
|
||||||
* or "|orgName=Engineering orgTitle:Manager"
|
|
||||||
*/
|
|
||||||
|
|
||||||
public string GroupFilter { get; set; }
|
|
||||||
public string UserFilter { get; set; }
|
|
||||||
|
|
||||||
public bool SyncGroups { get; set; } = true;
|
|
||||||
public bool SyncUsers { get; set; } = true;
|
|
||||||
public int IntervalMinutes { get; set; } = 5;
|
|
||||||
public bool RemoveDisabledUsers { get; set; }
|
|
||||||
public LdapSyncConfiguration Ldap { get; set; } = new LdapSyncConfiguration();
|
|
||||||
|
|
||||||
public class LdapSyncConfiguration
|
|
||||||
{
|
|
||||||
public LdapSyncConfiguration() { }
|
|
||||||
|
|
||||||
public LdapSyncConfiguration(DirectoryType type)
|
|
||||||
{
|
|
||||||
switch(type)
|
|
||||||
{
|
|
||||||
case DirectoryType.ActiveDirectory:
|
|
||||||
CreationDateAttribute = "whenCreated";
|
|
||||||
RevisionDateAttribute = "whenChanged";
|
|
||||||
UserEmailPrefixAttribute = "sAMAccountName";
|
|
||||||
UserPath = "CN=Users";
|
|
||||||
GroupPath = "CN=Users";
|
|
||||||
break;
|
|
||||||
case DirectoryType.Other:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string UserPath { get; set; }
|
|
||||||
public string GroupPath { get; set; }
|
|
||||||
public string UserObjectClass { get; set; } = "person";
|
|
||||||
public string GroupObjectClass { get; set; } = "group";
|
|
||||||
public string MemberAttribute { get; set; } = "member";
|
|
||||||
public string GroupNameAttribute { get; set; } = "name";
|
|
||||||
public string UserEmailAttribute { get; set; } = "mail";
|
|
||||||
public bool EmailPrefixSuffix { get; set; } = false;
|
|
||||||
public string UserEmailPrefixAttribute { get; set; } = "cn";
|
|
||||||
public string UserEmailSuffix { get; set; } = "@companyname.com";
|
|
||||||
public string CreationDateAttribute { get; set; }
|
|
||||||
public string RevisionDateAttribute { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class SyncResult
|
|
||||||
{
|
|
||||||
public bool Success { get; set; }
|
|
||||||
public string ErrorMessage { get; set; }
|
|
||||||
public List<GroupEntry> Groups { get; set; } = new List<GroupEntry>();
|
|
||||||
public List<UserEntry> Users { get; set; } = new List<UserEntry>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
using Bit.Core.Enums;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class TokenRequest
|
|
||||||
{
|
|
||||||
public string Email { get; set; }
|
|
||||||
public string MasterPasswordHash { get; set; }
|
|
||||||
public string Token { get; set; }
|
|
||||||
public TwoFactorProviderType? Provider { get; set; }
|
|
||||||
public bool Remember { get; set; }
|
|
||||||
|
|
||||||
public IDictionary<string, string> ToIdentityTokenRequest()
|
|
||||||
{
|
|
||||||
var dict = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{ "grant_type", "password" },
|
|
||||||
{ "username", Email },
|
|
||||||
{ "password", MasterPasswordHash },
|
|
||||||
{ "scope", "api offline_access" },
|
|
||||||
{ "client_id", "connector" }
|
|
||||||
};
|
|
||||||
|
|
||||||
if(Token != null && Provider.HasValue)
|
|
||||||
{
|
|
||||||
dict.Add("TwoFactorToken", Token);
|
|
||||||
dict.Add("TwoFactorProvider", ((byte)(Provider.Value)).ToString());
|
|
||||||
dict.Add("TwoFactorRemember", Remember ? "1" : "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
return dict;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using Bit.Core.Enums;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class TokenResponse
|
|
||||||
{
|
|
||||||
[JsonProperty("access_token")]
|
|
||||||
public string AccessToken { get; set; }
|
|
||||||
[JsonProperty("expires_in")]
|
|
||||||
public long ExpiresIn { get; set; }
|
|
||||||
[JsonProperty("refresh_token")]
|
|
||||||
public string RefreshToken { get; set; }
|
|
||||||
[JsonProperty("token_type")]
|
|
||||||
public string TokenType { get; set; }
|
|
||||||
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders2 { get; set; }
|
|
||||||
public string PrivateKey { get; set; }
|
|
||||||
public string TwoFactorToken { get; set; }
|
|
||||||
public string Key { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class TwoFactorEmailRequest
|
|
||||||
{
|
|
||||||
public string Email { get; set; }
|
|
||||||
public string MasterPasswordHash { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
using System.Reflection;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
// General Information about an assembly is controlled through the following
|
|
||||||
// set of attributes. Change these attribute values to modify the information
|
|
||||||
// associated with an assembly.
|
|
||||||
[assembly: AssemblyTitle("Core")]
|
|
||||||
[assembly: AssemblyDescription("")]
|
|
||||||
[assembly: AssemblyConfiguration("")]
|
|
||||||
[assembly: AssemblyCompany("")]
|
|
||||||
[assembly: AssemblyProduct("Core")]
|
|
||||||
[assembly: AssemblyCopyright("Copyright © 2017")]
|
|
||||||
[assembly: AssemblyTrademark("")]
|
|
||||||
[assembly: AssemblyCulture("")]
|
|
||||||
|
|
||||||
// Setting ComVisible to false makes the types in this assembly not visible
|
|
||||||
// to COM components. If you need to access a type in this assembly from
|
|
||||||
// COM, set the ComVisible attribute to true on that type.
|
|
||||||
[assembly: ComVisible(false)]
|
|
||||||
|
|
||||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
|
||||||
[assembly: Guid("ae082484-a34c-4b3a-a69f-49e5ef298b27")]
|
|
||||||
|
|
||||||
// Version information for an assembly consists of the following four values:
|
|
||||||
//
|
|
||||||
// Major Version
|
|
||||||
// Minor Version
|
|
||||||
// Build Number
|
|
||||||
// Revision
|
|
||||||
//
|
|
||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
|
||||||
// by using the '*' as shown below:
|
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
|
||||||
[assembly: AssemblyVersion("1.0.0.0")]
|
|
||||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
||||||
@@ -1,322 +0,0 @@
|
|||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Models;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
|
||||||
{
|
|
||||||
public class ApiService
|
|
||||||
{
|
|
||||||
private static ApiService _instance;
|
|
||||||
|
|
||||||
private ApiService()
|
|
||||||
{
|
|
||||||
Client = new HttpClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ApiService Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(_instance == null)
|
|
||||||
{
|
|
||||||
_instance = new ApiService();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected HttpClient Client { get; private set; }
|
|
||||||
|
|
||||||
public virtual async Task<ApiResult<TokenResponse>> PostTokenAsync(TokenRequest requestObj)
|
|
||||||
{
|
|
||||||
var requestMessage = new HttpRequestMessage
|
|
||||||
{
|
|
||||||
Method = HttpMethod.Post,
|
|
||||||
RequestUri = new Uri(string.Concat(SettingsService.Instance.IdentityBaseUrl, "/connect/token")),
|
|
||||||
Content = new FormUrlEncodedContent(requestObj.ToIdentityTokenRequest())
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await Client.SendAsync(requestMessage).ConfigureAwait(false);
|
|
||||||
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
if(!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
var errorResponse = JObject.Parse(responseContent);
|
|
||||||
if(errorResponse["TwoFactorProviders2"] != null)
|
|
||||||
{
|
|
||||||
return ApiResult<TokenResponse>.Success(new TokenResponse
|
|
||||||
{
|
|
||||||
TwoFactorProviders2 = errorResponse["TwoFactorProviders2"]
|
|
||||||
.ToObject<Dictionary<TwoFactorProviderType, Dictionary<string, object>>>()
|
|
||||||
}, response.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await HandleErrorAsync<TokenResponse>(response).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var responseObj = JsonConvert.DeserializeObject<TokenResponse>(responseContent);
|
|
||||||
return ApiResult<TokenResponse>.Success(responseObj, response.StatusCode);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return HandledWebException<TokenResponse>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual async Task<ApiResult> PostImportAsync(ImportRequest requestObj)
|
|
||||||
{
|
|
||||||
var tokenStateResponse = await HandleTokenStateAsync();
|
|
||||||
if(!tokenStateResponse.Succeeded)
|
|
||||||
{
|
|
||||||
return tokenStateResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
var stringContent = JsonConvert.SerializeObject(requestObj);
|
|
||||||
var requestMessage = new HttpRequestMessage
|
|
||||||
{
|
|
||||||
Method = HttpMethod.Post,
|
|
||||||
RequestUri = new Uri(string.Concat(SettingsService.Instance.ApiBaseUrl, "/organizations/",
|
|
||||||
SettingsService.Instance.Organization.Id, "/import")),
|
|
||||||
Content = new StringContent(stringContent, Encoding.UTF8, "application/json"),
|
|
||||||
};
|
|
||||||
|
|
||||||
requestMessage.Headers.Add("Authorization", $"Bearer3 {TokenService.Instance.AccessToken}");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await Client.SendAsync(requestMessage).ConfigureAwait(false);
|
|
||||||
if(!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
return await HandleErrorAsync(response).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ApiResult.Success(response.StatusCode);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return HandledWebException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual async Task<ApiResult<ProfileResponse>> GetProfileAsync()
|
|
||||||
{
|
|
||||||
var tokenStateResponse = await HandleTokenStateAsync<ProfileResponse>();
|
|
||||||
if(!tokenStateResponse.Succeeded)
|
|
||||||
{
|
|
||||||
return tokenStateResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
var requestMessage = new HttpRequestMessage()
|
|
||||||
{
|
|
||||||
Method = HttpMethod.Get,
|
|
||||||
RequestUri = new Uri(string.Concat(SettingsService.Instance.ApiBaseUrl, "/accounts/profile")),
|
|
||||||
};
|
|
||||||
|
|
||||||
requestMessage.Headers.Add("Authorization", $"Bearer3 {TokenService.Instance.AccessToken}");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await Client.SendAsync(requestMessage).ConfigureAwait(false);
|
|
||||||
if(!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
return await HandleErrorAsync<ProfileResponse>(response).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
||||||
var responseObj = JsonConvert.DeserializeObject<ProfileResponse>(responseContent);
|
|
||||||
return ApiResult<ProfileResponse>.Success(responseObj, response.StatusCode);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return HandledWebException<ProfileResponse>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual async Task<ApiResult> PostTwoFactorSendEmailLoginAsync(TwoFactorEmailRequest requestObj)
|
|
||||||
{
|
|
||||||
var stringContent = JsonConvert.SerializeObject(requestObj);
|
|
||||||
|
|
||||||
var requestMessage = new HttpRequestMessage()
|
|
||||||
{
|
|
||||||
Method = HttpMethod.Post,
|
|
||||||
RequestUri = new Uri(string.Concat(SettingsService.Instance.ApiBaseUrl, "/two-factor/send-email-login")),
|
|
||||||
Content = new StringContent(stringContent, Encoding.UTF8, "application/json")
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await Client.SendAsync(requestMessage).ConfigureAwait(false);
|
|
||||||
if(!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
return await HandleErrorAsync(response).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ApiResult.Success(response.StatusCode);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return HandledWebException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ApiResult HandledWebException()
|
|
||||||
{
|
|
||||||
return ApiResult.Failed(HttpStatusCode.BadGateway,
|
|
||||||
new ApiError { Message = "There is a problem connecting to the server." });
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ApiResult<T> HandledWebException<T>()
|
|
||||||
{
|
|
||||||
return ApiResult<T>.Failed(HttpStatusCode.BadGateway,
|
|
||||||
new ApiError { Message = "There is a problem connecting to the server." });
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task<ApiResult> HandleTokenStateAsync()
|
|
||||||
{
|
|
||||||
return await HandleTokenStateAsync(
|
|
||||||
() => ApiResult.Success(HttpStatusCode.OK),
|
|
||||||
() => HandledWebException(),
|
|
||||||
(r) => HandleErrorAsync(r));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task<ApiResult<T>> HandleTokenStateAsync<T>()
|
|
||||||
{
|
|
||||||
return await HandleTokenStateAsync(
|
|
||||||
() => ApiResult<T>.Success(default(T), HttpStatusCode.OK),
|
|
||||||
() => HandledWebException<T>(),
|
|
||||||
(r) => HandleErrorAsync<T>(r));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<T> HandleTokenStateAsync<T>(Func<T> success, Func<T> webException,
|
|
||||||
Func<HttpResponseMessage, Task<T>> error)
|
|
||||||
{
|
|
||||||
if(TokenService.Instance.AccessTokenNeedsRefresh && !string.IsNullOrWhiteSpace(TokenService.Instance.RefreshToken))
|
|
||||||
{
|
|
||||||
var requestMessage = new HttpRequestMessage
|
|
||||||
{
|
|
||||||
Method = HttpMethod.Post,
|
|
||||||
RequestUri = new Uri(string.Concat(SettingsService.Instance.IdentityBaseUrl, "/connect/token")),
|
|
||||||
Content = new FormUrlEncodedContent(
|
|
||||||
new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{ "grant_type", "refresh_token" },
|
|
||||||
{ "client_id", "connector" },
|
|
||||||
{ "refresh_token", TokenService.Instance.RefreshToken }
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await Client.SendAsync(requestMessage).ConfigureAwait(false);
|
|
||||||
if(!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
if(response.StatusCode == HttpStatusCode.BadRequest)
|
|
||||||
{
|
|
||||||
response.StatusCode = HttpStatusCode.Unauthorized;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await error.Invoke(response).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
||||||
var tokenResponse = JsonConvert.DeserializeObject<TokenResponse>(responseContent);
|
|
||||||
TokenService.Instance.AccessToken = tokenResponse.AccessToken;
|
|
||||||
TokenService.Instance.RefreshToken = tokenResponse.RefreshToken;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return webException.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return success.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task<ApiResult<T>> HandleErrorAsync<T>(HttpResponseMessage response)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var errors = await ParseErrorsAsync(response).ConfigureAwait(false);
|
|
||||||
return ApiResult<T>.Failed(response.StatusCode, errors.ToArray());
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{ }
|
|
||||||
|
|
||||||
return ApiResult<T>.Failed(response.StatusCode,
|
|
||||||
new ApiError { Message = "An unknown error has occurred." });
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task<ApiResult> HandleErrorAsync(HttpResponseMessage response)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var errors = await ParseErrorsAsync(response).ConfigureAwait(false);
|
|
||||||
return ApiResult.Failed(response.StatusCode, errors.ToArray());
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{ }
|
|
||||||
|
|
||||||
return ApiResult.Failed(response.StatusCode,
|
|
||||||
new ApiError { Message = "An unknown error has occurred." });
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<List<ApiError>> ParseErrorsAsync(HttpResponseMessage response)
|
|
||||||
{
|
|
||||||
var errors = new List<ApiError>();
|
|
||||||
var statusCode = (int)response.StatusCode;
|
|
||||||
if(statusCode >= 400 && statusCode <= 500)
|
|
||||||
{
|
|
||||||
ErrorResponse errorResponseModel = null;
|
|
||||||
|
|
||||||
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
||||||
if(!string.IsNullOrWhiteSpace(responseContent))
|
|
||||||
{
|
|
||||||
var errorResponse = JObject.Parse(responseContent);
|
|
||||||
if(errorResponse["ErrorModel"] != null && errorResponse["ErrorModel"]["Message"] != null)
|
|
||||||
{
|
|
||||||
errorResponseModel = errorResponse["ErrorModel"].ToObject<ErrorResponse>();
|
|
||||||
}
|
|
||||||
else if(errorResponse["Message"] != null)
|
|
||||||
{
|
|
||||||
errorResponseModel = errorResponse.ToObject<ErrorResponse>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(errorResponseModel != null)
|
|
||||||
{
|
|
||||||
if((errorResponseModel.ValidationErrors?.Count ?? 0) > 0)
|
|
||||||
{
|
|
||||||
foreach(var valError in errorResponseModel.ValidationErrors)
|
|
||||||
{
|
|
||||||
foreach(var errorMessage in valError.Value)
|
|
||||||
{
|
|
||||||
errors.Add(new ApiError { Message = errorMessage });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
errors.Add(new ApiError { Message = errorResponseModel.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(errors.Count == 0)
|
|
||||||
{
|
|
||||||
errors.Add(new ApiError { Message = "An unknown error has occurred." });
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Models;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
|
||||||
{
|
|
||||||
public class AuthService
|
|
||||||
{
|
|
||||||
private static AuthService _instance;
|
|
||||||
|
|
||||||
private AuthService() { }
|
|
||||||
|
|
||||||
public static AuthService Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(_instance == null)
|
|
||||||
{
|
|
||||||
_instance = new AuthService();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Authenticated => !string.IsNullOrWhiteSpace(TokenService.Instance.AccessToken);
|
|
||||||
public bool OrganizationSet => SettingsService.Instance.Organization != null;
|
|
||||||
|
|
||||||
public void LogOut()
|
|
||||||
{
|
|
||||||
TokenService.Instance.AccessToken = null;
|
|
||||||
TokenService.Instance.RefreshToken = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<LoginResult> LogInAsync(string email, string masterPassword)
|
|
||||||
{
|
|
||||||
var normalizedEmail = email.Trim().ToLower();
|
|
||||||
var key = Crypto.MakeKeyFromPassword(masterPassword, normalizedEmail);
|
|
||||||
|
|
||||||
var request = new TokenRequest
|
|
||||||
{
|
|
||||||
Email = normalizedEmail,
|
|
||||||
MasterPasswordHash = Crypto.HashPasswordBase64(key, masterPassword)
|
|
||||||
};
|
|
||||||
|
|
||||||
var response = await ApiService.Instance.PostTokenAsync(request);
|
|
||||||
|
|
||||||
masterPassword = null;
|
|
||||||
key = null;
|
|
||||||
|
|
||||||
var result = new LoginResult();
|
|
||||||
if(!response.Succeeded)
|
|
||||||
{
|
|
||||||
result.Success = false;
|
|
||||||
result.ErrorMessage = response.Errors.FirstOrDefault()?.Message;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Success = true;
|
|
||||||
if(response.Result.TwoFactorProviders2 != null && response.Result.TwoFactorProviders2.Count > 0)
|
|
||||||
{
|
|
||||||
result.TwoFactorProviders = response.Result.TwoFactorProviders2;
|
|
||||||
result.MasterPasswordHash = request.MasterPasswordHash;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await ProcessLogInSuccessAsync(response.Result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<LoginResult> LogInTwoFactorAsync(TwoFactorProviderType type, string token, string email,
|
|
||||||
string masterPassword)
|
|
||||||
{
|
|
||||||
var normalizedEmail = email.Trim().ToLower();
|
|
||||||
var key = Crypto.MakeKeyFromPassword(masterPassword, normalizedEmail);
|
|
||||||
|
|
||||||
var result = await LogInTwoFactorWithHashAsync(type, token, email, Crypto.HashPasswordBase64(key, masterPassword));
|
|
||||||
|
|
||||||
key = null;
|
|
||||||
masterPassword = null;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<LoginResult> LogInTwoFactorWithHashAsync(TwoFactorProviderType type, string token, string email,
|
|
||||||
string masterPasswordHash)
|
|
||||||
{
|
|
||||||
if(type == TwoFactorProviderType.Email || type == TwoFactorProviderType.Authenticator)
|
|
||||||
{
|
|
||||||
token = token.Trim().Replace(" ", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = new TokenRequest
|
|
||||||
{
|
|
||||||
Email = email.Trim().ToLower(),
|
|
||||||
MasterPasswordHash = masterPasswordHash,
|
|
||||||
Token = token,
|
|
||||||
Provider = type,
|
|
||||||
Remember = false
|
|
||||||
};
|
|
||||||
|
|
||||||
var response = await ApiService.Instance.PostTokenAsync(request);
|
|
||||||
|
|
||||||
if(!response.Succeeded)
|
|
||||||
{
|
|
||||||
var result = new LoginResult();
|
|
||||||
result.Success = false;
|
|
||||||
result.ErrorMessage = response.Errors.FirstOrDefault()?.Message;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await ProcessLogInSuccessAsync(response.Result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<LoginResult> ProcessLogInSuccessAsync(TokenResponse response)
|
|
||||||
{
|
|
||||||
TokenService.Instance.AccessToken = response.AccessToken;
|
|
||||||
TokenService.Instance.RefreshToken = response.RefreshToken;
|
|
||||||
|
|
||||||
var result = new LoginResult();
|
|
||||||
|
|
||||||
var profile = await ApiService.Instance.GetProfileAsync();
|
|
||||||
if(profile.Succeeded)
|
|
||||||
{
|
|
||||||
var adminOrgs = profile.Result.Organizations.Where(o =>
|
|
||||||
o.Status == OrganizationUserStatusType.Confirmed &&
|
|
||||||
o.Type != OrganizationUserType.User);
|
|
||||||
if(!adminOrgs.Any())
|
|
||||||
{
|
|
||||||
LogOut();
|
|
||||||
result.Success = false;
|
|
||||||
result.ErrorMessage = "You are not an admin of any organizations.";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Organizations = adminOrgs.Select(o => new Organization(o)).ToList();
|
|
||||||
if(result.Organizations.Count == 1)
|
|
||||||
{
|
|
||||||
SettingsService.Instance.Organization = new Organization(adminOrgs.First());
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Success = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LogOut();
|
|
||||||
result.Success = false;
|
|
||||||
result.ErrorMessage = "Could not load profile.";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,284 +0,0 @@
|
|||||||
using Bit.Core.Models;
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.Graph;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
|
||||||
{
|
|
||||||
public class AzureDirectoryService : IDirectoryService
|
|
||||||
{
|
|
||||||
private static AzureDirectoryService _instance;
|
|
||||||
private static GraphServiceClient _graphClient;
|
|
||||||
|
|
||||||
private AzureDirectoryService()
|
|
||||||
{
|
|
||||||
_graphClient = new GraphServiceClient(new AzureAuthenticationProvider());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IDirectoryService Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(_instance == null)
|
|
||||||
{
|
|
||||||
_instance = new AzureDirectoryService();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Tuple<List<GroupEntry>, List<UserEntry>>> GetEntriesAsync(bool force = false)
|
|
||||||
{
|
|
||||||
if(!AuthService.Instance.Authenticated || !AuthService.Instance.OrganizationSet)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Not logged in or have an org set.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Server?.Azure == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for directory server.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Sync == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for sync.");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<UserEntry> users = null;
|
|
||||||
if(SettingsService.Instance.Sync.SyncUsers)
|
|
||||||
{
|
|
||||||
users = await GetUsersAsync(force);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<GroupEntry> groups = null;
|
|
||||||
if(SettingsService.Instance.Sync.SyncGroups)
|
|
||||||
{
|
|
||||||
groups = await GetGroupsAsync(force || (users?.Any(u => !u.Deleted && !u.Disabled) ?? false));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Tuple<List<GroupEntry>, List<UserEntry>>(groups, users);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async static Task<List<GroupEntry>> GetGroupsAsync(bool force = false)
|
|
||||||
{
|
|
||||||
if(!SettingsService.Instance.Sync.SyncGroups)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Not configured to sync groups.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Server?.Azure == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for directory server.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Sync == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for sync.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!AuthService.Instance.Authenticated)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Not authenticated.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var entries = new List<GroupEntry>();
|
|
||||||
var changedGroupIds = new List<string>();
|
|
||||||
var getFullResults = SettingsService.Instance.GroupDeltaToken == null || force;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var delataRequest = _graphClient.Groups.Delta().Request().Filter(SettingsService.Instance.Sync.GroupFilter);
|
|
||||||
if(!getFullResults)
|
|
||||||
{
|
|
||||||
delataRequest.QueryOptions.Add(new QueryOption("$deltatoken", SettingsService.Instance.GroupDeltaToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
var groupsDelta = await delataRequest.GetAsync();
|
|
||||||
while(true)
|
|
||||||
{
|
|
||||||
if(getFullResults)
|
|
||||||
{
|
|
||||||
foreach(var group in groupsDelta)
|
|
||||||
{
|
|
||||||
var entry = await BuildGroupAsync(group);
|
|
||||||
entries.Add(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
changedGroupIds.AddRange(groupsDelta.Select(g => g.Id));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(groupsDelta.NextPageRequest == null)
|
|
||||||
{
|
|
||||||
object deltaLink;
|
|
||||||
if(groupsDelta.AdditionalData.TryGetValue("@odata.deltaLink", out deltaLink))
|
|
||||||
{
|
|
||||||
var deltaUriQuery = new Uri(deltaLink.ToString()).ParseQueryString();
|
|
||||||
if(deltaUriQuery["$deltatoken"] != null)
|
|
||||||
{
|
|
||||||
SettingsService.Instance.GroupDeltaToken = deltaUriQuery["$deltatoken"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
groupsDelta = await groupsDelta.NextPageRequest.GetAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
|
|
||||||
if(getFullResults || (!getFullResults && !changedGroupIds.Any()))
|
|
||||||
{
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
var groups = await _graphClient.Groups.Request().Filter(SettingsService.Instance.Sync.GroupFilter).GetAsync();
|
|
||||||
while(true)
|
|
||||||
{
|
|
||||||
foreach(var group in groups)
|
|
||||||
{
|
|
||||||
var entry = await BuildGroupAsync(group);
|
|
||||||
entries.Add(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(groups.NextPageRequest == null)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
groups = await groups.NextPageRequest.GetAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async static Task<GroupEntry> BuildGroupAsync(Group group)
|
|
||||||
{
|
|
||||||
var entry = new GroupEntry
|
|
||||||
{
|
|
||||||
ReferenceId = group.Id,
|
|
||||||
ExternalId = group.Id,
|
|
||||||
Name = group.DisplayName
|
|
||||||
};
|
|
||||||
|
|
||||||
var members = await _graphClient.Groups[group.Id].Members.Request().Select("id").GetAsync();
|
|
||||||
foreach(var member in members)
|
|
||||||
{
|
|
||||||
if(member is User)
|
|
||||||
{
|
|
||||||
entry.UserMemberExternalIds.Add(member.Id);
|
|
||||||
}
|
|
||||||
else if(member is Group)
|
|
||||||
{
|
|
||||||
entry.GroupMemberReferenceIds.Add(member.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async static Task<List<UserEntry>> GetUsersAsync(bool force = false)
|
|
||||||
{
|
|
||||||
if(!SettingsService.Instance.Sync.SyncUsers)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Not configured to sync users.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Server?.Azure == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for directory server.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Sync == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for sync.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!AuthService.Instance.Authenticated)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Not authenticated.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var entries = new List<UserEntry>();
|
|
||||||
|
|
||||||
var userRequest = _graphClient.Users.Delta();
|
|
||||||
IUserDeltaCollectionPage users = null;
|
|
||||||
|
|
||||||
if(!force && SettingsService.Instance.UserDeltaToken != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var delataRequest = userRequest.Request().Filter(SettingsService.Instance.Sync.UserFilter);
|
|
||||||
delataRequest.QueryOptions.Add(new QueryOption("$deltatoken", SettingsService.Instance.UserDeltaToken));
|
|
||||||
users = await delataRequest.GetAsync();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
users = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(users == null)
|
|
||||||
{
|
|
||||||
users = await userRequest.Request().Filter(SettingsService.Instance.Sync.UserFilter).GetAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
while(true)
|
|
||||||
{
|
|
||||||
foreach(var user in users)
|
|
||||||
{
|
|
||||||
var entry = new UserEntry
|
|
||||||
{
|
|
||||||
ReferenceId = user.Id,
|
|
||||||
ExternalId = user.Id,
|
|
||||||
Email = user.Mail ?? user.UserPrincipalName,
|
|
||||||
Disabled = !user.AccountEnabled.GetValueOrDefault(true)
|
|
||||||
};
|
|
||||||
|
|
||||||
object deleted;
|
|
||||||
if(user.AdditionalData.TryGetValue("@removed", out deleted) && deleted.ToString().Contains("changed"))
|
|
||||||
{
|
|
||||||
entry.Deleted = true;
|
|
||||||
}
|
|
||||||
else if(!entry.Disabled && (entry?.Email?.Contains("#") ?? true))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
entries.Add(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(users.NextPageRequest == null)
|
|
||||||
{
|
|
||||||
object deltaLink;
|
|
||||||
if(users.AdditionalData.TryGetValue("@odata.deltaLink", out deltaLink))
|
|
||||||
{
|
|
||||||
var deltaUriQuery = new Uri(deltaLink.ToString()).ParseQueryString();
|
|
||||||
if(deltaUriQuery["$deltatoken"] != null)
|
|
||||||
{
|
|
||||||
SettingsService.Instance.UserDeltaToken = deltaUriQuery["$deltatoken"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
users = await users.NextPageRequest.GetAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Models;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security;
|
|
||||||
using System.ServiceProcess;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
|
||||||
{
|
|
||||||
public class ControllerService
|
|
||||||
{
|
|
||||||
private static ControllerService _instance;
|
|
||||||
|
|
||||||
private ControllerService()
|
|
||||||
{
|
|
||||||
Controller = new ServiceController(Constants.ProgramName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ControllerService Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(_instance == null)
|
|
||||||
{
|
|
||||||
_instance = new ControllerService();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServiceController Controller { get; private set; }
|
|
||||||
public ServiceControllerStatus Status
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
Controller.Refresh();
|
|
||||||
return Controller.Status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public string StatusString => Controller == null ? "Unavailable" : Status.ToString();
|
|
||||||
public bool Running => Status == ServiceControllerStatus.Running;
|
|
||||||
public bool Paused => Status == ServiceControllerStatus.Paused;
|
|
||||||
public bool Stopped => Status == ServiceControllerStatus.Stopped;
|
|
||||||
public bool Pending =>
|
|
||||||
Status == ServiceControllerStatus.ContinuePending ||
|
|
||||||
Status == ServiceControllerStatus.PausePending ||
|
|
||||||
Status == ServiceControllerStatus.StartPending ||
|
|
||||||
Status == ServiceControllerStatus.StopPending;
|
|
||||||
|
|
||||||
public bool Start()
|
|
||||||
{
|
|
||||||
if(Controller == null || !Stopped)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Controller.Start();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Stop()
|
|
||||||
{
|
|
||||||
if(Controller == null || !Controller.CanStop)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Controller.Stop();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,306 +0,0 @@
|
|||||||
using Bit.Core.Models;
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Google.Apis.Admin.Directory.directory_v1;
|
|
||||||
using Google.Apis.Services;
|
|
||||||
using Google.Apis.Auth.OAuth2;
|
|
||||||
using System.IO;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using System.Linq;
|
|
||||||
using Google.Apis.Admin.Directory.directory_v1.Data;
|
|
||||||
using Google.Apis.Requests;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
|
||||||
{
|
|
||||||
public class GSuiteDirectoryService : IDirectoryService
|
|
||||||
{
|
|
||||||
private static GSuiteDirectoryService _instance;
|
|
||||||
private static DirectoryService _service;
|
|
||||||
|
|
||||||
private GSuiteDirectoryService()
|
|
||||||
{
|
|
||||||
ICredential creds;
|
|
||||||
|
|
||||||
var secretFilePath = Path.Combine(Constants.BaseStoragePath, SettingsService.Instance.Server.GSuite.SecretFile);
|
|
||||||
using(var stream = new FileStream(secretFilePath, FileMode.Open, FileAccess.Read))
|
|
||||||
{
|
|
||||||
var scopes = new List<string>
|
|
||||||
{
|
|
||||||
DirectoryService.Scope.AdminDirectoryUserReadonly,
|
|
||||||
DirectoryService.Scope.AdminDirectoryGroupReadonly,
|
|
||||||
DirectoryService.Scope.AdminDirectoryGroupMemberReadonly
|
|
||||||
};
|
|
||||||
|
|
||||||
creds = GoogleCredential.FromStream(stream)
|
|
||||||
.CreateScoped(scopes)
|
|
||||||
.CreateWithUser(SettingsService.Instance.Server.GSuite.AdminUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
_service = new DirectoryService(new BaseClientService.Initializer
|
|
||||||
{
|
|
||||||
HttpClientInitializer = creds,
|
|
||||||
ApplicationName = Constants.ProgramName
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IDirectoryService Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(_instance == null)
|
|
||||||
{
|
|
||||||
_instance = new GSuiteDirectoryService();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Tuple<List<GroupEntry>, List<UserEntry>>> GetEntriesAsync(bool force = false)
|
|
||||||
{
|
|
||||||
if(!AuthService.Instance.Authenticated || !AuthService.Instance.OrganizationSet)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Not logged in or have an org set.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Server?.GSuite == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for directory server.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Sync == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for sync.");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<UserEntry> users = null;
|
|
||||||
if(SettingsService.Instance.Sync.SyncUsers)
|
|
||||||
{
|
|
||||||
users = await GetUsersAsync(force);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<GroupEntry> groups = null;
|
|
||||||
if(SettingsService.Instance.Sync.SyncGroups)
|
|
||||||
{
|
|
||||||
groups = await GetGroupsAsync(force || (users?.Any(u => !u.Deleted && !u.Disabled) ?? false));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Tuple<List<GroupEntry>, List<UserEntry>>(groups, users);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<List<GroupEntry>> GetGroupsAsync(bool force)
|
|
||||||
{
|
|
||||||
var entries = new List<GroupEntry>();
|
|
||||||
|
|
||||||
var request = _service.Groups.List();
|
|
||||||
request.Domain = SettingsService.Instance.Server.GSuite.Domain;
|
|
||||||
request.Customer = SettingsService.Instance.Server.GSuite.Customer;
|
|
||||||
|
|
||||||
var pageStreamer = new PageStreamer<Group, GroupsResource.ListRequest, Groups, string>(
|
|
||||||
(req, token) => req.PageToken = token,
|
|
||||||
res => res.NextPageToken,
|
|
||||||
res => res.GroupsValue);
|
|
||||||
|
|
||||||
var filter = CreateSetFromFilter(SettingsService.Instance.Sync.GroupFilter);
|
|
||||||
foreach(var group in pageStreamer.Fetch(request))
|
|
||||||
{
|
|
||||||
if(FilterOutResult(filter, group.Name))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var entry = BuildGroup(group);
|
|
||||||
entries.Add(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.FromResult(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GroupEntry BuildGroup(Group group)
|
|
||||||
{
|
|
||||||
var entry = new GroupEntry
|
|
||||||
{
|
|
||||||
ReferenceId = group.Id,
|
|
||||||
ExternalId = group.Id,
|
|
||||||
Name = group.Name
|
|
||||||
};
|
|
||||||
|
|
||||||
var memberRequest = _service.Members.List(group.Id);
|
|
||||||
var pageStreamer = new PageStreamer<Member, MembersResource.ListRequest, Members, string>(
|
|
||||||
(req, token) => req.PageToken = token,
|
|
||||||
res => res.NextPageToken,
|
|
||||||
res => res.MembersValue);
|
|
||||||
|
|
||||||
foreach(var member in pageStreamer.Fetch(memberRequest))
|
|
||||||
{
|
|
||||||
if(!member.Role.Equals("member", StringComparison.InvariantCultureIgnoreCase) ||
|
|
||||||
!member.Status.Equals("active", StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(member.Type.Equals("user", StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
entry.UserMemberExternalIds.Add(member.Id);
|
|
||||||
}
|
|
||||||
else if(member.Type.Equals("group", StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
entry.GroupMemberReferenceIds.Add(member.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<List<UserEntry>> GetUsersAsync(bool force)
|
|
||||||
{
|
|
||||||
var entries = new List<UserEntry>();
|
|
||||||
var query = CreateGSuiteQueryFromFilter(SettingsService.Instance.Sync.UserFilter);
|
|
||||||
|
|
||||||
var request = _service.Users.List();
|
|
||||||
request.Domain = SettingsService.Instance.Server.GSuite.Domain;
|
|
||||||
request.Customer = SettingsService.Instance.Server.GSuite.Customer;
|
|
||||||
request.Query = query;
|
|
||||||
|
|
||||||
var pageStreamer = new PageStreamer<User, UsersResource.ListRequest, Users, string>(
|
|
||||||
(req, token) => req.PageToken = token,
|
|
||||||
res => res.NextPageToken,
|
|
||||||
res => res.UsersValue);
|
|
||||||
|
|
||||||
var filter = CreateSetFromFilter(SettingsService.Instance.Sync.UserFilter);
|
|
||||||
foreach(var user in pageStreamer.Fetch(request))
|
|
||||||
{
|
|
||||||
if(FilterOutResult(filter, user.PrimaryEmail))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var entry = BuildUser(user, false);
|
|
||||||
if(entry != null)
|
|
||||||
{
|
|
||||||
entries.Add(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var deletedRequest = _service.Users.List();
|
|
||||||
deletedRequest.Domain = SettingsService.Instance.Server.GSuite.Domain;
|
|
||||||
deletedRequest.Customer = SettingsService.Instance.Server.GSuite.Customer;
|
|
||||||
deletedRequest.Query = query;
|
|
||||||
deletedRequest.ShowDeleted = "true";
|
|
||||||
|
|
||||||
var deletedPageStreamer = new PageStreamer<User, UsersResource.ListRequest, Users, string>(
|
|
||||||
(req, token) => req.PageToken = token,
|
|
||||||
res => res.NextPageToken,
|
|
||||||
res => res.UsersValue);
|
|
||||||
|
|
||||||
foreach(var user in deletedPageStreamer.Fetch(deletedRequest))
|
|
||||||
{
|
|
||||||
if(FilterOutResult(filter, user.PrimaryEmail))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var entry = BuildUser(user, true);
|
|
||||||
if(entry != null)
|
|
||||||
{
|
|
||||||
entries.Add(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.FromResult(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
private UserEntry BuildUser(User user, bool deleted)
|
|
||||||
{
|
|
||||||
var entry = new UserEntry
|
|
||||||
{
|
|
||||||
ReferenceId = user.Id,
|
|
||||||
ExternalId = user.Id,
|
|
||||||
Email = user.PrimaryEmail,
|
|
||||||
Disabled = user.Suspended.GetValueOrDefault(false),
|
|
||||||
Deleted = deleted,
|
|
||||||
CreationDate = user.CreationTime
|
|
||||||
};
|
|
||||||
|
|
||||||
if(string.IsNullOrWhiteSpace(entry.Email) && !entry.Deleted)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string CreateGSuiteQueryFromFilter(string filter)
|
|
||||||
{
|
|
||||||
if(string.IsNullOrWhiteSpace(filter))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mainParts = filter.Split('|');
|
|
||||||
if(mainParts.Count() < 2 || string.IsNullOrWhiteSpace(mainParts[1]))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mainParts[1].Trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Tuple<bool, HashSet<string>> CreateSetFromFilter(string filter)
|
|
||||||
{
|
|
||||||
if(string.IsNullOrWhiteSpace(filter))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mainParts = filter.Split('|');
|
|
||||||
if(mainParts.Count() < 1 || string.IsNullOrWhiteSpace(mainParts[0]))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var parts = mainParts[0].Split(':');
|
|
||||||
if(parts.Count() != 2)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var exclude = true;
|
|
||||||
if(string.Equals(parts[0].Trim(), "include", StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
exclude = false;
|
|
||||||
}
|
|
||||||
else if(string.Equals(parts[0].Trim(), "exclude", StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
exclude = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var list = new HashSet<string>(parts[1].Split(',').Select(p => p.Trim()));
|
|
||||||
return new Tuple<bool, HashSet<string>>(exclude, list);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool FilterOutResult(Tuple<bool, HashSet<string>> filter, string result)
|
|
||||||
{
|
|
||||||
if(filter != null)
|
|
||||||
{
|
|
||||||
// excluded
|
|
||||||
if(filter.Item1 && filter.Item2.Contains(result, StringComparer.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// included
|
|
||||||
else if(!filter.Item1 && !filter.Item2.Contains(result, StringComparer.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using Bit.Core.Models;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
|
||||||
{
|
|
||||||
public interface IDirectoryService
|
|
||||||
{
|
|
||||||
Task<Tuple<List<GroupEntry>, List<UserEntry>>> GetEntriesAsync(bool force = false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,399 +0,0 @@
|
|||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Models;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.DirectoryServices;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
|
||||||
{
|
|
||||||
public class LdapDirectoryService : IDirectoryService
|
|
||||||
{
|
|
||||||
private static LdapDirectoryService _instance;
|
|
||||||
|
|
||||||
private LdapDirectoryService() { }
|
|
||||||
|
|
||||||
public static IDirectoryService Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(_instance == null)
|
|
||||||
{
|
|
||||||
_instance = new LdapDirectoryService();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Tuple<List<GroupEntry>, List<UserEntry>>> GetEntriesAsync(bool force = false)
|
|
||||||
{
|
|
||||||
if(!AuthService.Instance.Authenticated || !AuthService.Instance.OrganizationSet)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Not logged in or have an org set.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Server?.Ldap == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for directory server.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Sync == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for sync.");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<UserEntry> users = null;
|
|
||||||
if(SettingsService.Instance.Sync.SyncUsers)
|
|
||||||
{
|
|
||||||
users = await GetUsersAsync(force);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<GroupEntry> groups = null;
|
|
||||||
if(SettingsService.Instance.Sync.SyncGroups)
|
|
||||||
{
|
|
||||||
groups = await GetGroupsAsync(force || (users?.Any(u => !u.Deleted && !u.Disabled) ?? false));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Tuple<List<GroupEntry>, List<UserEntry>>(groups, users);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Task<List<GroupEntry>> GetGroupsAsync(bool force = false)
|
|
||||||
{
|
|
||||||
if(!SettingsService.Instance.Sync.SyncGroups)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Not configured to sync groups.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Server?.Ldap == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for directory server.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Sync == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for sync.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!AuthService.Instance.Authenticated)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Not authenticated.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var entry = SettingsService.Instance.Server.Ldap.GetGroupDirectoryEntry();
|
|
||||||
|
|
||||||
var originalFilter = BuildBaseFilter(SettingsService.Instance.Sync.Ldap.GroupObjectClass,
|
|
||||||
SettingsService.Instance.Sync.GroupFilter);
|
|
||||||
|
|
||||||
var filter = originalFilter;
|
|
||||||
var revisionFilter = BuildRevisionFilter(filter, force, SettingsService.Instance.LastGroupSyncDate);
|
|
||||||
var searchSinceRevision = filter != revisionFilter;
|
|
||||||
filter = revisionFilter;
|
|
||||||
|
|
||||||
Console.WriteLine("Group search: {0} => {1}", entry.Path, filter);
|
|
||||||
var searcher = new DirectorySearcher(entry, filter);
|
|
||||||
var result = searcher.FindAll();
|
|
||||||
|
|
||||||
var initialSearchGroupIds = new List<string>();
|
|
||||||
foreach(SearchResult item in result)
|
|
||||||
{
|
|
||||||
initialSearchGroupIds.Add(DNFromPath(item.Path));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(searchSinceRevision && !initialSearchGroupIds.Any())
|
|
||||||
{
|
|
||||||
return Task.FromResult(new List<GroupEntry>());
|
|
||||||
}
|
|
||||||
else if(searchSinceRevision)
|
|
||||||
{
|
|
||||||
searcher = new DirectorySearcher(entry, originalFilter);
|
|
||||||
result = searcher.FindAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
var userFilter = BuildBaseFilter(SettingsService.Instance.Sync.Ldap.UserObjectClass,
|
|
||||||
SettingsService.Instance.Sync.UserFilter);
|
|
||||||
var userSearcher = new DirectorySearcher(entry, userFilter);
|
|
||||||
var userResult = userSearcher.FindAll();
|
|
||||||
|
|
||||||
var userIdsDict = MakeIdIndex(userResult);
|
|
||||||
|
|
||||||
var groups = new List<GroupEntry>();
|
|
||||||
foreach(SearchResult item in result)
|
|
||||||
{
|
|
||||||
var group = BuildGroup(item, userIdsDict);
|
|
||||||
if(group == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
groups.Add(group);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.FromResult(groups);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Dictionary<string, string> MakeIdIndex(SearchResultCollection result)
|
|
||||||
{
|
|
||||||
var dict = new Dictionary<string, string>();
|
|
||||||
foreach(SearchResult item in result)
|
|
||||||
{
|
|
||||||
var referenceId = DNFromPath(item.Path);
|
|
||||||
var externalId = referenceId;
|
|
||||||
|
|
||||||
if(item.Properties.Contains("objectGUID") && item.Properties["objectGUID"].Count > 0)
|
|
||||||
{
|
|
||||||
externalId = item.Properties["objectGUID"][0].FromGuidToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
dict.Add(referenceId, externalId);
|
|
||||||
}
|
|
||||||
return dict;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GroupEntry BuildGroup(SearchResult item, Dictionary<string, string> userIndex)
|
|
||||||
{
|
|
||||||
var group = new GroupEntry
|
|
||||||
{
|
|
||||||
ReferenceId = DNFromPath(item.Path)
|
|
||||||
};
|
|
||||||
|
|
||||||
if(group.ReferenceId == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// External Id
|
|
||||||
if(item.Properties.Contains("objectGUID") && item.Properties["objectGUID"].Count > 0)
|
|
||||||
{
|
|
||||||
group.ExternalId = item.Properties["objectGUID"][0].FromGuidToString();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
group.ExternalId = group.ReferenceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name
|
|
||||||
if(item.Properties.Contains(SettingsService.Instance.Sync.Ldap.GroupNameAttribute) &&
|
|
||||||
item.Properties[SettingsService.Instance.Sync.Ldap.GroupNameAttribute].Count > 0)
|
|
||||||
{
|
|
||||||
group.Name = item.Properties[SettingsService.Instance.Sync.Ldap.GroupNameAttribute][0].ToString();
|
|
||||||
}
|
|
||||||
else if(item.Properties.Contains("cn") && item.Properties["cn"].Count > 0)
|
|
||||||
{
|
|
||||||
group.Name = item.Properties["cn"][0].ToString();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dates
|
|
||||||
group.CreationDate = item.Properties.ParseDateTime(SettingsService.Instance.Sync.Ldap.CreationDateAttribute);
|
|
||||||
group.RevisionDate = item.Properties.ParseDateTime(SettingsService.Instance.Sync.Ldap.RevisionDateAttribute);
|
|
||||||
|
|
||||||
// Members
|
|
||||||
if(item.Properties.Contains(SettingsService.Instance.Sync.Ldap.MemberAttribute) &&
|
|
||||||
item.Properties[SettingsService.Instance.Sync.Ldap.MemberAttribute].Count > 0)
|
|
||||||
{
|
|
||||||
foreach(var member in item.Properties[SettingsService.Instance.Sync.Ldap.MemberAttribute])
|
|
||||||
{
|
|
||||||
var memberDn = member.ToString();
|
|
||||||
if(userIndex.ContainsKey(memberDn) && !group.UserMemberExternalIds.Contains(userIndex[memberDn]))
|
|
||||||
{
|
|
||||||
group.UserMemberExternalIds.Add(userIndex[memberDn]);
|
|
||||||
}
|
|
||||||
else if(!group.GroupMemberReferenceIds.Contains(memberDn))
|
|
||||||
{
|
|
||||||
group.GroupMemberReferenceIds.Add(memberDn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Task<List<UserEntry>> GetUsersAsync(bool force = false)
|
|
||||||
{
|
|
||||||
if(!SettingsService.Instance.Sync.SyncUsers)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Not configured to sync users.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Server?.Ldap == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for directory server.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Sync == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for sync.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!AuthService.Instance.Authenticated)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Not authenticated.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var entry = SettingsService.Instance.Server.Ldap.GetUserDirectoryEntry();
|
|
||||||
var filter = BuildBaseFilter(SettingsService.Instance.Sync.Ldap.UserObjectClass,
|
|
||||||
SettingsService.Instance.Sync.UserFilter);
|
|
||||||
filter = BuildRevisionFilter(filter, force, SettingsService.Instance.LastUserSyncDate);
|
|
||||||
|
|
||||||
Console.WriteLine("User search: {0} => {1}", entry.Path, filter);
|
|
||||||
var searcher = new DirectorySearcher(entry, filter);
|
|
||||||
var result = searcher.FindAll();
|
|
||||||
|
|
||||||
var users = new List<UserEntry>();
|
|
||||||
foreach(SearchResult item in result)
|
|
||||||
{
|
|
||||||
var user = BuildUser(item, false);
|
|
||||||
if(user == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
users.Add(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deleted users
|
|
||||||
if(SettingsService.Instance.Server.Type == DirectoryType.ActiveDirectory)
|
|
||||||
{
|
|
||||||
var deletedEntry = SettingsService.Instance.Server.Ldap.GetBasePathDirectoryEntry();
|
|
||||||
var deletedFilter = BuildBaseFilter(SettingsService.Instance.Sync.Ldap.UserObjectClass, "(isDeleted=TRUE)");
|
|
||||||
deletedFilter = BuildRevisionFilter(deletedFilter, force, SettingsService.Instance.LastUserSyncDate);
|
|
||||||
|
|
||||||
var deletedSearcher = new DirectorySearcher(deletedEntry, deletedFilter);
|
|
||||||
deletedSearcher.Tombstone = true;
|
|
||||||
var deletedResult = deletedSearcher.FindAll();
|
|
||||||
foreach(SearchResult item in deletedResult)
|
|
||||||
{
|
|
||||||
var user = BuildUser(item, true);
|
|
||||||
if(user == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
users.Add(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.FromResult(users);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string BuildBaseFilter(string objectClass, string subFilter)
|
|
||||||
{
|
|
||||||
var filter = BuildObjectClassFilter(objectClass);
|
|
||||||
if(!string.IsNullOrWhiteSpace(subFilter))
|
|
||||||
{
|
|
||||||
filter = string.Format("(&{0}{1})", filter, subFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
return filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string BuildObjectClassFilter(string objectClass)
|
|
||||||
{
|
|
||||||
return string.Format("(&(objectClass={0}))", objectClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string BuildRevisionFilter(string baseFilter, bool force, DateTime? lastRevisionDate)
|
|
||||||
{
|
|
||||||
if(!force && lastRevisionDate.HasValue &&
|
|
||||||
!string.IsNullOrWhiteSpace(SettingsService.Instance.Sync.Ldap.RevisionDateAttribute))
|
|
||||||
{
|
|
||||||
baseFilter = string.Format("(&{0}({1}>={2}))",
|
|
||||||
baseFilter,
|
|
||||||
SettingsService.Instance.Sync.Ldap.RevisionDateAttribute,
|
|
||||||
lastRevisionDate.Value.ToGeneralizedTimeUTC());
|
|
||||||
}
|
|
||||||
|
|
||||||
return baseFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static UserEntry BuildUser(SearchResult item, bool deleted)
|
|
||||||
{
|
|
||||||
var user = new UserEntry
|
|
||||||
{
|
|
||||||
ReferenceId = DNFromPath(item.Path),
|
|
||||||
Deleted = deleted
|
|
||||||
};
|
|
||||||
|
|
||||||
if(user.ReferenceId == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// External Id
|
|
||||||
if(item.Properties.Contains("objectGUID") && item.Properties["objectGUID"].Count > 0)
|
|
||||||
{
|
|
||||||
user.ExternalId = item.Properties["objectGUID"][0].FromGuidToString();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
user.ExternalId = user.ReferenceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
user.Disabled = EntryDisabled(item);
|
|
||||||
|
|
||||||
// Email
|
|
||||||
if(item.Properties.Contains(SettingsService.Instance.Sync.Ldap.UserEmailAttribute) &&
|
|
||||||
item.Properties[SettingsService.Instance.Sync.Ldap.UserEmailAttribute].Count > 0)
|
|
||||||
{
|
|
||||||
user.Email = item.Properties[SettingsService.Instance.Sync.Ldap.UserEmailAttribute][0]
|
|
||||||
.ToString()
|
|
||||||
.ToLowerInvariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(string.IsNullOrWhiteSpace(user.Email) && SettingsService.Instance.Sync.Ldap.EmailPrefixSuffix &&
|
|
||||||
item.Properties.Contains(SettingsService.Instance.Sync.Ldap.UserEmailPrefixAttribute) &&
|
|
||||||
item.Properties[SettingsService.Instance.Sync.Ldap.UserEmailPrefixAttribute].Count > 0 &&
|
|
||||||
!string.IsNullOrWhiteSpace(SettingsService.Instance.Sync.Ldap.UserEmailSuffix))
|
|
||||||
{
|
|
||||||
user.Email = string.Concat(
|
|
||||||
item.Properties[SettingsService.Instance.Sync.Ldap.UserEmailPrefixAttribute][0].ToString(),
|
|
||||||
SettingsService.Instance.Sync.Ldap.UserEmailSuffix).ToLowerInvariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(string.IsNullOrWhiteSpace(user.Email) && !user.Deleted)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dates
|
|
||||||
user.CreationDate = item.Properties.ParseDateTime(SettingsService.Instance.Sync.Ldap.CreationDateAttribute);
|
|
||||||
user.RevisionDate = item.Properties.ParseDateTime(SettingsService.Instance.Sync.Ldap.RevisionDateAttribute);
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool EntryDisabled(SearchResult item)
|
|
||||||
{
|
|
||||||
if(!item.Properties.Contains("userAccountControl") || item.Properties["userAccountControl"].Count == 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
UserAccountControl control;
|
|
||||||
if(!Enum.TryParse(item.Properties["userAccountControl"][0].ToString(), out control))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (control & UserAccountControl.AccountDisabled) == UserAccountControl.AccountDisabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string DNFromPath(string path)
|
|
||||||
{
|
|
||||||
var dn = new Uri(path).Segments?.LastOrDefault();
|
|
||||||
if(dn == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return WebUtility.UrlDecode(dn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
using Bit.Core.Models;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
|
||||||
{
|
|
||||||
public class SettingsService
|
|
||||||
{
|
|
||||||
private static SettingsService _instance;
|
|
||||||
private static object _locker = new object();
|
|
||||||
|
|
||||||
private SettingsModel _settings;
|
|
||||||
|
|
||||||
private SettingsService() { }
|
|
||||||
|
|
||||||
public static SettingsService Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(_instance == null)
|
|
||||||
{
|
|
||||||
_instance = new SettingsService();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private SettingsModel Settings
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var filePath = $"{Constants.BaseStoragePath}\\settings.json";
|
|
||||||
if(_settings == null && File.Exists(filePath))
|
|
||||||
{
|
|
||||||
var serializer = new JsonSerializer();
|
|
||||||
using(var s = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
|
|
||||||
using(var sr = new StreamReader(s, Encoding.UTF8))
|
|
||||||
using(var jsonTextReader = new JsonTextReader(sr))
|
|
||||||
{
|
|
||||||
_settings = serializer.Deserialize<SettingsModel>(jsonTextReader);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
InitSettings();
|
|
||||||
return _settings;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SaveSettings()
|
|
||||||
{
|
|
||||||
lock(_locker)
|
|
||||||
{
|
|
||||||
if(!Directory.Exists(Constants.BaseStoragePath))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(Constants.BaseStoragePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
var filePath = $"{Constants.BaseStoragePath}\\settings.json";
|
|
||||||
using(var s = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read))
|
|
||||||
using(var sw = new StreamWriter(s, Encoding.UTF8))
|
|
||||||
{
|
|
||||||
var json = JsonConvert.SerializeObject(_settings, Formatting.Indented);
|
|
||||||
sw.Write(json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitSettings()
|
|
||||||
{
|
|
||||||
if(_settings == null)
|
|
||||||
{
|
|
||||||
_settings = new SettingsModel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ApiBaseUrl
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Settings.ApiBaseUrl;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
InitSettings();
|
|
||||||
_settings.ApiBaseUrl = value;
|
|
||||||
SaveSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string IdentityBaseUrl
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Settings.IdentityBaseUrl;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
InitSettings();
|
|
||||||
_settings.IdentityBaseUrl = value;
|
|
||||||
SaveSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public EncryptedData AccessToken
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Settings.AccessToken;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
InitSettings();
|
|
||||||
_settings.AccessToken = value;
|
|
||||||
SaveSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public EncryptedData RefreshToken
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Settings.RefreshToken;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
InitSettings();
|
|
||||||
_settings.RefreshToken = value;
|
|
||||||
SaveSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Organization Organization
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Settings.Organization;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
InitSettings();
|
|
||||||
_settings.Organization = value;
|
|
||||||
SaveSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerConfiguration Server
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Settings.Server;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
InitSettings();
|
|
||||||
_settings.Server = value;
|
|
||||||
SaveSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SyncConfiguration Sync
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Settings.Sync;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
InitSettings();
|
|
||||||
_settings.Sync = value;
|
|
||||||
SaveSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DateTime? LastGroupSyncDate
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Settings.LastGroupSyncDate;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
InitSettings();
|
|
||||||
_settings.LastGroupSyncDate = value;
|
|
||||||
SaveSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DateTime? LastUserSyncDate
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Settings.LastUserSyncDate;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
InitSettings();
|
|
||||||
_settings.LastUserSyncDate = value;
|
|
||||||
SaveSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GroupDeltaToken
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Settings.GroupDeltaToken;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
InitSettings();
|
|
||||||
_settings.GroupDeltaToken = value;
|
|
||||||
SaveSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string UserDeltaToken
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Settings.UserDeltaToken;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
InitSettings();
|
|
||||||
_settings.UserDeltaToken = value;
|
|
||||||
SaveSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string LastSyncHash
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Settings.LastSyncHash;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
InitSettings();
|
|
||||||
_settings.LastSyncHash = value;
|
|
||||||
SaveSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SettingsModel
|
|
||||||
{
|
|
||||||
public string ApiBaseUrl { get; set; } = "https://api.bitwarden.com";
|
|
||||||
public string IdentityBaseUrl { get; set; } = "https://identity.bitwarden.com";
|
|
||||||
public EncryptedData AccessToken { get; set; }
|
|
||||||
public EncryptedData RefreshToken { get; set; }
|
|
||||||
public ServerConfiguration Server { get; set; } = new ServerConfiguration();
|
|
||||||
public SyncConfiguration Sync { get; set; } = new SyncConfiguration(Enums.DirectoryType.ActiveDirectory);
|
|
||||||
public Organization Organization { get; set; } = new Organization();
|
|
||||||
public DateTime? LastGroupSyncDate { get; set; }
|
|
||||||
public DateTime? LastUserSyncDate { get; set; }
|
|
||||||
public string GroupDeltaToken { get; set; }
|
|
||||||
public string UserDeltaToken { get; set; }
|
|
||||||
public string LastSyncHash { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
using Bit.Core.Models;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
|
||||||
{
|
|
||||||
public class TokenService
|
|
||||||
{
|
|
||||||
private static TokenService _instance;
|
|
||||||
private static readonly DateTime _epoc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
|
||||||
|
|
||||||
private string _accessToken;
|
|
||||||
private dynamic _decodedAccessToken;
|
|
||||||
|
|
||||||
private TokenService() { }
|
|
||||||
|
|
||||||
public static TokenService Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(_instance == null)
|
|
||||||
{
|
|
||||||
_instance = new TokenService();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string AccessToken
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(_accessToken != null)
|
|
||||||
{
|
|
||||||
return _accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
var encBytes = SettingsService.Instance.AccessToken;
|
|
||||||
if(encBytes != null)
|
|
||||||
{
|
|
||||||
_accessToken = Encoding.ASCII.GetString(encBytes.Decrypt());
|
|
||||||
}
|
|
||||||
|
|
||||||
return _accessToken;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_accessToken = value;
|
|
||||||
if(_accessToken == null)
|
|
||||||
{
|
|
||||||
SettingsService.Instance.AccessToken = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var bytes = Encoding.ASCII.GetBytes(_accessToken);
|
|
||||||
SettingsService.Instance.AccessToken = new EncryptedData(bytes);
|
|
||||||
bytes = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DateTime AccessTokenExpiration
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var decoded = DecodeAccessToken();
|
|
||||||
if(decoded?["exp"] == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("No exp in token.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return _epoc.AddSeconds(Convert.ToDouble(decoded["exp"].Value<long>()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AccessTokenExpired => DateTime.UtcNow < AccessTokenExpiration;
|
|
||||||
public TimeSpan AccessTokenTimeRemaining => AccessTokenExpiration - DateTime.UtcNow;
|
|
||||||
public bool AccessTokenNeedsRefresh => AccessTokenTimeRemaining.TotalMinutes < 5;
|
|
||||||
public string AccessTokenUserId => DecodeAccessToken()?["sub"].Value<string>();
|
|
||||||
public string AccessTokenEmail => DecodeAccessToken()?["email"].Value<string>();
|
|
||||||
public string AccessTokenName => DecodeAccessToken()?["name"].Value<string>();
|
|
||||||
|
|
||||||
public string RefreshToken
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var encData = SettingsService.Instance.RefreshToken;
|
|
||||||
if(encData != null)
|
|
||||||
{
|
|
||||||
return Encoding.ASCII.GetString(encData.Decrypt());
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if(value == null)
|
|
||||||
{
|
|
||||||
SettingsService.Instance.RefreshToken = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var bytes = Encoding.ASCII.GetBytes(value);
|
|
||||||
SettingsService.Instance.RefreshToken = new EncryptedData(bytes);
|
|
||||||
bytes = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public JObject DecodeAccessToken()
|
|
||||||
{
|
|
||||||
if(_decodedAccessToken != null)
|
|
||||||
{
|
|
||||||
return _decodedAccessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(AccessToken == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"{nameof(AccessToken)} not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var parts = AccessToken.Split('.');
|
|
||||||
if(parts.Length != 3)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"{nameof(AccessToken)} must have 3 parts");
|
|
||||||
}
|
|
||||||
|
|
||||||
var decodedBytes = Base64UrlDecode(parts[1]);
|
|
||||||
if(decodedBytes == null || decodedBytes.Length < 1)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"{nameof(AccessToken)} must have 3 parts");
|
|
||||||
}
|
|
||||||
|
|
||||||
_decodedAccessToken = JObject.Parse(Encoding.UTF8.GetString(decodedBytes, 0, decodedBytes.Length));
|
|
||||||
return _decodedAccessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] Base64UrlDecode(string input)
|
|
||||||
{
|
|
||||||
var output = input;
|
|
||||||
// 62nd char of encoding
|
|
||||||
output = output.Replace('-', '+');
|
|
||||||
// 63rd char of encoding
|
|
||||||
output = output.Replace('_', '/');
|
|
||||||
// Pad with trailing '='s
|
|
||||||
switch(output.Length % 4)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
// No pad chars in this case
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
// Two pad chars
|
|
||||||
output += "=="; break;
|
|
||||||
case 3:
|
|
||||||
// One pad char
|
|
||||||
output += "="; break;
|
|
||||||
default:
|
|
||||||
throw new InvalidOperationException("Illegal base64url string!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Standard base64 decoder
|
|
||||||
return Convert.FromBase64String(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using Bit.Core.Services;
|
|
||||||
using Microsoft.Graph;
|
|
||||||
using Microsoft.IdentityModel.Clients.ActiveDirectory;
|
|
||||||
using System;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Utilities
|
|
||||||
{
|
|
||||||
public class AzureAuthenticationProvider : IAuthenticationProvider
|
|
||||||
{
|
|
||||||
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
|
|
||||||
{
|
|
||||||
if(SettingsService.Instance.Server?.Azure == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No server configuration.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var authContext = new AuthenticationContext(
|
|
||||||
$"https://login.windows.net/{SettingsService.Instance.Server.Azure.Tenant}/oauth2/token");
|
|
||||||
var creds = new ClientCredential(SettingsService.Instance.Server.Azure.Id,
|
|
||||||
SettingsService.Instance.Server.Azure.Secret.DecryptToString());
|
|
||||||
var authResult = await authContext.AcquireTokenAsync("https://graph.microsoft.com/", creds);
|
|
||||||
request.Headers.Add("Authorization", $"Bearer {authResult.AccessToken}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ref: https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/issues/511
|
|
||||||
private static void SomeMethodToLinkPlatform()
|
|
||||||
{
|
|
||||||
var creds = new UserPasswordCredential("user", "pass");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Utilities
|
|
||||||
{
|
|
||||||
public static class Constants
|
|
||||||
{
|
|
||||||
public static string BaseStoragePath = string.Concat(
|
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
|
||||||
"\\bitwarden\\Directory Connector");
|
|
||||||
|
|
||||||
public const string ProgramName = "bitwarden Directory Connector";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
using Org.BouncyCastle.Crypto.Digests;
|
|
||||||
using Org.BouncyCastle.Crypto.Generators;
|
|
||||||
using Org.BouncyCastle.Crypto.Parameters;
|
|
||||||
using System;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Bit.Core.Utilities
|
|
||||||
{
|
|
||||||
public static class Crypto
|
|
||||||
{
|
|
||||||
public static byte[] MakeKeyFromPassword(string password, string salt)
|
|
||||||
{
|
|
||||||
if(password == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(password));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(salt == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(salt));
|
|
||||||
}
|
|
||||||
|
|
||||||
var passwordBytes = Encoding.UTF8.GetBytes(password);
|
|
||||||
var saltBytes = Encoding.UTF8.GetBytes(salt);
|
|
||||||
|
|
||||||
var keyBytes = DeriveKey(passwordBytes, saltBytes, 5000);
|
|
||||||
|
|
||||||
password = null;
|
|
||||||
passwordBytes = null;
|
|
||||||
|
|
||||||
return keyBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string MakeKeyFromPasswordBase64(string password, string salt)
|
|
||||||
{
|
|
||||||
var key = MakeKeyFromPassword(password, salt);
|
|
||||||
password = null;
|
|
||||||
return Convert.ToBase64String(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] HashPassword(byte[] key, string password)
|
|
||||||
{
|
|
||||||
if(key == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(password == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(password));
|
|
||||||
}
|
|
||||||
|
|
||||||
var passwordBytes = Encoding.UTF8.GetBytes(password);
|
|
||||||
|
|
||||||
var hashBytes = DeriveKey(key, passwordBytes, 1);
|
|
||||||
|
|
||||||
password = null;
|
|
||||||
key = null;
|
|
||||||
|
|
||||||
return hashBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string HashPasswordBase64(byte[] key, string password)
|
|
||||||
{
|
|
||||||
var hash = HashPassword(key, password);
|
|
||||||
password = null;
|
|
||||||
key = null;
|
|
||||||
return Convert.ToBase64String(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] DeriveKey(byte[] password, byte[] salt, int rounds)
|
|
||||||
{
|
|
||||||
var generator = new Pkcs5S2ParametersGenerator(new Sha256Digest());
|
|
||||||
generator.Init(password, salt, rounds);
|
|
||||||
var key = ((KeyParameter)generator.GenerateDerivedMacParameters(256)).GetKey();
|
|
||||||
password = null;
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Specialized;
|
|
||||||
using System.DirectoryServices;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Utilities
|
|
||||||
{
|
|
||||||
public static class Extensions
|
|
||||||
{
|
|
||||||
private const string GeneralizedTimeFormat = "yyyyMMddHHmmss.f'Z'";
|
|
||||||
|
|
||||||
public static DateTime ToDateTime(this string generalizedTimeString)
|
|
||||||
{
|
|
||||||
return DateTime.ParseExact(generalizedTimeString, GeneralizedTimeFormat, CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ToGeneralizedTimeUTC(this DateTime date)
|
|
||||||
{
|
|
||||||
return date.ToString("yyyyMMddHHmmss.f'Z'");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DateTime? ParseDateTime(this ResultPropertyCollection collection, string dateKey)
|
|
||||||
{
|
|
||||||
DateTime date;
|
|
||||||
if(collection.Contains(dateKey) && collection[dateKey].Count > 0 &&
|
|
||||||
DateTime.TryParse(collection[dateKey][0].ToString(), out date))
|
|
||||||
{
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NameValueCollection ParseQueryString(this Uri uri)
|
|
||||||
{
|
|
||||||
var queryParameters = new NameValueCollection();
|
|
||||||
var querySegments = uri.Query.Split('&');
|
|
||||||
foreach(var segment in querySegments)
|
|
||||||
{
|
|
||||||
var parts = segment.Split('=');
|
|
||||||
if(parts.Length > 0)
|
|
||||||
{
|
|
||||||
var key = parts[0].Trim(new char[] { '?', ' ' });
|
|
||||||
var val = parts[1].Trim();
|
|
||||||
queryParameters.Add(key, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return queryParameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string FromGuidToString(this object property)
|
|
||||||
{
|
|
||||||
var propBytes = property as byte[];
|
|
||||||
if(propBytes != null)
|
|
||||||
{
|
|
||||||
return new Guid(propBytes).ToString();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return property.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Principal;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Utilities
|
|
||||||
{
|
|
||||||
public static class Helpers
|
|
||||||
{
|
|
||||||
public static bool IsAdministrator()
|
|
||||||
{
|
|
||||||
var identity = WindowsIdentity.GetCurrent();
|
|
||||||
var principal = new WindowsPrincipal(identity);
|
|
||||||
return principal.IsInRole(WindowsBuiltInRole.Administrator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
using Bit.Core.Models;
|
|
||||||
using Bit.Core.Services;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Utilities
|
|
||||||
{
|
|
||||||
public static class Sync
|
|
||||||
{
|
|
||||||
public static async Task<SyncResult> SyncAllAsync(bool force = false, bool sendToServer = true)
|
|
||||||
{
|
|
||||||
var startingGroupDelta = SettingsService.Instance.GroupDeltaToken;
|
|
||||||
var startingUserDelta = SettingsService.Instance.UserDeltaToken;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var now = DateTime.UtcNow;
|
|
||||||
var entriesResult = await GetDirectoryService().GetEntriesAsync(force);
|
|
||||||
var groups = entriesResult.Item1;
|
|
||||||
var users = entriesResult.Item2;
|
|
||||||
|
|
||||||
if(groups?.Any() ?? false)
|
|
||||||
{
|
|
||||||
FlattenUsersToGroups(groups, null, groups);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!sendToServer)
|
|
||||||
{
|
|
||||||
RestoreDeltas(startingGroupDelta, startingUserDelta);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!sendToServer || ((groups?.Count ?? 0) == 0 && (users?.Count ?? 0) == 0))
|
|
||||||
{
|
|
||||||
return new SyncResult
|
|
||||||
{
|
|
||||||
Success = true,
|
|
||||||
Groups = groups,
|
|
||||||
Users = users
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = new ImportRequest(groups, users);
|
|
||||||
var json = JsonConvert.SerializeObject(request);
|
|
||||||
var hash = ComputeHash(string.Concat(SettingsService.Instance.ApiBaseUrl, json));
|
|
||||||
|
|
||||||
if(hash == SettingsService.Instance.LastSyncHash)
|
|
||||||
{
|
|
||||||
return new SyncResult
|
|
||||||
{
|
|
||||||
Success = true,
|
|
||||||
Groups = groups,
|
|
||||||
Users = users
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var response = await ApiService.Instance.PostImportAsync(request);
|
|
||||||
if(response.Succeeded)
|
|
||||||
{
|
|
||||||
SettingsService.Instance.LastSyncHash = hash;
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Sync.SyncGroups)
|
|
||||||
{
|
|
||||||
SettingsService.Instance.LastGroupSyncDate = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Sync.SyncUsers)
|
|
||||||
{
|
|
||||||
SettingsService.Instance.LastUserSyncDate = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SyncResult
|
|
||||||
{
|
|
||||||
Success = true,
|
|
||||||
Groups = groups,
|
|
||||||
Users = users
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RestoreDeltas(startingGroupDelta, startingUserDelta);
|
|
||||||
return new SyncResult
|
|
||||||
{
|
|
||||||
Success = false,
|
|
||||||
ErrorMessage = response.Errors.FirstOrDefault()?.Message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
RestoreDeltas(startingGroupDelta, startingUserDelta);
|
|
||||||
return new SyncResult
|
|
||||||
{
|
|
||||||
Success = false,
|
|
||||||
ErrorMessage = e.Message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IDirectoryService GetDirectoryService()
|
|
||||||
{
|
|
||||||
switch(SettingsService.Instance.Server.Type)
|
|
||||||
{
|
|
||||||
case Enums.DirectoryType.AzureActiveDirectory:
|
|
||||||
return AzureDirectoryService.Instance;
|
|
||||||
case Enums.DirectoryType.GSuite:
|
|
||||||
return GSuiteDirectoryService.Instance;
|
|
||||||
default:
|
|
||||||
return LdapDirectoryService.Instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void FlattenUsersToGroups(List<GroupEntry> currentGroups, List<string> currentGroupsUsers,
|
|
||||||
List<GroupEntry> allGroups)
|
|
||||||
{
|
|
||||||
foreach(var group in currentGroups)
|
|
||||||
{
|
|
||||||
var groupsInThisGroup = allGroups.Where(g => group.GroupMemberReferenceIds.Contains(g.ReferenceId)).ToList();
|
|
||||||
var usersInThisGroup = group.UserMemberExternalIds.ToList();
|
|
||||||
|
|
||||||
if(currentGroupsUsers != null)
|
|
||||||
{
|
|
||||||
foreach(var id in currentGroupsUsers)
|
|
||||||
{
|
|
||||||
if(!group.UserMemberExternalIds.Contains(id))
|
|
||||||
{
|
|
||||||
group.UserMemberExternalIds.Add(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
usersInThisGroup.AddRange(currentGroupsUsers);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recurse it
|
|
||||||
FlattenUsersToGroups(groupsInThisGroup, usersInThisGroup, allGroups);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RestoreDeltas(string groupDelta, string userDelta)
|
|
||||||
{
|
|
||||||
if(SettingsService.Instance.Server.Type != Enums.DirectoryType.AzureActiveDirectory)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsService.Instance.GroupDeltaToken = groupDelta;
|
|
||||||
SettingsService.Instance.UserDeltaToken = userDelta;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ComputeHash(string value)
|
|
||||||
{
|
|
||||||
if(value == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
string result = null;
|
|
||||||
using(var hash = SHA256.Create())
|
|
||||||
{
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(value);
|
|
||||||
var hashBytes = hash.ComputeHash(bytes);
|
|
||||||
result = Convert.ToBase64String(hashBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<configuration>
|
|
||||||
<startup>
|
|
||||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
|
|
||||||
</startup>
|
|
||||||
<runtime>
|
|
||||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
|
||||||
<dependentAssembly>
|
|
||||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral"/>
|
|
||||||
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0"/>
|
|
||||||
</dependentAssembly>
|
|
||||||
</assemblyBinding>
|
|
||||||
</runtime>
|
|
||||||
</configuration>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<packages>
|
|
||||||
<package id="BouncyCastle" version="1.8.1" targetFramework="net452" />
|
|
||||||
<package id="Google.Apis" version="1.28.0" targetFramework="net452" />
|
|
||||||
<package id="Google.Apis.Admin.Directory.directory_v1" version="1.28.0.934" targetFramework="net452" />
|
|
||||||
<package id="Google.Apis.Auth" version="1.28.0" targetFramework="net452" />
|
|
||||||
<package id="Google.Apis.Core" version="1.28.0" targetFramework="net452" />
|
|
||||||
<package id="Microsoft.Graph" version="1.5.1" targetFramework="net452" />
|
|
||||||
<package id="Microsoft.Graph.Core" version="1.6.1" targetFramework="net452" />
|
|
||||||
<package id="Microsoft.IdentityModel.Clients.ActiveDirectory" version="3.16.0" targetFramework="net452" />
|
|
||||||
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net452" />
|
|
||||||
<package id="System.Net.Http" version="4.3.2" targetFramework="net452" requireReinstallation="true" />
|
|
||||||
<package id="Zlib.Portable.Signed" version="1.11.0" targetFramework="net452" />
|
|
||||||
</packages>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<configuration>
|
|
||||||
<startup>
|
|
||||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
|
|
||||||
</startup>
|
|
||||||
<runtime>
|
|
||||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
|
||||||
<dependentAssembly>
|
|
||||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral"/>
|
|
||||||
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0"/>
|
|
||||||
</dependentAssembly>
|
|
||||||
</assemblyBinding>
|
|
||||||
</runtime>
|
|
||||||
</configuration>
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.ServiceProcess;
|
|
||||||
using System.Configuration.Install;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Security.AccessControl;
|
|
||||||
using System.Security.Principal;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
|
|
||||||
namespace Service
|
|
||||||
{
|
|
||||||
[RunInstaller(true)]
|
|
||||||
[DesignerCategory("Code")]
|
|
||||||
public class Installer : System.Configuration.Install.Installer
|
|
||||||
{
|
|
||||||
private IContainer _components = null;
|
|
||||||
private ServiceProcessInstaller _serviceProcessInstaller;
|
|
||||||
private ServiceInstaller _serviceInstaller;
|
|
||||||
|
|
||||||
public Installer()
|
|
||||||
{
|
|
||||||
Init();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Init()
|
|
||||||
{
|
|
||||||
_components = new Container();
|
|
||||||
_serviceProcessInstaller = new ServiceProcessInstaller();
|
|
||||||
_serviceInstaller = new ServiceInstaller();
|
|
||||||
|
|
||||||
_serviceProcessInstaller.Account = ServiceAccount.LocalSystem;
|
|
||||||
_serviceProcessInstaller.AfterInstall += new InstallEventHandler(AfterInstalled);
|
|
||||||
_serviceProcessInstaller.BeforeInstall += new InstallEventHandler(BeforeInstalled);
|
|
||||||
|
|
||||||
_serviceInstaller.ServiceName = Constants.ProgramName;
|
|
||||||
_serviceInstaller.Description = "Sync directory groups and users to your bitwarden organization.";
|
|
||||||
Installers.AddRange(new System.Configuration.Install.Installer[] { _serviceProcessInstaller, _serviceInstaller });
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AfterInstalled(object sender, InstallEventArgs e)
|
|
||||||
{
|
|
||||||
var info = new DirectoryInfo(Constants.BaseStoragePath);
|
|
||||||
if(!info.Exists)
|
|
||||||
{
|
|
||||||
info.Create();
|
|
||||||
}
|
|
||||||
|
|
||||||
var sec = info.GetAccessControl();
|
|
||||||
AddPermission(new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null), sec);
|
|
||||||
AddPermission(new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null), sec);
|
|
||||||
AddPermission(new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null), sec);
|
|
||||||
AddPermission(new SecurityIdentifier(WellKnownSidType.CreatorOwnerSid, null), sec);
|
|
||||||
AddPermission(WindowsIdentity.GetCurrent().User, sec);
|
|
||||||
sec.SetAccessRuleProtection(isProtected: true, preserveInheritance: false);
|
|
||||||
info.SetAccessControl(sec);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddPermission(IdentityReference sid, DirectorySecurity sec)
|
|
||||||
{
|
|
||||||
var rule = new FileSystemAccessRule(
|
|
||||||
sid,
|
|
||||||
FileSystemRights.FullControl | FileSystemRights.Write | FileSystemRights.Read,
|
|
||||||
InheritanceFlags.None,
|
|
||||||
PropagationFlags.NoPropagateInherit,
|
|
||||||
AccessControlType.Allow);
|
|
||||||
sec.AddAccessRule(rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void BeforeInstalled(object sender, InstallEventArgs e)
|
|
||||||
{
|
|
||||||
if(EventLog.SourceExists(_serviceInstaller.ServiceName))
|
|
||||||
{
|
|
||||||
EventLog.DeleteEventSource(_serviceInstaller.ServiceName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.ServiceProcess;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Service
|
|
||||||
{
|
|
||||||
static class Program
|
|
||||||
{
|
|
||||||
static void Main()
|
|
||||||
{
|
|
||||||
//DebugMode();
|
|
||||||
|
|
||||||
ServiceBase.Run(new ServiceBase[]
|
|
||||||
{
|
|
||||||
new Service()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[Conditional("DEBUG")]
|
|
||||||
private static void DebugMode()
|
|
||||||
{
|
|
||||||
Debugger.Launch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
using System.Reflection;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
// General Information about an assembly is controlled through the following
|
|
||||||
// set of attributes. Change these attribute values to modify the information
|
|
||||||
// associated with an assembly.
|
|
||||||
[assembly: AssemblyTitle("Service")]
|
|
||||||
[assembly: AssemblyDescription("")]
|
|
||||||
[assembly: AssemblyConfiguration("")]
|
|
||||||
[assembly: AssemblyCompany("")]
|
|
||||||
[assembly: AssemblyProduct("Service")]
|
|
||||||
[assembly: AssemblyCopyright("Copyright © 2017")]
|
|
||||||
[assembly: AssemblyTrademark("")]
|
|
||||||
[assembly: AssemblyCulture("")]
|
|
||||||
|
|
||||||
// Setting ComVisible to false makes the types in this assembly not visible
|
|
||||||
// to COM components. If you need to access a type in this assembly from
|
|
||||||
// COM, set the ComVisible attribute to true on that type.
|
|
||||||
[assembly: ComVisible(false)]
|
|
||||||
|
|
||||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
|
||||||
[assembly: Guid("a8fd8ced-5510-4ebd-aace-5d3cbb7516db")]
|
|
||||||
|
|
||||||
// Version information for an assembly consists of the following four values:
|
|
||||||
//
|
|
||||||
// Major Version
|
|
||||||
// Minor Version
|
|
||||||
// Build Number
|
|
||||||
// Revision
|
|
||||||
//
|
|
||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
|
||||||
// by using the '*' as shown below:
|
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
|
||||||
[assembly: AssemblyVersion("1.0.0.0")]
|
|
||||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Data;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.ServiceProcess;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Service
|
|
||||||
{
|
|
||||||
[DesignerCategory("Code")]
|
|
||||||
public class Service : ServiceBase
|
|
||||||
{
|
|
||||||
private IContainer _components;
|
|
||||||
private EventLog _eventLog;
|
|
||||||
private Timer _timer;
|
|
||||||
|
|
||||||
public Service()
|
|
||||||
{
|
|
||||||
ServiceName = Constants.ProgramName;
|
|
||||||
|
|
||||||
_components = new Container();
|
|
||||||
|
|
||||||
_eventLog = new EventLog();
|
|
||||||
_eventLog.Source = ServiceName;
|
|
||||||
_eventLog.Log = "Application";
|
|
||||||
|
|
||||||
if(!EventLog.SourceExists(_eventLog.Source))
|
|
||||||
{
|
|
||||||
EventLog.CreateEventSource(_eventLog.Source, _eventLog.Log);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if(disposing)
|
|
||||||
{
|
|
||||||
_eventLog?.Dispose();
|
|
||||||
_eventLog = null;
|
|
||||||
|
|
||||||
_components?.Dispose();
|
|
||||||
_components = null;
|
|
||||||
|
|
||||||
_timer?.Dispose();
|
|
||||||
_timer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnStart(string[] args)
|
|
||||||
{
|
|
||||||
_eventLog.WriteEntry("Service started!", EventLogEntryType.Information);
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Server == null)
|
|
||||||
{
|
|
||||||
_eventLog.WriteEntry("Server not configured.", EventLogEntryType.Error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Sync == null)
|
|
||||||
{
|
|
||||||
_eventLog.WriteEntry("Sync not configured.", EventLogEntryType.Error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!AuthService.Instance.Authenticated || !AuthService.Instance.OrganizationSet)
|
|
||||||
{
|
|
||||||
_eventLog.WriteEntry("Not authenticated with proper organization set.", EventLogEntryType.Error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var intervalMinutes = SettingsService.Instance.Sync.IntervalMinutes;
|
|
||||||
if(intervalMinutes < 5)
|
|
||||||
{
|
|
||||||
intervalMinutes = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
_eventLog.WriteEntry($"Starting timer with {intervalMinutes} minute interval.", EventLogEntryType.Information);
|
|
||||||
var timerDelegate = new TimerCallback(Callback);
|
|
||||||
_timer = new Timer(timerDelegate, null, 1000, 60 * 1000 * intervalMinutes);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnStop()
|
|
||||||
{
|
|
||||||
_eventLog.WriteEntry("Service stopped!", EventLogEntryType.Information);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Callback(object stateInfo)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var sw = Stopwatch.StartNew();
|
|
||||||
var result = Sync.SyncAllAsync(false, true).GetAwaiter().GetResult();
|
|
||||||
sw.Stop();
|
|
||||||
if(result.Success)
|
|
||||||
{
|
|
||||||
_eventLog.WriteEntry($"Synced {result.Groups?.Count ?? 0} groups, {result.Users?.Count ?? 0} users. " +
|
|
||||||
$"The sync took {(int)sw.Elapsed.TotalSeconds} seconds to complete.",
|
|
||||||
EventLogEntryType.SuccessAudit);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_eventLog.WriteEntry($"Sync failed after {(int)sw.Elapsed.TotalSeconds} seconds: {result.ErrorMessage}.",
|
|
||||||
EventLogEntryType.FailureAudit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(ApplicationException e)
|
|
||||||
{
|
|
||||||
_eventLog.WriteEntry($"Sync exception: {e.Message}", EventLogEntryType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
|
||||||
<PropertyGroup>
|
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
|
||||||
<ProjectGuid>{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}</ProjectGuid>
|
|
||||||
<OutputType>WinExe</OutputType>
|
|
||||||
<RootNamespace>Service</RootNamespace>
|
|
||||||
<AssemblyName>Service</AssemblyName>
|
|
||||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
|
||||||
<FileAlignment>512</FileAlignment>
|
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
|
||||||
<TargetFrameworkProfile />
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
<DebugType>full</DebugType>
|
|
||||||
<Optimize>false</Optimize>
|
|
||||||
<OutputPath>bin\Debug\</OutputPath>
|
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
|
||||||
<DebugType>pdbonly</DebugType>
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<OutputPath>bin\Release\</OutputPath>
|
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="System" />
|
|
||||||
<Reference Include="System.Configuration.Install" />
|
|
||||||
<Reference Include="System.Core" />
|
|
||||||
<Reference Include="System.Xml.Linq" />
|
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
|
||||||
<Reference Include="Microsoft.CSharp" />
|
|
||||||
<Reference Include="System.Data" />
|
|
||||||
<Reference Include="System.Net.Http" />
|
|
||||||
<Reference Include="System.ServiceProcess" />
|
|
||||||
<Reference Include="System.Xml" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="Installer.cs" />
|
|
||||||
<Compile Include="Service.cs" />
|
|
||||||
<Compile Include="Program.cs" />
|
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="App.config" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Core\Core.csproj">
|
|
||||||
<Project>{ae082484-a34c-4b3a-a69f-49e5ef298b27}</Project>
|
|
||||||
<Name>Core</Name>
|
|
||||||
</ProjectReference>
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
|
||||||
</Project>
|
|
||||||