diff --git a/awx/ui_next/package-lock.json b/awx/ui_next/package-lock.json
index d4ed859ea980..719d14ac6732 100644
--- a/awx/ui_next/package-lock.json
+++ b/awx/ui_next/package-lock.json
@@ -11098,6 +11098,11 @@
"integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
"dev": true
},
+ "klona": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz",
+ "integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA=="
+ },
"language-subtag-registry": {
"version": "0.3.21",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz",
@@ -11818,6 +11823,11 @@
"dev": true,
"optional": true
},
+ "nanoid": {
+ "version": "3.1.20",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz",
+ "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw=="
+ },
"nanomatch": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
@@ -12551,6 +12561,11 @@
"json-parse-better-errors": "^1.0.1"
}
},
+ "parse-srcset": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
+ "integrity": "sha1-8r0iH2zJcKk42IVWq8WJyqqiveE="
+ },
"parse5": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz",
@@ -15607,6 +15622,106 @@
}
}
},
+ "sanitize-html": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.3.2.tgz",
+ "integrity": "sha512-p7neuskvC8pSurUjdVmbWPXmc9A4+QpOXIL+4gwFC+av5h+lYCXFT8uEneqsFQg/wEA1IH+cKQA60AaQI6p3cg==",
+ "requires": {
+ "deepmerge": "^4.2.2",
+ "escape-string-regexp": "^4.0.0",
+ "htmlparser2": "^6.0.0",
+ "is-plain-object": "^5.0.0",
+ "klona": "^2.0.3",
+ "parse-srcset": "^1.0.2",
+ "postcss": "^8.0.2"
+ },
+ "dependencies": {
+ "colorette": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
+ "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w=="
+ },
+ "deepmerge": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
+ "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
+ },
+ "dom-serializer": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.2.0.tgz",
+ "integrity": "sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==",
+ "requires": {
+ "domelementtype": "^2.0.1",
+ "domhandler": "^4.0.0",
+ "entities": "^2.0.0"
+ }
+ },
+ "domelementtype": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz",
+ "integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w=="
+ },
+ "domhandler": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz",
+ "integrity": "sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==",
+ "requires": {
+ "domelementtype": "^2.1.0"
+ }
+ },
+ "domutils": {
+ "version": "2.4.4",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.4.4.tgz",
+ "integrity": "sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==",
+ "requires": {
+ "dom-serializer": "^1.0.1",
+ "domelementtype": "^2.0.1",
+ "domhandler": "^4.0.0"
+ }
+ },
+ "entities": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
+ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="
+ },
+ "escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
+ },
+ "htmlparser2": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.0.0.tgz",
+ "integrity": "sha512-numTQtDZMoh78zJpaNdJ9MXb2cv5G3jwUoe3dMQODubZvLoGvTE/Ofp6sHvH8OGKcN/8A47pGLi/k58xHP/Tfw==",
+ "requires": {
+ "domelementtype": "^2.0.1",
+ "domhandler": "^4.0.0",
+ "domutils": "^2.4.4",
+ "entities": "^2.0.0"
+ }
+ },
+ "is-plain-object": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
+ "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
+ },
+ "postcss": {
+ "version": "8.2.7",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.7.tgz",
+ "integrity": "sha512-DsVLH3xJzut+VT+rYr0mtvOtpTjSyqDwPf5EZWXcb0uAKfitGpTY9Ec+afi2+TgdN8rWS9Cs88UDYehKo/RvOw==",
+ "requires": {
+ "colorette": "^1.2.2",
+ "nanoid": "^3.1.20",
+ "source-map": "^0.6.1"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+ }
+ }
+ },
"sanitize.css": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-10.0.0.tgz",
diff --git a/awx/ui_next/package.json b/awx/ui_next/package.json
index 24371abfc791..2cfae063a57a 100644
--- a/awx/ui_next/package.json
+++ b/awx/ui_next/package.json
@@ -27,6 +27,7 @@
"react-router-dom": "^5.1.2",
"react-virtualized": "^9.21.1",
"rrule": "^2.6.4",
+ "sanitize-html": "^2.3.2",
"styled-components": "^4.2.0"
},
"devDependencies": {
diff --git a/awx/ui_next/src/screens/Login/Login.jsx b/awx/ui_next/src/screens/Login/Login.jsx
index 8887d7351fe9..b0055b93945e 100644
--- a/awx/ui_next/src/screens/Login/Login.jsx
+++ b/awx/ui_next/src/screens/Login/Login.jsx
@@ -4,12 +4,20 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { Formik } from 'formik';
import styled from 'styled-components';
+import sanitizeHtml from 'sanitize-html';
import {
+ Brand,
LoginMainFooterLinksItem,
LoginForm,
- LoginPage as PFLoginPage,
+ Login as PFLogin,
+ LoginHeader,
+ LoginFooter,
+ LoginMainHeader,
+ LoginMainBody,
+ LoginMainFooter,
Tooltip,
} from '@patternfly/react-core';
+
import {
AzureIcon,
GoogleIcon,
@@ -23,7 +31,7 @@ import ErrorDetail from '../../components/ErrorDetail';
const loginLogoSrc = '/static/media/logo-login.svg';
-const LoginPage = styled(PFLoginPage)`
+const Login = styled(PFLogin)`
& .pf-c-brand {
max-height: 285px;
}
@@ -112,171 +120,185 @@ function AWXLogin({ alt, i18n, isAuthenticated }) {
helperText = i18n._(t`There was a problem signing in. Please try again.`);
}
- return (
-
- {socialAuthOptions &&
- Object.keys(socialAuthOptions).map(authKey => {
- const loginUrl = socialAuthOptions[authKey].login_url;
- if (authKey === 'azuread-oauth2') {
- return (
-
-
-
-
-
- );
- }
- if (authKey === 'github') {
- return (
-
-
-
-
-
- );
- }
- if (authKey === 'github-org') {
- return (
-
-
-
-
-
- );
- }
- if (authKey === 'github-team') {
- return (
-
-
-
-
-
- );
- }
- if (authKey === 'github-enterprise') {
- return (
-
-
-
-
-
- );
- }
- if (authKey === 'github-enterprise-org') {
- return (
-
-
-
-
-
- );
- }
- if (authKey === 'github-enterprise-team') {
- return (
-
-
-
-
-
- );
- }
- if (authKey === 'google-oauth2') {
- return (
-
-
-
-
-
- );
- }
- if (authKey.startsWith('saml')) {
- const samlIDP = authKey.split(':')[1] || null;
- return (
-
-
-
-
-
- );
- }
+ const HeaderBrand = ;
+ const Header = ;
+ const Footer = (
+
+ );
- return null;
- })}
- >
- }
- >
-
- {formik => (
- {
- formik.setFieldValue('password', val);
- dismissAuthError();
- }}
- onChangeUsername={val => {
- formik.setFieldValue('username', val);
- dismissAuthError();
- }}
- onLoginButtonClick={formik.handleSubmit}
- passwordLabel={i18n._(t`Password`)}
- passwordValue={formik.values.password}
- showHelperText={authError}
- usernameLabel={i18n._(t`Username`)}
- usernameValue={formik.values.username}
- />
- )}
-
- {loginInfoError && (
-
+
+
+
- {i18n._(
- t`Failed to fetch custom login configuration settings. System defaults will be shown instead.`
+ {formik => (
+ {
+ formik.setFieldValue('password', val);
+ dismissAuthError();
+ }}
+ onChangeUsername={val => {
+ formik.setFieldValue('username', val);
+ dismissAuthError();
+ }}
+ onLoginButtonClick={formik.handleSubmit}
+ passwordLabel={i18n._(t`Password`)}
+ passwordValue={formik.values.password}
+ showHelperText={authError}
+ usernameLabel={i18n._(t`Username`)}
+ usernameValue={formik.values.username}
+ />
)}
-
-
- )}
-
+
+ {loginInfoError && (
+
+ {i18n._(
+ t`Failed to fetch custom login configuration settings. System defaults will be shown instead.`
+ )}
+
+
+ )}
+
+
+ {socialAuthOptions &&
+ Object.keys(socialAuthOptions).map(authKey => {
+ const loginUrl = socialAuthOptions[authKey].login_url;
+ if (authKey === 'azuread-oauth2') {
+ return (
+
+
+
+
+
+ );
+ }
+ if (authKey === 'github') {
+ return (
+
+
+
+
+
+ );
+ }
+ if (authKey === 'github-org') {
+ return (
+
+
+
+
+
+ );
+ }
+ if (authKey === 'github-team') {
+ return (
+
+
+
+
+
+ );
+ }
+ if (authKey === 'github-enterprise') {
+ return (
+
+
+
+
+
+ );
+ }
+ if (authKey === 'github-enterprise-org') {
+ return (
+
+
+
+
+
+ );
+ }
+ if (authKey === 'github-enterprise-team') {
+ return (
+
+
+
+
+
+ );
+ }
+ if (authKey === 'google-oauth2') {
+ return (
+
+
+
+
+
+ );
+ }
+ if (authKey.startsWith('saml')) {
+ const samlIDP = authKey.split(':')[1] || null;
+ return (
+
+
+
+
+
+ );
+ }
+
+ return null;
+ })}
+ >
+ }
+ />
+
);
}
diff --git a/awx/ui_next/src/screens/Login/Login.test.jsx b/awx/ui_next/src/screens/Login/Login.test.jsx
index 5e9aa989096d..c5be9f12242a 100644
--- a/awx/ui_next/src/screens/Login/Login.test.jsx
+++ b/awx/ui_next/src/screens/Login/Login.test.jsx
@@ -24,7 +24,6 @@ describe('', () => {
async function findChildren(wrapper) {
const [
awxLogin,
- loginPage,
loginForm,
usernameInput,
passwordInput,
@@ -32,7 +31,6 @@ describe('', () => {
loginHeaderLogo,
] = await Promise.all([
waitForElement(wrapper, 'AWXLogin', el => el.length === 1),
- waitForElement(wrapper, 'LoginPage', el => el.length === 1),
waitForElement(wrapper, 'LoginForm', el => el.length === 1),
waitForElement(
wrapper,
@@ -49,7 +47,6 @@ describe('', () => {
]);
return {
awxLogin,
- loginPage,
loginForm,
usernameInput,
passwordInput,
@@ -61,7 +58,8 @@ describe('', () => {
beforeEach(() => {
RootAPI.read.mockResolvedValue({
data: {
- custom_login_info: '',
+ custom_login_info:
+ 'TEST
',
custom_logo: 'images/foo.jpg',
},
});
@@ -114,6 +112,16 @@ describe('', () => {
done();
});
+ test('custom login info handled correctly', async done => {
+ let wrapper;
+ await act(async () => {
+ wrapper = mountWithContexts( false} />);
+ });
+ await findChildren(wrapper);
+ expect(wrapper.find('footer').html()).toContain('TEST
');
+ done();
+ });
+
test('data initialization error is properly handled', async done => {
RootAPI.read.mockRejectedValueOnce(
new Error({