diff --git a/awx/main/tests/functional/api/test_settings.py b/awx/main/tests/functional/api/test_settings.py index 8a1f50035f..c478b70817 100644 --- a/awx/main/tests/functional/api/test_settings.py +++ b/awx/main/tests/functional/api/test_settings.py @@ -393,3 +393,43 @@ def test_saml_x509cert_validation(patch, get, admin, headers): } }) assert resp.status_code == 200 + + +@pytest.mark.django_db +def test_github_settings(get, put, patch, delete, admin): + url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'github'}) + get(url, user=admin, expect=200) + delete(url, user=admin, expect=204) + response = get(url, user=admin, expect=200) + data = dict(response.data.items()) + put(url, user=admin, data=data, expect=200) + patch(url, user=admin, data={'SOCIAL_AUTH_GITHUB_KEY': '???'}, expect=200) + response = get(url, user=admin, expect=200) + assert response.data['SOCIAL_AUTH_GITHUB_KEY'] == '???' + data.pop('SOCIAL_AUTH_GITHUB_KEY') + put(url, user=admin, data=data, expect=200) + response = get(url, user=admin, expect=200) + assert response.data['SOCIAL_AUTH_GITHUB_KEY'] == '' + + +@pytest.mark.django_db +def test_github_enterprise_settings(get, put, patch, delete, admin): + url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'github-enterprise'}) + get(url, user=admin, expect=200) + delete(url, user=admin, expect=204) + response = get(url, user=admin, expect=200) + data = dict(response.data.items()) + put(url, user=admin, data=data, expect=200) + patch(url, user=admin, data={ + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL': 'example.com', + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL': 'example.com', + }, expect=200) + response = get(url, user=admin, expect=200) + assert response.data['SOCIAL_AUTH_GITHUB_ENTERPRISE_URL'] == 'example.com' + assert response.data['SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL'] == 'example.com' + data.pop('SOCIAL_AUTH_GITHUB_ENTERPRISE_URL') + data.pop('SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL') + put(url, user=admin, data=data, expect=200) + response = get(url, user=admin, expect=200) + assert response.data['SOCIAL_AUTH_GITHUB_ENTERPRISE_URL'] == '' + assert response.data['SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL'] == '' diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index da79b0fc4c..31422ebb33 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -344,6 +344,9 @@ AUTHENTICATION_BACKENDS = ( 'social_core.backends.github.GithubOAuth2', 'social_core.backends.github.GithubOrganizationOAuth2', 'social_core.backends.github.GithubTeamOAuth2', + 'social_core.backends.github_enterprise.GithubEnterpriseOAuth2', + 'social_core.backends.github_enterprise.GithubEnterpriseOrganizationOAuth2', + 'social_core.backends.github_enterprise.GithubEnterpriseTeamOAuth2', 'social_core.backends.azuread.AzureADOAuth2', 'awx.sso.backends.SAMLAuth', 'django.contrib.auth.backends.ModelBackend', @@ -520,6 +523,20 @@ SOCIAL_AUTH_GITHUB_TEAM_SECRET = '' SOCIAL_AUTH_GITHUB_TEAM_ID = '' SOCIAL_AUTH_GITHUB_TEAM_SCOPE = ['user:email', 'read:org'] +SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY = '' +SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET = '' +SOCIAL_AUTH_GITHUB_ENTERPRISE_SCOPE = ['user:email', 'read:org'] + +SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY = '' +SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET = '' +SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME = '' +SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SCOPE = ['user:email', 'read:org'] + +SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY = '' +SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET = '' +SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID = '' +SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SCOPE = ['user:email', 'read:org'] + SOCIAL_AUTH_AZUREAD_OAUTH2_KEY = '' SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET = '' diff --git a/awx/sso/conf.py b/awx/sso/conf.py index 5f595517cc..8f21eb4d0a 100644 --- a/awx/sso/conf.py +++ b/awx/sso/conf.py @@ -842,6 +842,298 @@ register( placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, ) +############################################################################### +# GITHUB ENTERPRISE OAUTH2 AUTHENTICATION SETTINGS +############################################################################### + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_CALLBACK_URL', + field_class=fields.CharField, + read_only=True, + default=SocialAuthCallbackURL('github-enterprise'), + label=_('GitHub Enterprise OAuth2 Callback URL'), + help_text=_('Provide this URL as the callback URL for your application as part ' + 'of your registration process. Refer to the Ansible Tower ' + 'documentation for more detail.'), + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise', + depends_on=['TOWER_URL_BASE'], +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise URL'), + help_text=_('The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise ' + 'documentation for more details.'), + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise', +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise API URL'), + help_text=_('The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github ' + 'Enterprise documentation for more details.'), + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise', +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise OAuth2 Key'), + help_text=_('The OAuth2 key (Client ID) from your GitHub Enterprise developer application.'), + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise', +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise OAuth2 Secret'), + help_text=_('The OAuth2 secret (Client Secret) from your GitHub Enterprise developer application.'), + category=_('GitHub OAuth2'), + category_slug='github-enterprise', + encrypted=True, +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP', + field_class=SocialOrganizationMapField, + allow_null=True, + default=None, + label=_('GitHub Enterprise OAuth2 Organization Map'), + help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise', + placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_MAP', + field_class=SocialTeamMapField, + allow_null=True, + default=None, + label=_('GitHub Enterprise OAuth2 Team Map'), + help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise', + placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, +) + +############################################################################### +# GITHUB ENTERPRISE ORG OAUTH2 AUTHENTICATION SETTINGS +############################################################################### + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_CALLBACK_URL', + field_class=fields.CharField, + read_only=True, + default=SocialAuthCallbackURL('github-enterprise-org'), + label=_('GitHub Enterprise Organization OAuth2 Callback URL'), + help_text=_('Provide this URL as the callback URL for your application as part ' + 'of your registration process. Refer to the Ansible Tower ' + 'documentation for more detail.'), + category=_('GitHub Enterprise Organization OAuth2'), + category_slug='github-enterprise-org', + depends_on=['TOWER_URL_BASE'], +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Organization URL'), + help_text=_('The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise ' + 'documentation for more details.'), + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise-org', +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Organization API URL'), + help_text=_('The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github ' + 'Enterprise documentation for more details.'), + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise-org', +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Organization OAuth2 Key'), + help_text=_('The OAuth2 key (Client ID) from your GitHub Enterprise organization application.'), + category=_('GitHub Enterprise Organization OAuth2'), + category_slug='github-enterprise-org', +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Organization OAuth2 Secret'), + help_text=_('The OAuth2 secret (Client Secret) from your GitHub Enterprise organization application.'), + category=_('GitHub Enterprise Organization OAuth2'), + category_slug='github-enterprise-org', + encrypted=True, +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Organization Name'), + help_text=_('The name of your GitHub Enterprise organization, as used in your ' + 'organization\'s URL: https://github.com//.'), + category=_('GitHub Enterprise Organization OAuth2'), + category_slug='github-enterprise-org', +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP', + field_class=SocialOrganizationMapField, + allow_null=True, + default=None, + label=_('GitHub Enterprise Organization OAuth2 Organization Map'), + help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, + category=_('GitHub Enterprise Organization OAuth2'), + category_slug='github-enterprise-org', + placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_TEAM_MAP', + field_class=SocialTeamMapField, + allow_null=True, + default=None, + label=_('GitHub Enterprise Organization OAuth2 Team Map'), + help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, + category=_('GitHub Enterprise Organization OAuth2'), + category_slug='github-enterprise-org', + placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, +) + +############################################################################### +# GITHUB ENTERPRISE TEAM OAUTH2 AUTHENTICATION SETTINGS +############################################################################### + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_CALLBACK_URL', + field_class=fields.CharField, + read_only=True, + default=SocialAuthCallbackURL('github-enterprise-team'), + label=_('GitHub Enterprise Team OAuth2 Callback URL'), + help_text=_('Create an organization-owned application at ' + 'https://github.com/organizations//settings/applications ' + 'and obtain an OAuth2 key (Client ID) and secret (Client Secret). ' + 'Provide this URL as the callback URL for your application.'), + category=_('GitHub Enterprise Team OAuth2'), + category_slug='github-enterprise-team', + depends_on=['TOWER_URL_BASE'], +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Team URL'), + help_text=_('The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise ' + 'documentation for more details.'), + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise-team', +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Team API URL'), + help_text=_('The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github ' + 'Enterprise documentation for more details.'), + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise-team', +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Team OAuth2 Key'), + help_text=_('The OAuth2 key (Client ID) from your GitHub Enterprise organization application.'), + category=_('GitHub Enterprise Team OAuth2'), + category_slug='github-enterprise-team', +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Team OAuth2 Secret'), + help_text=_('The OAuth2 secret (Client Secret) from your GitHub Enterprise organization application.'), + category=_('GitHub Enterprise Team OAuth2'), + category_slug='github-enterprise-team', + encrypted=True, +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Team ID'), + help_text=_('Find the numeric team ID using the Github Enterprise API: ' + 'http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/.'), + category=_('GitHub Enterprise Team OAuth2'), + category_slug='github-enterprise-team', +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP', + field_class=SocialOrganizationMapField, + allow_null=True, + default=None, + label=_('GitHub Enterprise Team OAuth2 Organization Map'), + help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, + category=_('GitHub Enterprise Team OAuth2'), + category_slug='github-enterprise-team', + placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, +) + +register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_TEAM_MAP', + field_class=SocialTeamMapField, + allow_null=True, + default=None, + label=_('GitHub Enterprise Team OAuth2 Team Map'), + help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, + category=_('GitHub Enterprise Team OAuth2'), + category_slug='github-enterprise-team', + placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, +) + ############################################################################### # MICROSOFT AZURE ACTIVE DIRECTORY SETTINGS ############################################################################### diff --git a/awx/sso/fields.py b/awx/sso/fields.py index 38d8bde0d4..5df5f894c9 100644 --- a/awx/sso/fields.py +++ b/awx/sso/fields.py @@ -187,6 +187,26 @@ class AuthenticationBackendsField(fields.StringListField): 'SOCIAL_AUTH_GITHUB_TEAM_SECRET', 'SOCIAL_AUTH_GITHUB_TEAM_ID', ]), + ('social_core.backends.github_enterprise.GithubEnterpriseOAuth2', [ + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL', + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL', + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY', + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET', + ]), + ('social_core.backends.github_enterprise.GithubEnterpriseOrganizationOAuth2', [ + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL', + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL', + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY', + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET', + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME', + ]), + ('social_core.backends.github_enterprise.GithubEnterpriseTeamOAuth2', [ + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL', + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL', + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY', + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET', + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID', + ]), ('social_core.backends.azuread.AzureADOAuth2', [ 'SOCIAL_AUTH_AZUREAD_OAUTH2_KEY', 'SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET', diff --git a/awx/ui_next/src/screens/Login/Login.jsx b/awx/ui_next/src/screens/Login/Login.jsx index cad87f1dfa..8887d7351f 100644 --- a/awx/ui_next/src/screens/Login/Login.jsx +++ b/awx/ui_next/src/screens/Login/Login.jsx @@ -165,6 +165,41 @@ function AWXLogin({ alt, i18n, isAuthenticated }) { ); } + if (authKey === 'github-enterprise') { + return ( + + + + + + ); + } + if (authKey === 'github-enterprise-org') { + return ( + + + + + + ); + } + if (authKey === 'github-enterprise-team') { + return ( + + + + + + ); + } if (authKey === 'google-oauth2') { return ( diff --git a/awx/ui_next/src/screens/Setting/GitHub/GitHub.jsx b/awx/ui_next/src/screens/Setting/GitHub/GitHub.jsx index 03d92e5899..aa23ae58ce 100644 --- a/awx/ui_next/src/screens/Setting/GitHub/GitHub.jsx +++ b/awx/ui_next/src/screens/Setting/GitHub/GitHub.jsx @@ -8,6 +8,9 @@ import GitHubDetail from './GitHubDetail'; import GitHubEdit from './GitHubEdit'; import GitHubOrgEdit from './GitHubOrgEdit'; import GitHubTeamEdit from './GitHubTeamEdit'; +import GitHubEnterpriseEdit from './GitHubEnterpriseEdit'; +import GitHubEnterpriseOrgEdit from './GitHubEnterpriseOrgEdit'; +import GitHubEnterpriseTeamEdit from './GitHubEnterpriseTeamEdit'; function GitHub({ i18n }) { const baseURL = '/settings/github'; @@ -40,6 +43,15 @@ function GitHub({ i18n }) { + + + + + + + + + diff --git a/awx/ui_next/src/screens/Setting/GitHub/GitHub.test.jsx b/awx/ui_next/src/screens/Setting/GitHub/GitHub.test.jsx index 68572d6c35..12a2a95261 100644 --- a/awx/ui_next/src/screens/Setting/GitHub/GitHub.test.jsx +++ b/awx/ui_next/src/screens/Setting/GitHub/GitHub.test.jsx @@ -48,6 +48,44 @@ describe('', () => { SOCIAL_AUTH_GITHUB_TEAM_TEAM_MAP: {}, }, }); + SettingsAPI.readCategory.mockResolvedValueOnce({ + data: { + SOCIAL_AUTH_GITHUB_ENTERPRISE_CALLBACK_URL: + 'https://towerhost/sso/complete/github-enterprise/', + SOCIAL_AUTH_GITHUB_ENTERPRISE_URL: 'https://localhost/url', + SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL: 'https://localhost/apiurl', + SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY: 'ent_key', + SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET: '$encrypted', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP: {}, + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_MAP: {}, + }, + }); + SettingsAPI.readCategory.mockResolvedValueOnce({ + data: { + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_CALLBACK_URL: + 'https://towerhost/sso/complete/github-enterprise-org/', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL: 'https://localhost/url', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL: 'https://localhost/apiurl', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY: 'ent_org_key', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET: '$encrypted$', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME: 'ent_org_name', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP: {}, + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_TEAM_MAP: {}, + }, + }); + SettingsAPI.readCategory.mockResolvedValueOnce({ + data: { + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_CALLBACK_URL: + 'https://towerhost/sso/complete/github-enterprise-team/', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL: 'https://localhost/url', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL: 'https://localhost/apiurl', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY: 'ent_team_key', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET: '$encrypted$', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID: 'ent_team_id', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP: {}, + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_TEAM_MAP: {}, + }, + }); }); afterEach(() => { diff --git a/awx/ui_next/src/screens/Setting/GitHub/GitHubDetail/GitHubDetail.jsx b/awx/ui_next/src/screens/Setting/GitHub/GitHubDetail/GitHubDetail.jsx index 5dc76348fb..816ae229b8 100644 --- a/awx/ui_next/src/screens/Setting/GitHub/GitHubDetail/GitHubDetail.jsx +++ b/awx/ui_next/src/screens/Setting/GitHub/GitHubDetail/GitHubDetail.jsx @@ -31,21 +31,33 @@ function GitHubDetail({ i18n }) { { data: gitHubDefault }, { data: gitHubOrganization }, { data: gitHubTeam }, + { data: gitHubEnterprise }, + { data: gitHubEnterpriseOrganization }, + { data: gitHubEnterpriseTeam }, ] = await Promise.all([ SettingsAPI.readCategory('github'), SettingsAPI.readCategory('github-org'), SettingsAPI.readCategory('github-team'), + SettingsAPI.readCategory('github-enterprise'), + SettingsAPI.readCategory('github-enterprise-org'), + SettingsAPI.readCategory('github-enterprise-team'), ]); return { default: gitHubDefault, organization: gitHubOrganization, team: gitHubTeam, + enterprise: gitHubEnterprise, + enterprise_organization: gitHubEnterpriseOrganization, + enterprise_team: gitHubEnterpriseTeam, }; }, []), { default: null, organization: null, team: null, + enterprise: null, + enterprise_organization: null, + enterprise_team: null, } ); @@ -79,6 +91,21 @@ function GitHubDetail({ i18n }) { link: `${baseURL}/team/details`, id: 2, }, + { + name: i18n._(t`GitHub Enterprise`), + link: `${baseURL}/enterprise/details`, + id: 3, + }, + { + name: i18n._(t`GitHub Enterprise Organization`), + link: `${baseURL}/enterprise_organization/details`, + id: 4, + }, + { + name: i18n._(t`GitHub Enterprise Team`), + link: `${baseURL}/enterprise_team/details`, + id: 5, + }, ]; if (!Object.keys(gitHubDetails).includes(category)) { diff --git a/awx/ui_next/src/screens/Setting/GitHub/GitHubDetail/GitHubDetail.test.jsx b/awx/ui_next/src/screens/Setting/GitHub/GitHubDetail/GitHubDetail.test.jsx index 11db4b57f0..3d2551e3a3 100644 --- a/awx/ui_next/src/screens/Setting/GitHub/GitHubDetail/GitHubDetail.test.jsx +++ b/awx/ui_next/src/screens/Setting/GitHub/GitHubDetail/GitHubDetail.test.jsx @@ -51,6 +51,44 @@ const mockTeam = { SOCIAL_AUTH_GITHUB_TEAM_TEAM_MAP: {}, }, }; +const mockEnterprise = { + data: { + SOCIAL_AUTH_GITHUB_ENTERPRISE_CALLBACK_URL: + 'https://towerhost/sso/complete/github-enterprise/', + SOCIAL_AUTH_GITHUB_ENTERPRISE_URL: 'https://localhost/enterpriseurl', + SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL: 'https://localhost/enterpriseapi', + SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY: 'foobar', + SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET: '$encrypted$', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP: null, + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_MAP: null, + }, +}; +const mockEnterpriseOrg = { + data: { + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_CALLBACK_URL: + 'https://towerhost/sso/complete/github-enterprise-org/', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL: 'https://localhost/orgurl', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL: 'https://localhost/orgapi', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY: 'foobar', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET: '$encrypted$', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME: 'foo', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP: null, + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_TEAM_MAP: null, + }, +}; +const mockEnterpriseTeam = { + data: { + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_CALLBACK_URL: + 'https://towerhost/sso/complete/github-enterprise-team/', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL: 'https://localhost/teamurl', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL: 'https://localhost/teamapi', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY: 'foobar', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET: '$encrypted$', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID: 'foo', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP: null, + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_TEAM_MAP: null, + }, +}; describe('', () => { describe('Default', () => { @@ -60,6 +98,9 @@ describe('', () => { SettingsAPI.readCategory.mockResolvedValueOnce(mockDefault); SettingsAPI.readCategory.mockResolvedValueOnce(mockOrg); SettingsAPI.readCategory.mockResolvedValueOnce(mockTeam); + SettingsAPI.readCategory.mockResolvedValueOnce(mockEnterprise); + SettingsAPI.readCategory.mockResolvedValueOnce(mockEnterpriseOrg); + SettingsAPI.readCategory.mockResolvedValueOnce(mockEnterpriseTeam); useRouteMatch.mockImplementation(() => ({ url: '/settings/github/default/details', path: '/settings/github/:category/details', @@ -90,6 +131,9 @@ describe('', () => { 'GitHub Default', 'GitHub Organization', 'GitHub Team', + 'GitHub Enterprise', + 'GitHub Enterprise Organization', + 'GitHub Enterprise Team', ]; wrapper.find('RoutedTabs li').forEach((tab, index) => { expect(tab.text()).toEqual(expectedTabs[index]); @@ -149,6 +193,9 @@ describe('', () => { SettingsAPI.readCategory.mockResolvedValueOnce(mockDefault); SettingsAPI.readCategory.mockResolvedValueOnce(mockOrg); SettingsAPI.readCategory.mockResolvedValueOnce(mockTeam); + SettingsAPI.readCategory.mockResolvedValueOnce(mockEnterprise); + SettingsAPI.readCategory.mockResolvedValueOnce(mockEnterpriseOrg); + SettingsAPI.readCategory.mockResolvedValueOnce(mockEnterpriseTeam); useRouteMatch.mockImplementation(() => ({ url: '/settings/github/organization/details', path: '/settings/github/:category/details', @@ -198,6 +245,9 @@ describe('', () => { SettingsAPI.readCategory.mockResolvedValueOnce(mockDefault); SettingsAPI.readCategory.mockResolvedValueOnce(mockOrg); SettingsAPI.readCategory.mockResolvedValueOnce(mockTeam); + SettingsAPI.readCategory.mockResolvedValueOnce(mockEnterprise); + SettingsAPI.readCategory.mockResolvedValueOnce(mockEnterpriseOrg); + SettingsAPI.readCategory.mockResolvedValueOnce(mockEnterpriseTeam); useRouteMatch.mockImplementation(() => ({ url: '/settings/github/team/details', path: '/settings/github/:category/details', @@ -236,6 +286,199 @@ describe('', () => { }); }); + describe('Enterprise', () => { + let wrapper; + + beforeAll(async () => { + SettingsAPI.readCategory.mockResolvedValueOnce(mockDefault); + SettingsAPI.readCategory.mockResolvedValueOnce(mockOrg); + SettingsAPI.readCategory.mockResolvedValueOnce(mockTeam); + SettingsAPI.readCategory.mockResolvedValueOnce(mockEnterprise); + SettingsAPI.readCategory.mockResolvedValueOnce(mockEnterpriseOrg); + SettingsAPI.readCategory.mockResolvedValueOnce(mockEnterpriseTeam); + useRouteMatch.mockImplementation(() => ({ + url: '/settings/github/enterprise/details', + path: '/settings/github/:category/details', + params: { category: 'enterprise' }, + })); + await act(async () => { + wrapper = mountWithContexts( + + + + ); + }); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + }); + + afterAll(() => { + wrapper.unmount(); + jest.clearAllMocks(); + }); + + test('should render expected details', () => { + assertDetail( + wrapper, + 'GitHub Enterprise OAuth2 Callback URL', + 'https://towerhost/sso/complete/github-enterprise/' + ); + assertDetail( + wrapper, + 'GitHub Enterprise URL', + 'https://localhost/enterpriseurl' + ); + assertDetail( + wrapper, + 'GitHub Enterprise API URL', + 'https://localhost/enterpriseapi' + ); + assertDetail(wrapper, 'GitHub Enterprise OAuth2 Key', 'foobar'); + assertDetail(wrapper, 'GitHub Enterprise OAuth2 Secret', 'Encrypted'); + assertVariableDetail( + wrapper, + 'GitHub Enterprise OAuth2 Organization Map', + '{}' + ); + assertVariableDetail(wrapper, 'GitHub Enterprise OAuth2 Team Map', '{}'); + }); + }); + + describe('Enterprise Org', () => { + let wrapper; + + beforeAll(async () => { + SettingsAPI.readCategory.mockResolvedValueOnce(mockDefault); + SettingsAPI.readCategory.mockResolvedValueOnce(mockOrg); + SettingsAPI.readCategory.mockResolvedValueOnce(mockTeam); + SettingsAPI.readCategory.mockResolvedValueOnce(mockEnterprise); + SettingsAPI.readCategory.mockResolvedValueOnce(mockEnterpriseOrg); + SettingsAPI.readCategory.mockResolvedValueOnce(mockEnterpriseTeam); + useRouteMatch.mockImplementation(() => ({ + url: '/settings/github/enterprise_organization/details', + path: '/settings/github/:category/details', + params: { category: 'enterprise_organization' }, + })); + await act(async () => { + wrapper = mountWithContexts( + + + + ); + }); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + }); + + afterAll(() => { + wrapper.unmount(); + jest.clearAllMocks(); + }); + + test('should render expected details', () => { + assertDetail( + wrapper, + 'GitHub Enterprise Organization OAuth2 Callback URL', + 'https://towerhost/sso/complete/github-enterprise-org/' + ); + assertDetail( + wrapper, + 'GitHub Enterprise Organization URL', + 'https://localhost/orgurl' + ); + assertDetail( + wrapper, + 'GitHub Enterprise Organization API URL', + 'https://localhost/orgapi' + ); + assertDetail( + wrapper, + 'GitHub Enterprise Organization OAuth2 Key', + 'foobar' + ); + assertDetail( + wrapper, + 'GitHub Enterprise Organization OAuth2 Secret', + 'Encrypted' + ); + assertDetail(wrapper, 'GitHub Enterprise Organization Name', 'foo'); + assertVariableDetail( + wrapper, + 'GitHub Enterprise Organization OAuth2 Organization Map', + '{}' + ); + assertVariableDetail( + wrapper, + 'GitHub Enterprise Organization OAuth2 Team Map', + '{}' + ); + }); + }); + + describe('Enterprise Team', () => { + let wrapper; + + beforeAll(async () => { + SettingsAPI.readCategory.mockResolvedValueOnce(mockDefault); + SettingsAPI.readCategory.mockResolvedValueOnce(mockOrg); + SettingsAPI.readCategory.mockResolvedValueOnce(mockTeam); + SettingsAPI.readCategory.mockResolvedValueOnce(mockEnterprise); + SettingsAPI.readCategory.mockResolvedValueOnce(mockEnterpriseOrg); + SettingsAPI.readCategory.mockResolvedValueOnce(mockEnterpriseTeam); + useRouteMatch.mockImplementation(() => ({ + url: '/settings/github/enterprise_team/details', + path: '/settings/github/:category/details', + params: { category: 'enterprise_team' }, + })); + await act(async () => { + wrapper = mountWithContexts( + + + + ); + }); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + }); + + afterAll(() => { + wrapper.unmount(); + jest.clearAllMocks(); + }); + + test('should render expected details', () => { + assertDetail( + wrapper, + 'GitHub Enterprise Team OAuth2 Callback URL', + 'https://towerhost/sso/complete/github-enterprise-team/' + ); + assertDetail( + wrapper, + 'GitHub Enterprise Team URL', + 'https://localhost/teamurl' + ); + assertDetail( + wrapper, + 'GitHub Enterprise Team API URL', + 'https://localhost/teamapi' + ); + assertDetail(wrapper, 'GitHub Enterprise Team OAuth2 Key', 'foobar'); + assertDetail( + wrapper, + 'GitHub Enterprise Team OAuth2 Secret', + 'Encrypted' + ); + assertDetail(wrapper, 'GitHub Enterprise Team ID', 'foo'); + assertVariableDetail( + wrapper, + 'GitHub Enterprise Team OAuth2 Organization Map', + '{}' + ); + assertVariableDetail( + wrapper, + 'GitHub Enterprise Team OAuth2 Team Map', + '{}' + ); + }); + }); + describe('Redirect', () => { test('should render redirect when user navigates to erroneous category', async () => { let wrapper; diff --git a/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseEdit/GitHubEnterpriseEdit.jsx b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseEdit/GitHubEnterpriseEdit.jsx new file mode 100644 index 0000000000..3d6ee60c3e --- /dev/null +++ b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseEdit/GitHubEnterpriseEdit.jsx @@ -0,0 +1,151 @@ +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 { RevertAllAlert, RevertFormActionGroup } from '../../shared'; +import { + EncryptedField, + InputField, + ObjectField, +} from '../../shared/SharedFields'; +import { formatJson } from '../../shared/settingUtils'; +import useModal from '../../../../util/useModal'; +import useRequest from '../../../../util/useRequest'; +import { SettingsAPI } from '../../../../api'; + +function GitHubEnterpriseEdit() { + const history = useHistory(); + const { isModalOpen, toggleModal, closeModal } = useModal(); + const { PUT: options } = useSettings(); + + const { isLoading, error, request: fetchGithub, result: github } = useRequest( + useCallback(async () => { + const { data } = await SettingsAPI.readCategory('github-enterprise'); + 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(() => { + fetchGithub(); + }, [fetchGithub]); + + const { error: submitError, request: submitForm } = useRequest( + useCallback( + async values => { + await SettingsAPI.updateAll(values); + history.push('/settings/github/enterprise/details'); + }, + [history] + ), + null + ); + + const handleSubmit = async form => { + await submitForm({ + ...form, + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP: formatJson( + form.SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP + ), + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_MAP: formatJson( + form.SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_MAP + ), + }); + }; + + const handleRevertAll = async () => { + const defaultValues = Object.assign( + ...Object.entries(github).map(([key, value]) => ({ + [key]: value.default, + })) + ); + await submitForm(defaultValues); + closeModal(); + }; + + const handleCancel = () => { + history.push('/settings/github/enterprise/details'); + }; + + const initialValues = fields => + Object.keys(fields).reduce((acc, key) => { + if (fields[key].type === 'list' || fields[key].type === 'nested object') { + const emptyDefault = fields[key].type === 'list' ? '[]' : '{}'; + acc[key] = fields[key].value + ? JSON.stringify(fields[key].value, null, 2) + : emptyDefault; + } else { + acc[key] = fields[key].value ?? ''; + } + return acc; + }, {}); + + return ( + + {isLoading && } + {!isLoading && error && } + {!isLoading && github && ( + + {formik => ( +
+ + + + + + + + {submitError && } + + + {isModalOpen && ( + + )} + + )} +
+ )} +
+ ); +} + +export default GitHubEnterpriseEdit; diff --git a/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseEdit/GitHubEnterpriseEdit.test.jsx b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseEdit/GitHubEnterpriseEdit.test.jsx new file mode 100644 index 0000000000..f0bb46ab23 --- /dev/null +++ b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseEdit/GitHubEnterpriseEdit.test.jsx @@ -0,0 +1,196 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { createMemoryHistory } from 'history'; +import { + mountWithContexts, + waitForElement, +} from '../../../../../testUtils/enzymeHelpers'; +import mockAllOptions from '../../shared/data.allSettingOptions.json'; +import { SettingsProvider } from '../../../../contexts/Settings'; +import { SettingsAPI } from '../../../../api'; +import GitHubEnterpriseEdit from './GitHubEnterpriseEdit'; + +jest.mock('../../../../api/models/Settings'); +SettingsAPI.updateAll.mockResolvedValue({}); +SettingsAPI.readCategory.mockResolvedValue({ + data: { + SOCIAL_AUTH_GITHUB_ENTERPRISE_CALLBACK_URL: + 'https://towerhost/sso/complete/github-enterprise/', + SOCIAL_AUTH_GITHUB_ENTERPRISE_URL: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET: '$encrypted$', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP: null, + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_MAP: null, + }, +}); + +describe('', () => { + let wrapper; + let history; + + afterEach(() => { + wrapper.unmount(); + jest.clearAllMocks(); + }); + + beforeEach(async () => { + history = createMemoryHistory({ + initialEntries: ['/settings/github/enterprise/edit'], + }); + await act(async () => { + wrapper = mountWithContexts( + + + , + { + context: { router: { history } }, + } + ); + }); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + }); + + test('initially renders without crashing', () => { + expect(wrapper.find('GitHubEnterpriseEdit').length).toBe(1); + }); + + test('should display expected form fields', async () => { + expect( + wrapper.find('FormGroup[label="GitHub Enterprise URL"]').length + ).toBe(1); + expect( + wrapper.find('FormGroup[label="GitHub Enterprise API URL"]').length + ).toBe(1); + expect( + wrapper.find('FormGroup[label="GitHub Enterprise OAuth2 Key"]').length + ).toBe(1); + expect( + wrapper.find('FormGroup[label="GitHub Enterprise OAuth2 Secret"]').length + ).toBe(1); + expect( + wrapper.find( + 'FormGroup[label="GitHub Enterprise OAuth2 Organization Map"]' + ).length + ).toBe(1); + expect( + wrapper.find('FormGroup[label="GitHub Enterprise OAuth2 Team Map"]') + .length + ).toBe(1); + }); + + test('should successfully send default values to api on form revert all', async () => { + expect(SettingsAPI.updateAll).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.updateAll).toHaveBeenCalledTimes(1); + expect(SettingsAPI.updateAll).toHaveBeenCalledWith({ + SOCIAL_AUTH_GITHUB_ENTERPRISE_URL: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP: null, + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_MAP: null, + }); + }); + + test('should successfully send request to api on form submission', async () => { + act(() => { + wrapper + .find( + 'FormGroup[fieldId="SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET"] button[aria-label="Revert"]' + ) + .invoke('onClick')(); + wrapper + .find('input#SOCIAL_AUTH_GITHUB_ENTERPRISE_URL') + .simulate('change', { + target: { + value: 'https://localhost', + name: 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL', + }, + }); + wrapper + .find('CodeMirrorInput#SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP') + .invoke('onChange')('{\n"Default":{\n"users":\nfalse\n}\n}'); + }); + wrapper.update(); + await act(async () => { + wrapper.find('Form').invoke('onSubmit')(); + }); + expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1); + expect(SettingsAPI.updateAll).toHaveBeenCalledWith({ + SOCIAL_AUTH_GITHUB_ENTERPRISE_URL: 'https://localhost', + SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_MAP: {}, + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP: { + Default: { + users: false, + }, + }, + }); + }); + + test('should navigate to github enterprise detail on successful submission', async () => { + await act(async () => { + wrapper.find('Form').invoke('onSubmit')(); + }); + expect(history.location.pathname).toEqual( + '/settings/github/enterprise/details' + ); + }); + + test('should navigate to github enterprise detail when cancel is clicked', async () => { + await act(async () => { + wrapper.find('button[aria-label="Cancel"]').invoke('onClick')(); + }); + expect(history.location.pathname).toEqual( + '/settings/github/enterprise/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( + + + + ); + }); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + expect(wrapper.find('ContentError').length).toBe(1); + }); +}); diff --git a/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseEdit/index.js b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseEdit/index.js new file mode 100644 index 0000000000..5b5377e423 --- /dev/null +++ b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseEdit/index.js @@ -0,0 +1 @@ +export { default } from './GitHubEnterpriseEdit'; diff --git a/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseOrgEdit/GitHubEnterpriseOrgEdit.jsx b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseOrgEdit/GitHubEnterpriseOrgEdit.jsx new file mode 100644 index 0000000000..272b1866ff --- /dev/null +++ b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseOrgEdit/GitHubEnterpriseOrgEdit.jsx @@ -0,0 +1,157 @@ +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 { RevertAllAlert, RevertFormActionGroup } from '../../shared'; +import { + EncryptedField, + InputField, + ObjectField, +} from '../../shared/SharedFields'; +import { formatJson } from '../../shared/settingUtils'; +import useModal from '../../../../util/useModal'; +import useRequest from '../../../../util/useRequest'; +import { SettingsAPI } from '../../../../api'; + +function GitHubEnterpriseOrgEdit() { + const history = useHistory(); + const { isModalOpen, toggleModal, closeModal } = useModal(); + const { PUT: options } = useSettings(); + + const { isLoading, error, request: fetchGithub, result: github } = useRequest( + useCallback(async () => { + const { data } = await SettingsAPI.readCategory('github-enterprise-org'); + 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(() => { + fetchGithub(); + }, [fetchGithub]); + + const { error: submitError, request: submitForm } = useRequest( + useCallback( + async values => { + await SettingsAPI.updateAll(values); + history.push('/settings/github/enterprise_organization/details'); + }, + [history] + ), + null + ); + + const handleSubmit = async form => { + await submitForm({ + ...form, + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP: formatJson( + form.SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP + ), + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_TEAM_MAP: formatJson( + form.SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_TEAM_MAP + ), + }); + }; + + const handleRevertAll = async () => { + const defaultValues = Object.assign( + ...Object.entries(github).map(([key, value]) => ({ + [key]: value.default, + })) + ); + await submitForm(defaultValues); + closeModal(); + }; + + const handleCancel = () => { + history.push('/settings/github/enterprise_organization/details'); + }; + + const initialValues = fields => + Object.keys(fields).reduce((acc, key) => { + if (fields[key].type === 'list' || fields[key].type === 'nested object') { + const emptyDefault = fields[key].type === 'list' ? '[]' : '{}'; + acc[key] = fields[key].value + ? JSON.stringify(fields[key].value, null, 2) + : emptyDefault; + } else { + acc[key] = fields[key].value ?? ''; + } + return acc; + }, {}); + + return ( + + {isLoading && } + {!isLoading && error && } + {!isLoading && github && ( + + {formik => ( +
+ + + + + + + + + {submitError && } + + + {isModalOpen && ( + + )} + + )} +
+ )} +
+ ); +} + +export default GitHubEnterpriseOrgEdit; diff --git a/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseOrgEdit/GitHubEnterpriseOrgEdit.test.jsx b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseOrgEdit/GitHubEnterpriseOrgEdit.test.jsx new file mode 100644 index 0000000000..278dd5d37e --- /dev/null +++ b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseOrgEdit/GitHubEnterpriseOrgEdit.test.jsx @@ -0,0 +1,212 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { createMemoryHistory } from 'history'; +import { + mountWithContexts, + waitForElement, +} from '../../../../../testUtils/enzymeHelpers'; +import mockAllOptions from '../../shared/data.allSettingOptions.json'; +import { SettingsProvider } from '../../../../contexts/Settings'; +import { SettingsAPI } from '../../../../api'; +import GitHubEnterpriseOrgEdit from './GitHubEnterpriseOrgEdit'; + +jest.mock('../../../../api/models/Settings'); +SettingsAPI.updateAll.mockResolvedValue({}); +SettingsAPI.readCategory.mockResolvedValue({ + data: { + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_CALLBACK_URL: + 'https://towerhost/sso/complete/github-enterprise-org/', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET: '$encrypted$', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP: null, + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_TEAM_MAP: null, + }, +}); + +describe('', () => { + let wrapper; + let history; + + afterEach(() => { + wrapper.unmount(); + jest.clearAllMocks(); + }); + + beforeEach(async () => { + history = createMemoryHistory({ + initialEntries: ['/settings/github/enterprise_organization/edit'], + }); + await act(async () => { + wrapper = mountWithContexts( + + + , + { + context: { router: { history } }, + } + ); + }); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + }); + + test('initially renders without crashing', () => { + expect(wrapper.find('GitHubEnterpriseOrgEdit').length).toBe(1); + }); + + test('should display expected form fields', async () => { + expect( + wrapper.find('FormGroup[label="GitHub Enterprise Organization URL"]') + .length + ).toBe(1); + expect( + wrapper.find('FormGroup[label="GitHub Enterprise Organization API URL"]') + .length + ).toBe(1); + expect( + wrapper.find( + 'FormGroup[label="GitHub Enterprise Organization OAuth2 Key"]' + ).length + ).toBe(1); + expect( + wrapper.find( + 'FormGroup[label="GitHub Enterprise Organization OAuth2 Secret"]' + ).length + ).toBe(1); + expect( + wrapper.find('FormGroup[label="GitHub Enterprise Organization Name"]') + .length + ).toBe(1); + expect( + wrapper.find( + 'FormGroup[label="GitHub Enterprise Organization OAuth2 Organization Map"]' + ).length + ).toBe(1); + expect( + wrapper.find( + 'FormGroup[label="GitHub Enterprise Organization OAuth2 Team Map"]' + ).length + ).toBe(1); + }); + + test('should successfully send default values to api on form revert all', async () => { + expect(SettingsAPI.updateAll).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.updateAll).toHaveBeenCalledTimes(1); + expect(SettingsAPI.updateAll).toHaveBeenCalledWith({ + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP: null, + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_TEAM_MAP: null, + }); + }); + + test('should successfully send request to api on form submission', async () => { + act(() => { + wrapper + .find( + 'FormGroup[fieldId="SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET"] button[aria-label="Revert"]' + ) + .invoke('onClick')(); + wrapper + .find('input#SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL') + .simulate('change', { + target: { + value: 'https://localhost', + name: 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL', + }, + }); + wrapper + .find( + 'CodeMirrorInput#SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP' + ) + .invoke('onChange')('{\n"Default":{\n"users":\nfalse\n}\n}'); + }); + wrapper.update(); + await act(async () => { + wrapper.find('Form').invoke('onSubmit')(); + }); + expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1); + expect(SettingsAPI.updateAll).toHaveBeenCalledWith({ + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL: 'https://localhost', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_TEAM_MAP: {}, + SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP: { + Default: { + users: false, + }, + }, + }); + }); + + test('should navigate to github enterprise org detail on successful submission', async () => { + await act(async () => { + wrapper.find('Form').invoke('onSubmit')(); + }); + expect(history.location.pathname).toEqual( + '/settings/github/enterprise_organization/details' + ); + }); + + test('should navigate to github enterprise org detail when cancel is clicked', async () => { + await act(async () => { + wrapper.find('button[aria-label="Cancel"]').invoke('onClick')(); + }); + expect(history.location.pathname).toEqual( + '/settings/github/enterprise_organization/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( + + + + ); + }); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + expect(wrapper.find('ContentError').length).toBe(1); + }); +}); diff --git a/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseOrgEdit/index.js b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseOrgEdit/index.js new file mode 100644 index 0000000000..e86b1f78f0 --- /dev/null +++ b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseOrgEdit/index.js @@ -0,0 +1 @@ +export { default } from './GitHubEnterpriseOrgEdit'; diff --git a/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseTeamEdit/GitHubEnterpriseTeamEdit.jsx b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseTeamEdit/GitHubEnterpriseTeamEdit.jsx new file mode 100644 index 0000000000..d9b725dc13 --- /dev/null +++ b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseTeamEdit/GitHubEnterpriseTeamEdit.jsx @@ -0,0 +1,157 @@ +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 { RevertAllAlert, RevertFormActionGroup } from '../../shared'; +import { + EncryptedField, + InputField, + ObjectField, +} from '../../shared/SharedFields'; +import { formatJson } from '../../shared/settingUtils'; +import useModal from '../../../../util/useModal'; +import useRequest from '../../../../util/useRequest'; +import { SettingsAPI } from '../../../../api'; + +function GitHubEnterpriseTeamEdit() { + const history = useHistory(); + const { isModalOpen, toggleModal, closeModal } = useModal(); + const { PUT: options } = useSettings(); + + const { isLoading, error, request: fetchGithub, result: github } = useRequest( + useCallback(async () => { + const { data } = await SettingsAPI.readCategory('github-enterprise-team'); + 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(() => { + fetchGithub(); + }, [fetchGithub]); + + const { error: submitError, request: submitForm } = useRequest( + useCallback( + async values => { + await SettingsAPI.updateAll(values); + history.push('/settings/github/enterprise_team/details'); + }, + [history] + ), + null + ); + + const handleSubmit = async form => { + await submitForm({ + ...form, + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP: formatJson( + form.SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP + ), + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_TEAM_MAP: formatJson( + form.SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_TEAM_MAP + ), + }); + }; + + const handleRevertAll = async () => { + const defaultValues = Object.assign( + ...Object.entries(github).map(([key, value]) => ({ + [key]: value.default, + })) + ); + await submitForm(defaultValues); + closeModal(); + }; + + const handleCancel = () => { + history.push('/settings/github/enterprise_team/details'); + }; + + const initialValues = fields => + Object.keys(fields).reduce((acc, key) => { + if (fields[key].type === 'list' || fields[key].type === 'nested object') { + const emptyDefault = fields[key].type === 'list' ? '[]' : '{}'; + acc[key] = fields[key].value + ? JSON.stringify(fields[key].value, null, 2) + : emptyDefault; + } else { + acc[key] = fields[key].value ?? ''; + } + return acc; + }, {}); + + return ( + + {isLoading && } + {!isLoading && error && } + {!isLoading && github && ( + + {formik => ( +
+ + + + + + + + + {submitError && } + + + {isModalOpen && ( + + )} + + )} +
+ )} +
+ ); +} + +export default GitHubEnterpriseTeamEdit; diff --git a/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseTeamEdit/GitHubEnterpriseTeamEdit.test.jsx b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseTeamEdit/GitHubEnterpriseTeamEdit.test.jsx new file mode 100644 index 0000000000..764c9fa377 --- /dev/null +++ b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseTeamEdit/GitHubEnterpriseTeamEdit.test.jsx @@ -0,0 +1,206 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { createMemoryHistory } from 'history'; +import { + mountWithContexts, + waitForElement, +} from '../../../../../testUtils/enzymeHelpers'; +import mockAllOptions from '../../shared/data.allSettingOptions.json'; +import { SettingsProvider } from '../../../../contexts/Settings'; +import { SettingsAPI } from '../../../../api'; +import GitHubEnterpriseTeamEdit from './GitHubEnterpriseTeamEdit'; + +jest.mock('../../../../api/models/Settings'); +SettingsAPI.updateAll.mockResolvedValue({}); +SettingsAPI.readCategory.mockResolvedValue({ + data: { + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_CALLBACK_URL: + 'https://towerhost/sso/complete/github-enterprise-team/', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET: '$encrypted$', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP: null, + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_TEAM_MAP: null, + }, +}); + +describe('', () => { + let wrapper; + let history; + + afterEach(() => { + wrapper.unmount(); + jest.clearAllMocks(); + }); + + beforeEach(async () => { + history = createMemoryHistory({ + initialEntries: ['/settings/github/enterprise_team/edit'], + }); + await act(async () => { + wrapper = mountWithContexts( + + + , + { + context: { router: { history } }, + } + ); + }); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + }); + + test('initially renders without crashing', () => { + expect(wrapper.find('GitHubEnterpriseTeamEdit').length).toBe(1); + }); + + test('should display expected form fields', async () => { + expect( + wrapper.find('FormGroup[label="GitHub Enterprise Team URL"]').length + ).toBe(1); + expect( + wrapper.find('FormGroup[label="GitHub Enterprise Team API URL"]').length + ).toBe(1); + expect( + wrapper.find('FormGroup[label="GitHub Enterprise Team OAuth2 Key"]') + .length + ).toBe(1); + expect( + wrapper.find('FormGroup[label="GitHub Enterprise Team OAuth2 Secret"]') + .length + ).toBe(1); + expect( + wrapper.find('FormGroup[label="GitHub Enterprise Team ID"]').length + ).toBe(1); + expect( + wrapper.find( + 'FormGroup[label="GitHub Enterprise Team OAuth2 Organization Map"]' + ).length + ).toBe(1); + expect( + wrapper.find('FormGroup[label="GitHub Enterprise Team OAuth2 Team Map"]') + .length + ).toBe(1); + }); + + test('should successfully send default values to api on form revert all', async () => { + expect(SettingsAPI.updateAll).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.updateAll).toHaveBeenCalledTimes(1); + expect(SettingsAPI.updateAll).toHaveBeenCalledWith({ + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP: null, + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_TEAM_MAP: null, + }); + }); + + test('should successfully send request to api on form submission', async () => { + act(() => { + wrapper + .find( + 'FormGroup[fieldId="SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET"] button[aria-label="Revert"]' + ) + .invoke('onClick')(); + wrapper + .find('input#SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL') + .simulate('change', { + target: { + value: 'https://localhost', + name: 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL', + }, + }); + wrapper + .find( + 'CodeMirrorInput#SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP' + ) + .invoke('onChange')('{\n"Default":{\n"users":\nfalse\n}\n}'); + }); + wrapper.update(); + await act(async () => { + wrapper.find('Form').invoke('onSubmit')(); + }); + expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1); + expect(SettingsAPI.updateAll).toHaveBeenCalledWith({ + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL: 'https://localhost', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID: '', + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_TEAM_MAP: {}, + SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP: { + Default: { + users: false, + }, + }, + }); + }); + + test('should navigate to github enterprise team detail on successful submission', async () => { + await act(async () => { + wrapper.find('Form').invoke('onSubmit')(); + }); + expect(history.location.pathname).toEqual( + '/settings/github/enterprise_team/details' + ); + }); + + test('should navigate to github enterprise team detail when cancel is clicked', async () => { + await act(async () => { + wrapper.find('button[aria-label="Cancel"]').invoke('onClick')(); + }); + expect(history.location.pathname).toEqual( + '/settings/github/enterprise_team/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( + + + + ); + }); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + expect(wrapper.find('ContentError').length).toBe(1); + }); +}); diff --git a/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseTeamEdit/index.js b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseTeamEdit/index.js new file mode 100644 index 0000000000..002e319910 --- /dev/null +++ b/awx/ui_next/src/screens/Setting/GitHub/GitHubEnterpriseTeamEdit/index.js @@ -0,0 +1 @@ +export { default } from './GitHubEnterpriseTeamEdit'; diff --git a/awx/ui_next/src/screens/Setting/Settings.jsx b/awx/ui_next/src/screens/Setting/Settings.jsx index a535384afa..8b9c2db334 100644 --- a/awx/ui_next/src/screens/Setting/Settings.jsx +++ b/awx/ui_next/src/screens/Setting/Settings.jsx @@ -57,6 +57,17 @@ function Settings({ i18n }) { '/settings/github/team': i18n._(t`GitHub Team`), '/settings/github/team/details': i18n._(t`Details`), '/settings/github/team/edit': i18n._(t`Edit Details`), + '/settings/github/enterprise': i18n._(t`GitHub Enterprise`), + '/settings/github/enterprise/details': i18n._(t`Details`), + '/settings/github/enterprise/edit': i18n._(t`Edit Details`), + '/settings/github/enterprise_organization': i18n._( + t`GitHub Enterprise Organization` + ), + '/settings/github/enterprise_organization/details': i18n._(t`Details`), + '/settings/github/enterprise_organization/edit': i18n._(t`Edit Details`), + '/settings/github/enterprise_team': i18n._(t`GitHub Enterprise Team`), + '/settings/github/enterprise_team/details': i18n._(t`Details`), + '/settings/github/enterprise_team/edit': i18n._(t`Edit Details`), '/settings/google_oauth2': i18n._(t`Google OAuth2`), '/settings/google_oauth2/details': i18n._(t`Details`), '/settings/google_oauth2/edit': i18n._(t`Edit Details`), diff --git a/awx/ui_next/src/screens/Setting/shared/data.allSettingOptions.json b/awx/ui_next/src/screens/Setting/shared/data.allSettingOptions.json index 231ca0b87c..758e267ed3 100644 --- a/awx/ui_next/src/screens/Setting/shared/data.allSettingOptions.json +++ b/awx/ui_next/src/screens/Setting/shared/data.allSettingOptions.json @@ -78,7 +78,7 @@ "REDHAT_USERNAME": { "type": "string", "label": "Red Hat customer username", - "help_text": "This username is used to retrieve license information and to send Automation Analytics", + "help_text": "This username is used to send data to Automation Analytics", "category": "System", "category_slug": "system", "defined_in_file": false @@ -86,7 +86,23 @@ "REDHAT_PASSWORD": { "type": "string", "label": "Red Hat customer password", - "help_text": "This password is used to retrieve license information and to send Automation Analytics", + "help_text": "This password is used to send data to Automation Analytics", + "category": "System", + "category_slug": "system", + "defined_in_file": false + }, + "SUBSCRIPTIONS_USERNAME": { + "type": "string", + "label": "Red Hat or Satellite username", + "help_text": "This username is used to retrieve subscription and content information", + "category": "System", + "category_slug": "system", + "defined_in_file": false + }, + "SUBSCRIPTIONS_PASSWORD": { + "type": "string", + "label": "Red Hat or Satellite password", + "help_text": "This password is used to retrieve subscription and content information", "category": "System", "category_slug": "system", "defined_in_file": false @@ -2449,6 +2465,238 @@ } } }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_CALLBACK_URL": { + "type": "string", + "label": "GitHub Enterprise OAuth2 Callback URL", + "help_text": "Provide this URL as the callback URL for your application as part of your registration process. Refer to the Ansible Tower documentation for more detail.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise", + "defined_in_file": false + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_URL": { + "type": "string", + "label": "GitHub Enterprise URL", + "help_text": "The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise documentation for more details.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise", + "defined_in_file": false + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL": { + "type": "string", + "label": "GitHub Enterprise API URL", + "help_text": "The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github Enterprise documentation for more details.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise", + "defined_in_file": false + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY": { + "type": "string", + "label": "GitHub Enterprise OAuth2 Key", + "help_text": "The OAuth2 key (Client ID) from your GitHub Enterprise developer application.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise", + "defined_in_file": false + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET": { + "type": "string", + "label": "GitHub Enterprise OAuth2 Secret", + "help_text": "The OAuth2 secret (Client Secret) from your GitHub Enterprise developer application.", + "category": "GitHub OAuth2", + "category_slug": "github-enterprise", + "defined_in_file": false + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP": { + "type": "nested object", + "label": "GitHub Enterprise OAuth2 Organization Map", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which Tower organizations based on their\nusername and email address. Configuration details are available in the Ansible\nTower documentation.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise", + "defined_in_file": false, + "child": { + "type": "nested object", + "child": { + "type": "field", + "required": true, + "read_only": false + } + } + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_MAP": { + "type": "nested object", + "label": "GitHub Enterprise OAuth2 Team Map", + "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in Tower documentation.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise", + "defined_in_file": false, + "child": { + "type": "nested object", + "child": { + "type": "field", + "required": true, + "read_only": false + } + } + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_CALLBACK_URL": { + "type": "string", + "label": "GitHub Enterprise Organization OAuth2 Callback URL", + "help_text": "Provide this URL as the callback URL for your application as part of your registration process. Refer to the Ansible Tower documentation for more detail.", + "category": "GitHub Enterprise Organization OAuth2", + "category_slug": "github-enterprise-org", + "defined_in_file": false + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL": { + "type": "string", + "label": "GitHub Enterprise Organization URL", + "help_text": "The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise documentation for more details.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise-org", + "defined_in_file": false + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL": { + "type": "string", + "label": "GitHub Enterprise Organization API URL", + "help_text": "The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github Enterprise documentation for more details.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise-org", + "defined_in_file": false + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY": { + "type": "string", + "label": "GitHub Enterprise Organization OAuth2 Key", + "help_text": "The OAuth2 key (Client ID) from your GitHub Enterprise organization application.", + "category": "GitHub Enterprise Organization OAuth2", + "category_slug": "github-enterprise-org", + "defined_in_file": false + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET": { + "type": "string", + "label": "GitHub Enterprise Organization OAuth2 Secret", + "help_text": "The OAuth2 secret (Client Secret) from your GitHub Enterprise organization application.", + "category": "GitHub Enterprise Organization OAuth2", + "category_slug": "github-enterprise-org", + "defined_in_file": false + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME": { + "type": "string", + "label": "GitHub Enterprise Organization Name", + "help_text": "The name of your GitHub Enterprise organization, as used in your organization's URL: https://github.com//.", + "category": "GitHub Enterprise Organization OAuth2", + "category_slug": "github-enterprise-org", + "defined_in_file": false + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP": { + "type": "nested object", + "label": "GitHub Enterprise Organization OAuth2 Organization Map", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which Tower organizations based on their\nusername and email address. Configuration details are available in the Ansible\nTower documentation.", + "category": "GitHub Enterprise Organization OAuth2", + "category_slug": "github-enterprise-org", + "defined_in_file": false, + "child": { + "type": "nested object", + "child": { + "type": "field", + "required": true, + "read_only": false + } + } + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_TEAM_MAP": { + "type": "nested object", + "label": "GitHub Enterprise Organization OAuth2 Team Map", + "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in Tower documentation.", + "category": "GitHub Enterprise Organization OAuth2", + "category_slug": "github-enterprise-org", + "defined_in_file": false, + "child": { + "type": "nested object", + "child": { + "type": "field", + "required": true, + "read_only": false + } + } + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_CALLBACK_URL": { + "type": "string", + "label": "GitHub Enterprise Team OAuth2 Callback URL", + "help_text": "Create an organization-owned application at https://github.com/organizations//settings/applications and obtain an OAuth2 key (Client ID) and secret (Client Secret). Provide this URL as the callback URL for your application.", + "category": "GitHub Enterprise Team OAuth2", + "category_slug": "github-enterprise-team", + "defined_in_file": false + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL": { + "type": "string", + "label": "GitHub Enterprise Team URL", + "help_text": "The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise documentation for more details.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise-team", + "defined_in_file": false + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL": { + "type": "string", + "label": "GitHub Enterprise Team API URL", + "help_text": "The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github Enterprise documentation for more details.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise-team", + "defined_in_file": false + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY": { + "type": "string", + "label": "GitHub Enterprise Team OAuth2 Key", + "help_text": "The OAuth2 key (Client ID) from your GitHub Enterprise organization application.", + "category": "GitHub Enterprise Team OAuth2", + "category_slug": "github-enterprise-team", + "defined_in_file": false + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET": { + "type": "string", + "label": "GitHub Enterprise Team OAuth2 Secret", + "help_text": "The OAuth2 secret (Client Secret) from your GitHub Enterprise organization application.", + "category": "GitHub Enterprise Team OAuth2", + "category_slug": "github-enterprise-team", + "defined_in_file": false + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID": { + "type": "string", + "label": "GitHub Enterprise Team ID", + "help_text": "Find the numeric team ID using the Github Enterprise API: http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/.", + "category": "GitHub Enterprise Team OAuth2", + "category_slug": "github-enterprise-team", + "defined_in_file": false + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP": { + "type": "nested object", + "label": "GitHub Enterprise Team OAuth2 Organization Map", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which Tower organizations based on their\nusername and email address. Configuration details are available in the Ansible\nTower documentation.", + "category": "GitHub Enterprise Team OAuth2", + "category_slug": "github-enterprise-team", + "defined_in_file": false, + "child": { + "type": "nested object", + "child": { + "type": "field", + "required": true, + "read_only": false + } + } + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_TEAM_MAP": { + "type": "nested object", + "label": "GitHub Enterprise Team OAuth2 Team Map", + "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in Tower documentation.", + "category": "GitHub Enterprise Team OAuth2", + "category_slug": "github-enterprise-team", + "defined_in_file": false, + "child": { + "type": "nested object", + "child": { + "type": "field", + "required": true, + "read_only": false + } + } + }, "SOCIAL_AUTH_AZUREAD_OAUTH2_CALLBACK_URL": { "type": "string", "label": "Azure AD OAuth2 Callback URL", @@ -2745,29 +2993,6 @@ "category_slug": "system", "default": true }, - "PENDO_TRACKING_STATE": { - "default": "off", - "type": "choice", - "required": true, - "label": "User Analytics Tracking State", - "help_text": "Enable or Disable User Analytics Tracking.", - "category": "UI", - "category_slug": "ui", - "choices": [ - [ - "off", - "Off" - ], - [ - "anonymous", - "Anonymous" - ], - [ - "detailed", - "Detailed" - ] - ] - }, "MANAGE_ORGANIZATION_AUTH": { "type": "boolean", "required": true, @@ -2821,7 +3046,7 @@ "type": "string", "required": false, "label": "Red Hat customer username", - "help_text": "This username is used to retrieve license information and to send Automation Analytics", + "help_text": "This username is used to send data to Automation Analytics", "category": "System", "category_slug": "system", "default": "" @@ -2830,7 +3055,25 @@ "type": "string", "required": false, "label": "Red Hat customer password", - "help_text": "This password is used to retrieve license information and to send Automation Analytics", + "help_text": "This password is used to send data to Automation Analytics", + "category": "System", + "category_slug": "system", + "default": "" + }, + "SUBSCRIPTIONS_USERNAME": { + "type": "string", + "required": false, + "label": "Red Hat or Satellite username", + "help_text": "This username is used to retrieve subscription and content information", + "category": "System", + "category_slug": "system", + "default": "" + }, + "SUBSCRIPTIONS_PASSWORD": { + "type": "string", + "required": false, + "label": "Red Hat or Satellite password", + "help_text": "This password is used to retrieve subscription and content information", "category": "System", "category_slug": "system", "default": "" @@ -3513,6 +3756,29 @@ "category_slug": "authentication", "default": "" }, + "PENDO_TRACKING_STATE": { + "default": "off", + "type": "choice", + "required": true, + "label": "User Analytics Tracking State", + "help_text": "Enable or Disable User Analytics Tracking.", + "category": "UI", + "category_slug": "ui", + "choices": [ + [ + "off", + "Off" + ], + [ + "anonymous", + "Anonymous" + ], + [ + "detailed", + "Detailed" + ] + ] + }, "CUSTOM_LOGIN_INFO": { "type": "string", "required": false, @@ -6067,6 +6333,357 @@ } } }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_URL": { + "type": "string", + "required": false, + "label": "GitHub Enterprise URL", + "help_text": "The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise documentation for more details.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise", + "default": "" + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL": { + "type": "string", + "required": false, + "label": "GitHub Enterprise API URL", + "help_text": "The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github Enterprise documentation for more details.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise", + "default": "" + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY": { + "type": "string", + "required": false, + "label": "GitHub Enterprise OAuth2 Key", + "help_text": "The OAuth2 key (Client ID) from your GitHub Enterprise developer application.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise", + "default": "" + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET": { + "type": "string", + "required": false, + "label": "GitHub Enterprise OAuth2 Secret", + "help_text": "The OAuth2 secret (Client Secret) from your GitHub Enterprise developer application.", + "category": "GitHub OAuth2", + "category_slug": "github-enterprise", + "default": "" + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP": { + "type": "nested object", + "required": false, + "label": "GitHub Enterprise OAuth2 Organization Map", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which Tower organizations based on their\nusername and email address. Configuration details are available in the Ansible\nTower documentation.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise", + "placeholder": { + "Default": { + "users": true + }, + "Test Org": { + "admins": [ + "admin@example.com" + ], + "auditors": [ + "auditor@example.com" + ], + "users": true + }, + "Test Org 2": { + "admins": [ + "admin@example.com", + "/^tower-[^@]+*?@.*$/" + ], + "remove_admins": true, + "users": "/^[^@].*?@example\\.com$/i", + "remove_users": true + } + }, + "default": null, + "child": { + "type": "nested object", + "required": true, + "read_only": false, + "child": { + "type": "field", + "required": true, + "read_only": false + } + } + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_MAP": { + "type": "nested object", + "required": false, + "label": "GitHub Enterprise OAuth2 Team Map", + "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in Tower documentation.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise", + "placeholder": { + "My Team": { + "organization": "Test Org", + "users": [ + "/^[^@]+?@test\\.example\\.com$/" + ], + "remove": true + }, + "Other Team": { + "organization": "Test Org 2", + "users": "/^[^@]+?@test2\\.example\\.com$/i", + "remove": false + } + }, + "default": null, + "child": { + "type": "nested object", + "required": true, + "read_only": false, + "child": { + "type": "field", + "required": true, + "read_only": false + } + } + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL": { + "type": "string", + "required": false, + "label": "GitHub Enterprise Organization URL", + "help_text": "The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise documentation for more details.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise-org", + "default": "" + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL": { + "type": "string", + "required": false, + "label": "GitHub Enterprise Organization API URL", + "help_text": "The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github Enterprise documentation for more details.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise-org", + "default": "" + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY": { + "type": "string", + "required": false, + "label": "GitHub Enterprise Organization OAuth2 Key", + "help_text": "The OAuth2 key (Client ID) from your GitHub Enterprise organization application.", + "category": "GitHub Enterprise Organization OAuth2", + "category_slug": "github-enterprise-org", + "default": "" + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET": { + "type": "string", + "required": false, + "label": "GitHub Enterprise Organization OAuth2 Secret", + "help_text": "The OAuth2 secret (Client Secret) from your GitHub Enterprise organization application.", + "category": "GitHub Enterprise Organization OAuth2", + "category_slug": "github-enterprise-org", + "default": "" + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME": { + "type": "string", + "required": false, + "label": "GitHub Enterprise Organization Name", + "help_text": "The name of your GitHub Enterprise organization, as used in your organization's URL: https://github.com//.", + "category": "GitHub Enterprise Organization OAuth2", + "category_slug": "github-enterprise-org", + "default": "" + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP": { + "type": "nested object", + "required": false, + "label": "GitHub Enterprise Organization OAuth2 Organization Map", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which Tower organizations based on their\nusername and email address. Configuration details are available in the Ansible\nTower documentation.", + "category": "GitHub Enterprise Organization OAuth2", + "category_slug": "github-enterprise-org", + "placeholder": { + "Default": { + "users": true + }, + "Test Org": { + "admins": [ + "admin@example.com" + ], + "auditors": [ + "auditor@example.com" + ], + "users": true + }, + "Test Org 2": { + "admins": [ + "admin@example.com", + "/^tower-[^@]+*?@.*$/" + ], + "remove_admins": true, + "users": "/^[^@].*?@example\\.com$/i", + "remove_users": true + } + }, + "default": null, + "child": { + "type": "nested object", + "required": true, + "read_only": false, + "child": { + "type": "field", + "required": true, + "read_only": false + } + } + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_TEAM_MAP": { + "type": "nested object", + "required": false, + "label": "GitHub Enterprise Organization OAuth2 Team Map", + "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in Tower documentation.", + "category": "GitHub Enterprise Organization OAuth2", + "category_slug": "github-enterprise-org", + "placeholder": { + "My Team": { + "organization": "Test Org", + "users": [ + "/^[^@]+?@test\\.example\\.com$/" + ], + "remove": true + }, + "Other Team": { + "organization": "Test Org 2", + "users": "/^[^@]+?@test2\\.example\\.com$/i", + "remove": false + } + }, + "default": null, + "child": { + "type": "nested object", + "required": true, + "read_only": false, + "child": { + "type": "field", + "required": true, + "read_only": false + } + } + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL": { + "type": "string", + "required": false, + "label": "GitHub Enterprise Team URL", + "help_text": "The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise documentation for more details.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise-team", + "default": "" + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL": { + "type": "string", + "required": false, + "label": "GitHub Enterprise Team API URL", + "help_text": "The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github Enterprise documentation for more details.", + "category": "GitHub Enterprise OAuth2", + "category_slug": "github-enterprise-team", + "default": "" + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY": { + "type": "string", + "required": false, + "label": "GitHub Enterprise Team OAuth2 Key", + "help_text": "The OAuth2 key (Client ID) from your GitHub Enterprise organization application.", + "category": "GitHub Enterprise Team OAuth2", + "category_slug": "github-enterprise-team", + "default": "" + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET": { + "type": "string", + "required": false, + "label": "GitHub Enterprise Team OAuth2 Secret", + "help_text": "The OAuth2 secret (Client Secret) from your GitHub Enterprise organization application.", + "category": "GitHub Enterprise Team OAuth2", + "category_slug": "github-enterprise-team", + "default": "" + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID": { + "type": "string", + "required": false, + "label": "GitHub Enterprise Team ID", + "help_text": "Find the numeric team ID using the Github Enterprise API: http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/.", + "category": "GitHub Enterprise Team OAuth2", + "category_slug": "github-enterprise-team", + "default": "" + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP": { + "type": "nested object", + "required": false, + "label": "GitHub Enterprise Team OAuth2 Organization Map", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which Tower organizations based on their\nusername and email address. Configuration details are available in the Ansible\nTower documentation.", + "category": "GitHub Enterprise Team OAuth2", + "category_slug": "github-enterprise-team", + "placeholder": { + "Default": { + "users": true + }, + "Test Org": { + "admins": [ + "admin@example.com" + ], + "auditors": [ + "auditor@example.com" + ], + "users": true + }, + "Test Org 2": { + "admins": [ + "admin@example.com", + "/^tower-[^@]+*?@.*$/" + ], + "remove_admins": true, + "users": "/^[^@].*?@example\\.com$/i", + "remove_users": true + } + }, + "default": null, + "child": { + "type": "nested object", + "required": true, + "read_only": false, + "child": { + "type": "field", + "required": true, + "read_only": false + } + } + }, + "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_TEAM_MAP": { + "type": "nested object", + "required": false, + "label": "GitHub Enterprise Team OAuth2 Team Map", + "help_text": "Mapping of team members (users) from social auth accounts. Configuration\ndetails are available in Tower documentation.", + "category": "GitHub Enterprise Team OAuth2", + "category_slug": "github-enterprise-team", + "placeholder": { + "My Team": { + "organization": "Test Org", + "users": [ + "/^[^@]+?@test\\.example\\.com$/" + ], + "remove": true + }, + "Other Team": { + "organization": "Test Org 2", + "users": "/^[^@]+?@test2\\.example\\.com$/i", + "remove": false + } + }, + "default": null, + "child": { + "type": "nested object", + "required": true, + "read_only": false, + "child": { + "type": "field", + "required": true, + "read_only": false + } + } + }, "SOCIAL_AUTH_AZUREAD_OAUTH2_KEY": { "type": "string", "required": false, @@ -6520,4 +7137,4 @@ } } } -} \ No newline at end of file +} diff --git a/docs/auth/README.md b/docs/auth/README.md index 50578947aa..38e9b52bd1 100644 --- a/docs/auth/README.md +++ b/docs/auth/README.md @@ -5,6 +5,9 @@ When a user wants to log into Tower, she can explicitly choose some of the suppo * Github OAuth2 * Github Organization OAuth2 * Github Team OAuth2 +* Github Enterprise OAuth2 +* Github Enterprise Organization OAuth2 +* Github Enterprise Team OAuth2 * Microsoft Azure Active Directory (AD) OAuth2 On the other hand, the other authentication methods use the same types of login info as Tower (username and password), but authenticate using external auth systems rather than Tower's own database. If some of these methods are enabled, Tower will try authenticating using the enabled methods *before Tower's own authentication method*. The order of precedence is: