diff --git a/frontend/src/component/common/flags.js b/frontend/src/component/common/flags.js
index ad16f84839..155282a816 100644
--- a/frontend/src/component/common/flags.js
+++ b/frontend/src/component/common/flags.js
@@ -1,3 +1,4 @@
export const P = 'P';
export const C = 'C';
export const RBAC = 'RBAC';
+export const OIDC = 'OIDC';
diff --git a/frontend/src/page/admin/auth/authentication.jsx b/frontend/src/page/admin/auth/authentication.jsx
index 31f9dc34af..482a404d86 100644
--- a/frontend/src/page/admin/auth/authentication.jsx
+++ b/frontend/src/page/admin/auth/authentication.jsx
@@ -4,11 +4,12 @@ import AdminMenu from '../admin-menu';
import { Alert } from '@material-ui/lab';
import GoogleAuth from './google-auth-container';
import SamlAuth from './saml-auth-container';
+import OidcAuth from './oidc-auth-container';
import TabNav from '../../../component/common/TabNav/TabNav';
import PageContent from '../../../component/common/PageContent/PageContent';
import ConditionallyRender from '../../../component/common/ConditionallyRender/ConditionallyRender';
-function AdminAuthPage({ authenticationType, history }) {
+function AdminAuthPage({ authenticationType, history, enableOIDC }) {
const tabs = [
{
label: 'SAML 2.0',
@@ -20,6 +21,13 @@ function AdminAuthPage({ authenticationType, history }) {
},
];
+ if(enableOIDC) {
+ tabs.unshift( {
+ label: 'OpenID Connect',
+ component: ,
+ },)
+ }
+
return (
@@ -63,6 +71,7 @@ AdminAuthPage.propTypes = {
match: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
authenticationType: PropTypes.string,
+ enableOIDC: PropTypes.bool,
};
export default AdminAuthPage;
diff --git a/frontend/src/page/admin/auth/google-auth.jsx b/frontend/src/page/admin/auth/google-auth.jsx
index 7773419909..d2e694f31a 100644
--- a/frontend/src/page/admin/auth/google-auth.jsx
+++ b/frontend/src/page/admin/auth/google-auth.jsx
@@ -115,10 +115,11 @@ function GoogleAuth({
label="Client ID"
name="clientId"
placeholder=""
- value={data.clientId}
+ value={data.clientId || ''}
style={{ width: '400px' }}
variant="outlined"
size="small"
+ required
/>
@@ -135,11 +136,12 @@ function GoogleAuth({
onChange={updateField}
label="Client Secret"
name="clientSecret"
- value={data.clientSecret}
+ value={data.clientSecret || ''}
placeholder=""
style={{ width: '400px' }}
variant="outlined"
size="small"
+ required
/>
@@ -163,7 +165,7 @@ function GoogleAuth({
label="Unleash Hostname"
name="unleashHostname"
placeholder=""
- value={data.unleashHostname}
+ value={data.unleashHostname || ''}
style={{ width: '400px' }}
variant="outlined"
size="small"
diff --git a/frontend/src/page/admin/auth/index.js b/frontend/src/page/admin/auth/index.js
index 07d89be806..cc34e9cfa4 100644
--- a/frontend/src/page/admin/auth/index.js
+++ b/frontend/src/page/admin/auth/index.js
@@ -1,8 +1,10 @@
import { connect } from 'react-redux';
import component from './authentication';
+import { OIDC } from '../../../component/common/flags';
const mapStateToProps = state => ({
authenticationType: state.uiConfig.toJS().authenticationType,
+ enableOIDC: !!state.uiConfig.toJS().flags[OIDC],
});
const Container = connect(mapStateToProps, { })(component);
diff --git a/frontend/src/page/admin/auth/oidc-auth-container.js b/frontend/src/page/admin/auth/oidc-auth-container.js
new file mode 100644
index 0000000000..903a76a203
--- /dev/null
+++ b/frontend/src/page/admin/auth/oidc-auth-container.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux';
+import OidcAuth from './oidc-auth';
+import { getOidcConfig, updateOidcConfig } from '../../../store/e-admin-auth/actions';
+
+const mapStateToProps = state => ({
+ config: state.authAdmin.get('oidc'),
+ unleashUrl: state.uiConfig.toJS().unleashUrl,
+});
+
+const OidcContainer = connect(mapStateToProps, { getOidcConfig, updateOidcConfig })(OidcAuth);
+
+export default OidcContainer;
diff --git a/frontend/src/page/admin/auth/oidc-auth.jsx b/frontend/src/page/admin/auth/oidc-auth.jsx
new file mode 100644
index 0000000000..226f7c0406
--- /dev/null
+++ b/frontend/src/page/admin/auth/oidc-auth.jsx
@@ -0,0 +1,225 @@
+import React, { useState, useEffect, useContext } from 'react';
+import PropTypes from 'prop-types';
+import { Button, Grid, Switch, TextField } from '@material-ui/core';
+import { Alert } from '@material-ui/lab';
+import PageContent from '../../../component/common/PageContent/PageContent';
+import AccessContext from '../../../contexts/AccessContext';
+import { ADMIN } from '../../../component/AccessProvider/permissions';
+
+const initialState = {
+ enabled: false,
+ autoCreate: false,
+ unleashHostname: location.hostname,
+};
+
+function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) {
+ const [data, setData] = useState(initialState);
+ const [info, setInfo] = useState();
+ const [error, setError] = useState();
+ const { hasAccess } = useContext(AccessContext);
+
+ useEffect(() => {
+ getOidcConfig();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ useEffect(() => {
+ if (config.discoverUrl) {
+ setData(config);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [config]);
+
+ if (!hasAccess(ADMIN)) {
+ return (
+
+ You need to be a root admin to access this section.
+
+ );
+ }
+
+ const updateField = e => {
+ setData({
+ ...data,
+ [e.target.name]: e.target.value,
+ });
+ };
+
+ const updateEnabled = () => {
+ setData({ ...data, enabled: !data.enabled });
+ };
+
+ const updateAutoCreate = () => {
+ setData({ ...data, autoCreate: !data.autoCreate });
+ };
+
+ const onSubmit = async e => {
+ e.preventDefault();
+ setInfo('...saving');
+ setError('');
+ try {
+ await updateOidcConfig(data);
+ setInfo('Settings stored');
+ setTimeout(() => setInfo(''), 2000);
+ } catch (e) {
+ setInfo('');
+ setError(e.message);
+ }
+ };
+ return (
+
+
+
+
+ Please read the{' '}
+
+ documentation
+ {' '}
+ to learn how to integrate with specific Open Id Connect
+ providers (Okta, Keycloak, Google, etc).
+ Callback URL:{' '}
+ {unleashUrl}/auth/oidc/callback
+
+
+
+
+
+ );
+}
+
+OidcAuth.propTypes = {
+ config: PropTypes.object,
+ unleash: PropTypes.string,
+ getOidcConfig: PropTypes.func.isRequired,
+ updateOidcConfig: PropTypes.func.isRequired,
+};
+
+export default OidcAuth;
diff --git a/frontend/src/page/admin/auth/saml-auth.jsx b/frontend/src/page/admin/auth/saml-auth.jsx
index 04ef60ec44..1496e9ad33 100644
--- a/frontend/src/page/admin/auth/saml-auth.jsx
+++ b/frontend/src/page/admin/auth/saml-auth.jsx
@@ -114,6 +114,7 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) {
style={{ width: '400px' }}
variant="outlined"
size="small"
+ required
/>
@@ -134,6 +135,7 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) {
style={{ width: '400px' }}
variant="outlined"
size="small"
+ required
/>
@@ -161,6 +163,7 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) {
rowsMax={14}
variant="outlined"
size="small"
+ required
/>
diff --git a/frontend/src/store/e-admin-auth/actions.js b/frontend/src/store/e-admin-auth/actions.js
index 6a071e5574..5d91ceb428 100644
--- a/frontend/src/store/e-admin-auth/actions.js
+++ b/frontend/src/store/e-admin-auth/actions.js
@@ -8,6 +8,10 @@ export const RECIEVE_SAML_CONFIG = 'RECIEVE_SAML_CONFIG';
export const RECIEVE_SAML_CONFIG_ERROR = 'RECIEVE_SAML_CONFIG_ERROR';
export const UPDATE_SAML_AUTH = 'UPDATE_SAML_AUTH';
export const UPDATE_SAML_AUTH_ERROR = 'UPDATE_SAML_AUTH_ERROR';
+export const RECIEVE_OIDC_CONFIG = 'RECIEVE_OIDC_CONFIG';
+export const RECIEVE_OIDC_CONFIG_ERROR = 'RECIEVE_OIDC_CONFIG_ERROR';
+export const UPDATE_OIDC_AUTH = 'UPDATE_OIDC_AUTH';
+export const UPDATE_OIDC_AUTH_ERROR = 'UPDATE_OIDC_AUTH_ERROR';
const debug = require('debug')('unleash:e-admin-auth-actions');
@@ -30,7 +34,10 @@ export function updateGoogleConfig(data) {
api
.updateGoogleConfig(data)
.then(config => dispatch({ type: UPDATE_GOOGLE_AUTH, config }))
- .catch(dispatchError(dispatch, UPDATE_GOOGLE_AUTH_ERROR));
+ .catch(e => {
+ dispatchError(dispatch, UPDATE_GOOGLE_AUTH_ERROR)(e);
+ throw e;
+ });
}
export function getSamlConfig() {
@@ -52,5 +59,33 @@ export function updateSamlConfig(data) {
api
.updateSamlConfig(data)
.then(config => dispatch({ type: UPDATE_SAML_AUTH, config }))
- .catch(dispatchError(dispatch, UPDATE_SAML_AUTH_ERROR));
+ .catch(e => {
+ dispatchError(dispatch, UPDATE_SAML_AUTH_ERROR)(e);
+ throw e;
+ });
}
+
+export function getOidcConfig() {
+ debug('Start fetching OIDC-auth config');
+ return dispatch =>
+ api
+ .getOidcConfig()
+ .then(config =>
+ dispatch({
+ type: RECIEVE_OIDC_CONFIG,
+ config,
+ })
+ )
+ .catch(dispatchError(dispatch, RECIEVE_OIDC_CONFIG_ERROR));
+}
+
+export function updateOidcConfig(data) {
+ return dispatch =>
+ api
+ .updateOidcConfig(data)
+ .then(config => dispatch({ type: UPDATE_OIDC_AUTH, config }))
+ .catch(e => {
+ dispatchError(dispatch, UPDATE_OIDC_AUTH_ERROR)(e);
+ throw e;
+ });
+}
\ No newline at end of file
diff --git a/frontend/src/store/e-admin-auth/api.js b/frontend/src/store/e-admin-auth/api.js
index 00b6e20688..35333d6c06 100644
--- a/frontend/src/store/e-admin-auth/api.js
+++ b/frontend/src/store/e-admin-auth/api.js
@@ -3,6 +3,7 @@ import { formatApiPath } from '../../utils/format-path';
const GOOGLE_URI = formatApiPath('api/admin/auth/google/settings');
const SAML_URI = formatApiPath('api/admin/auth/saml/settings');
+const OIDC_URI = formatApiPath('api/admin/auth/oidc/settings');
function getGoogleConfig() {
return fetch(GOOGLE_URI, { headers, credentials: 'include' })
@@ -38,9 +39,28 @@ function updateSamlConfig(data) {
.then(response => response.json());
}
+function getOidcConfig() {
+ return fetch(OIDC_URI, { headers, credentials: 'include' })
+ .then(throwIfNotSuccess)
+ .then(response => response.json());
+}
+
+function updateOidcConfig(data) {
+ return fetch(OIDC_URI, {
+ method: 'POST',
+ headers,
+ body: JSON.stringify(data),
+ credentials: 'include',
+ })
+ .then(throwIfNotSuccess)
+ .then(response => response.json());
+}
+
export default {
getGoogleConfig,
updateGoogleConfig,
getSamlConfig,
updateSamlConfig,
+ getOidcConfig,
+ updateOidcConfig,
};
diff --git a/frontend/src/store/e-admin-auth/index.js b/frontend/src/store/e-admin-auth/index.js
index 0f45ce9b4c..4f0a546000 100644
--- a/frontend/src/store/e-admin-auth/index.js
+++ b/frontend/src/store/e-admin-auth/index.js
@@ -1,7 +1,7 @@
import { Map as $Map } from 'immutable';
-import { RECIEVE_GOOGLE_CONFIG, UPDATE_GOOGLE_AUTH, RECIEVE_SAML_CONFIG, UPDATE_SAML_AUTH } from './actions';
+import { RECIEVE_GOOGLE_CONFIG, UPDATE_GOOGLE_AUTH, RECIEVE_SAML_CONFIG, UPDATE_SAML_AUTH, UPDATE_OIDC_AUTH, RECIEVE_OIDC_CONFIG } from './actions';
-const store = (state = new $Map({ google: {}, saml: {} }), action) => {
+const store = (state = new $Map({ google: {}, saml: {}, oidc: {} }), action) => {
switch (action.type) {
case UPDATE_GOOGLE_AUTH:
case RECIEVE_GOOGLE_CONFIG:
@@ -9,6 +9,9 @@ const store = (state = new $Map({ google: {}, saml: {} }), action) => {
case UPDATE_SAML_AUTH:
case RECIEVE_SAML_CONFIG:
return state.set('saml', action.config);
+ case UPDATE_OIDC_AUTH:
+ case RECIEVE_OIDC_CONFIG:
+ return state.set('oidc', action.config);
default:
return state;
}