Skip to content

Commit

Permalink
Add support for deprecated roles (#57209) (#59197)
Browse files Browse the repository at this point in the history
* Add support for deprecated roles

* address PR feedback

* remove unused import

* copy edits

* fix snapshots

Co-authored-by: Elastic Machine <[email protected]>

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
legrego and elasticmachine authored Mar 3, 2020
1 parent 95cf5f9 commit 4e624b2
Show file tree
Hide file tree
Showing 55 changed files with 1,477 additions and 304 deletions.
3 changes: 2 additions & 1 deletion docs/management/advanced-options.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ cluster alert notifications from Monitoring.
==== Dashboard

[horizontal]
`xpackDashboardMode:roles`:: The roles that belong to <<xpack-dashboard-only-mode, dashboard only mode>>.
`xpackDashboardMode:roles`:: **Deprecated. Use <<kibana-feature-privileges,feature privileges>> instead.**
The roles that belong to <<xpack-dashboard-only-mode, dashboard only mode>>.

[float]
[[kibana-discover-settings]]
Expand Down
1 change: 1 addition & 0 deletions src/core/public/doc_links/doc_links_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export class DocLinksService {
},
management: {
kibanaSearchSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-search-settings`,
dashboardSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-dashboard-settings`,
},
},
});
Expand Down
9 changes: 9 additions & 0 deletions x-pack/legacy/plugins/dashboard_mode/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ export function dashboardMode(kibana) {
),
value: ['kibana_dashboard_only_user'],
category: ['dashboard'],
deprecation: {
message: i18n.translate(
'xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDeprecation',
{
defaultMessage: 'This setting is deprecated and will be removed in Kibana 8.0.',
}
),
docLinksKey: 'dashboardSettings',
},
},
},
app: {
Expand Down
6 changes: 4 additions & 2 deletions x-pack/plugins/security/common/model/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ export {
RoleIndexPrivilege,
RoleKibanaPrivilege,
copyRole,
isReadOnlyRole,
isReservedRole,
isRoleDeprecated,
isRoleReadOnly,
isRoleReserved,
isRoleEnabled,
prepareRoleClone,
getExtendedRoleDeprecationNotice,
} from './role';
export { KibanaPrivileges } from './kibana_privileges';
export {
Expand Down
73 changes: 63 additions & 10 deletions x-pack/plugins/security/common/model/role.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { Role, isReadOnlyRole, isReservedRole, isRoleEnabled, copyRole, prepareRoleClone } from '.';
import {
Role,
isRoleEnabled,
isRoleReserved,
isRoleDeprecated,
isRoleReadOnly,
copyRole,
prepareRoleClone,
getExtendedRoleDeprecationNotice,
} from '../../common/model';

describe('role', () => {
describe('isRoleEnabled', () => {
Expand Down Expand Up @@ -32,14 +41,14 @@ describe('role', () => {
});
});

describe('isReservedRole', () => {
describe('isRoleReserved', () => {
test('should return false if role is explicitly not reserved', () => {
const testRole = {
metadata: {
_reserved: false,
},
};
expect(isReservedRole(testRole)).toBe(false);
expect(isRoleReserved(testRole)).toBe(false);
});

test('should return true if role is explicitly reserved', () => {
Expand All @@ -48,30 +57,74 @@ describe('role', () => {
_reserved: true,
},
};
expect(isReservedRole(testRole)).toBe(true);
expect(isRoleReserved(testRole)).toBe(true);
});

test('should return false if role is NOT explicitly reserved or not reserved', () => {
const testRole = {};
expect(isReservedRole(testRole)).toBe(false);
expect(isRoleReserved(testRole)).toBe(false);
});
});

describe('isReadOnlyRole', () => {
describe('isRoleDeprecated', () => {
test('should return false if role is explicitly not deprecated', () => {
const testRole = {
metadata: {
_deprecated: false,
},
};
expect(isRoleDeprecated(testRole)).toBe(false);
});

test('should return true if role is explicitly deprecated', () => {
const testRole = {
metadata: {
_deprecated: true,
},
};
expect(isRoleDeprecated(testRole)).toBe(true);
});

test('should return false if role is NOT explicitly deprecated or not deprecated', () => {
const testRole = {};
expect(isRoleDeprecated(testRole)).toBe(false);
});
});

describe('getExtendedRoleDeprecationNotice', () => {
test('advises not to use the deprecated role', () => {
const testRole = { name: 'test-role' };
expect(getExtendedRoleDeprecationNotice(testRole)).toMatchInlineSnapshot(
`"The test-role role is deprecated. "`
);
});

test('includes the deprecation reason when provided', () => {
const testRole = {
name: 'test-role',
metadata: { _deprecated_reason: "We just don't like this role anymore" },
};
expect(getExtendedRoleDeprecationNotice(testRole)).toMatchInlineSnapshot(
`"The test-role role is deprecated. We just don't like this role anymore"`
);
});
});

describe('isRoleReadOnly', () => {
test('returns true for reserved roles', () => {
const testRole = {
metadata: {
_reserved: true,
},
};
expect(isReadOnlyRole(testRole)).toBe(true);
expect(isRoleReadOnly(testRole)).toBe(true);
});

test('returns true for roles with transform errors', () => {
const testRole = {
_transform_error: ['kibana'],
};
expect(isReadOnlyRole(testRole)).toBe(true);
expect(isRoleReadOnly(testRole)).toBe(true);
});

test('returns false for disabled roles', () => {
Expand All @@ -80,12 +133,12 @@ describe('role', () => {
enabled: false,
},
};
expect(isReadOnlyRole(testRole)).toBe(false);
expect(isRoleReadOnly(testRole)).toBe(false);
});

test('returns false for all other roles', () => {
const testRole = {};
expect(isReadOnlyRole(testRole)).toBe(false);
expect(isRoleReadOnly(testRole)).toBe(false);
});
});

Expand Down
40 changes: 37 additions & 3 deletions x-pack/plugins/security/common/model/role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import { cloneDeep } from 'lodash';
import { i18n } from '@kbn/i18n';
import { FeaturesPrivileges } from './features_privileges';

export interface RoleIndexPrivilege {
Expand Down Expand Up @@ -57,17 +58,41 @@ export function isRoleEnabled(role: Partial<Role>) {
*
* @param role Role as returned by roles API
*/
export function isReservedRole(role: Partial<Role>) {
export function isRoleReserved(role: Partial<Role>) {
return (role.metadata?._reserved as boolean) ?? false;
}

/**
* Returns whether given role is deprecated or not.
*
* @param {role} the Role as returned by roles API
*/
export function isRoleDeprecated(role: Partial<Role>) {
return role.metadata?._deprecated ?? false;
}

/**
* Returns the extended deprecation notice for the provided role.
*
* @param role the Role as returned by roles API
*/
export function getExtendedRoleDeprecationNotice(role: Partial<Role>) {
return i18n.translate('xpack.security.common.extendedRoleDeprecationNotice', {
defaultMessage: `The {roleName} role is deprecated. {reason}`,
values: {
roleName: role.name,
reason: getRoleDeprecatedReason(role),
},
});
}

/**
* Returns whether given role is editable through the UI or not.
*
* @param role the Role as returned by roles API
*/
export function isReadOnlyRole(role: Partial<Role>): boolean {
return isReservedRole(role) || (role._transform_error?.length ?? 0) > 0;
export function isRoleReadOnly(role: Partial<Role>): boolean {
return isRoleReserved(role) || (role._transform_error?.length ?? 0) > 0;
}

/**
Expand All @@ -91,3 +116,12 @@ export function prepareRoleClone(role: Role): Role {

return clone;
}

/**
* Returns the reason this role is deprecated.
*
* @param role the Role as returned by roles API
*/
function getRoleDeprecatedReason(role: Partial<Role>) {
return role.metadata?._deprecated_reason ?? '';
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe('<AccountManagementPage>', () => {
<AccountManagementPage
authc={getSecuritySetupMock({ currentUser: user }).authc}
notifications={coreMock.createStart().notifications}
apiClient={userAPIClientMock.create()}
userAPIClient={userAPIClientMock.create()}
/>
);

Expand All @@ -70,7 +70,7 @@ describe('<AccountManagementPage>', () => {
<AccountManagementPage
authc={getSecuritySetupMock({ currentUser: user }).authc}
notifications={coreMock.createStart().notifications}
apiClient={userAPIClientMock.create()}
userAPIClient={userAPIClientMock.create()}
/>
);

Expand All @@ -88,7 +88,7 @@ describe('<AccountManagementPage>', () => {
<AccountManagementPage
authc={getSecuritySetupMock({ currentUser: user }).authc}
notifications={coreMock.createStart().notifications}
apiClient={userAPIClientMock.create()}
userAPIClient={userAPIClientMock.create()}
/>
);

Expand All @@ -106,7 +106,7 @@ describe('<AccountManagementPage>', () => {
<AccountManagementPage
authc={getSecuritySetupMock({ currentUser: user }).authc}
notifications={coreMock.createStart().notifications}
apiClient={userAPIClientMock.create()}
userAPIClient={userAPIClientMock.create()}
/>
);

Expand All @@ -125,7 +125,7 @@ describe('<AccountManagementPage>', () => {
<AccountManagementPage
authc={getSecuritySetupMock({ currentUser: user }).authc}
notifications={coreMock.createStart().notifications}
apiClient={userAPIClientMock.create()}
userAPIClient={userAPIClientMock.create()}
/>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import { PersonalInfo } from './personal_info';

interface Props {
authc: AuthenticationServiceSetup;
apiClient: PublicMethodsOf<UserAPIClient>;
userAPIClient: PublicMethodsOf<UserAPIClient>;
notifications: NotificationsStart;
}

export const AccountManagementPage = ({ apiClient, authc, notifications }: Props) => {
export const AccountManagementPage = ({ userAPIClient, authc, notifications }: Props) => {
const [currentUser, setCurrentUser] = useState<AuthenticatedUser | null>(null);
useEffect(() => {
authc.getCurrentUser().then(setCurrentUser);
Expand All @@ -40,7 +40,11 @@ export const AccountManagementPage = ({ apiClient, authc, notifications }: Props

<PersonalInfo user={currentUser} />

<ChangePassword user={currentUser} apiClient={apiClient} notifications={notifications} />
<ChangePassword
user={currentUser}
userAPIClient={userAPIClient}
notifications={notifications}
/>
</EuiPanel>
</EuiPageBody>
</EuiPage>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { ChangePasswordForm } from '../../management/users/components/change_pas

interface Props {
user: AuthenticatedUser;
apiClient: PublicMethodsOf<UserAPIClient>;
userAPIClient: PublicMethodsOf<UserAPIClient>;
notifications: NotificationsSetup;
}

Expand Down Expand Up @@ -48,7 +48,7 @@ export class ChangePassword extends Component<Props, {}> {
<ChangePasswordForm
user={this.props.user}
isUserChangingOwnPassword={true}
apiClient={this.props.apiClient}
userAPIClient={this.props.userAPIClient}
notifications={this.props.notifications}
/>
</EuiDescribedFormGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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 React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiBadge, EuiToolTipProps } from '@elastic/eui';
import { OptionalToolTip } from './optional_tooltip';

interface Props {
'data-test-subj'?: string;
tooltipContent?: EuiToolTipProps['content'];
}

export const DeprecatedBadge = (props: Props) => {
return (
<OptionalToolTip tooltipContent={props.tooltipContent}>
<EuiBadge data-test-subj={props['data-test-subj']} color="warning">
<FormattedMessage
id="xpack.security.management.deprecatedBadge"
defaultMessage="Deprecated"
/>
</EuiBadge>
</OptionalToolTip>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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 React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiBadge, EuiToolTipProps } from '@elastic/eui';
import { OptionalToolTip } from './optional_tooltip';

interface Props {
'data-test-subj'?: string;
tooltipContent?: EuiToolTipProps['content'];
}

export const DisabledBadge = (props: Props) => {
return (
<OptionalToolTip tooltipContent={props.tooltipContent}>
<EuiBadge data-test-subj={props['data-test-subj']} color="hollow">
<FormattedMessage id="xpack.security.management.disabledBadge" defaultMessage="Disabled" />
</EuiBadge>
</OptionalToolTip>
);
};
Loading

0 comments on commit 4e624b2

Please sign in to comment.