1
0
mirror of https://github.com/bitwarden/directory-connector synced 2025-12-05 23:53:21 +00:00

Compare commits

...

211 Commits

Author SHA1 Message Date
Kyle Spearrin
7cb2147569 update jslib 2018-10-16 08:57:14 -04:00
Kyle Spearrin
327de4d714 update jslib 2018-10-13 23:29:54 -04:00
Kyle Spearrin
9b57955d72 update jslib 2018-10-11 21:27:04 -04:00
Kyle Spearrin
e63198e130 update electron 2018-10-09 17:56:23 -04:00
Kyle Spearrin
03eac9006f update jslib 2018-10-09 15:32:23 -04:00
Kyle Spearrin
d3402cca5e update jslib 2018-10-05 13:57:36 -04:00
Kyle Spearrin
076b320e70 dont await void methods 2018-10-04 12:05:47 -04:00
Kyle Spearrin
44180f95a5 convert toaster and analytics to platform utils 2018-10-03 10:15:00 -04:00
Kyle Spearrin
e23ebf13b7 update jslib 2018-10-02 09:22:53 -04:00
Kyle Spearrin
e0fd1a2e93 fix null ref on trim, resolves #8 2018-09-20 08:17:21 -04:00
Kyle Spearrin
c39986b37c import webfonts 2018-09-18 15:49:48 -04:00
Kyle Spearrin
1e0029dc15 bump version 2018-09-18 11:52:32 -04:00
Kyle Spearrin
63bc5e4161 ssl options for ldap 2018-09-18 11:31:11 -04:00
Kyle Spearrin
8c8b4da595 no minimize 2018-09-14 15:48:50 -04:00
Kyle Spearrin
1a7536270a bump version 2018-09-14 12:19:16 -04:00
Kyle Spearrin
d92e3c8d7b preserveWhitespaces 2018-09-13 12:04:13 -04:00
Kyle Spearrin
c30aedf491 update jslib 2018-09-12 13:22:54 -04:00
Kyle Spearrin
0e96a462ee update libs and move to webpack 4 2018-09-12 10:17:06 -04:00
Kyle Spearrin
3ef60cb3a0 update jslib 2018-09-11 08:45:46 -04:00
Kyle Spearrin
34b7638e11 trim email also 2018-09-08 08:13:40 -04:00
Kyle Spearrin
63ef932469 dont use group deltas since they do not update on membership changes 2018-09-07 08:41:55 -04:00
Kyle Spearrin
e09163fc72 update lunr 2018-09-03 21:51:37 -04:00
Kyle Spearrin
6a737f6d7d Fix !== null checks 2018-08-30 21:48:17 -04:00
Kyle Spearrin
ccc7b7b213 update jslib 2018-08-29 09:22:37 -04:00
Kyle Spearrin
1e98bc6430 fall back to userPrincipalName in other conditions 2018-08-29 08:49:59 -04:00
Kyle Spearrin
c63a945057 update jslib 2018-08-28 23:17:53 -04:00
Kyle Spearrin
a13d9fa00f add csp 2018-08-28 12:15:12 -04:00
Kyle Spearrin
1518d5eafc update jslib 2018-08-27 23:05:39 -04:00
Kyle Spearrin
c74800fd8c update bootstrap 2018-08-21 15:28:12 -04:00
Kyle Spearrin
9e282efe28 npm audit fix 2018-08-21 15:18:55 -04:00
Kyle Spearrin
26efc9158d ignore notifications service 2018-08-20 22:22:48 -04:00
Kyle Spearrin
e2d3d35d71 update jslib 2018-08-20 17:08:27 -04:00
Kyle Spearrin
c115bf84f0 setUrlsFromStorage on init factory 2018-08-20 17:08:07 -04:00
Kyle Spearrin
5b56d32492 update electron 2018-08-18 21:44:10 -04:00
Kyle Spearrin
2fe294ad8a fix click issue on 2fa provider selection 2018-08-17 08:56:31 -04:00
Kyle Spearrin
1343897fa9 update jslib 2018-08-17 08:32:38 -04:00
Kyle Spearrin
9b2b0e4ea6 update jslib 2018-08-16 15:13:34 -04:00
Kyle Spearrin
489e775b08 update jslib 2018-08-15 09:01:30 -04:00
Kyle Spearrin
0d76a45181 bump version 2018-08-14 21:44:34 -04:00
Kyle Spearrin
4787f9a462 prelogin kdf info 2018-08-14 15:13:44 -04:00
Kyle Spearrin
b228e12c81 update jslib 2018-08-13 23:41:14 -04:00
Kyle Spearrin
0e28100ec6 add papaparse types cause whoknowswhy 2018-08-08 17:00:33 -04:00
Kyle Spearrin
d2c1697144 switch to gulp file to get google fonts 2018-08-08 12:53:09 -04:00
Kyle Spearrin
b924ea8dd1 update packages for node 10 2018-08-08 12:28:32 -04:00
Kyle Spearrin
30936d3ad1 update jslib 2018-08-07 09:26:14 -04:00
Kyle Spearrin
289589dce9 toaster message paragraph styles 2018-08-02 08:48:59 -04:00
Kyle Spearrin
48734b8109 update jslib 2018-07-31 23:49:46 -04:00
Kyle Spearrin
2e69a41943 update jslib 2018-07-31 11:26:33 -04:00
Kyle Spearrin
f414a3ac0a update jslib 2018-07-30 17:08:49 -04:00
Kyle Spearrin
81833b134c update jslib 2018-07-25 14:30:40 -04:00
Kyle Spearrin
5a5b74a0d5 bump version 2018-07-24 15:29:48 -04:00
Kyle Spearrin
5a7a230193 update libs 2018-07-24 15:29:00 -04:00
Jacob
bc5ab6252e Fetch image from bitwarden/brand (#7)
That was the last of them, sorry for all the pull request spams :P
2018-07-19 10:51:35 -04:00
Kyle Spearrin
0e4d5e0973 update jslib 2018-07-18 09:56:19 -04:00
Kyle Spearrin
08e254e34c version bump 2018-07-17 08:49:46 -04:00
Kyle Spearrin
2c20796ea1 make sure entry ids are unique 2018-07-17 08:49:15 -04:00
Kyle Spearrin
8b9d7a7e2e remove sync service ref 2018-07-13 10:51:14 -04:00
Kyle Spearrin
1a0709b02c update jslib 2018-07-13 09:31:32 -04:00
Kyle Spearrin
0f386ee8d2 remember email on login 2018-07-13 09:29:12 -04:00
Kyle Spearrin
c0b41155e9 update jslib 2018-07-09 09:14:46 -04:00
Kyle Spearrin
ddd1165728 dummy module update 2018-07-07 23:57:24 -04:00
Kyle Spearrin
651dbe59c8 update jslib 2018-07-07 23:52:19 -04:00
Kyle Spearrin
aeb6f28f9a update jslib 2018-07-05 14:55:06 -04:00
Kyle Spearrin
012eefad4e update jslib 2018-07-05 14:42:58 -04:00
Kyle Spearrin
b05346d4a3 update jslib 2018-06-30 13:51:32 -04:00
Kyle Spearrin
854e6e84fe ignore jslib importers 2018-06-28 08:07:02 -04:00
Kyle Spearrin
20436bf07d tolowercase all the emails 2018-06-28 07:53:58 -04:00
Kyle Spearrin
4110e4b8cb update jslib 2018-06-13 22:14:28 -04:00
Kyle Spearrin
e179edc2e0 update jslib 2018-06-13 17:15:21 -04:00
Kyle Spearrin
133ef7eb16 load DuoWebSDK as a module 2018-06-11 13:33:03 -04:00
Kyle Spearrin
5a98982fc2 move dummy module into project 2018-06-11 08:54:34 -04:00
Kyle Spearrin
f6e92bf597 bump version 2018-06-08 16:08:12 -04:00
Kyle Spearrin
af4a8d5ae8 update jslib 2018-06-04 12:06:04 -04:00
Kyle Spearrin
c3704bbb67 bump version 2018-06-01 16:42:01 -04:00
Kyle Spearrin
8620f2f80d update jslib 2018-06-01 12:34:11 -04:00
Kyle Spearrin
e0124474cf update jslib 2018-06-01 12:00:58 -04:00
Kyle Spearrin
ac21b78225 Update README.md 2018-06-01 09:32:08 -04:00
Kyle Spearrin
dca69a5428 assign BitwardenToasterService global 2018-05-31 14:41:30 -04:00
Kyle Spearrin
6b1fa0f50c intall for package lock file 2018-05-31 09:22:17 -04:00
Kyle Spearrin
3ab35dc4e0 add lowdb typings 2018-05-31 09:16:34 -04:00
Kyle Spearrin
2aa1fc1acb replace electron store with lowdb 2018-05-31 09:09:05 -04:00
Kyle Spearrin
e6643fa4cb update to electron 2.x 2018-05-31 08:12:35 -04:00
Kyle Spearrin
4848dd8502 update jslib 2018-05-24 09:01:53 -04:00
Kyle Spearrin
4b08d7b4b2 exclude export service 2018-05-17 16:22:37 -04:00
Kyle Spearrin
839189c772 update jslib 2018-05-17 16:01:08 -04:00
Kyle Spearrin
a06ff45c21 update jslib 2018-05-17 10:58:05 -04:00
Kyle Spearrin
c598efcb9f update jslib 2018-05-16 15:30:57 -04:00
Kyle Spearrin
878476d195 make logout async 2018-05-15 23:48:20 -04:00
Kyle Spearrin
82e599fc13 version bump 2018-05-14 08:49:08 -04:00
Kyle Spearrin
79b76687a0 allow settings for user and groups path on ad 2018-05-14 08:46:57 -04:00
Kyle Spearrin
af5349c4cb Merge branch 'master' of github.com:bitwarden/directory-connector 2018-05-09 16:10:42 -04:00
Kyle Spearrin
3de35b481e no need to store response variable 2018-05-09 16:10:40 -04:00
Kyle Spearrin
008bb308e7 Update README.md 2018-05-08 22:54:49 -04:00
Kyle Spearrin
9b44fc4a35 Update README.md 2018-05-08 22:52:03 -04:00
Kyle Spearrin
3d374fc792 bump version 2018-05-08 15:57:42 -04:00
Kyle Spearrin
b51c842d18 lint fix 2018-05-08 15:37:56 -04:00
Kyle Spearrin
95b595d5bf display fixes 2018-05-08 15:36:31 -04:00
Kyle Spearrin
445c3f220a handle group syncs even whenever there are no users 2018-05-08 15:11:16 -04:00
Kyle Spearrin
9644d95b9b done checking for update on reset too 2018-05-08 13:36:48 -04:00
Kyle Spearrin
eef9f151ef option to min to tray icon 2018-05-08 12:05:20 -04:00
Kyle Spearrin
6c05a4ec25 adjust menu 2018-05-08 09:40:54 -04:00
Kyle Spearrin
2ea71e1feb parameter validation 2018-05-07 23:36:14 -04:00
Kyle Spearrin
294404750d are 2018-05-07 21:41:30 -04:00
Kyle Spearrin
ada957c7a3 icns icon 2018-05-07 18:05:20 -04:00
Kyle Spearrin
cda747a1f5 icons 2018-05-07 18:01:46 -04:00
Kyle Spearrin
3f9db95eba fix returns to continues 2018-05-07 16:23:55 -04:00
Kyle Spearrin
18d77f5ff5 fix syncCount message 2018-05-07 15:14:49 -04:00
Kyle Spearrin
e7e8cfe8da fix flatten 2018-05-07 14:53:50 -04:00
Kyle Spearrin
7abc791050 welcome message on login 2018-05-07 14:51:46 -04:00
Kyle Spearrin
913098916e Merge branch 'master' of github.com:bitwarden/directory-connector 2018-05-05 00:29:23 -04:00
Kyle Spearrin
cbf1f3bd9f up lib 2018-05-05 00:29:19 -04:00
Kyle Spearrin
a0a1fe6737 Update README.md 2018-05-04 20:29:49 -04:00
Kyle Spearrin
cb2a702829 Update README.md 2018-05-04 20:29:15 -04:00
Kyle Spearrin
65f171bde0 Update README.md 2018-05-04 20:14:56 -04:00
Kyle Spearrin
85531e2133 up sub 2018-05-04 17:33:23 -04:00
Kyle Spearrin
17e058dce2 fa-fw 2018-05-04 16:38:46 -04:00
Kyle Spearrin
566f187a3d update gitignore for electron apps 2018-05-04 16:30:12 -04:00
Kyle Spearrin
85c528ce27 package update 2018-05-04 16:29:33 -04:00
Kyle Spearrin
9a87c8bffb fixes for mac build 2018-05-04 16:28:33 -04:00
Kyle Spearrin
ae931b2dde init sub on postinstall 2018-05-04 16:09:47 -04:00
Kyle Spearrin
183be70d44 storage dir adjustments 2018-05-04 16:04:05 -04:00
Kyle Spearrin
eb6e5d7a75 package app with electorn builder 2018-05-04 15:58:19 -04:00
Kyle Spearrin
e1101bdd62 more examples 2018-05-04 15:33:35 -04:00
Kyle Spearrin
954cf2c0ff filter examples 2018-05-04 15:28:50 -04:00
Kyle Spearrin
17c7806850 i18n the dashboard 2018-05-04 15:10:23 -04:00
Kyle Spearrin
2594773a12 mb on tabs 2018-05-04 14:52:45 -04:00
Kyle Spearrin
a0025e933a wire up updater 2018-05-04 14:42:46 -04:00
Kyle Spearrin
aea11afb5a query deactivated users 2018-05-03 16:33:01 -04:00
Kyle Spearrin
e40ee1e3a8 get sync status. clear sync cache 2018-05-03 15:46:20 -04:00
Kyle Spearrin
1632af3e22 organization settings 2018-05-03 14:18:47 -04:00
Kyle Spearrin
fd3793d3d7 stop and start syncing 2018-05-03 12:27:30 -04:00
Kyle Spearrin
fef65c5209 set proper page title 2018-05-03 11:33:32 -04:00
Kyle Spearrin
173ab8cf91 centralize more code 2018-05-03 11:25:34 -04:00
Kyle Spearrin
9d6cb6e044 centralize custom filters into base directory service 2018-05-03 11:09:28 -04:00
Kyle Spearrin
27c5909d7f okta filters 2018-05-03 08:49:26 -04:00
Kyle Spearrin
91b0943a8b query okta users and groups 2018-05-03 00:00:19 -04:00
Kyle Spearrin
a8b741bb22 stub out okta directory service 2018-05-02 23:31:49 -04:00
Kyle Spearrin
4cd1a8a6a0 verison is now 2.0.0 2018-05-02 22:45:05 -04:00
Kyle Spearrin
db501f9f72 sync ui updates on dashboard 2018-05-02 22:41:50 -04:00
Kyle Spearrin
95d98181fc sync logic 2018-05-02 22:00:44 -04:00
Kyle Spearrin
3981cb95fc sort results 2018-05-02 17:00:31 -04:00
Kyle Spearrin
c80fa93515 sim fixes 2018-05-02 16:36:47 -04:00
Kyle Spearrin
5e2895ce33 group enabled, disabled, and deleted users 2018-05-02 16:28:03 -04:00
Kyle Spearrin
9c5e0df719 simualtion testing from dashboard 2018-05-02 16:00:59 -04:00
Kyle Spearrin
da73fcd973 more tab with about and logout 2018-05-02 14:26:33 -04:00
Kyle Spearrin
f8ae050556 reduce menu. added gsuite file picker 2018-05-02 14:01:44 -04:00
Kyle Spearrin
d53c1211cf move placeholders 2018-05-02 13:11:32 -04:00
Kyle Spearrin
905ebd76ae styling updates 2018-05-02 12:34:47 -04:00
Kyle Spearrin
179687490c cleanup settings and set defautls for ldap ad 2018-05-01 16:52:18 -04:00
Kyle Spearrin
85aff3e75a ldap cleanup and fixes 2018-05-01 15:46:46 -04:00
Kyle Spearrin
388a69fe4e guid is 5 parts 2018-05-01 15:19:29 -04:00
Kyle Spearrin
4e9086a042 buf to arr when guid 2018-05-01 15:15:31 -04:00
Kyle Spearrin
d6707488c8 buf to guid 2018-05-01 15:05:12 -04:00
Kyle Spearrin
bbcc65f149 no current user binding 2018-05-01 14:25:54 -04:00
Kyle Spearrin
e74b99ec65 current user bind 2018-05-01 14:03:34 -04:00
Kyle Spearrin
125de3e35e new ldap.Control 2018-05-01 13:52:42 -04:00
Kyle Spearrin
9689a63bdc ldap control for deleted objects 2018-05-01 13:41:43 -04:00
Kyle Spearrin
89b468e443 query deleted objects 2018-05-01 12:24:59 -04:00
Kyle Spearrin
db6b6fe8fa bitwise check for account disabled 2018-05-01 11:55:42 -04:00
Kyle Spearrin
e5718dd742 parse guid ids 2018-05-01 11:34:28 -04:00
Kyle Spearrin
39622fe263 build import request models 2018-05-01 11:34:22 -04:00
Kyle Spearrin
1d723000a9 remove creation/revision date from entries 2018-04-30 17:19:29 -04:00
Kyle Spearrin
36101fd7dd some sync work 2018-04-30 17:01:01 -04:00
Kyle Spearrin
b8bb6cb55c process ldap directory 2018-04-30 15:06:33 -04:00
Kyle Spearrin
995249e421 format 2018-04-28 22:55:10 -04:00
Kyle Spearrin
4533dd72ae reuse access token 2018-04-28 22:43:33 -04:00
Kyle Spearrin
f5c12d9ea6 implement azure users and groups fetching for sync 2018-04-28 22:25:52 -04:00
Kyle Spearrin
ce8d58199a get users for azure service 2018-04-28 15:13:32 -04:00
Kyle Spearrin
5556a57685 gsuite directory query logic to make entries result 2018-04-28 00:13:04 -04:00
Kyle Spearrin
39f760e135 models for entries 2018-04-27 22:53:46 -04:00
Kyle Spearrin
e8351c3246 cleanup 2018-04-27 22:41:39 -04:00
Kyle Spearrin
35bf1f0f77 save on destroy 2018-04-27 22:40:10 -04:00
Kyle Spearrin
87039fa784 conditional settings for ldap ad 2018-04-27 22:35:32 -04:00
Kyle Spearrin
dea8e48895 wire up directory services to settings 2018-04-27 19:31:15 -04:00
Kyle Spearrin
4145de7662 layout styling 2018-04-27 18:22:27 -04:00
Kyle Spearrin
2800c2f077 settings configuration 2018-04-27 17:16:54 -04:00
Kyle Spearrin
e7787ea95c stub out directory service auth/querying 2018-04-27 14:13:22 -04:00
Kyle Spearrin
e7809b405d tabs 2018-04-26 17:26:19 -04:00
Kyle Spearrin
b04b3ef924 setup bootstrap scss 2018-04-26 17:09:19 -04:00
Kyle Spearrin
6876b905cd cleanup imports 2018-04-26 16:19:25 -04:00
Kyle Spearrin
371101bf69 remove unneeded features 2018-04-26 16:17:34 -04:00
Kyle Spearrin
e17ecf967d email address string 2018-04-26 16:02:21 -04:00
Kyle Spearrin
b0705a911d wire up message service to menu 2018-04-26 16:00:47 -04:00
Kyle Spearrin
99c7f619e0 main messaging service to jslib 2018-04-26 15:45:52 -04:00
Kyle Spearrin
c4a37b2a85 added i18n strings 2018-04-26 15:29:57 -04:00
Kyle Spearrin
c12f40ecf7 debug main and renderer 2018-04-26 13:09:16 -04:00
Kyle Spearrin
fe6055c402 update sub 2018-04-26 00:43:28 -04:00
Kyle Spearrin
d2b36c4d23 update sub 2018-04-26 00:20:20 -04:00
Kyle Spearrin
fdd29190a4 use concurrently tool 2018-04-25 16:48:34 -04:00
Kyle Spearrin
6d31d0a60e node crypto service 2018-04-25 15:52:44 -04:00
Kyle Spearrin
a4cb908390 menu and main keytar storage service 2018-04-25 15:49:10 -04:00
Kyle Spearrin
b8d3c35e34 dont set crypto keys 2018-04-25 12:43:06 -04:00
Kyle Spearrin
48bde677bc routing with guards 2018-04-25 12:30:27 -04:00
Kyle Spearrin
cafb7da93e remove sync dependency 2018-04-25 12:08:34 -04:00
Kyle Spearrin
ad6c3cb132 wire up services 2018-04-25 09:01:29 -04:00
Kyle Spearrin
e1e532ed91 stub out electron app 2018-04-24 17:31:40 -04:00
Kyle Spearrin
2afbeb1c10 stub out directory services 2018-04-18 21:10:28 -04:00
Kyle Spearrin
598db5c83b add organization duo 2fa type 2018-04-03 16:16:48 -04:00
Kyle Spearrin
ceddb83d2d remove full namespace 2018-03-31 11:06:24 -04:00
Kyle Spearrin
4e70378b46 uppercase more bitwarden 2018-02-27 16:31:59 -05:00
Kyle Spearrin
5cd7f3568e Uppercase Bitwarden 2018-02-27 14:28:51 -05:00
Kyle Spearrin
2c2f1921c1 filter users from filtered groups 2017-12-12 15:55:24 -05:00
Kyle Spearrin
1e5e28e2b6 use proper entry for group user search 2017-12-11 11:29:08 -05:00
Kyle Spearrin
41f8263a7c setup reference file manually. 2017-12-07 12:17:26 -05:00
Kyle Spearrin
818a4db96e update libs. convert service to new csproj format 2017-12-07 11:53:17 -05:00
Kyle Spearrin
51ab260fe6 remove framework netcore2.0 target for now
- setup installer does not pick correct output
2017-12-06 12:31:27 -05:00
Kyle Spearrin
be393f7a63 client side filtering for azure ad 2017-12-05 09:10:24 -05:00
Kyle Spearrin
28c0509886 null checks 2017-11-30 15:50:05 -05:00
Kyle Spearrin
835c9f9cac added clear cache to menu option 9 2017-11-10 15:36:09 -05:00
Kyle Spearrin
6e4e78c30e proper default namespace 2017-10-24 17:21:19 -04:00
Kyle Spearrin
7d3ea444f4 convert projects to netstandard lib & netcore app 2017-10-24 17:13:56 -04:00
174 changed files with 18440 additions and 9626 deletions

16
.editorconfig Normal file
View 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

273
.gitignore vendored
View File

@@ -1,261 +1,14 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# 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
.vs
.idea
node_modules
npm-debug.log
vwd.webinfo
dist/
css/
*.crx
*.pem
build/
yarn-error.log
.DS_Store
*.nupkg
# The packages folder can be ignored because of Package Restore
**/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
*.provisionprofile

4
.gitmodules vendored Normal file
View 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
View 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
View File

@@ -0,0 +1,13 @@
Code contributions are welcome! Please commit any pull requests against the `master` branch.
# Localization (l10n)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/bitwarden-desktop/localized.svg)](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
View 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
-->

View File

@@ -1,5 +1,5 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
@@ -7,15 +7,17 @@
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
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
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
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
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
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.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
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.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
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
modification follow.
@@ -60,7 +72,7 @@ modification follow.
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
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
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU 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.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
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
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
3 of the GNU General Public License.
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
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
will be similar in spirit to the present version, but may differ in detail to
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
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
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
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.
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
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
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.>
Copyright (C) <year> <name of author>
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
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
by the Free Software Foundation, either version 3 of the License, or
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
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/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
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
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
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,
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/>.
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>.

View File

@@ -1,29 +1,39 @@
[![appveyor build](https://ci.appveyor.com/api/projects/status/github/bitwarden/directory-connector?branch=master&svg=true)](https://ci.appveyor.com/project/bitwarden/directory-connector)
[![Join the chat at https://gitter.im/bitwarden/Lobby](https://badges.gitter.im/bitwarden/Lobby.svg)](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.
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.
The Bitwarden Directory Connector is a a desktop application used to sync your Bitwarden enterprise organization to an existing directory of users and groups.
Supported directories:
- Active Directory
- Azure Active Directory
- GSuite (Google)
- Any other LDAP-based directory
- Azure Active Directory
- G Suite (Google)
- 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.
[![Platforms](https://imgur.com/SLv9paA.png "Windows, macOS, and Linux")](https://help.bitwarden.com/article/directory-sync/#download-and-install)
![Directory Connector](https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/directory-connector-macos.png "Dashboard")
# Build/Run
**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
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.

View File

@@ -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
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
- 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
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:
- 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.
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
upstream maintainer.
- Attacks requiring physical access to a user's device.
- Self-XSS
- Issues related to software or protocols not under bitwarden's control
- Vulnerabilities in outdated versions of bitwarden
- Issues related to software or protocols not under Bitwarden's control
- Vulnerabilities in outdated versions of Bitwarden
- Missing security best practices that do not directly lead to a vulnerability
- Issues that do not have any impact on the general public
@@ -39,7 +39,7 @@ While researching, we'd like to ask you to refrain from:
- Denial of service
- Spamming
- Social engineering (including phishing) of bitwarden staff or contractors
- Any physical attempts against bitwarden property or data centers
- Social engineering (including phishing) of Bitwarden staff or contractors
- 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!

View File

@@ -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

View File

@@ -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

31
gulpfile.js Normal file
View File

@@ -0,0 +1,31 @@
const gulp = require('gulp');
const googleWebFonts = require('gulp-google-webfonts');
const del = require('del');
const paths = {
cssDir: './src/css/',
};
function clean() {
return del([paths.cssDir]);
}
function webfonts() {
return gulp.src('./webfonts.list')
.pipe(googleWebFonts({
fontsDir: 'webfonts',
cssFilename: 'webfonts.css',
format: 'woff',
}))
.pipe(gulp.dest(paths.cssDir));
}
// ref: https://github.com/angular/angular/issues/22524
function cleanupAotIssue() {
return del(['./node_modules/@types/uglify-js/node_modules/source-map/source-map.d.ts']);
}
gulp.task('clean', clean);
gulp.task('cleanupAotIssue', cleanupAotIssue);
gulp.task('webfonts', ['clean'], webfonts);
gulp.task('prebuild:renderer', ['webfonts', 'cleanupAotIssue']);

1
jslib Submodule

Submodule jslib added at 2f6426deb4

13053
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

179
package.json Normal file
View File

@@ -0,0 +1,179 @@
{
"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": "gulp prebuild:renderer && webpack --config webpack.renderer.js",
"build:renderer:watch": "gulp prebuild:renderer && 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": "^6.1.7",
"@microsoft/microsoft-graph-types": "^1.4.0",
"@ngtools/webpack": "^6.2.1",
"@types/ldapjs": "^1.0.3",
"@types/lowdb": "^1.0.5",
"@types/lunr": "^2.1.6",
"@types/node-forge": "^0.7.5",
"@types/papaparse": "^4.5.3",
"@types/semver": "^5.5.0",
"@types/source-map": "0.5.2",
"@types/webcrypto": "^0.0.28",
"@types/webpack": "^4.4.11",
"clean-webpack-plugin": "^0.1.19",
"concurrently": "^4.0.1",
"copy-webpack-plugin": "^4.5.2",
"css-loader": "^1.0.0",
"del": "^3.0.0",
"electron": "2.0.11",
"electron-builder": "^20.25.0",
"electron-rebuild": "1.8.1",
"electron-reload": "1.2.5",
"extract-text-webpack-plugin": "next",
"file-loader": "^2.0.0",
"font-awesome": "4.7.0",
"gulp": "^3.9.1",
"gulp-google-webfonts": "^2.0.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"node-loader": "^0.6.0",
"node-sass": "^4.9.3",
"rimraf": "^2.6.2",
"sass-loader": "^7.1.0",
"ts-loader": "^5.1.0",
"tslint": "^5.11.0",
"tslint-loader": "^3.6.0",
"typescript": "^2.7.2",
"webpack": "^4.18.0",
"webpack-cli": "^3.1.0",
"webpack-merge": "^4.1.4",
"webpack-node-externals": "^1.7.2"
},
"dependencies": {
"@angular/animations": "6.1.7",
"@angular/common": "6.1.7",
"@angular/compiler": "6.1.7",
"@angular/core": "6.1.7",
"@angular/forms": "6.1.7",
"@angular/http": "6.1.7",
"@angular/platform-browser": "6.1.7",
"@angular/platform-browser-dynamic": "6.1.7",
"@angular/router": "6.1.7",
"@angular/upgrade": "6.1.7",
"@microsoft/microsoft-graph-client": "1.2.0",
"@okta/okta-sdk-nodejs": "1.2.0",
"angular2-toaster": "6.1.0",
"angulartics2": "6.3.0",
"bootstrap": "4.1.3",
"core-js": "2.5.7",
"electron-log": "2.2.14",
"electron-updater": "3.0.3",
"googleapis": "33.0.0",
"keytar": "4.2.1",
"ldapjs": "1.0.2",
"lowdb": "1.0.0",
"lunr": "2.3.3",
"node-forge": "0.7.6",
"rxjs": "6.3.2",
"zone.js": "0.8.26"
}
}

BIN
resources/background.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
resources/dmg.icns Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

BIN
resources/icon.icns Normal file

Binary file not shown.

BIN
resources/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 823 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
resources/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
resources/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
resources/icons/16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

BIN
resources/icons/256x256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
resources/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 B

BIN
resources/icons/48x48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

BIN
resources/icons/512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
resources/icons/64x64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 961 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

View File

@@ -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>

View File

@@ -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>

File diff suppressed because it is too large Load Diff

View File

@@ -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")]

View File

@@ -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>

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -1,12 +0,0 @@
namespace Bit.Core.Enums
{
public enum TwoFactorProviderType : byte
{
Authenticator = 0,
Email = 1,
Duo = 2,
YubiKey = 3,
U2f = 4,
Remember = 5
}
}

View File

@@ -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,
}
}

View File

@@ -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; }
}
}

View File

@@ -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;
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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;
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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";
}
}

View File

@@ -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; }
}
}
}

View File

@@ -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}";
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}
}

View File

@@ -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>();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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; }
}
}

View File

@@ -1,8 +0,0 @@
namespace Bit.Core.Models
{
public class TwoFactorEmailRequest
{
public string Email { get; set; }
public string MasterPasswordHash { get; set; }
}
}

View File

@@ -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")]

View File

@@ -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", $"Bearer {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", $"Bearer {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;
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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; }
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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";
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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")]

View File

@@ -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);
}
}
}
}

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