mirror of
https://github.com/ansible/awx.git
synced 2026-01-20 06:01:25 -03:30
Merge pull request #9519 from mabashian/7603-custom-login
Adds support for html in custom login text SUMMARY link #7603 I couldn't come up with a way to do this without breaking up the component and discontinuing use of the LoginPage PF component. This is because LoginPage expects the textContent component (what we use to display the custom login text) to be a string. By using the underlying LoginPage components I reconstructed the login page and got more control over that prop. The custom message in the old UI supported both strings and HTML: So we need to support rendering HTML but we need to do it in a safe way. Our solution to that was https://docs.angularjs.org/api/ngSanitize. React doesn't seem to have anything like this built in so I went looking for outside help. html-entities is already included in our project but as best as I can tell that lib is mainly focused on swapping special characters out for html entities. I wanted something that was going to strip the HTML of bits that could be exploited by a malicious actor. I settled on https://www.npmjs.com/package/sanitize-html because it was a) small and b) actively maintained. The API was simple and let me sanitize the HTML before setting it using dangerouslySetInnerHTML. If we need to tweak the configuration away from the default values then we can certainly do that. ISSUE TYPE Feature Pull Request COMPONENT NAME UI Reviewed-by: Jake McDermott <yo@jakemcdermott.me>
This commit is contained in:
commit
cfff30f024
@ -46,6 +46,7 @@ To learn more about Ansible Builder and Execution Environments, see: https://www
|
||||
- Added toast message to show notification template test result to notification templates list https://github.com/ansible/awx/pull/9318
|
||||
- Replaced CodeMirror with AceEditor for editing template variables and notification templates https://github.com/ansible/awx/pull/9281
|
||||
- Added support for filtering and pagination on job output https://github.com/ansible/awx/pull/9208
|
||||
- Added support for html in custom login text https://github.com/ansible/awx/pull/9519
|
||||
|
||||
# 17.1.0 (March 9th, 2021)
|
||||
- Addressed a security issue in AWX (CVE-2021-20253)
|
||||
|
||||
115
awx/ui_next/package-lock.json
generated
115
awx/ui_next/package-lock.json
generated
@ -11103,6 +11103,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",
|
||||
@ -11827,6 +11832,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",
|
||||
@ -12560,6 +12570,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",
|
||||
@ -15623,6 +15638,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",
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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,227 @@ function AWXLogin({ alt, i18n, isAuthenticated }) {
|
||||
helperText = i18n._(t`There was a problem signing in. Please try again.`);
|
||||
}
|
||||
|
||||
return (
|
||||
<LoginPage
|
||||
brandImgSrc={logo}
|
||||
brandImgAlt={alt || brandName}
|
||||
loginTitle={
|
||||
brandName
|
||||
? i18n._(t`Welcome to Ansible ${brandName}! Please Sign In.`)
|
||||
: ''
|
||||
}
|
||||
textContent={loginInfo}
|
||||
socialMediaLoginContent={
|
||||
<>
|
||||
{socialAuthOptions &&
|
||||
Object.keys(socialAuthOptions).map(authKey => {
|
||||
const loginUrl = socialAuthOptions[authKey].login_url;
|
||||
if (authKey === 'azuread-oauth2') {
|
||||
return (
|
||||
<LoginMainFooterLinksItem href={loginUrl} key={authKey}>
|
||||
<Tooltip content={i18n._(t`Sign in with Azure AD`)}>
|
||||
<AzureIcon />
|
||||
</Tooltip>
|
||||
</LoginMainFooterLinksItem>
|
||||
);
|
||||
}
|
||||
if (authKey === 'github') {
|
||||
return (
|
||||
<LoginMainFooterLinksItem href={loginUrl} key={authKey}>
|
||||
<Tooltip content={i18n._(t`Sign in with GitHub`)}>
|
||||
<GithubIcon />
|
||||
</Tooltip>
|
||||
</LoginMainFooterLinksItem>
|
||||
);
|
||||
}
|
||||
if (authKey === 'github-org') {
|
||||
return (
|
||||
<LoginMainFooterLinksItem href={loginUrl} key={authKey}>
|
||||
<Tooltip
|
||||
content={i18n._(t`Sign in with GitHub Organizations`)}
|
||||
>
|
||||
<GithubIcon />
|
||||
</Tooltip>
|
||||
</LoginMainFooterLinksItem>
|
||||
);
|
||||
}
|
||||
if (authKey === 'github-team') {
|
||||
return (
|
||||
<LoginMainFooterLinksItem href={loginUrl} key={authKey}>
|
||||
<Tooltip content={i18n._(t`Sign in with GitHub Teams`)}>
|
||||
<GithubIcon />
|
||||
</Tooltip>
|
||||
</LoginMainFooterLinksItem>
|
||||
);
|
||||
}
|
||||
if (authKey === 'github-enterprise') {
|
||||
return (
|
||||
<LoginMainFooterLinksItem href={loginUrl} key={authKey}>
|
||||
<Tooltip
|
||||
content={i18n._(t`Sign in with GitHub Enterprise`)}
|
||||
>
|
||||
<GithubIcon />
|
||||
</Tooltip>
|
||||
</LoginMainFooterLinksItem>
|
||||
);
|
||||
}
|
||||
if (authKey === 'github-enterprise-org') {
|
||||
return (
|
||||
<LoginMainFooterLinksItem href={loginUrl} key={authKey}>
|
||||
<Tooltip
|
||||
content={i18n._(
|
||||
t`Sign in with GitHub Enterprise Organizations`
|
||||
)}
|
||||
>
|
||||
<GithubIcon />
|
||||
</Tooltip>
|
||||
</LoginMainFooterLinksItem>
|
||||
);
|
||||
}
|
||||
if (authKey === 'github-enterprise-team') {
|
||||
return (
|
||||
<LoginMainFooterLinksItem href={loginUrl} key={authKey}>
|
||||
<Tooltip
|
||||
content={i18n._(t`Sign in with GitHub Enterprise Teams`)}
|
||||
>
|
||||
<GithubIcon />
|
||||
</Tooltip>
|
||||
</LoginMainFooterLinksItem>
|
||||
);
|
||||
}
|
||||
if (authKey === 'google-oauth2') {
|
||||
return (
|
||||
<LoginMainFooterLinksItem href={loginUrl} key={authKey}>
|
||||
<Tooltip content={i18n._(t`Sign in with Google`)}>
|
||||
<GoogleIcon />
|
||||
</Tooltip>
|
||||
</LoginMainFooterLinksItem>
|
||||
);
|
||||
}
|
||||
if (authKey.startsWith('saml')) {
|
||||
const samlIDP = authKey.split(':')[1] || null;
|
||||
return (
|
||||
<LoginMainFooterLinksItem href={loginUrl} key={authKey}>
|
||||
<Tooltip
|
||||
content={
|
||||
samlIDP
|
||||
? i18n._(t`Sign in with SAML ${samlIDP}`)
|
||||
: i18n._(t`Sign in with SAML`)
|
||||
}
|
||||
>
|
||||
<UserCircleIcon />
|
||||
</Tooltip>
|
||||
</LoginMainFooterLinksItem>
|
||||
);
|
||||
}
|
||||
const HeaderBrand = (
|
||||
<Brand dataCy="brand-logo" src={logo} alt={alt || brandName} />
|
||||
);
|
||||
const Header = <LoginHeader headerBrand={HeaderBrand} />;
|
||||
const Footer = (
|
||||
<LoginFooter
|
||||
dataCy="login-footer"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: sanitizeHtml(loginInfo),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
return null;
|
||||
})}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Formik
|
||||
initialValues={{
|
||||
password: '',
|
||||
username: '',
|
||||
}}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{formik => (
|
||||
<LoginForm
|
||||
className={authError ? 'pf-m-error' : ''}
|
||||
helperText={helperText}
|
||||
isLoginButtonDisabled={isAuthenticating}
|
||||
isValidPassword={!authError}
|
||||
isValidUsername={!authError}
|
||||
loginButtonLabel={i18n._(t`Log In`)}
|
||||
onChangePassword={val => {
|
||||
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}
|
||||
/>
|
||||
)}
|
||||
</Formik>
|
||||
{loginInfoError && (
|
||||
<AlertModal
|
||||
isOpen={loginInfoError}
|
||||
variant="error"
|
||||
title={i18n._(t`Error!`)}
|
||||
onClose={dismissLoginInfoError}
|
||||
return (
|
||||
<Login header={Header} footer={Footer}>
|
||||
<LoginMainHeader
|
||||
dataCy="login-header"
|
||||
title={
|
||||
brandName
|
||||
? i18n._(t`Welcome to Ansible ${brandName}! Please Sign In.`)
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
<LoginMainBody>
|
||||
<Formik
|
||||
initialValues={{
|
||||
password: '',
|
||||
username: '',
|
||||
}}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{i18n._(
|
||||
t`Failed to fetch custom login configuration settings. System defaults will be shown instead.`
|
||||
{formik => (
|
||||
<LoginForm
|
||||
dataCy="login-form"
|
||||
className={authError ? 'pf-m-error' : ''}
|
||||
helperText={helperText}
|
||||
isLoginButtonDisabled={isAuthenticating}
|
||||
isValidPassword={!authError}
|
||||
isValidUsername={!authError}
|
||||
loginButtonLabel={i18n._(t`Log In`)}
|
||||
onChangePassword={val => {
|
||||
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}
|
||||
/>
|
||||
)}
|
||||
<ErrorDetail error={loginInfoError} />
|
||||
</AlertModal>
|
||||
)}
|
||||
</LoginPage>
|
||||
</Formik>
|
||||
{loginInfoError && (
|
||||
<AlertModal
|
||||
isOpen={loginInfoError}
|
||||
variant="error"
|
||||
title={i18n._(t`Error!`)}
|
||||
onClose={dismissLoginInfoError}
|
||||
dataCy="login-info-error"
|
||||
>
|
||||
{i18n._(
|
||||
t`Failed to fetch custom login configuration settings. System defaults will be shown instead.`
|
||||
)}
|
||||
<ErrorDetail error={loginInfoError} />
|
||||
</AlertModal>
|
||||
)}
|
||||
</LoginMainBody>
|
||||
<LoginMainFooter
|
||||
socialMediaLoginContent={
|
||||
<>
|
||||
{socialAuthOptions &&
|
||||
Object.keys(socialAuthOptions).map(authKey => {
|
||||
const loginUrl = socialAuthOptions[authKey].login_url;
|
||||
if (authKey === 'azuread-oauth2') {
|
||||
return (
|
||||
<LoginMainFooterLinksItem
|
||||
dataCy="social-auth-azure"
|
||||
href={loginUrl}
|
||||
key={authKey}
|
||||
>
|
||||
<Tooltip content={i18n._(t`Sign in with Azure AD`)}>
|
||||
<AzureIcon />
|
||||
</Tooltip>
|
||||
</LoginMainFooterLinksItem>
|
||||
);
|
||||
}
|
||||
if (authKey === 'github') {
|
||||
return (
|
||||
<LoginMainFooterLinksItem
|
||||
dataCy="social-auth-github"
|
||||
href={loginUrl}
|
||||
key={authKey}
|
||||
>
|
||||
<Tooltip content={i18n._(t`Sign in with GitHub`)}>
|
||||
<GithubIcon />
|
||||
</Tooltip>
|
||||
</LoginMainFooterLinksItem>
|
||||
);
|
||||
}
|
||||
if (authKey === 'github-org') {
|
||||
return (
|
||||
<LoginMainFooterLinksItem
|
||||
dataCy="social-auth-github-org"
|
||||
href={loginUrl}
|
||||
key={authKey}
|
||||
>
|
||||
<Tooltip
|
||||
content={i18n._(t`Sign in with GitHub Organizations`)}
|
||||
>
|
||||
<GithubIcon />
|
||||
</Tooltip>
|
||||
</LoginMainFooterLinksItem>
|
||||
);
|
||||
}
|
||||
if (authKey === 'github-team') {
|
||||
return (
|
||||
<LoginMainFooterLinksItem
|
||||
dataCy="social-auth-github-team"
|
||||
href={loginUrl}
|
||||
key={authKey}
|
||||
>
|
||||
<Tooltip content={i18n._(t`Sign in with GitHub Teams`)}>
|
||||
<GithubIcon />
|
||||
</Tooltip>
|
||||
</LoginMainFooterLinksItem>
|
||||
);
|
||||
}
|
||||
if (authKey === 'github-enterprise') {
|
||||
return (
|
||||
<LoginMainFooterLinksItem
|
||||
dataCy="social-auth-github-enterprise"
|
||||
href={loginUrl}
|
||||
key={authKey}
|
||||
>
|
||||
<Tooltip
|
||||
content={i18n._(t`Sign in with GitHub Enterprise`)}
|
||||
>
|
||||
<GithubIcon />
|
||||
</Tooltip>
|
||||
</LoginMainFooterLinksItem>
|
||||
);
|
||||
}
|
||||
if (authKey === 'github-enterprise-org') {
|
||||
return (
|
||||
<LoginMainFooterLinksItem
|
||||
dataCy="social-auth-github-enterprise-org"
|
||||
href={loginUrl}
|
||||
key={authKey}
|
||||
>
|
||||
<Tooltip
|
||||
content={i18n._(
|
||||
t`Sign in with GitHub Enterprise Organizations`
|
||||
)}
|
||||
>
|
||||
<GithubIcon />
|
||||
</Tooltip>
|
||||
</LoginMainFooterLinksItem>
|
||||
);
|
||||
}
|
||||
if (authKey === 'github-enterprise-team') {
|
||||
return (
|
||||
<LoginMainFooterLinksItem
|
||||
dataCy="social-auth-github-enterprise-team"
|
||||
href={loginUrl}
|
||||
key={authKey}
|
||||
>
|
||||
<Tooltip
|
||||
content={i18n._(
|
||||
t`Sign in with GitHub Enterprise Teams`
|
||||
)}
|
||||
>
|
||||
<GithubIcon />
|
||||
</Tooltip>
|
||||
</LoginMainFooterLinksItem>
|
||||
);
|
||||
}
|
||||
if (authKey === 'google-oauth2') {
|
||||
return (
|
||||
<LoginMainFooterLinksItem
|
||||
dataCy="social-auth-google"
|
||||
href={loginUrl}
|
||||
key={authKey}
|
||||
>
|
||||
<Tooltip content={i18n._(t`Sign in with Google`)}>
|
||||
<GoogleIcon />
|
||||
</Tooltip>
|
||||
</LoginMainFooterLinksItem>
|
||||
);
|
||||
}
|
||||
if (authKey.startsWith('saml')) {
|
||||
const samlIDP = authKey.split(':')[1] || null;
|
||||
return (
|
||||
<LoginMainFooterLinksItem
|
||||
dataCy="social-auth-saml"
|
||||
href={loginUrl}
|
||||
key={authKey}
|
||||
>
|
||||
<Tooltip
|
||||
content={
|
||||
samlIDP
|
||||
? i18n._(t`Sign in with SAML ${samlIDP}`)
|
||||
: i18n._(t`Sign in with SAML`)
|
||||
}
|
||||
>
|
||||
<UserCircleIcon />
|
||||
</Tooltip>
|
||||
</LoginMainFooterLinksItem>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Login>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -24,7 +24,6 @@ describe('<Login />', () => {
|
||||
async function findChildren(wrapper) {
|
||||
const [
|
||||
awxLogin,
|
||||
loginPage,
|
||||
loginForm,
|
||||
usernameInput,
|
||||
passwordInput,
|
||||
@ -32,7 +31,6 @@ describe('<Login />', () => {
|
||||
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('<Login />', () => {
|
||||
]);
|
||||
return {
|
||||
awxLogin,
|
||||
loginPage,
|
||||
loginForm,
|
||||
usernameInput,
|
||||
passwordInput,
|
||||
@ -61,7 +58,8 @@ describe('<Login />', () => {
|
||||
beforeEach(() => {
|
||||
RootAPI.read.mockResolvedValue({
|
||||
data: {
|
||||
custom_login_info: '',
|
||||
custom_login_info:
|
||||
'<div id="custom-button" onmouseover="alert()">TEST</div>',
|
||||
custom_logo: 'images/foo.jpg',
|
||||
},
|
||||
});
|
||||
@ -114,6 +112,16 @@ describe('<Login />', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
test('custom login info handled correctly', async done => {
|
||||
let wrapper;
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<AWXLogin isAuthenticated={() => false} />);
|
||||
});
|
||||
await findChildren(wrapper);
|
||||
expect(wrapper.find('footer').html()).toContain('<div>TEST</div>');
|
||||
done();
|
||||
});
|
||||
|
||||
test('data initialization error is properly handled', async done => {
|
||||
RootAPI.read.mockRejectedValueOnce(
|
||||
new Error({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user