mirror of
https://github.com/ansible/awx.git
synced 2026-03-05 02:31:03 -03:30
Merge pull request #12552 from whitej6/jlw-generic-oidc
Implement Generic OIDC Provider
This commit is contained in:
@@ -379,6 +379,7 @@ AUTHENTICATION_BACKENDS = (
|
|||||||
'social_core.backends.github_enterprise.GithubEnterpriseOAuth2',
|
'social_core.backends.github_enterprise.GithubEnterpriseOAuth2',
|
||||||
'social_core.backends.github_enterprise.GithubEnterpriseOrganizationOAuth2',
|
'social_core.backends.github_enterprise.GithubEnterpriseOrganizationOAuth2',
|
||||||
'social_core.backends.github_enterprise.GithubEnterpriseTeamOAuth2',
|
'social_core.backends.github_enterprise.GithubEnterpriseTeamOAuth2',
|
||||||
|
'social_core.backends.open_id_connect.OpenIdConnectAuth',
|
||||||
'social_core.backends.azuread.AzureADOAuth2',
|
'social_core.backends.azuread.AzureADOAuth2',
|
||||||
'awx.sso.backends.SAMLAuth',
|
'awx.sso.backends.SAMLAuth',
|
||||||
'awx.main.backends.AWXModelBackend',
|
'awx.main.backends.AWXModelBackend',
|
||||||
|
|||||||
@@ -1215,6 +1215,54 @@ register(
|
|||||||
placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER,
|
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
|
# SAML AUTHENTICATION SETTINGS
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ class AuthenticationBackendsField(fields.StringListField):
|
|||||||
('awx.sso.backends.RADIUSBackend', ['RADIUS_SERVER']),
|
('awx.sso.backends.RADIUSBackend', ['RADIUS_SERVER']),
|
||||||
('social_core.backends.google.GoogleOAuth2', ['SOCIAL_AUTH_GOOGLE_OAUTH2_KEY', 'SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET']),
|
('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.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_core.backends.github.GithubOrganizationOAuth2',
|
||||||
['SOCIAL_AUTH_GITHUB_ORG_KEY', 'SOCIAL_AUTH_GITHUB_ORG_SECRET', 'SOCIAL_AUTH_GITHUB_ORG_NAME'],
|
['SOCIAL_AUTH_GITHUB_ORG_KEY', 'SOCIAL_AUTH_GITHUB_ORG_SECRET', 'SOCIAL_AUTH_GITHUB_ORG_NAME'],
|
||||||
|
|||||||
@@ -346,6 +346,20 @@ function AWXLogin({ alt, isAuthenticated }) {
|
|||||||
</LoginMainFooterLinksItem>
|
</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')) {
|
if (authKey.startsWith('saml')) {
|
||||||
const samlIDP = authKey.split(':')[1] || null;
|
const samlIDP = authKey.split(':')[1] || null;
|
||||||
return (
|
return (
|
||||||
|
|||||||
34
awx/ui/src/screens/Setting/OIDC/OIDC.js
Normal file
34
awx/ui/src/screens/Setting/OIDC/OIDC.js
Normal 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;
|
||||||
75
awx/ui/src/screens/Setting/OIDC/OIDC.test.js
Normal file
75
awx/ui/src/screens/Setting/OIDC/OIDC.test.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
98
awx/ui/src/screens/Setting/OIDC/OIDCDetail/OIDCDetail.js
Normal file
98
awx/ui/src/screens/Setting/OIDC/OIDCDetail/OIDCDetail.js
Normal 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;
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
1
awx/ui/src/screens/Setting/OIDC/OIDCDetail/index.js
Normal file
1
awx/ui/src/screens/Setting/OIDC/OIDCDetail/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './OIDCDetail';
|
||||||
147
awx/ui/src/screens/Setting/OIDC/OIDCEdit/OIDCEdit.js
Normal file
147
awx/ui/src/screens/Setting/OIDC/OIDCEdit/OIDCEdit.js
Normal 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;
|
||||||
161
awx/ui/src/screens/Setting/OIDC/OIDCEdit/OIDCEdit.test.js
Normal file
161
awx/ui/src/screens/Setting/OIDC/OIDCEdit/OIDCEdit.test.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
1
awx/ui/src/screens/Setting/OIDC/OIDCEdit/index.js
Normal file
1
awx/ui/src/screens/Setting/OIDC/OIDCEdit/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './OIDCEdit';
|
||||||
1
awx/ui/src/screens/Setting/OIDC/index.js
Normal file
1
awx/ui/src/screens/Setting/OIDC/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './OIDC';
|
||||||
@@ -81,6 +81,10 @@ function SettingList() {
|
|||||||
title: t`TACACS+ settings`,
|
title: t`TACACS+ settings`,
|
||||||
path: '/settings/tacacs',
|
path: '/settings/tacacs',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t`Generic OIDC settings`,
|
||||||
|
path: '/settings/oidc',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import useRequest from 'hooks/useRequest';
|
|||||||
import AzureAD from './AzureAD';
|
import AzureAD from './AzureAD';
|
||||||
import GitHub from './GitHub';
|
import GitHub from './GitHub';
|
||||||
import GoogleOAuth2 from './GoogleOAuth2';
|
import GoogleOAuth2 from './GoogleOAuth2';
|
||||||
|
import OIDC from './OIDC';
|
||||||
import Jobs from './Jobs';
|
import Jobs from './Jobs';
|
||||||
import LDAP from './LDAP';
|
import LDAP from './LDAP';
|
||||||
import Subscription from './Subscription';
|
import Subscription from './Subscription';
|
||||||
@@ -68,6 +69,9 @@ function Settings() {
|
|||||||
'/settings/google_oauth2': t`Google OAuth2`,
|
'/settings/google_oauth2': t`Google OAuth2`,
|
||||||
'/settings/google_oauth2/details': t`Details`,
|
'/settings/google_oauth2/details': t`Details`,
|
||||||
'/settings/google_oauth2/edit': t`Edit 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': t`Jobs`,
|
||||||
'/settings/jobs/details': t`Details`,
|
'/settings/jobs/details': t`Details`,
|
||||||
'/settings/jobs/edit': t`Edit Details`,
|
'/settings/jobs/edit': t`Edit Details`,
|
||||||
@@ -153,6 +157,9 @@ function Settings() {
|
|||||||
<Route path="/settings/google_oauth2">
|
<Route path="/settings/google_oauth2">
|
||||||
<GoogleOAuth2 />
|
<GoogleOAuth2 />
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="/settings/oidc">
|
||||||
|
<OIDC />
|
||||||
|
</Route>
|
||||||
<Route path="/settings/jobs">
|
<Route path="/settings/jobs">
|
||||||
<Jobs />
|
<Jobs />
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
@@ -840,6 +840,39 @@
|
|||||||
"read_only": false
|
"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": {
|
"AUTH_LDAP_SERVER_URI": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"required": false,
|
"required": false,
|
||||||
@@ -4485,6 +4518,38 @@
|
|||||||
"type": "string"
|
"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": {
|
"AUTH_LDAP_SERVER_URI": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"label": "LDAP Server URI",
|
"label": "LDAP Server URI",
|
||||||
|
|||||||
@@ -253,6 +253,10 @@
|
|||||||
"SOCIAL_AUTH_SAML_ORGANIZATION_ATTR":{},
|
"SOCIAL_AUTH_SAML_ORGANIZATION_ATTR":{},
|
||||||
"SOCIAL_AUTH_SAML_TEAM_ATTR":{},
|
"SOCIAL_AUTH_SAML_TEAM_ATTR":{},
|
||||||
"SOCIAL_AUTH_SAML_USER_FLAGS_BY_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":{
|
"NAMED_URL_FORMATS":{
|
||||||
"organizations":"<name>",
|
"organizations":"<name>",
|
||||||
"teams":"<name>++<organization.name>",
|
"teams":"<name>++<organization.name>",
|
||||||
|
|||||||
24
docs/licenses/ecdsa.txt
Normal file
24
docs/licenses/ecdsa.txt
Normal 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.
|
||||||
21
docs/licenses/python-jose.txt
Normal file
21
docs/licenses/python-jose.txt
Normal 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.
|
||||||
@@ -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
|
pyyaml>=5.4.1 # minimum to fix https://github.com/yaml/pyyaml/issues/478
|
||||||
receptorctl==1.2.3
|
receptorctl==1.2.3
|
||||||
schedule==0.6.0
|
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
|
social-auth-app-django==5.0.0 # see UPGRADE BLOCKERs
|
||||||
redis
|
redis
|
||||||
requests
|
requests
|
||||||
|
|||||||
@@ -130,6 +130,8 @@ djangorestframework-yaml==2.0.0
|
|||||||
# via -r /awx_devel/requirements/requirements.in
|
# via -r /awx_devel/requirements/requirements.in
|
||||||
docutils==0.16
|
docutils==0.16
|
||||||
# via python-daemon
|
# via python-daemon
|
||||||
|
ecdsa==0.18.0
|
||||||
|
# via python-jose
|
||||||
# via
|
# via
|
||||||
# -r /awx_devel/requirements/requirements_git.txt
|
# -r /awx_devel/requirements/requirements_git.txt
|
||||||
# django-radius
|
# django-radius
|
||||||
@@ -246,6 +248,7 @@ ptyprocess==0.6.0
|
|||||||
pyasn1==0.4.8
|
pyasn1==0.4.8
|
||||||
# via
|
# via
|
||||||
# pyasn1-modules
|
# pyasn1-modules
|
||||||
|
# python-jose
|
||||||
# python-ldap
|
# python-ldap
|
||||||
# rsa
|
# rsa
|
||||||
# service-identity
|
# service-identity
|
||||||
@@ -283,6 +286,8 @@ python-dateutil==2.8.1
|
|||||||
# receptorctl
|
# receptorctl
|
||||||
python-dsv-sdk==0.0.1
|
python-dsv-sdk==0.0.1
|
||||||
# via -r /awx_devel/requirements/requirements.in
|
# via -r /awx_devel/requirements/requirements.in
|
||||||
|
python-jose==3.3.0
|
||||||
|
# via social-auth-core
|
||||||
python-ldap==3.4.0
|
python-ldap==3.4.0
|
||||||
# via
|
# via
|
||||||
# -r /awx_devel/requirements/requirements.in
|
# -r /awx_devel/requirements/requirements.in
|
||||||
@@ -334,7 +339,9 @@ requests-oauthlib==1.3.1
|
|||||||
# msrest
|
# msrest
|
||||||
# social-auth-core
|
# social-auth-core
|
||||||
rsa==4.7.2
|
rsa==4.7.2
|
||||||
# via google-auth
|
# via
|
||||||
|
# google-auth
|
||||||
|
# python-jose
|
||||||
schedule==0.6.0
|
schedule==0.6.0
|
||||||
# via -r /awx_devel/requirements/requirements.in
|
# via -r /awx_devel/requirements/requirements.in
|
||||||
semantic-version==2.9.0
|
semantic-version==2.9.0
|
||||||
@@ -351,6 +358,7 @@ six==1.14.0
|
|||||||
# automat
|
# automat
|
||||||
# django-extensions
|
# django-extensions
|
||||||
# django-pglocks
|
# django-pglocks
|
||||||
|
# ecdsa
|
||||||
# google-auth
|
# google-auth
|
||||||
# isodate
|
# isodate
|
||||||
# jaraco-collections
|
# jaraco-collections
|
||||||
@@ -372,7 +380,7 @@ smmap==3.0.1
|
|||||||
# via gitdb
|
# via gitdb
|
||||||
social-auth-app-django==5.0.0
|
social-auth-app-django==5.0.0
|
||||||
# via -r /awx_devel/requirements/requirements.in
|
# via -r /awx_devel/requirements/requirements.in
|
||||||
social-auth-core==4.2.0
|
social-auth-core[openidconnect]==4.3.0
|
||||||
# via
|
# via
|
||||||
# -r /awx_devel/requirements/requirements.in
|
# -r /awx_devel/requirements/requirements.in
|
||||||
# social-auth-app-django
|
# social-auth-app-django
|
||||||
|
|||||||
Reference in New Issue
Block a user