From f97d2329e6eb4da8b67c03ebf0a2c2f3cdcdfd04 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 24 Feb 2023 17:45:41 +0100 Subject: [PATCH] feat(xo-server-auth-oidc): OpenID Connect authentication plugin (#6684) Fixes #6627 --- CHANGELOG.unreleased.md | 2 + packages/xo-server-auth-oidc/.USAGE.md | 11 +++ packages/xo-server-auth-oidc/.npmignore | 1 + packages/xo-server-auth-oidc/README.md | 32 +++++++ packages/xo-server-auth-oidc/index.js | 101 ++++++++++++++++++++++ packages/xo-server-auth-oidc/package.json | 23 +++++ yarn.lock | 8 ++ 7 files changed, 178 insertions(+) create mode 100644 packages/xo-server-auth-oidc/.USAGE.md create mode 120000 packages/xo-server-auth-oidc/.npmignore create mode 100644 packages/xo-server-auth-oidc/README.md create mode 100644 packages/xo-server-auth-oidc/index.js create mode 100644 packages/xo-server-auth-oidc/package.json diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 8a4202d81ab..aac5f8129ff 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -9,6 +9,7 @@ - [VM/Advanced] Warning message when enabling Windows update tools [#6627](https://github.com/vatesfr/xen-orchestra/issues/6627) (PR [#6681](https://github.com/vatesfr/xen-orchestra/issues/6681)) - [Continuous Replication] : add HealthCheck support to Continuous Replication (PR [#6668](https://github.com/vatesfr/xen-orchestra/pull/6668)) +- [Plugin/auth-oidc] [OpenID Connect]() authentication plugin [#6641](https://github.com/vatesfr/xen-orchestra/issues/6641) (PR [#6684](https://github.com/vatesfr/xen-orchestra/issues/6684)) ### Bug fixes @@ -37,6 +38,7 @@ - @xen-orchestra/backups minor - xen-api patch - xo-cli minor +- xo-server-auth-oidc minor - xo-web minor - xo-server patch diff --git a/packages/xo-server-auth-oidc/.USAGE.md b/packages/xo-server-auth-oidc/.USAGE.md new file mode 100644 index 00000000000..f63ed76d0fc --- /dev/null +++ b/packages/xo-server-auth-oidc/.USAGE.md @@ -0,0 +1,11 @@ +This plugin allows users to authenticate to Xen-Orchestra using [OpenID Connect](). + +The first time a user signs in, XO will create a new XO user with the +same identifier. + +Like all other xo-server plugins, it can be configured directly via +the web interface, see [the plugin documentation](https://xen-orchestra.com/docs/plugins.html). + +> Important: When registering your instance to your identity provider, +> you must configure its callback URL to +> `http://xo.company.net/signin/oidc/callback`! diff --git a/packages/xo-server-auth-oidc/.npmignore b/packages/xo-server-auth-oidc/.npmignore new file mode 120000 index 00000000000..008d1b9b986 --- /dev/null +++ b/packages/xo-server-auth-oidc/.npmignore @@ -0,0 +1 @@ +../../scripts/npmignore \ No newline at end of file diff --git a/packages/xo-server-auth-oidc/README.md b/packages/xo-server-auth-oidc/README.md new file mode 100644 index 00000000000..a0cd359f8a6 --- /dev/null +++ b/packages/xo-server-auth-oidc/README.md @@ -0,0 +1,32 @@ + + +# xo-server-auth-oidc + +## Usage + +This plugin allows users to authenticate to Xen-Orchestra using [OpenID Connect](). + +The first time a user signs in, XO will create a new XO user with the +same identifier. + +Like all other xo-server plugins, it can be configured directly via +the web interface, see [the plugin documentation](https://xen-orchestra.com/docs/plugins.html). + +> Important: When registering your instance to your identity provider, +> you must configure its callback URL to +> `http://xo.company.net/signin/oidc/callback`! + +## Contributions + +Contributions are _very_ welcomed, either on the documentation or on +the code. + +You may: + +- report any [issue](https://github.com/vatesfr/xen-orchestra/issues) + you've encountered; +- fork and create a pull request. + +## License + +[AGPL-3.0-or-later](https://spdx.org/licenses/AGPL-3.0-or-later) © [Vates SAS](https://vates.fr) diff --git a/packages/xo-server-auth-oidc/index.js b/packages/xo-server-auth-oidc/index.js new file mode 100644 index 00000000000..aa24c417371 --- /dev/null +++ b/packages/xo-server-auth-oidc/index.js @@ -0,0 +1,101 @@ +'use strict' + +const { Strategy } = require('passport-openidconnect') + +// =================================================================== + +const DISCOVERABLE_SETTINGS = ['authorizationURL', 'issuer', 'userInfoURL', 'tokenURL'] + +exports.configurationSchema = { + type: 'object', + properties: { + discoveryURL: { + description: 'If this field is not used, you will need to manually enter settings in the *Advanced* section.', + title: 'Auto-discovery URL', + type: 'string', + }, + clientID: { title: 'Client identifier (key)', type: 'string' }, + clientSecret: { title: 'Client secret', type: 'string' }, + + advanced: { + title: 'Advanced', + type: 'object', + properties: { + authorizationURL: { title: 'Authorization URL', type: 'string' }, + callbackURL: { + description: 'Default to https:///signin/oidc/callback`.', + title: 'Callback URL', + type: 'string', + }, + issuer: { title: 'Issuer', type: 'string' }, + tokenURL: { title: 'Token URL', type: 'string' }, + userInfoURL: { title: 'User info URL', type: 'string' }, + usernameField: { + default: 'username', + description: 'Field to use as the XO username', + title: 'Username field', + type: 'string', + }, + }, + }, + }, + required: ['clientID', 'clientSecret'], + anyOf: [{ required: ['discoveryURL'] }, { properties: { advanced: { required: DISCOVERABLE_SETTINGS } } }], +} + +// =================================================================== + +class AuthOidc { + #conf + #unregisterPassportStrategy + #xo + + constructor(xo) { + this.#xo = xo + } + + async configure({ advanced, ...conf }, { loaded }) { + this.#conf = { callbackURL: '/signin/oidc/callback', ...advanced, ...conf } + + if (loaded) { + await this.unload() + await this.load() + } + } + + async load() { + const xo = this.#xo + const { discoveryURL, usernameField, ...conf } = this.#conf + + if (discoveryURL !== undefined) { + const res = await this.#xo.httpRequest(discoveryURL) + const data = await res.json() + + for (const key of DISCOVERABLE_SETTINGS) { + if (!conf[key]) { + conf[key] = data[key.endsWith('URL') ? key.slice(0, -3).toLowerCase() + '_endpoint' : key] + } + } + } + + this.#unregisterPassportStrategy = xo.registerPassportStrategy( + new Strategy(conf, async (issuer, profile, done) => { + try { + const { id, [usernameField]: name } = profile + done(null, await xo.registerUser2('oidc:' + issuer, { user: { id, name } })) + } catch (error) { + done(error.message) + } + }), + { label: 'OpenID Connect', name: 'oidc' } + ) + } + + unload() { + this.#unregisterPassportStrategy() + } +} + +// =================================================================== + +exports.default = ({ xo }) => new AuthOidc(xo) diff --git a/packages/xo-server-auth-oidc/package.json b/packages/xo-server-auth-oidc/package.json new file mode 100644 index 00000000000..b9b1418f874 --- /dev/null +++ b/packages/xo-server-auth-oidc/package.json @@ -0,0 +1,23 @@ +{ + "private": true, + "name": "xo-server-auth-oidc", + "homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-auth-oidc", + "bugs": "https://github.com/vatesfr/xen-orchestra/issues", + "repository": { + "directory": "packages/xo-server-auth-oidc", + "type": "git", + "url": "https://github.com/vatesfr/xen-orchestra.git" + }, + "author": { + "name": "Vates SAS", + "url": "https://vates.fr" + }, + "license": "AGPL-3.0-or-later", + "version": "0.0.0", + "engines": { + "node": ">=12" + }, + "dependencies": { + "passport-openidconnect": "^0.1.1" + } +} diff --git a/yarn.lock b/yarn.lock index 09d28418d92..2d159a458d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15061,6 +15061,14 @@ passport-oauth2@1.x.x: uid2 "0.0.x" utils-merge "1.x.x" +passport-openidconnect@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/passport-openidconnect/-/passport-openidconnect-0.1.1.tgz#83921ff5f87f634079f65262dada834af1972244" + integrity sha512-r0QJiWEzwCg2MeCIXVP5G6YxVRqnEsZ2HpgKRthZ9AiQHJrgGUytXpsdcGF9BRwd3yMrEesb/uG/Yxb86rrY0g== + dependencies: + oauth "0.9.x" + passport-strategy "1.x.x" + passport-saml@^3.2.0: version "3.2.4" resolved "https://registry.yarnpkg.com/passport-saml/-/passport-saml-3.2.4.tgz#e8e9523f954988a3a47d12e425d7fa0f20a74dc9"