This commit is contained in:
Jeremy White 2022-08-23 09:51:04 -05:00
parent bcd018707a
commit 9f3396d867
21 changed files with 815 additions and 3 deletions

View File

@ -379,6 +379,7 @@ AUTHENTICATION_BACKENDS = (
'social_core.backends.github_enterprise.GithubEnterpriseOAuth2',
'social_core.backends.github_enterprise.GithubEnterpriseOrganizationOAuth2',
'social_core.backends.github_enterprise.GithubEnterpriseTeamOAuth2',
'social_core.backends.open_id_connect.OpenIdConnectAuth',
'social_core.backends.azuread.AzureADOAuth2',
'awx.sso.backends.SAMLAuth',
'awx.main.backends.AWXModelBackend',

View File

@ -1215,6 +1215,54 @@ register(
placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER,
)
###############################################################################
# Generic OIDC AUTHENTICATION SETTINGS
###############################################################################
register(
'SOCIAL_AUTH_OIDC_KEY',
field_class=fields.CharField,
allow_null=False,
default=None,
label=_('OIDC Key'),
help_text='The OIDC key (Client ID) from your IDP.',
category=_('Generic OIDC'),
category_slug='oidc',
)
register(
'SOCIAL_AUTH_OIDC_SECRET',
field_class=fields.CharField,
allow_blank=True,
default='',
label=_('OIDC Secret'),
help_text=_('The OIDC secret (Client Secret) from your IDP.'),
category=_('Generic OIDC'),
category_slug='oidc',
encrypted=True,
)
register(
'SOCIAL_AUTH_OIDC_OIDC_ENDPOINT',
field_class=fields.CharField,
allow_blank=True,
default='',
label=_('OIDC Provider URL'),
help_text=_('The URL for your OIDC provider including the path up to /.well-known/openid-configuration'),
category=_('Generic OIDC'),
category_slug='oidc',
)
register(
'SOCIAL_AUTH_OIDC_VERIFY_SSL',
field_class=fields.BooleanField,
default=True,
label=_('Verify OIDC Provider Certificate'),
help_text=_('Verify the OIDV provider ssl certificate.'),
category=_('Generic OIDC'),
category_slug='oidc',
)
###############################################################################
# SAML AUTHENTICATION SETTINGS
###############################################################################

View File

@ -149,6 +149,7 @@ class AuthenticationBackendsField(fields.StringListField):
('awx.sso.backends.RADIUSBackend', ['RADIUS_SERVER']),
('social_core.backends.google.GoogleOAuth2', ['SOCIAL_AUTH_GOOGLE_OAUTH2_KEY', 'SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET']),
('social_core.backends.github.GithubOAuth2', ['SOCIAL_AUTH_GITHUB_KEY', 'SOCIAL_AUTH_GITHUB_SECRET']),
('social_core.backends.open_id_connect.OpenIdConnectAuth', ['SOCIAL_AUTH_OIDC_KEY', 'SOCIAL_AUTH_OIDC_SECRET', 'SOCIAL_AUTH_OIDC_OIDC_ENDPOINT']),
(
'social_core.backends.github.GithubOrganizationOAuth2',
['SOCIAL_AUTH_GITHUB_ORG_KEY', 'SOCIAL_AUTH_GITHUB_ORG_SECRET', 'SOCIAL_AUTH_GITHUB_ORG_NAME'],

View File

@ -346,6 +346,20 @@ function AWXLogin({ alt, isAuthenticated }) {
</LoginMainFooterLinksItem>
);
}
if (authKey === 'oidc') {
return (
<LoginMainFooterLinksItem
data-cy="social-auth-oidc"
href={loginUrl}
key={authKey}
onClick={setSessionRedirect}
>
<Tooltip content={t`Sign in with OIDC`}>
<UserCircleIcon size="lg" />
</Tooltip>
</LoginMainFooterLinksItem>
);
}
if (authKey.startsWith('saml')) {
const samlIDP = authKey.split(':')[1] || null;
return (

View File

@ -0,0 +1,34 @@
import React from 'react';
import { Link, Redirect, Route, Switch } from 'react-router-dom';
import { t } from '@lingui/macro';
import { PageSection, Card } from '@patternfly/react-core';
import ContentError from 'components/ContentError';
import OIDCDetail from './OIDCDetail';
import OIDCEdit from './OIDCEdit';
function OIDC() {
const baseURL = '/settings/oidc';
return (
<PageSection>
<Card>
<Switch>
<Redirect from={baseURL} to={`${baseURL}/details`} exact />
<Route path={`${baseURL}/details`}>
<OIDCDetail />
</Route>
<Route path={`${baseURL}/edit`}>
<OIDCEdit />
</Route>
<Route key="not-found" path={`${baseURL}/*`}>
<ContentError isNotFound>
<Link to={`${baseURL}/details`}>{t`View OIDC settings`}</Link>
</ContentError>
</Route>
</Switch>
</Card>
</PageSection>
);
}
export default OIDC;

View File

@ -0,0 +1,75 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { createMemoryHistory } from 'history';
import { SettingsProvider } from 'contexts/Settings';
import { SettingsAPI } from 'api';
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import mockAllOptions from '../shared/data.allSettingOptions.json';
import OIDC from './OIDC';
jest.mock('../../../api');
describe('<OIDC />', () => {
let wrapper;
beforeEach(() => {
SettingsAPI.readCategory.mockResolvedValue({
data: {
SOCIAL_AUTH_OIDC_KEY: 'mock key',
SOCIAL_AUTH_OIDC_SECRET: '$encrypted$',
SOCIAL_AUTH_OIDC_OIDC_ENDPOINT: 'https://example.com',
SOCIAL_AUTH_OIDC_VERIFY_SSL: true,
},
});
});
afterEach(() => {
jest.clearAllMocks();
});
test('should render OIDC details', async () => {
const history = createMemoryHistory({
initialEntries: ['/settings/oidc/details'],
});
await act(async () => {
wrapper = mountWithContexts(
<SettingsProvider value={mockAllOptions.actions}>
<OIDC />
</SettingsProvider>,
{
context: { router: { history } },
}
);
});
expect(wrapper.find('OIDCDetail').length).toBe(1);
});
test('should render OIDC edit', async () => {
const history = createMemoryHistory({
initialEntries: ['/settings/oidc/edit'],
});
await act(async () => {
wrapper = mountWithContexts(
<SettingsProvider value={mockAllOptions.actions}>
<OIDC />
</SettingsProvider>,
{
context: { router: { history } },
}
);
});
expect(wrapper.find('OIDCEdit').length).toBe(1);
});
test('should show content error when user navigates to erroneous route', async () => {
const history = createMemoryHistory({
initialEntries: ['/settings/oidc/foo'],
});
await act(async () => {
wrapper = mountWithContexts(<OIDC />, {
context: { router: { history } },
});
});
expect(wrapper.find('ContentError').length).toBe(1);
});
});

View File

@ -0,0 +1,98 @@
import React, { useEffect, useCallback } from 'react';
import { Link } from 'react-router-dom';
import { t } from '@lingui/macro';
import { Button } from '@patternfly/react-core';
import { CaretLeftIcon } from '@patternfly/react-icons';
import { CardBody, CardActionsRow } from 'components/Card';
import ContentLoading from 'components/ContentLoading';
import ContentError from 'components/ContentError';
import RoutedTabs from 'components/RoutedTabs';
import { SettingsAPI } from 'api';
import useRequest from 'hooks/useRequest';
import { DetailList } from 'components/DetailList';
import { useConfig } from 'contexts/Config';
import { useSettings } from 'contexts/Settings';
import { SettingDetail } from '../../shared';
function OIDCDetail() {
const { me } = useConfig();
const { GET: options } = useSettings();
const {
isLoading,
error,
request,
result: OIDC,
} = useRequest(
useCallback(async () => {
const { data } = await SettingsAPI.readCategory('oidc');
return data;
}, []),
null
);
useEffect(() => {
request();
}, [request]);
const tabsArray = [
{
name: (
<>
<CaretLeftIcon />
{t`Back to Settings`}
</>
),
link: `/settings`,
id: 99,
},
{
name: t`Details`,
link: `/settings/oidc/details`,
id: 0,
},
];
return (
<>
<RoutedTabs tabsArray={tabsArray} />
<CardBody>
{isLoading && <ContentLoading />}
{!isLoading && error && <ContentError error={error} />}
{!isLoading && OIDC && (
<DetailList>
{Object.keys(OIDC).map((key) => {
const record = options?.[key];
return (
<SettingDetail
key={key}
id={key}
helpText={record?.help_text}
label={record?.label}
type={record?.type}
unit={record?.unit}
value={OIDC?.[key]}
/>
);
})}
</DetailList>
)}
{me?.is_superuser && (
<CardActionsRow>
<Button
ouiaId="oidc-detail-edit-button"
aria-label={t`Edit`}
component={Link}
to="/settings/oidc/edit"
>
{t`Edit`}
</Button>
</CardActionsRow>
)}
</CardBody>
</>
);
}
export default OIDCDetail;

View File

@ -0,0 +1,97 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { SettingsProvider } from 'contexts/Settings';
import { SettingsAPI } from 'api';
import {
mountWithContexts,
waitForElement,
} from '../../../../../testUtils/enzymeHelpers';
import {
assertDetail,
assertVariableDetail,
} from '../../shared/settingTestUtils';
import mockAllOptions from '../../shared/data.allSettingOptions.json';
import OIDCDetail from './OIDCDetail';
jest.mock('../../../../api');
describe('<OIDCDetail />', () => {
let wrapper;
beforeEach(() => {
SettingsAPI.readCategory.mockResolvedValue({
data: {
SOCIAL_AUTH_OIDC_KEY: 'mock key',
SOCIAL_AUTH_OIDC_SECRET: '$encrypted$',
SOCIAL_AUTH_OIDC_OIDC_ENDPOINT: 'https://example.com',
SOCIAL_AUTH_OIDC_VERIFY_SSL: true,
},
});
});
beforeEach(async () => {
await act(async () => {
wrapper = mountWithContexts(
<SettingsProvider value={mockAllOptions.actions}>
<OIDCDetail />
</SettingsProvider>
);
});
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
});
afterAll(() => {
jest.clearAllMocks();
});
test('initially renders without crashing', () => {
expect(wrapper.find('OIDCDetail').length).toBe(1);
});
test('should render expected tabs', () => {
const expectedTabs = ['Back to Settings', 'Details'];
wrapper.find('RoutedTabs li').forEach((tab, index) => {
expect(tab.text()).toEqual(expectedTabs[index]);
});
});
test('should render expected details', () => {
assertDetail(wrapper, 'OIDC Key', 'mock key');
assertDetail(wrapper, 'OIDC Secret', 'Encrypted');
assertDetail(wrapper, 'OIDC Provider URL', 'https://example.com');
assertDetail(wrapper, 'Verify OIDC Provider Certificate', 'On');
});
test('should hide edit button from non-superusers', async () => {
const config = {
me: {
is_superuser: false,
},
};
await act(async () => {
wrapper = mountWithContexts(
<SettingsProvider value={mockAllOptions.actions}>
<OIDCDetail />
</SettingsProvider>,
{
context: { config },
}
);
});
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
expect(wrapper.find('Button[aria-label="Edit"]').exists()).toBeFalsy();
});
test('should display content error when api throws error on initial render', async () => {
SettingsAPI.readCategory.mockRejectedValue(new Error());
await act(async () => {
wrapper = mountWithContexts(
<SettingsProvider value={mockAllOptions.actions}>
<OIDCDetail />
</SettingsProvider>
);
});
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
expect(wrapper.find('ContentError').length).toBe(1);
});
});

View File

@ -0,0 +1 @@
export { default } from './OIDCDetail';

View File

@ -0,0 +1,147 @@
import React, { useCallback, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { Formik } from 'formik';
import { Form } from '@patternfly/react-core';
import { CardBody } from 'components/Card';
import ContentError from 'components/ContentError';
import ContentLoading from 'components/ContentLoading';
import { FormSubmitError } from 'components/FormField';
import { FormColumnLayout } from 'components/FormLayout';
import { useSettings } from 'contexts/Settings';
import useModal from 'hooks/useModal';
import useRequest from 'hooks/useRequest';
import { SettingsAPI } from 'api';
import { RevertAllAlert, RevertFormActionGroup } from '../../shared';
import {
EncryptedField,
InputField,
BooleanField,
} from '../../shared/SharedFields';
function OIDCEdit() {
const history = useHistory();
const { isModalOpen, toggleModal, closeModal } = useModal();
const { PUT: options } = useSettings();
const {
isLoading,
error,
request: fetchOIDC,
result: OIDC,
} = useRequest(
useCallback(async () => {
const { data } = await SettingsAPI.readCategory('oidc');
const mergedData = {};
Object.keys(data).forEach((key) => {
if (!options[key]) {
return;
}
mergedData[key] = options[key];
mergedData[key].value = data[key];
});
return mergedData;
}, [options]),
null
);
useEffect(() => {
fetchOIDC();
}, [fetchOIDC]);
const { error: submitError, request: submitForm } = useRequest(
useCallback(
async (values) => {
await SettingsAPI.updateAll(values);
history.push('/settings/oidc/details');
},
[history]
),
null
);
const { error: revertError, request: revertAll } = useRequest(
useCallback(async () => {
await SettingsAPI.revertCategory('oidc');
}, []),
null
);
const handleSubmit = async (form) => {
await submitForm({
...form,
});
};
const handleRevertAll = async () => {
await revertAll();
closeModal();
history.push('/settings/oidc/details');
};
const handleCancel = () => {
history.push('/settings/oidc/details');
};
const initialValues = (fields) =>
Object.keys(fields).reduce((acc, key) => {
if (fields[key].type === 'list' || fields[key].type === 'nested object') {
acc[key] = fields[key].value
? JSON.stringify(fields[key].value, null, 2)
: null;
} else {
acc[key] = fields[key].value ?? '';
}
return acc;
}, {});
return (
<CardBody>
{isLoading && <ContentLoading />}
{!isLoading && error && <ContentError error={error} />}
{!isLoading && OIDC && (
<Formik initialValues={initialValues(OIDC)} onSubmit={handleSubmit}>
{(formik) => (
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
<FormColumnLayout>
<InputField
name="SOCIAL_AUTH_OIDC_KEY"
config={OIDC.SOCIAL_AUTH_OIDC_KEY}
/>
<EncryptedField
name="SOCIAL_AUTH_OIDC_SECRET"
config={OIDC.SOCIAL_AUTH_OIDC_SECRET}
/>
<InputField
name="SOCIAL_AUTH_OIDC_OIDC_ENDPOINT"
config={OIDC.SOCIAL_AUTH_OIDC_OIDC_ENDPOINT}
type="url"
/>
<BooleanField
name="SOCIAL_AUTH_OIDC_VERIFY_SSL"
config={OIDC.SOCIAL_AUTH_OIDC_VERIFY_SSL}
/>
{submitError && <FormSubmitError error={submitError} />}
{revertError && <FormSubmitError error={revertError} />}
</FormColumnLayout>
<RevertFormActionGroup
onCancel={handleCancel}
onSubmit={formik.handleSubmit}
onRevert={toggleModal}
/>
{isModalOpen && (
<RevertAllAlert
onClose={closeModal}
onRevertAll={handleRevertAll}
/>
)}
</Form>
)}
</Formik>
)}
</CardBody>
);
}
export default OIDCEdit;

View File

@ -0,0 +1,161 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { createMemoryHistory } from 'history';
import { SettingsProvider } from 'contexts/Settings';
import { SettingsAPI } from 'api';
import {
mountWithContexts,
waitForElement,
} from '../../../../../testUtils/enzymeHelpers';
import mockAllOptions from '../../shared/data.allSettingOptions.json';
import OIDCEdit from './OIDCEdit';
jest.mock('../../../../api');
describe('<OIDCEdit />', () => {
let wrapper;
let history;
beforeEach(() => {
SettingsAPI.revertCategory.mockResolvedValue({});
SettingsAPI.updateAll.mockResolvedValue({});
SettingsAPI.readCategory.mockResolvedValue({
data: {
SOCIAL_AUTH_OIDC_KEY: 'mock key',
SOCIAL_AUTH_OIDC_SECRET: '$encrypted$',
SOCIAL_AUTH_OIDC_OIDC_ENDPOINT: 'https://example.com',
SOCIAL_AUTH_OIDC_VERIFY_SSL: true,
},
});
});
afterEach(() => {
jest.clearAllMocks();
});
beforeEach(async () => {
history = createMemoryHistory({
initialEntries: ['/settings/oidc/edit'],
});
await act(async () => {
wrapper = mountWithContexts(
<SettingsProvider value={mockAllOptions.actions}>
<OIDCEdit />
</SettingsProvider>,
{
context: { router: { history } },
}
);
});
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
});
test('initially renders without crashing', () => {
expect(wrapper.find('OIDCEdit').length).toBe(1);
});
test('should display expected form fields', async () => {
expect(wrapper.find('FormGroup[label="OIDC Key"]').length).toBe(1);
expect(wrapper.find('FormGroup[label="OIDC Secret"]').length).toBe(1);
expect(wrapper.find('FormGroup[label="OIDC Provider URL"]').length).toBe(1);
expect(
wrapper.find('FormGroup[label="Verify OIDC Provider Certificate"]').length
).toBe(1);
});
test('should successfully send default values to api on form revert all', async () => {
expect(SettingsAPI.revertCategory).toHaveBeenCalledTimes(0);
expect(wrapper.find('RevertAllAlert')).toHaveLength(0);
await act(async () => {
wrapper
.find('button[aria-label="Revert all to default"]')
.invoke('onClick')();
});
wrapper.update();
expect(wrapper.find('RevertAllAlert')).toHaveLength(1);
await act(async () => {
wrapper
.find('RevertAllAlert button[aria-label="Confirm revert all"]')
.invoke('onClick')();
});
wrapper.update();
expect(SettingsAPI.revertCategory).toHaveBeenCalledTimes(1);
expect(SettingsAPI.revertCategory).toHaveBeenCalledWith('oidc');
});
test('should successfully send request to api on form submission', async () => {
act(() => {
wrapper
.find(
'FormGroup[fieldId="SOCIAL_AUTH_OIDC_SECRET"] button[aria-label="Revert"]'
)
.invoke('onClick')();
wrapper.find('input#SOCIAL_AUTH_OIDC_KEY').simulate('change', {
target: { value: 'new key', name: 'SOCIAL_AUTH_OIDC_KEY' },
});
wrapper.find('input#SOCIAL_AUTH_OIDC_OIDC_ENDPOINT').simulate('change', {
target: {
value: 'https://example.com',
name: 'SOCIAL_AUTH_OIDC_OIDC_ENDPOINT',
},
});
});
wrapper.update();
await act(async () => {
wrapper.find('Form').invoke('onSubmit')();
});
expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1);
expect(SettingsAPI.updateAll).toHaveBeenCalledWith({
SOCIAL_AUTH_OIDC_KEY: 'new key',
SOCIAL_AUTH_OIDC_SECRET: '',
SOCIAL_AUTH_OIDC_OIDC_ENDPOINT: 'https://example.com',
SOCIAL_AUTH_OIDC_VERIFY_SSL: true,
});
});
test('should navigate to OIDC detail on successful submission', async () => {
await act(async () => {
wrapper.find('Form').invoke('onSubmit')();
});
expect(history.location.pathname).toEqual('/settings/oidc/details');
});
test('should navigate to OIDC detail when cancel is clicked', async () => {
await act(async () => {
wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
});
expect(history.location.pathname).toEqual('/settings/oidc/details');
});
test('should display error message on unsuccessful submission', async () => {
const error = {
response: {
data: { detail: 'An error occurred' },
},
};
SettingsAPI.updateAll.mockImplementation(() => Promise.reject(error));
expect(wrapper.find('FormSubmitError').length).toBe(0);
expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(0);
await act(async () => {
wrapper.find('Form').invoke('onSubmit')();
});
wrapper.update();
expect(wrapper.find('FormSubmitError').length).toBe(1);
expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1);
});
test('should display ContentError on throw', async () => {
SettingsAPI.readCategory.mockImplementationOnce(() =>
Promise.reject(new Error())
);
await act(async () => {
wrapper = mountWithContexts(
<SettingsProvider value={mockAllOptions.actions}>
<OIDCEdit />
</SettingsProvider>
);
});
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
expect(wrapper.find('ContentError').length).toBe(1);
});
});

View File

@ -0,0 +1 @@
export { default } from './OIDCEdit';

View File

@ -0,0 +1 @@
export { default } from './OIDC';

View File

@ -81,6 +81,10 @@ function SettingList() {
title: t`TACACS+ settings`,
path: '/settings/tacacs',
},
{
title: t`Generic OIDC settings`,
path: '/settings/oidc',
},
],
},
{

View File

@ -12,6 +12,7 @@ import useRequest from 'hooks/useRequest';
import AzureAD from './AzureAD';
import GitHub from './GitHub';
import GoogleOAuth2 from './GoogleOAuth2';
import OIDC from './OIDC';
import Jobs from './Jobs';
import LDAP from './LDAP';
import Subscription from './Subscription';
@ -68,6 +69,9 @@ function Settings() {
'/settings/google_oauth2': t`Google OAuth2`,
'/settings/google_oauth2/details': t`Details`,
'/settings/google_oauth2/edit': t`Edit Details`,
'/settings/oidc': t`Generic OIDC`,
'/settings/oidc/details': t`Details`,
'/settings/oidc/edit': t`Edit Details`,
'/settings/jobs': t`Jobs`,
'/settings/jobs/details': t`Details`,
'/settings/jobs/edit': t`Edit Details`,
@ -153,6 +157,9 @@ function Settings() {
<Route path="/settings/google_oauth2">
<GoogleOAuth2 />
</Route>
<Route path="/settings/oidc">
<OIDC />
</Route>
<Route path="/settings/jobs">
<Jobs />
</Route>

View File

@ -840,6 +840,39 @@
"read_only": false
}
},
"SOCIAL_AUTH_OIDC_KEY": {
"type": "string",
"label": "OIDC Key",
"help_text": "The OIDC key (Client ID) from your IDP.",
"category": "Generic OIDC",
"category_slug": "oidc",
"default": ""
},
"SOCIAL_AUTH_OIDC_SECRET": {
"type": "string",
"label": "OIDC Secret",
"help_text": "The OIDC secret (Client Secret) from your IDP.",
"category": "Generic OIDC",
"category_slug": "oidc",
"default": ""
},
"SOCIAL_AUTH_OIDC_OIDC_ENDPOINT": {
"type": "string",
"label": "OIDC Provider URL",
"help_text": "The URL for your OIDC provider, e.g.: http(s)://hostname/.",
"category": "Generic OIDC",
"category_slug": "oidc",
"default": ""
},
"SOCIAL_AUTH_OIDC_VERIFY_SSL": {
"type": "boolean",
"required": false,
"label": "Verify OIDC Provider Certificate",
"help_text": "Verify the OIDV provider ssl certificate.",
"category": "Generic OIDC",
"category_slug": "oidc",
"default": true
},
"AUTH_LDAP_SERVER_URI": {
"type": "string",
"required": false,
@ -4485,6 +4518,38 @@
"type": "string"
}
},
"SOCIAL_AUTH_OIDC_KEY": {
"type": "string",
"label": "OIDC Key",
"help_text": "The OIDC key (Client ID) from your IDP.",
"category": "Generic OIDC",
"category_slug": "oidc",
"default": ""
},
"SOCIAL_AUTH_OIDC_SECRET": {
"type": "string",
"label": "OIDC Secret",
"help_text": "The OIDC secret (Client Secret) from your IDP.",
"category": "Generic OIDC",
"category_slug": "oidc",
"default": ""
},
"SOCIAL_AUTH_OIDC_OIDC_ENDPOINT": {
"type": "string",
"label": "OIDC Provider URL",
"help_text": "The URL for your OIDC provider, e.g.: http(s)://hostname/.",
"category": "Generic OIDC",
"category_slug": "oidc",
"default": ""
},
"SOCIAL_AUTH_OIDC_VERIFY_SSL": {
"type": "boolean",
"label": "Verify OIDC Provider Certificate",
"help_text": "Verify the OIDV provider ssl certificate.",
"category": "Generic OIDC",
"category_slug": "oidc",
"default": true
},
"AUTH_LDAP_SERVER_URI": {
"type": "string",
"label": "LDAP Server URI",

View File

@ -253,6 +253,10 @@
"SOCIAL_AUTH_SAML_ORGANIZATION_ATTR":{},
"SOCIAL_AUTH_SAML_TEAM_ATTR":{},
"SOCIAL_AUTH_SAML_USER_FLAGS_BY_ATTR":{},
"SOCIAL_AUTH_OIDC_KEY":"",
"SOCIAL_AUTH_OIDC_SECRET":"",
"SOCIAL_AUTH_OIDC_OIDC_ENDPOINT":"",
"SOCIAL_AUTH_OIDC_VERIFY_SSL":true,
"NAMED_URL_FORMATS":{
"organizations":"<name>",
"teams":"<name>++<organization.name>",

24
docs/licenses/ecdsa.txt Normal file
View File

@ -0,0 +1,24 @@
"python-ecdsa" Copyright (c) 2010 Brian Warner
Portions written in 2005 by Peter Pearson and placed in the public domain.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Michael Davis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -47,7 +47,7 @@ python-ldap>=3.4.0 # https://github.com/ansible/awx/security/dependabot/20
pyyaml>=5.4.1 # minimum to fix https://github.com/yaml/pyyaml/issues/478
receptorctl==1.2.3
schedule==0.6.0
social-auth-core==4.2.0 # see UPGRADE BLOCKERs
social-auth-core[openidconnect]==4.3.0 # see UPGRADE BLOCKERs
social-auth-app-django==5.0.0 # see UPGRADE BLOCKERs
redis
requests

View File

@ -130,6 +130,8 @@ djangorestframework-yaml==2.0.0
# via -r /awx_devel/requirements/requirements.in
docutils==0.16
# via python-daemon
ecdsa==0.18.0
# via python-jose
# via
# -r /awx_devel/requirements/requirements_git.txt
# django-radius
@ -246,6 +248,7 @@ ptyprocess==0.6.0
pyasn1==0.4.8
# via
# pyasn1-modules
# python-jose
# python-ldap
# rsa
# service-identity
@ -283,6 +286,8 @@ python-dateutil==2.8.1
# receptorctl
python-dsv-sdk==0.0.1
# via -r /awx_devel/requirements/requirements.in
python-jose==3.3.0
# via social-auth-core
python-ldap==3.4.0
# via
# -r /awx_devel/requirements/requirements.in
@ -334,7 +339,9 @@ requests-oauthlib==1.3.1
# msrest
# social-auth-core
rsa==4.7.2
# via google-auth
# via
# google-auth
# python-jose
schedule==0.6.0
# via -r /awx_devel/requirements/requirements.in
semantic-version==2.9.0
@ -351,6 +358,7 @@ six==1.14.0
# automat
# django-extensions
# django-pglocks
# ecdsa
# google-auth
# isodate
# jaraco-collections
@ -372,7 +380,7 @@ smmap==3.0.1
# via gitdb
social-auth-app-django==5.0.0
# via -r /awx_devel/requirements/requirements.in
social-auth-core==4.2.0
social-auth-core[openidconnect]==4.3.0
# via
# -r /awx_devel/requirements/requirements.in
# social-auth-app-django