diff options
author | Wouter Admiraal <wouter.admiraal@sonarsource.com> | 2021-03-11 15:08:43 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2021-03-18 20:08:12 +0000 |
commit | 8837d04e3587d8c83c0ef28c81acf3d118718e29 (patch) | |
tree | de8173a132a2e1b7daa0f7e7a4efcd0f326f452b /server/sonar-web | |
parent | af9c2c4a14a88befda90f871aa039194ad27b7ca (diff) | |
download | sonarqube-8837d04e3587d8c83c0ef28c81acf3d118718e29.tar.gz sonarqube-8837d04e3587d8c83c0ef28c81acf3d118718e29.zip |
SONAR-14586 New form to secure the admin user account
Diffstat (limited to 'server/sonar-web')
9 files changed, 935 insertions, 0 deletions
diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx index 6455491dddd..65afaf311a4 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx @@ -325,6 +325,14 @@ export default function startReactApp( component={lazyLoadComponent(() => import('../components/ResetPassword'))} /> <Route + // We don't want this route to have any menu. This is why we define it here + // rather than under the admin routes. + path="admin/change_admin_password" + component={lazyLoadComponent(() => + import('../../apps/change-admin-password/ChangeAdminPasswordApp') + )} + /> + <Route path="not_found" component={lazyLoadComponent(() => import('../components/NotFound'))} /> diff --git a/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordApp.tsx b/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordApp.tsx new file mode 100644 index 00000000000..1737206e6a1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordApp.tsx @@ -0,0 +1,120 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { connect } from 'react-redux'; +import { changePassword } from '../../api/users'; +import { Location, withRouter } from '../../components/hoc/withRouter'; +import { getAppState, Store } from '../../store/rootReducer'; +import ChangeAdminPasswordAppRenderer from './ChangeAdminPasswordAppRenderer'; +import { DEFAULT_ADMIN_LOGIN, DEFAULT_ADMIN_PASSWORD } from './constants'; + +interface Props { + canAdmin?: boolean; + instanceUsesDefaultAdminCredentials?: boolean; + location: Location; +} + +interface State { + passwordValue: string; + confirmPasswordValue: string; + canSubmit?: boolean; + submitting: boolean; + success: boolean; +} + +export class ChangeAdminPasswordApp extends React.PureComponent<Props, State> { + mounted = false; + + constructor(props: Props) { + super(props); + + this.state = { + passwordValue: '', + confirmPasswordValue: '', + submitting: false, + success: !props.instanceUsesDefaultAdminCredentials + }; + } + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handlePasswordChange = (passwordValue: string) => { + this.setState({ passwordValue }, this.checkCanSubmit); + }; + + handleConfirmPasswordChange = (confirmPasswordValue: string) => { + this.setState({ confirmPasswordValue }, this.checkCanSubmit); + }; + + handleSubmit = async () => { + const { canSubmit, passwordValue } = this.state; + if (canSubmit) { + this.setState({ submitting: true }); + const success = await changePassword({ + login: DEFAULT_ADMIN_LOGIN, + password: passwordValue + }).then( + () => true, + () => false + ); + if (this.mounted) { + this.setState({ submitting: false, success }); + } + } + }; + + checkCanSubmit = () => { + this.setState(({ passwordValue, confirmPasswordValue }) => ({ + canSubmit: passwordValue === confirmPasswordValue && passwordValue !== DEFAULT_ADMIN_PASSWORD + })); + }; + + render() { + const { canAdmin, location } = this.props; + const { canSubmit, confirmPasswordValue, passwordValue, submitting, success } = this.state; + return ( + <ChangeAdminPasswordAppRenderer + canAdmin={canAdmin} + passwordValue={passwordValue} + confirmPasswordValue={confirmPasswordValue} + canSubmit={canSubmit} + onPasswordChange={this.handlePasswordChange} + onConfirmPasswordChange={this.handleConfirmPasswordChange} + onSubmit={this.handleSubmit} + submitting={submitting} + success={success} + location={location} + /> + ); + } +} + +export const mapStateToProps = (state: Store) => { + const { canAdmin, instanceUsesDefaultAdminCredentials } = getAppState(state); + return { canAdmin, instanceUsesDefaultAdminCredentials }; +}; + +export default connect(mapStateToProps)(withRouter(ChangeAdminPasswordApp)); diff --git a/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordAppRenderer.tsx b/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordAppRenderer.tsx new file mode 100644 index 00000000000..0f01456ccba --- /dev/null +++ b/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordAppRenderer.tsx @@ -0,0 +1,150 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { SubmitButton } from 'sonar-ui-common/components/controls/buttons'; +import { Alert } from 'sonar-ui-common/components/ui/Alert'; +import MandatoryFieldMarker from 'sonar-ui-common/components/ui/MandatoryFieldMarker'; +import MandatoryFieldsExplanation from 'sonar-ui-common/components/ui/MandatoryFieldsExplanation'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { getReturnUrl } from 'sonar-ui-common/helpers/urls'; +import GlobalMessagesContainer from '../../app/components/GlobalMessagesContainer'; +import { Location } from '../../components/hoc/withRouter'; +import Unauthorized from '../sessions/components/Unauthorized'; +import { DEFAULT_ADMIN_PASSWORD } from './constants'; + +export interface ChangeAdminPasswordAppRendererProps { + passwordValue: string; + confirmPasswordValue: string; + onConfirmPasswordChange: (password: string) => void; + onPasswordChange: (password: string) => void; + onSubmit: () => void; + canAdmin?: boolean; + canSubmit?: boolean; + submitting: boolean; + success: boolean; + location: Location; +} + +export default function ChangeAdminPasswordAppRenderer(props: ChangeAdminPasswordAppRendererProps) { + const { + canAdmin, + canSubmit, + confirmPasswordValue, + passwordValue, + location, + submitting, + success + } = props; + + if (!canAdmin) { + return <Unauthorized />; + } + + return ( + <div className="page-wrapper-simple"> + <div className="page-simple"> + {success ? ( + <Alert variant="success"> + <p className="spacer-bottom">{translate('users.change_admin_password.form.success')}</p> + {/* We must not use Link here, because we need a refresh of the /api/navigation/global call. */} + <a href={getReturnUrl(location)}> + {translate('users.change_admin_password.form.continue_to_app')} + </a> + </Alert> + ) : ( + <> + <GlobalMessagesContainer /> + + <h1 className="text-center bg-danger big padded"> + {translate('users.change_admin_password.instance_is_at_risk')} + </h1> + <p className="text-center huge huge-spacer-top"> + {translate('users.change_admin_password.header')} + </p> + <p className="text-center huge-spacer-top huge-spacer-bottom"> + {translate('users.change_admin_password.description')} + </p> + + <form + className="text-center" + onSubmit={(e: React.SyntheticEvent<HTMLFormElement>) => { + e.preventDefault(); + props.onSubmit(); + }}> + <h2 className="big-spacer-bottom big"> + {translate('users.change_admin_password.form.header')} + </h2> + + <MandatoryFieldsExplanation className="form-field" /> + + <div className="form-field"> + <label htmlFor="user-password"> + {translate('users.change_admin_password.form.password')} + <MandatoryFieldMarker /> + </label> + <input + id="user-password" + name="password" + onChange={(e: React.SyntheticEvent<HTMLInputElement>) => { + props.onPasswordChange(e.currentTarget.value); + }} + required={true} + type="password" + value={passwordValue} + /> + </div> + + <div className="form-field"> + <label htmlFor="confirm-user-password"> + {translate('users.change_admin_password.form.confirm')} + <MandatoryFieldMarker /> + </label> + <input + id="confirm-user-password" + name="confirm-password" + onChange={(e: React.SyntheticEvent<HTMLInputElement>) => { + props.onConfirmPasswordChange(e.currentTarget.value); + }} + required={true} + type="password" + value={confirmPasswordValue} + /> + + {confirmPasswordValue === passwordValue && + passwordValue === DEFAULT_ADMIN_PASSWORD && ( + <Alert className="spacer-top" variant="warning"> + {translate('users.change_admin_password.form.cannot_use_default_password')} + </Alert> + )} + </div> + + <div className="form-field"> + <SubmitButton disabled={!canSubmit || submitting}> + {translate('change_verb')} + {submitting && <i className="spinner spacer-left" />} + </SubmitButton> + </div> + </form> + </> + )} + </div> + </div> + ); +} diff --git a/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/ChangeAdminPasswordApp-test.tsx b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/ChangeAdminPasswordApp-test.tsx new file mode 100644 index 00000000000..efc3e980c75 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/ChangeAdminPasswordApp-test.tsx @@ -0,0 +1,130 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { shallow } from 'enzyme'; +import * as React from 'react'; +import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; +import { changePassword } from '../../../api/users'; +import { mockLocation } from '../../../helpers/testMocks'; +import { getAppState, Store } from '../../../store/rootReducer'; +import { ChangeAdminPasswordApp, mapStateToProps } from '../ChangeAdminPasswordApp'; +import { DEFAULT_ADMIN_LOGIN, DEFAULT_ADMIN_PASSWORD } from '../constants'; + +jest.mock('react-redux', () => ({ + connect: jest.fn(() => (a: any) => a) +})); + +jest.mock('../../../store/rootReducer', () => ({ + ...jest.requireActual('../../../store/rootReducer'), + getAppState: jest.fn() +})); + +jest.mock('../../../api/users', () => ({ + changePassword: jest.fn().mockResolvedValue(null) +})); + +beforeEach(jest.clearAllMocks); + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot('default'); + expect(shallowRender({ instanceUsesDefaultAdminCredentials: undefined })).toMatchSnapshot( + 'admin is not using the default password' + ); +}); + +it('should correctly handle input changes', () => { + const wrapper = shallowRender(); + + // Set different values; should not allow submission. + wrapper.instance().handlePasswordChange('new pass'); + wrapper.instance().handleConfirmPasswordChange('confirm pass'); + + expect(wrapper.state().passwordValue).toBe('new pass'); + expect(wrapper.state().confirmPasswordValue).toBe('confirm pass'); + expect(wrapper.state().canSubmit).toBe(false); + + // Set the same values; should allow submission. + wrapper.instance().handleConfirmPasswordChange('new pass'); + expect(wrapper.state().canSubmit).toBe(true); + + // Set the default admin password; should not allow submission. + wrapper.instance().handlePasswordChange(DEFAULT_ADMIN_PASSWORD); + expect(wrapper.state().canSubmit).toBe(false); +}); + +it('should correctly update the password', async () => { + (changePassword as jest.Mock).mockResolvedValueOnce(null).mockRejectedValueOnce(null); + const wrapper = shallowRender(); + wrapper.setState({ canSubmit: false, passwordValue: 'new pass' }); + + wrapper.instance().handleSubmit(); + expect(wrapper.state().submitting).toBe(false); + expect(changePassword).not.toBeCalled(); + + wrapper.setState({ canSubmit: true }); + wrapper.instance().handleSubmit(); + expect(wrapper.state().submitting).toBe(true); + + await waitAndUpdate(wrapper); + + expect(wrapper.state().submitting).toBe(false); + expect(wrapper.state().success).toBe(true); + expect(changePassword).toBeCalledWith({ + login: DEFAULT_ADMIN_LOGIN, + password: 'new pass' + }); + + wrapper.instance().handleSubmit(); + expect(wrapper.state().submitting).toBe(true); + + await waitAndUpdate(wrapper); + + expect(wrapper.state().submitting).toBe(false); + expect(wrapper.state().success).toBe(false); +}); + +describe('redux', () => { + it('should correctly map state props', () => { + (getAppState as jest.Mock) + .mockReturnValueOnce({}) + .mockReturnValueOnce({ canAdmin: false, instanceUsesDefaultAdminCredentials: true }); + + expect(mapStateToProps({} as Store)).toEqual({ + canAdmin: undefined, + instanceUsesDefaultAdminCredentials: undefined + }); + + expect(mapStateToProps({} as Store)).toEqual({ + canAdmin: false, + instanceUsesDefaultAdminCredentials: true + }); + }); +}); + +function shallowRender(props: Partial<ChangeAdminPasswordApp['props']> = {}) { + return shallow<ChangeAdminPasswordApp>( + <ChangeAdminPasswordApp + canAdmin={true} + instanceUsesDefaultAdminCredentials={true} + location={mockLocation()} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/ChangeAdminPasswordAppRenderer-test.tsx b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/ChangeAdminPasswordAppRenderer-test.tsx new file mode 100644 index 00000000000..4861353e498 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/ChangeAdminPasswordAppRenderer-test.tsx @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { shallow } from 'enzyme'; +import * as React from 'react'; +import { change, submit } from 'sonar-ui-common/helpers/testUtils'; +import { mockLocation } from '../../../helpers/testMocks'; +import ChangeAdminPasswordAppRenderer, { + ChangeAdminPasswordAppRendererProps +} from '../ChangeAdminPasswordAppRenderer'; +import { DEFAULT_ADMIN_PASSWORD } from '../constants'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot('default'); + expect(shallowRender({ canAdmin: false })).toMatchSnapshot('access denied'); + expect(shallowRender({ canSubmit: false })).toMatchSnapshot('cannot submit'); + expect(shallowRender({ submitting: true })).toMatchSnapshot('submitting'); + expect( + shallowRender({ + passwordValue: DEFAULT_ADMIN_PASSWORD, + confirmPasswordValue: DEFAULT_ADMIN_PASSWORD + }) + ).toMatchSnapshot('trying to use default admin password'); + expect(shallowRender({ success: true })).toMatchSnapshot('success'); +}); + +it('should correctly react to input changes', () => { + const onConfirmPasswordChange = jest.fn(); + const onPasswordChange = jest.fn(); + const wrapper = shallowRender({ onConfirmPasswordChange, onPasswordChange }); + + change(wrapper.find('#user-password'), 'new pass'); + change(wrapper.find('#confirm-user-password'), 'confirm pass'); + expect(onPasswordChange).toBeCalledWith('new pass'); + expect(onConfirmPasswordChange).toBeCalledWith('confirm pass'); +}); + +it('should correctly submit the form', () => { + const onSubmit = jest.fn(); + const wrapper = shallowRender({ onSubmit }); + + submit(wrapper.find('form')); + expect(onSubmit).toBeCalled(); +}); + +function shallowRender(props: Partial<ChangeAdminPasswordAppRendererProps> = {}) { + return shallow<ChangeAdminPasswordAppRendererProps>( + <ChangeAdminPasswordAppRenderer + canAdmin={true} + canSubmit={true} + passwordValue="password" + confirmPasswordValue="confirm" + onConfirmPasswordChange={jest.fn()} + onPasswordChange={jest.fn()} + onSubmit={jest.fn()} + submitting={false} + success={false} + location={mockLocation()} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordApp-test.tsx.snap new file mode 100644 index 00000000000..5371a8f4386 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordApp-test.tsx.snap @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly: admin is not using the default password 1`] = ` +<ChangeAdminPasswordAppRenderer + canAdmin={true} + confirmPasswordValue="" + location={ + Object { + "action": "PUSH", + "hash": "", + "key": "key", + "pathname": "/path", + "query": Object {}, + "search": "", + "state": Object {}, + } + } + onConfirmPasswordChange={[Function]} + onPasswordChange={[Function]} + onSubmit={[Function]} + passwordValue="" + submitting={false} + success={true} +/> +`; + +exports[`should render correctly: default 1`] = ` +<ChangeAdminPasswordAppRenderer + canAdmin={true} + confirmPasswordValue="" + location={ + Object { + "action": "PUSH", + "hash": "", + "key": "key", + "pathname": "/path", + "query": Object {}, + "search": "", + "state": Object {}, + } + } + onConfirmPasswordChange={[Function]} + onPasswordChange={[Function]} + onSubmit={[Function]} + passwordValue="" + submitting={false} + success={false} +/> +`; diff --git a/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordAppRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordAppRenderer-test.tsx.snap new file mode 100644 index 00000000000..5c5895a5782 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordAppRenderer-test.tsx.snap @@ -0,0 +1,377 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly: access denied 1`] = `<Unauthorized />`; + +exports[`should render correctly: cannot submit 1`] = ` +<div + className="page-wrapper-simple" +> + <div + className="page-simple" + > + <Connect(GlobalMessages) /> + <h1 + className="text-center bg-danger big padded" + > + users.change_admin_password.instance_is_at_risk + </h1> + <p + className="text-center huge huge-spacer-top" + > + users.change_admin_password.header + </p> + <p + className="text-center huge-spacer-top huge-spacer-bottom" + > + users.change_admin_password.description + </p> + <form + className="text-center" + onSubmit={[Function]} + > + <h2 + className="big-spacer-bottom big" + > + users.change_admin_password.form.header + </h2> + <MandatoryFieldsExplanation + className="form-field" + /> + <div + className="form-field" + > + <label + htmlFor="user-password" + > + users.change_admin_password.form.password + <MandatoryFieldMarker /> + </label> + <input + id="user-password" + name="password" + onChange={[Function]} + required={true} + type="password" + value="password" + /> + </div> + <div + className="form-field" + > + <label + htmlFor="confirm-user-password" + > + users.change_admin_password.form.confirm + <MandatoryFieldMarker /> + </label> + <input + id="confirm-user-password" + name="confirm-password" + onChange={[Function]} + required={true} + type="password" + value="confirm" + /> + </div> + <div + className="form-field" + > + <SubmitButton + disabled={true} + > + change_verb + </SubmitButton> + </div> + </form> + </div> +</div> +`; + +exports[`should render correctly: default 1`] = ` +<div + className="page-wrapper-simple" +> + <div + className="page-simple" + > + <Connect(GlobalMessages) /> + <h1 + className="text-center bg-danger big padded" + > + users.change_admin_password.instance_is_at_risk + </h1> + <p + className="text-center huge huge-spacer-top" + > + users.change_admin_password.header + </p> + <p + className="text-center huge-spacer-top huge-spacer-bottom" + > + users.change_admin_password.description + </p> + <form + className="text-center" + onSubmit={[Function]} + > + <h2 + className="big-spacer-bottom big" + > + users.change_admin_password.form.header + </h2> + <MandatoryFieldsExplanation + className="form-field" + /> + <div + className="form-field" + > + <label + htmlFor="user-password" + > + users.change_admin_password.form.password + <MandatoryFieldMarker /> + </label> + <input + id="user-password" + name="password" + onChange={[Function]} + required={true} + type="password" + value="password" + /> + </div> + <div + className="form-field" + > + <label + htmlFor="confirm-user-password" + > + users.change_admin_password.form.confirm + <MandatoryFieldMarker /> + </label> + <input + id="confirm-user-password" + name="confirm-password" + onChange={[Function]} + required={true} + type="password" + value="confirm" + /> + </div> + <div + className="form-field" + > + <SubmitButton + disabled={false} + > + change_verb + </SubmitButton> + </div> + </form> + </div> +</div> +`; + +exports[`should render correctly: submitting 1`] = ` +<div + className="page-wrapper-simple" +> + <div + className="page-simple" + > + <Connect(GlobalMessages) /> + <h1 + className="text-center bg-danger big padded" + > + users.change_admin_password.instance_is_at_risk + </h1> + <p + className="text-center huge huge-spacer-top" + > + users.change_admin_password.header + </p> + <p + className="text-center huge-spacer-top huge-spacer-bottom" + > + users.change_admin_password.description + </p> + <form + className="text-center" + onSubmit={[Function]} + > + <h2 + className="big-spacer-bottom big" + > + users.change_admin_password.form.header + </h2> + <MandatoryFieldsExplanation + className="form-field" + /> + <div + className="form-field" + > + <label + htmlFor="user-password" + > + users.change_admin_password.form.password + <MandatoryFieldMarker /> + </label> + <input + id="user-password" + name="password" + onChange={[Function]} + required={true} + type="password" + value="password" + /> + </div> + <div + className="form-field" + > + <label + htmlFor="confirm-user-password" + > + users.change_admin_password.form.confirm + <MandatoryFieldMarker /> + </label> + <input + id="confirm-user-password" + name="confirm-password" + onChange={[Function]} + required={true} + type="password" + value="confirm" + /> + </div> + <div + className="form-field" + > + <SubmitButton + disabled={true} + > + change_verb + <i + className="spinner spacer-left" + /> + </SubmitButton> + </div> + </form> + </div> +</div> +`; + +exports[`should render correctly: success 1`] = ` +<div + className="page-wrapper-simple" +> + <div + className="page-simple" + > + <Alert + variant="success" + > + <p + className="spacer-bottom" + > + users.change_admin_password.form.success + </p> + <a + href="/" + > + users.change_admin_password.form.continue_to_app + </a> + </Alert> + </div> +</div> +`; + +exports[`should render correctly: trying to use default admin password 1`] = ` +<div + className="page-wrapper-simple" +> + <div + className="page-simple" + > + <Connect(GlobalMessages) /> + <h1 + className="text-center bg-danger big padded" + > + users.change_admin_password.instance_is_at_risk + </h1> + <p + className="text-center huge huge-spacer-top" + > + users.change_admin_password.header + </p> + <p + className="text-center huge-spacer-top huge-spacer-bottom" + > + users.change_admin_password.description + </p> + <form + className="text-center" + onSubmit={[Function]} + > + <h2 + className="big-spacer-bottom big" + > + users.change_admin_password.form.header + </h2> + <MandatoryFieldsExplanation + className="form-field" + /> + <div + className="form-field" + > + <label + htmlFor="user-password" + > + users.change_admin_password.form.password + <MandatoryFieldMarker /> + </label> + <input + id="user-password" + name="password" + onChange={[Function]} + required={true} + type="password" + value="admin" + /> + </div> + <div + className="form-field" + > + <label + htmlFor="confirm-user-password" + > + users.change_admin_password.form.confirm + <MandatoryFieldMarker /> + </label> + <input + id="confirm-user-password" + name="confirm-password" + onChange={[Function]} + required={true} + type="password" + value="admin" + /> + <Alert + className="spacer-top" + variant="warning" + > + users.change_admin_password.form.cannot_use_default_password + </Alert> + </div> + <div + className="form-field" + > + <SubmitButton + disabled={false} + > + change_verb + </SubmitButton> + </div> + </form> + </div> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/change-admin-password/constants.ts b/server/sonar-web/src/main/js/apps/change-admin-password/constants.ts new file mode 100644 index 00000000000..eecbed8b92f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/change-admin-password/constants.ts @@ -0,0 +1,21 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +export const DEFAULT_ADMIN_LOGIN = 'admin'; +export const DEFAULT_ADMIN_PASSWORD = 'admin'; diff --git a/server/sonar-web/src/main/js/types/types.d.ts b/server/sonar-web/src/main/js/types/types.d.ts index 223967cd14e..404c070c28f 100644 --- a/server/sonar-web/src/main/js/types/types.d.ts +++ b/server/sonar-web/src/main/js/types/types.d.ts @@ -90,6 +90,7 @@ declare namespace T { canAdmin?: boolean; edition: 'community' | 'developer' | 'enterprise' | 'datacenter' | undefined; globalPages?: Extension[]; + instanceUsesDefaultAdminCredentials?: boolean; multipleAlmEnabled?: boolean; needIssueSync?: boolean; productionDatabase: boolean; |