Skip to content

Commit

Permalink
Convert account screen to React/EUI (#30977)
Browse files Browse the repository at this point in the history
* WIP account management redesign

* style updates

* start implementing change password logic

* restyle

* remove api key management section

* improved change password validation

* first round of design edits

* cleanup and testing

* fix import

* fix translations

* fix error handling on user management page

* consolidate password change logic

* fix tests

* happy linter, happy life

* finish change password test

* removes unused translations

* fix typo in test

* fix change password functional test

* Design edits (#19)

- Made `fullWidth`
- Added a consistent password requirement help text
- Use `title` for toast
- Change username/email to us `dl`
- Don’t use html elements in tests

* clear password form on success

* copy edits

* fix handling of Change Password button

* use encodeURIComponent for user supplied data
  • Loading branch information
legrego committed Apr 15, 2019
1 parent a6d977e commit 031682d
Show file tree
Hide file tree
Showing 24 changed files with 948 additions and 256 deletions.
1 change: 0 additions & 1 deletion x-pack/plugins/security/common/constants.ts
Expand Up @@ -6,6 +6,5 @@

export const GLOBAL_RESOURCE = '*';
export const IGNORED_TYPES = ['space'];
export const REALMS_ELIGIBLE_FOR_PASSWORD_CHANGE = ['reserved', 'native'];
export const APPLICATION_PREFIX = 'kibana-';
export const RESERVED_PRIVILEGES_APPLICATION_WILDCARD = 'kibana-*';
62 changes: 62 additions & 0 deletions x-pack/plugins/security/common/model/user.test.ts
@@ -0,0 +1,62 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { canUserChangePassword, getUserDisplayName, User } from './user';

describe('#getUserDisplayName', () => {
it(`uses the full name when available`, () => {
expect(
getUserDisplayName({
full_name: 'my full name',
username: 'foo',
} as User)
).toEqual('my full name');
});

it(`uses the username when full name is not available`, () => {
expect(
getUserDisplayName({
username: 'foo',
} as User)
).toEqual('foo');
});
});

describe('#canUserChangePassword', () => {
['reserved', 'native'].forEach(realm => {
it(`returns true for users in the ${realm} realm`, () => {
expect(
canUserChangePassword({
username: 'foo',
authentication_realm: {
name: 'the realm name',
type: realm,
},
} as User)
).toEqual(true);
});
});

it(`returns true when no realm is provided`, () => {
expect(
canUserChangePassword({
username: 'foo',
} as User)
).toEqual(true);
});

it(`returns false for all other realms`, () => {
expect(
canUserChangePassword({
username: 'foo',
authentication_realm: {
name: 'the realm name',
type: 'does not matter',
},
} as User)
).toEqual(false);
});
});
37 changes: 37 additions & 0 deletions x-pack/plugins/security/common/model/user.ts
@@ -0,0 +1,37 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export interface User {
username: string;
email: string;
full_name: string;
roles: string[];
enabled: boolean;
authentication_realm?: {
name: string;
type: string;
};
lookup_realm?: {
name: string;
type: string;
};
}

const REALMS_ELIGIBLE_FOR_PASSWORD_CHANGE = ['reserved', 'native'];

export function getUserDisplayName(user: User): string {
return user.full_name || user.username;
}

export function canUserChangePassword(user: User): boolean {
const { authentication_realm: authenticationRealm } = user;

if (!authenticationRealm) {
return true;
}

return REALMS_ELIGIBLE_FOR_PASSWORD_CHANGE.includes(authenticationRealm.type);
}
@@ -0,0 +1,111 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
jest.mock('../../../lib/api', () => {
return {
UserAPIClient: {
changePassword: jest.fn(),
},
};
});
import { EuiFieldText } from '@elastic/eui';
import { ReactWrapper } from 'enzyme';
import React from 'react';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { User } from '../../../../common/model/user';
import { UserAPIClient } from '../../../lib/api';
import { ChangePasswordForm } from './change_password_form';

function getCurrentPasswordField(wrapper: ReactWrapper<any>) {
return wrapper.find(EuiFieldText).filter('[data-test-subj="currentPassword"]');
}

function getNewPasswordField(wrapper: ReactWrapper<any>) {
return wrapper.find(EuiFieldText).filter('[data-test-subj="newPassword"]');
}

function getConfirmPasswordField(wrapper: ReactWrapper<any>) {
return wrapper.find(EuiFieldText).filter('[data-test-subj="confirmNewPassword"]');
}

describe('<ChangePasswordForm>', () => {
describe('for the current user', () => {
it('shows fields for current and new passwords', () => {
const user: User = {
username: 'user',
full_name: 'john smith',
email: 'john@smith.com',
enabled: true,
roles: [],
};

const wrapper = mountWithIntl(
<ChangePasswordForm user={user} isUserChangingOwnPassword={true} />
);

expect(getCurrentPasswordField(wrapper)).toHaveLength(1);
expect(getNewPasswordField(wrapper)).toHaveLength(1);
expect(getConfirmPasswordField(wrapper)).toHaveLength(1);
});

it('allows a password to be changed', () => {
const user: User = {
username: 'user',
full_name: 'john smith',
email: 'john@smith.com',
enabled: true,
roles: [],
};

const callback = jest.fn();

const wrapper = mountWithIntl(
<ChangePasswordForm
user={user}
isUserChangingOwnPassword={true}
onChangePassword={callback}
/>
);

const currentPassword = getCurrentPasswordField(wrapper);
currentPassword.props().onChange!({ target: { value: 'myCurrentPassword' } } as any);

const newPassword = getNewPasswordField(wrapper);
newPassword.props().onChange!({ target: { value: 'myNewPassword' } } as any);

const confirmPassword = getConfirmPasswordField(wrapper);
confirmPassword.props().onChange!({ target: { value: 'myNewPassword' } } as any);

wrapper.find('button[data-test-subj="changePasswordButton"]').simulate('click');

expect(UserAPIClient.changePassword).toHaveBeenCalledTimes(1);
expect(UserAPIClient.changePassword).toHaveBeenCalledWith(
'user',
'myNewPassword',
'myCurrentPassword'
);
});
});

describe('for another user', () => {
it('shows fields for new password only', () => {
const user: User = {
username: 'user',
full_name: 'john smith',
email: 'john@smith.com',
enabled: true,
roles: [],
};

const wrapper = mountWithIntl(
<ChangePasswordForm user={user} isUserChangingOwnPassword={false} />
);

expect(getCurrentPasswordField(wrapper)).toHaveLength(0);
expect(getNewPasswordField(wrapper)).toHaveLength(1);
expect(getConfirmPasswordField(wrapper)).toHaveLength(1);
});
});
});

0 comments on commit 031682d

Please sign in to comment.