Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OIDC SSO Re: Issue #246 #1955

Closed
wants to merge 23 commits into from
Closed

OIDC SSO Re: Issue #246 #1955

wants to merge 23 commits into from

Conversation

Sheap
Copy link

@Sheap Sheap commented Sep 1, 2021

This branch adds the minimal required changes to allow OIDC SSO (at least with keycloak. There are many options/features of the process which I did not address, which may rule out other providers).

This is so far my biggest contact point with rust in a web server, as well as in implementing OIDC, so I wouldn't be surprised if there were places where I strayed from the path. But I have tested it, and at least it works. I'd welcome any feedback here. My greatest concern is that I'm currently ignoring the nonce. I believe it should be checked against, but I'm not sure where would be a sensible place to store it between generating it alongside the auth url, and consuming it when exchanging the code for an access token.

Note: There are associated changes required to the webvault here - I wasn't sure how I should link them, so to keep things simple for now, I've simply included a .patch file in this repo.

@williamdes
Copy link
Contributor

Hi @Sheap
Could you import your public GPG key into your GitHub account ?

@Sheap
Copy link
Author

Sheap commented Sep 2, 2021

Done

Copy link
Owner

@dani-garcia dani-garcia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some general, haven't tested it and I don't know how the workflow between SSO and bitwarden interacts, so I can't comment about that in greater detail.

If you know a way to quickly setup a keycloak server to test this that would be great too

name TEXT NOT NULL,
billing_email TEXT NOT NULL
uuid VARCHAR(40) NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would even say this should be in a separate table by it self and have foreign keys on the org id.
This so the org table keeps clean, and what if bitwarden will support multiple providers for example.

@@ -200,6 +213,35 @@ fn post_organization(

org.name = data.Name;
org.billing_email = data.BillingEmail;
org.identifier = match data.Identifier {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
org.identifier = match data.Identifier {
org.identifier = data.Identifier.unwrap_or_default();

@@ -102,6 +169,15 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult
err!("This user has been disabled", format!("IP: {}. Username: {}.", ip.ip, username))
}

// Check if org policy prevents password login
let user_orgs = UserOrganization::find_by_user_and_policy(&user.uuid, OrgPolicyType::RequireSso, &conn);
if user_orgs.len() == 1 && user_orgs[0].atype >= 2 {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if user_orgs.len() == 1 && user_orgs[0].atype >= 2 {
if user_orgs.len() >= 1 && user_orgs[0].atype < UserOrgType::Admin {

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with a version which better reflects the documentation. Specifically:

if user_orgs.len() >= 1 && user_orgs[0].atype != UserOrgType::Owner && user_orgs[0].atype != UserOrgType::Admin {

err_msg += &format!("\n caused by: {}", cause);
cur_fail = cause.source();
}
panic!("{}", err_msg);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't panic during the normal server operation, it's better to return a Result instead.

@@ -131,13 +138,20 @@ impl Organization {
billing_email,
private_key,
public_key,
identifier: String::from(""),
use_sso: false,
callback_path: String::from("http://localhost/#/sso/"),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should all use String::new(), or ideally be an Option when not defined.


#[get("/account/prevalidate?<domainHint>")]
#[allow(non_snake_case)]
fn prevalidate(domainHint: &RawStr, conn: DbConn) -> JsonResult {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use a String domainHint? Using a String it should be percent decoded already. Same goes for authorize()

@@ -432,3 +513,151 @@ fn _check_is_some<T>(value: &Option<T>, msg: &str) -> EmptyResult {
}
Ok(())
}

fn invalid_json(error_message: &str, exception: bool) -> JsonResult {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is simple enough that it could be inlined. Also it always uses exception == false, as far as I can see.


// TODO store the nonce for validation on authorization token exchange - unclear where to store
// this
let (mut authorize_url, _csrf_state, _nonce) = client
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not the same, but as an example, any nonce/challenges we use for the two factor authentication we save in its database table, check the type TwoFactorType, the values >= 1000 are used for this kind of temp authentication values and they are deleted once used.

In your case SSO is not two factor authentication, but you could create a new database table just for those.

Cargo.toml Outdated
@@ -137,6 +137,8 @@ backtrace = "0.3.60"

# Macro ident concatenation
paste = "1.0.5"
openidconnect = "2.0.1"
urlencoding = "1.1.1"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This urlencoding library doesn't seem to be used.

@pinpox
Copy link

pinpox commented Sep 9, 2021

If you know a way to quickly setup a keycloak server to test this that would be great too

Running a docker instance of keycloak seems like the simplest option:

docker run -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin quay.io/keycloak/keycloak:15.0.2

I'm trying to test this PR locally, but haven't succeeded yet. The code builds and runs, but I don't see the "Login with SSO" button.

@Sheap What config (.env file) are you using, do I need to change anything besides the data folder? Also, how did you apply the patch?

@pinpox
Copy link

pinpox commented Sep 15, 2021

I manged to apply the patch, but am running into build errors. This is what I did:

git clone https://github.com/bitwarden/web.git
cd web
git checkout 62cd45030ad5b0a0bdbd08f0579f8ffac91a48a4
wget https://raw.githubusercontent.com/dani-garcia/vaultwarden/635a48514fad1f279379c954361bc493d64f7f0a/web-vault-sso.patch
git submodule update --init --recursive
git apply web-vault-sso.patch
npm install
npm run dist

Build output:

Build output

» npm run dist


> [email protected] dist /home/pinpox/code/github.com/bitwarden/web
> npm run build:prod && gulp postdist


> [email protected] build:prod /home/pinpox/code/github.com/bitwarden/web
> gulp prebuild && cross-env NODE_ENV=production ENV=production webpack

[14:04:31] Using gulpfile ~/code/github.com/bitwarden/web/gulpfile.js
[14:04:31] Starting 'prebuild'...
[14:04:31] Starting 'clean'...
[14:04:31] Finished 'clean' after 9.89 ms
[14:04:31] Starting 'webfonts'...
[14:04:32] Finished 'webfonts' after 360 ms
[14:04:32] Finished 'prebuild' after 372 ms
==================================================
envConfig
  proxyApi: https://api.bitwarden.com
  proxyIdentity: https://identity.bitwarden.com
  proxyEvents: https://events.bitwarden.com
  proxyNotifications: https://notifications.bitwarden.com
  proxyPortal: https://portal.bitwarden.com
  allowedHosts:
==================================================
Compiling @angular/core : module as esm2015
Compiling @angular/animations : module as esm2015
Compiling @angular/common : module as esm2015
Compiling @angular/cdk/collections : module as esm2015
Compiling @angular/animations/browser : module as esm2015
Compiling @angular/cdk/platform : module as esm2015
Compiling @angular/platform-browser : module as esm2015
Compiling ngx-infinite-scroll : module as esm5
Compiling @angular/cdk/bidi : module as esm2015
Compiling @angular/platform-browser-dynamic : module as esm2015
Compiling @angular/cdk/scrolling : module as esm2015
Compiling angular2-toaster : module as esm2015
Compiling @angular/router : module as esm2015
Compiling @angular/cdk/drag-drop : module as esm2015
Compiling @angular/forms : module as esm2015
Compiling @angular/platform-browser/animations : module as esm2015
Hash: 9e346d1a06390c2e3b8f
Version: webpack 4.46.0
Time: 12596ms
Built at: 15/09/2021 14.04.53
                                                   Asset       Size  Chunks               Chunk Names
                                               .nojekyll    0 bytes
                                                404.html   2.28 KiB
                                   404/bootstrap.min.css    158 KiB
                                404/font-awesome.min.css   30.3 KiB
                                          404/styles.css   2.28 KiB
                                             app-id.json  276 bytes
                        app/main.9e346d1a06390c2e3b8f.js  986 bytes       0  [immutable]  app/main
                    app/main.9e346d1a06390c2e3b8f.js.map   4.54 KiB       0  [dev]        app/main
                   app/polyfills.9e346d1a06390c2e3b8f.js  990 bytes       1  [immutable]  app/polyfills
               app/polyfills.9e346d1a06390c2e3b8f.js.map   4.55 KiB       1  [dev]        app/polyfills
                                       browserconfig.xml  234 bytes
              connectors/captcha.9e346d1a06390c2e3b8f.js  989 bytes       2  [immutable]  connectors/captcha
          connectors/captcha.9e346d1a06390c2e3b8f.js.map   4.55 KiB       2  [dev]        connectors/captcha
                  connectors/duo.9e346d1a06390c2e3b8f.js  985 bytes       3  [immutable]  connectors/duo
              connectors/duo.9e346d1a06390c2e3b8f.js.map   4.55 KiB       3  [dev]        connectors/duo
                  connectors/sso.9e346d1a06390c2e3b8f.js  985 bytes       4  [immutable]  connectors/sso
              connectors/sso.9e346d1a06390c2e3b8f.js.map   4.55 KiB       4  [dev]        connectors/sso
                  connectors/u2f.9e346d1a06390c2e3b8f.js   2.34 KiB       5  [immutable]  connectors/u2f
              connectors/u2f.9e346d1a06390c2e3b8f.js.map   9.74 KiB       5  [dev]        connectors/u2f
    connectors/webauthn-fallback.9e346d1a06390c2e3b8f.js  999 bytes       7  [immutable]  connectors/webauthn-fallback
connectors/webauthn-fallback.9e346d1a06390c2e3b8f.js.map   4.56 KiB       7  [dev]        connectors/webauthn-fallback
             connectors/webauthn.9e346d1a06390c2e3b8f.js  990 bytes       6  [immutable]  connectors/webauthn
         connectors/webauthn.9e346d1a06390c2e3b8f.js.map   4.55 KiB       6  [dev]        connectors/webauthn
                                             favicon.ico   33.7 KiB
                                            images/0.png   5.91 KiB
                                            images/1.png   2.61 KiB
                                            images/2.png   1.18 KiB
                                            images/3.png   1.59 KiB
                                          images/404.png     92 KiB
                                            images/7.png   1.58 KiB
                                        images/cards.png   17.1 KiB
                                     images/fa-globe.png  344 bytes
                 images/icons/android-chrome-192x192.png   5.15 KiB
                 images/icons/android-chrome-512x512.png   13.4 KiB
                       images/icons/apple-touch-icon.png   4.19 KiB
                          images/icons/favicon-16x16.png   2.11 KiB
                          images/icons/favicon-32x32.png    3.5 KiB
                         images/icons/mstile-150x150.png   5.24 KiB
                      images/icons/safari-pinned-tab.svg    2.6 KiB
                                      images/loading.svg  291 bytes
                                 images/[email protected]   10.1 KiB
                        images/logo-horizontal-white.png   16.4 KiB
        images/register-layout/logo-horizontal-white.png   16.4 KiB
                   images/register-layout/wired-logo.png   2.86 KiB
                               images/totp-countdown.png    1.6 KiB
                                 images/two-factor/0.png   5.91 KiB
                                 images/two-factor/1.png   2.61 KiB
                                 images/two-factor/2.png   1.18 KiB
                                 images/two-factor/3.png   1.59 KiB
                                 images/two-factor/4.png    4.7 KiB
                                 images/two-factor/6.png   1.18 KiB
                                 images/two-factor/7.png   1.58 KiB
                                       images/u2fkey.jpg    174 KiB
                                   images/wired-logo.png   2.86 KiB
                                      images/yubikey.jpg   27.5 KiB
                                locales/af/messages.json    116 KiB
                                locales/az/messages.json    115 KiB
                                locales/be/messages.json    123 KiB
                                locales/bg/messages.json    161 KiB
                                locales/bn/messages.json    125 KiB
                                locales/ca/messages.json    125 KiB
                                locales/cs/messages.json    122 KiB
                                locales/da/messages.json    118 KiB
                                locales/de/messages.json    126 KiB
                                locales/el/messages.json    166 KiB
                                locales/en/messages.json    115 KiB
                             locales/en_GB/messages.json    115 KiB
                             locales/en_IN/messages.json    115 KiB
                                locales/eo/messages.json    117 KiB
                                locales/es/messages.json    123 KiB
                                locales/et/messages.json    118 KiB
                                locales/fi/messages.json    121 KiB
                                locales/fr/messages.json    129 KiB
                                locales/he/messages.json    130 KiB
                                locales/hr/messages.json    118 KiB
                                locales/hu/messages.json    126 KiB
                                locales/id/messages.json    119 KiB
                                locales/it/messages.json    123 KiB
                                locales/ja/messages.json    129 KiB
                                locales/kn/messages.json    165 KiB
                                locales/ko/messages.json    124 KiB
                                locales/lv/messages.json    122 KiB
                                locales/ml/messages.json    160 KiB
                                locales/nb/messages.json    119 KiB
                                locales/nl/messages.json    120 KiB
                                locales/pl/messages.json    122 KiB
                             locales/pt_BR/messages.json    123 KiB
                             locales/pt_PT/messages.json    122 KiB
                                locales/ro/messages.json    124 KiB
                                locales/ru/messages.json    160 KiB
                                locales/si/messages.json    115 KiB
                                locales/sk/messages.json    122 KiB
                                locales/sl/messages.json    116 KiB
                                locales/sr/messages.json    116 KiB
                                locales/sv/messages.json    120 KiB
                                locales/tr/messages.json    121 KiB
                                locales/uk/messages.json    159 KiB
                                locales/vi/messages.json    120 KiB
                             locales/zh_CN/messages.json    111 KiB
                             locales/zh_TW/messages.json    112 KiB
                                           manifest.json  352 bytes
                                       scripts/dropin.js    466 KiB
                                   scripts/qrious.min.js   17.1 KiB
                       scripts/qrious.min.js.LICENSE.txt  127 bytes
                                          scripts/u2f.js    6.6 KiB
Entrypoint app/polyfills = app/polyfills.9e346d1a06390c2e3b8f.js app/polyfills.9e346d1a06390c2e3b8f.js.map
Entrypoint app/main = app/main.9e346d1a06390c2e3b8f.js app/main.9e346d1a06390c2e3b8f.js.map
Entrypoint connectors/u2f = connectors/u2f.9e346d1a06390c2e3b8f.js connectors/u2f.9e346d1a06390c2e3b8f.js.map
Entrypoint connectors/webauthn = connectors/webauthn.9e346d1a06390c2e3b8f.js connectors/webauthn.9e346d1a06390c2e3b8f.js.map
Entrypoint connectors/webauthn-fallback = connectors/webauthn-fallback.9e346d1a06390c2e3b8f.js connectors/webauthn-fallback.9e346d1a06390c2e3b8f.js.map
Entrypoint connectors/duo = connectors/duo.9e346d1a06390c2e3b8f.js connectors/duo.9e346d1a06390c2e3b8f.js.map
Entrypoint connectors/sso = connectors/sso.9e346d1a06390c2e3b8f.js connectors/sso.9e346d1a06390c2e3b8f.js.map
Entrypoint connectors/captcha = connectors/captcha.9e346d1a06390c2e3b8f.js connectors/captcha.9e346d1a06390c2e3b8f.js.map
[0] external "u2f" 42 bytes {5} [built]
[1] ./src/app/polyfills.ts 0 bytes {1} [built]
[2] ./src/app/main.ts 0 bytes {0} [built]
[3] ./src/connectors/u2f.js 3 KiB {5} [built]
[4] ./src/connectors/webauthn.ts 0 bytes {6} [built]
[5] ./src/connectors/webauthn-fallback.ts 0 bytes {7} [built]
[6] ./src/connectors/duo.ts 0 bytes {3} [built]
[7] ./src/connectors/sso.ts 0 bytes {4} [built]
[8] ./src/connectors/captcha.ts 0 bytes {2} [built]

ERROR in jslib/common/src/abstractions/api.service.ts:36:46 - error TS2307: Cannot find module '../models/request/organizationSsoUpdateRequest' or its corresponding type declarations.

36 import { OrganizationSsoUpdateRequest } from '../models/request/organizationSsoUpdateRequest';
                                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/app/app-routing.module.ts:36:49 - error TS2307: Cannot find module './organizations/settings/sso.component' or its corresponding type declarations.

36 import { SsoComponent as OrgSsoComponent } from './organizations/settings/sso.component';
                                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
jslib/common/src/services/api.service.ts:40:46 - error TS2307: Cannot find module '../models/request/organizationSsoUpdateRequest' or its corresponding type declarations.

40 import { OrganizationSsoUpdateRequest } from '../models/request/organizationSsoUpdateRequest';
                                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/app/app.module.ts:70:49 - error TS2307: Cannot find module './organizations/settings/sso.component' or its corresponding type declarations.

70 import { SsoComponent as OrgSsoComponent } from './organizations/settings/sso.component';
                                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Child HtmlWebpackCompiler:
                          Asset      Size  Chunks  Chunk Names
    __child-HtmlWebpackPlugin_0  5.75 KiB       0  HtmlWebpackPlugin_0
    __child-HtmlWebpackPlugin_1  3.89 KiB       1  HtmlWebpackPlugin_1
    __child-HtmlWebpackPlugin_2  3.79 KiB       2  HtmlWebpackPlugin_2
    __child-HtmlWebpackPlugin_3  5.16 KiB       3  HtmlWebpackPlugin_3
    __child-HtmlWebpackPlugin_4   5.8 KiB       4  HtmlWebpackPlugin_4
    __child-HtmlWebpackPlugin_5  5.74 KiB       5  HtmlWebpackPlugin_5
    __child-HtmlWebpackPlugin_6  3.87 KiB       6  HtmlWebpackPlugin_6
        images/[email protected]  10.1 KiB
              images/u2fkey.jpg   174 KiB
    Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0
    Entrypoint HtmlWebpackPlugin_1 = __child-HtmlWebpackPlugin_1
    Entrypoint HtmlWebpackPlugin_2 = __child-HtmlWebpackPlugin_2
    Entrypoint HtmlWebpackPlugin_3 = __child-HtmlWebpackPlugin_3
    Entrypoint HtmlWebpackPlugin_4 = __child-HtmlWebpackPlugin_4
    Entrypoint HtmlWebpackPlugin_5 = __child-HtmlWebpackPlugin_5
    Entrypoint HtmlWebpackPlugin_6 = __child-HtmlWebpackPlugin_6
    [0] ./node_modules/html-loader/dist/runtime/getUrl.js 548 bytes {0} {3} {4} {5} [built]
    [1] ./src/images/[email protected] 67 bytes {0} {4} {5} [built]
    [2] ./node_modules/html-webpack-plugin/lib/loader.js!./src/index.html 1.31 KiB {0} [built]
    [3] ./node_modules/html-webpack-plugin/lib/loader.js!./src/connectors/duo.html 287 bytes {1} [built]
    [4] ./node_modules/html-webpack-plugin/lib/loader.js!./src/connectors/u2f.html 183 bytes {2} [built]
    [5] ./node_modules/html-webpack-plugin/lib/loader.js!./src/connectors/webauthn.html 701 bytes {3} [built]
    [6] ./src/images/u2fkey.jpg 61 bytes {3} [built]
    [7] ./node_modules/html-webpack-plugin/lib/loader.js!./src/connectors/webauthn-fallback.html 1.32 KiB {4} [built]
    [8] ./node_modules/html-webpack-plugin/lib/loader.js!./src/connectors/sso.html 1.3 KiB {5} [built]
    [9] ./node_modules/html-webpack-plugin/lib/loader.js!./src/connectors/captcha.html 271 bytes {6} [built]
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] build:prod: `gulp prebuild && cross-env NODE_ENV=production ENV=production webpack`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] build:prod script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/pinpox/.npm/_logs/2021-09-15T12_04_53_567Z-debug.log
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] dist: `npm run build:prod && gulp postdist`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] dist script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/pinpox/.npm/_logs/2021-09-15T12_04_53_601Z-debug.log

@Sheap
Copy link
Author

Sheap commented Sep 15, 2021

@pinpox sorry for the delay in getting back to you - I was busy last week, then had a long weekend ☺️
Regarding .env file, I just copied the template. The webvault build should be fixed, but this isn't the way I'm running things locally. I have the webvault repo cloned within my vaultwarden one, and then make the following changes to get it running in docker locally:

diff --git a/.dockerignore b/.dockerignore
index 69f51d2..6bc2dbd 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -3,7 +3,6 @@ target

 # Data folder
 data
-.env
 .env.template
 .gitattributes

@@ -24,4 +23,4 @@ hooks
 tools

 # Web vault
-web-vault
\ No newline at end of file
+#web-vault
diff --git a/docker/amd64/Dockerfile b/docker/amd64/Dockerfile
index 70d9c8f..8737ad5 100644
--- a/docker/amd64/Dockerfile
+++ b/docker/amd64/Dockerfile
@@ -22,7 +22,6 @@
 #     $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:29a4fa7bf3790fff9d908b02ac5a154913491f4bf30c95b87b06d8cf1c5516b5
 #     [vaultwarden/web-vault:v2.21.1]
 #
-FROM vaultwarden/web-vault@sha256:29a4fa7bf3790fff9d908b02ac5a154913491f4bf30c95b87b06d8cf1c5516b5 as vault

 ########################## BUILD IMAGE  ##########################
 FROM rust:1.53 as build
@@ -101,7 +100,7 @@ EXPOSE 3012
 # and the binary from the "build" stage to the current stage
 WORKDIR /
 COPY Rocket.toml .
-COPY --from=vault /web-vault ./web-vault
+COPY ./web-vault/build ./web-vault
 COPY --from=build /app/target/release/vaultwarden .

 COPY docker/healthcheck.sh /healthcheck.sh

@dani-garcia Thanks for the comments - I've addressed the simpler points already, but I didn't have time today to look into the ones which required more consideration. I'll hopefully get to them this week.

@technowhizz
Copy link

@Sheap I just wanted to say thank you for getting the ball rolling on this. It's amazing!

@pinpox
Copy link

pinpox commented Sep 20, 2021

@Sheap I got it to work, but am running into a panic when trying to Login via SSO:

[2021-09-20 09:42:35.537][request][INFO] GET /identity/account/prevalidate?domainHint=test-org
[2021-09-20 09:42:35.537][response][INFO] GET /identity/account/prevalidate?<domainHint> (prevalidate) => 200 OK
[2021-09-20 09:42:35.596][request][INFO] GET /identity/connect/authorize?client_id=web&redirect_uri=htt
[2021-09-20 09:42:35.612][panic][ERROR] thread 'unnamed' panicked at 'invalid issuer URL: RelativeUrlWithoutBase': src/api/identity.rs:583
   0: vaultwarden::init_logging::{{closure}}
   1: std::panicking::rust_panic_with_hook
             at rustc/d3e2578c31688619ddc0a10ddf8543bf4ebcba5b/library/std/src/panicking.rs:628:17
   2: std::panicking::begin_panic_handler::{{closure}}
             at rustc/d3e2578c31688619ddc0a10ddf8543bf4ebcba5b/library/std/src/panicking.rs:521:13
   3: std::sys_common::backtrace::__rust_end_short_backtrace
             at rustc/d3e2578c31688619ddc0a10ddf8543bf4ebcba5b/library/std/src/sys_common/backtrace.rs:141:18
   4: rust_begin_unwind
             at rustc/d3e2578c31688619ddc0a10ddf8543bf4ebcba5b/library/std/src/panicking.rs:517:5
   5: core::panicking::panic_fmt
             at rustc/d3e2578c31688619ddc0a10ddf8543bf4ebcba5b/library/core/src/panicking.rs:93:14
   6: core::result::unwrap_failed
             at rustc/d3e2578c31688619ddc0a10ddf8543bf4ebcba5b/library/core/src/result.rs:1617:5
   7: vaultwarden::api::identity::get_client_from_identifier
   8: vaultwarden::api::identity::rocket_route_fn_authorize
   9: <F as rocket::handler::Handler>::handle
  10: rocket::rocket::Rocket::route_and_process
  11: <rocket::rocket::Rocket as hyper::server::Handler>::handle
  12: hyper::server::Worker<H>::handle_connection
  13: hyper::server::listener::spawn_with::{{closure}}
  14: std::sys_common::backtrace::__rust_begin_short_backtrace
  15: core::ops::function::FnOnce::call_once{{vtable.shim}}
  16: <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once
             at rustc/d3e2578c31688619ddc0a10ddf8543bf4ebcba5b/library/alloc/src/boxed.rs:1636:9
      <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once
             at rustc/d3e2578c31688619ddc0a10ddf8543bf4ebcba5b/library/alloc/src/boxed.rs:1636:9
      std::sys::unix::thread::Thread::new::thread_start
             at rustc/d3e2578c31688619ddc0a10ddf8543bf4ebcba5b/library/std/src/sys/unix/thread.rs:106:17
  17: start_thread
  18: clone

Might be a configuration error, but a panic seems a bit harsh?

@Sheap
Copy link
Author

Sheap commented Sep 20, 2021

It's not an error I've seen. Removing the panics in favour of graceful errors is one of the things I'm looking into at the moment.

@Sheap
Copy link
Author

Sheap commented Sep 22, 2021

Okay, big changes 😪. I think I've addressed all of the above points now.
Hopefully we're close, I'm going to have some other commitments taking up most of my time now, so I won't have much more time to work on this.

@pinpox
Copy link

pinpox commented Sep 27, 2021

The newest patch fails to apply again:

» git apply ../web-vault-sso.patch
error: src/app/organizations/settings/sso.component.html: No such file or directory
error: src/app/organizations/settings/sso.component.ts: No such file or directory

Are these files missing?

@pinpox
Copy link

pinpox commented Apr 25, 2022

I've got a version with the login process working now. I get redirected correctly to the authentication page of the authentication provider) and can login. However, I've not been able to find out what values are needed for callback_path and signed_out_callback_path to get OIDC to work.
Offcial bitwarden has these defaults:

           callback_path = https://sso.bitwarden.com/oidc-signin
signed_out_callback_path = https://sso.bitwarden.com/oidc-signedout

What do I have to set for the Callback Path and Callback Signed Out Path in vaultwarden? I have not been able to find the endpoint browsing through the diff of this PR. Can anyone help?

@dorianim
Copy link

I think, those fields should be read only. In a vanilla bitwarden they are https://bitwarden.example.com/sso/oidc-signin for callback and https://bitwarden.example.com/sso/oidc-signedout for Signed Out Callback.
As far as I understand it, they are used as redirect targets by the identity provider.

@pinpox
Copy link

pinpox commented May 2, 2022

As far as I understand it, they are used as redirect targets by the identity provider.

That's true, but they have to be set to something, otherwise the login process fails as far as I could find out.

I've been continuing to work on this PR. Not sure if @Sheap is still planning on working on it, should I open a second on (maybe as draft?) to track progress? I've rebased against the current master so that it builds with the updated web vault, but still am running into a few bugs and some feedback would be great to help me get it in a mergable state. @BlackDex let me know what way you prefer.

@BlackDex
Copy link
Collaborator

BlackDex commented May 2, 2022

If the comments above are addressed i don't mind how this will be getting to a mergeable PR.
I would though be sure to give credit to all the correct people by using the following: https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors#creating-co-authored-commits-on-the-command-line

@pinpox pinpox mentioned this pull request May 2, 2022
@Sheap
Copy link
Author

Sheap commented May 10, 2022

@pinpox looking over my code, I believe those fields should be {root}/#/sso. Although both being the same seems odd, I don't remember my reasoning. That worked at least enough for my testing locally.
Thanks for making a new MR - I unfortunately don't have the time to get this ready for merge. I'll continue checking on the progress and trying to offer any help I can.

@pinpox
Copy link

pinpox commented May 23, 2022

I'm running into a problem here: The code seems to work fine now, I can login and get redirected to the URL I specify. However {root}/#/sso is not a valid URL for ADFS.

image

Where is that defined and can it be changed to something else that does not include # or ? ?

@Sheap
Copy link
Author

Sheap commented Jun 1, 2022

@pinpox The path is defined in webvault src/app/oss-routing.module.ts. But I don't think that will help with the #. I believe that's down to the configuration of the angular router. The only other option would be a query, but it doesn't look like that would help.

If ADFS don't allow fragments or queries, then the only option I can think of would be to set up a subdomain. But I don't know how to do that in this context.

@BlackDex BlackDex mentioned this pull request Jun 29, 2022
61 tasks
@pReya
Copy link

pReya commented Jul 26, 2022

@Sheap @pinpox @BlackDex @dani-garcia There are two open PRs regarding (pseudo) SSO in vaultwarden (#1955 and #2449). Both seem to be stuck in the review stage for many weeks. I know how hard it is to find time for these projects – totally understandable.

If we (the user community) got together and collected some money to be split among all of you, would that speed up to process? Probably won't be much, but still more than nothing, and might be enough to buy a nice dinner for everyone. Are you open to the idea of such a bounty model? I'd also appreciate any reactions by the community, to show you'd be willing to donate some money to this cause (just to get an idea about how many people are willing to pay some money for this feature).

@pinpox
Copy link

pinpox commented Jul 27, 2022

@pReya I am still actively working on my PR, but progress is a bit slow. I currently have the time to work on it and money is not the show stopper, what I would need to speed this up would be some technical assistance in questions specific to this project and to rust. I'm currently in the process of applying all the fixes from the review that was made on my PR, but it took so long that I have since changed quite a few things and have to adapt a lot.

I'll try the matrix channel later with a few questions I still have to get this finished. Would be nice to speed up the feedback loop.

@pinpox
Copy link

pinpox commented Jul 27, 2022

@Sheap did this work for you?
https://github.com/dani-garcia/vaultwarden/pull/2449/files#diff-1c5b0c87dec2154a167b89110c637a7a4bc04f59af0b83e8ddba39eb2134518cR126
It is panicing for me, because the token is missing an email field. Has that changed, was it there before? If so, how do I find the user now?

I added a print statement to see the token, it is:

eyJhbGciOiJSUzI1NiIsImtpZCI6IkxVOUJFbkNlc3dUd3FVWGJBNGx2THQ1Z3hIcW4xdzVlakFPUmFFRnR5bzgiLCJ0eXAiOiJKV1QifQ.eyJnbnQiOjEsInR0IjowLCJleHAiOjE2NTg5MjM3NjIsImlhdCI6MTY1ODkyMDE2Mn0.eHLHopIUslDKBj4Fk5ubfRUYGYbikD1yHw-f_H41R8KQGEPEjIbnlZ1UyxT9f2_LivJE7PVxZ0jNSZe2jWEYkRcZyGdlUWJd_ElxaRJVZ-qqmjZ3pfQJHzOXHXvGI6D9z7A9najpxYtno9pNG5rY4K-iDfxEUAql36eLFlJJuzvCx5UXwwfCw-isKOE63qykZVix67kffu9QzQVX0Ms1wetCocNoik9rzR2g3KCmflqZHA9xtXLB0kgrOTd70VPsGwUJwgfm7AncGBDwkjcuiZMRzjh8rnBbr5-lsz5vj6Sgl7MDhhzNnlS-w-oN_Mq4VIB1VWBtf50Qvmu3-d3x2X-wOrrV8MM8VNWjDXQFvMKp8xpn3Qdz6EtrVdKwNDYM29Z0LAhCpT8OJP_5kvsJYATeO1gox5gn_y2lbdW1Ty4zVg8yQ_ZY-mY98dmejMC1svRy6jKeEsWlaBM4vZYiu5gXTWA5P6ccLMmQqxmT_r_uvD0LNAu7ZIZF4yZ-X3TAPy8Y4qt_uFAE3E4E1mVmkcFgb6eVHFBYx9L8I2fxdEc9BY1PiNMU4ml1wIw_66_dOtidQkH0ohz1YDt3xVIS8zbaTRU9pLeAO1Yl5hIPXYG2CzyQfvZd6GyHYt_DHHy9zjKHQ1Q9rQPkwy2ZwX8f3svNYejO-bQTbrsacOkaw6

Which according to jwt.io decodes to the payload:

{
  "gnt": 1,
  "tt": 0,
  "exp": 1658923762,
  "iat": 1658920162
}

@gianlucapisati
Copy link

@pinpox I think that the email is a mandatory field inside an OAuth token. Which identity provider are you using? I think you should add the email claim to it.

@Lurkars
Copy link

Lurkars commented Jul 28, 2022

I think that the email is a mandatory field inside an OAuth token. Which identity provider are

This is not right. Required claims are listed here
https://openid.net/specs/openid-connect-core-1_0.html#IDToken but this is something I noticed multiple times, that email is used instead of sub by certain clients.

@gianlucapisati
Copy link

Ok @Lurkars you are right about the theory, but neither sub or email are present in that token (which by the way is an access_token, not an id_token), my question is still valid, which system generated that token?

@Kab1r
Copy link

Kab1r commented Jul 28, 2022

I think Gitea's Oauth2 provider implementation is only meant to allow third party apps to access Gitea's API. It's doesn't look like it's really meant to authenticate users on a external service.

@Lurkars
Copy link

Lurkars commented Jul 29, 2022

Ok @Lurkars you are right about the theory, but neither sub or email are present in that token (which by the way is an access_token, not an id_token), my question is still valid, which system generated that token?

The format of an access_token is not defined, it can also be a random string and not containing any encoded data. So don't rely any requirements on getting information from an access_token. It seems like Gitea generates access_tokens as JWTs, I don't know why, but this is not OAuth2 standard and cannot be assumed for other providers.

Claims like email, sub inside an ID Token are the additions OpenID Connect added to the OAuth2 spec. That's why we are asking for OpenID support and not only OAuth2.

The Gitea documentation is a bit confusing (https://docs.gitea.io/en-us/oauth2-provider/) because, they claim to support OpenID Connect, but sample token response does not contain an ID Token. I will now try to test if Gitea can be used as valid OpenID Connect provider.

Anyways, I am 0% into Rust, so cannot contribute directly here. But I am very deep into OpenID Connect protocol. So if you need any help here, ping me!

Edit: I just tested with a gitea instance. So it returns a valid ID token, so seems fine to me.

@Lurkars
Copy link

Lurkars commented Jul 29, 2022

A quick look at the code, I found this

let token = jsonwebtoken::dangerous_insecure_decode::<TokenPayload>(access_token.as_str()).unwrap().claims;

So it seems to try to read the access_token as ID token. Please use id_token instead. Every OpenID Provider will return id_token next to access_token in Token Response!

Taking a quick look at library example:

https://github.com/ramosbugs/openidconnect-rs/blob/main/examples/google.rs#L215

seems like you can get claims directly from this and don't need to decode JWT yourself! So I would recommend to simple use the library to get email and sub etc.

@pinpox
Copy link

pinpox commented Aug 1, 2022

I think Gitea's Oauth2 provider implementation is only meant to allow third party apps to access Gitea's API. It's doesn't look like it's really meant to authenticate users on a external service.

@Kab1r This can't be correct. I have an account on bitwarden.com and have tested the configuration there. Gitea works perfectly with an official bitwarden account.

Edit: I just tested with a gitea instance. So it returns a valid ID token, so seems fine to me.

Can confirm.

@Lurkars

So it seems to try to read the access_token as ID token. Please use id_token instead. Every OpenID Provider will return
id_token next to access_token in Token Response!

Perfect, thanks that was what I was looking for! Indeed using the id_token works, it contains the email field as expected (among other which are not used atm)
I have adapted the code to use it in 13c169c This could probably be simplified as @Lurkars pointed out, but should work for now.

Logging it seems to work now via OIDC, but I still don't know what the callback path is supposed to be. Currently it just redirect to the login screen again after login. /#/sso seems to be incorrect, not sure how that worked for @Sheap

If anyone (with more front-end) knowledge) can find out what the callback path is supposed to be, that would be of great help!

@alexanderadam
Copy link

Wow, that's a wonderful development! 🤩
Will this also work with Authelia?

@pinpox
Copy link

pinpox commented Aug 1, 2022

Will this also work with Authelia?

I haven't used authelia personally, but their website says it supports OpenID connect in the beta version currently. If it can operate as OIDC provider, it should work.

@NathanHuisman
Copy link

If anyone (with more front-end) knowledge) can find out what the callback path is supposed to be, that would be of great help!

@pinpox According to the Bitwarden Docs the correct callback path should be https://your.domain.com/sso/oidc-signin, I believe. I could be wrong though, I'm not an expert in OIDC (yet ;)).

@Ryamonster10
Copy link

Is this still in progress or has this been abandoned?

@fdaone
Copy link

fdaone commented Nov 10, 2022

Is this still in progress or has this been abandoned?

Still in progress. See #2449

@germanbravouy
Copy link

Hi this pr is in progress?

@toabi
Copy link

toabi commented Oct 10, 2023

Hi this pr is in progress?

I think the most recent progress is tracked… there: #3899

@BlackDex
Copy link
Collaborator

Closing this PR in favor of #3899 to keep overview of it all.

@BlackDex BlackDex closed this Dec 19, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.