Skip to content

Commit 512c4e6

Browse files
committed
Properly encode links to edit user page (#81562)
1 parent 1bc7eb8 commit 512c4e6

File tree

4 files changed

+63
-6
lines changed

4 files changed

+63
-6
lines changed

x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ describe('<RolesGridPage />', () => {
6666
kibana: [{ base: [], spaces: [], feature: {} }],
6767
transient_metadata: { enabled: false },
6868
},
69+
{
70+
name: 'special%chars%role',
71+
elasticsearch: { cluster: [], indices: [], run_as: [] },
72+
kibana: [{ base: [], spaces: [], feature: {} }],
73+
},
6974
]);
7075
});
7176

@@ -121,7 +126,7 @@ describe('<RolesGridPage />', () => {
121126
expect(wrapper.find(PermissionDenied)).toMatchSnapshot();
122127
});
123128

124-
it('renders role actions as appropriate', async () => {
129+
it('renders role actions as appropriate, escaping when necessary', async () => {
125130
const wrapper = mountWithIntl(
126131
<RolesGridPage
127132
rolesAPIClient={apiClientMock}
@@ -137,16 +142,26 @@ describe('<RolesGridPage />', () => {
137142

138143
expect(wrapper.find(PermissionDenied)).toHaveLength(0);
139144

140-
const editButton = wrapper.find('EuiButtonIcon[data-test-subj="edit-role-action-test-role-1"]');
145+
let editButton = wrapper.find('EuiButtonIcon[data-test-subj="edit-role-action-test-role-1"]');
141146
expect(editButton).toHaveLength(1);
142147
expect(editButton.prop('href')).toBe('/edit/test-role-1');
143148

144-
const cloneButton = wrapper.find(
145-
'EuiButtonIcon[data-test-subj="clone-role-action-test-role-1"]'
149+
editButton = wrapper.find(
150+
'EuiButtonIcon[data-test-subj="edit-role-action-special%chars%role"]'
146151
);
152+
expect(editButton).toHaveLength(1);
153+
expect(editButton.prop('href')).toBe('/edit/special%25chars%25role');
154+
155+
let cloneButton = wrapper.find('EuiButtonIcon[data-test-subj="clone-role-action-test-role-1"]');
147156
expect(cloneButton).toHaveLength(1);
148157
expect(cloneButton.prop('href')).toBe('/clone/test-role-1');
149158

159+
cloneButton = wrapper.find(
160+
'EuiButtonIcon[data-test-subj="clone-role-action-special%chars%role"]'
161+
);
162+
expect(cloneButton).toHaveLength(1);
163+
expect(cloneButton.prop('href')).toBe('/clone/special%25chars%25role');
164+
150165
expect(
151166
wrapper.find('EuiButtonIcon[data-test-subj="edit-role-action-disabled-role"]')
152167
).toHaveLength(1);
@@ -182,6 +197,11 @@ describe('<RolesGridPage />', () => {
182197
kibana: [{ base: [], spaces: [], feature: {} }],
183198
metadata: { _reserved: true },
184199
},
200+
{
201+
name: 'special%chars%role',
202+
elasticsearch: { cluster: [], indices: [], run_as: [] },
203+
kibana: [{ base: [], spaces: [], feature: {} }],
204+
},
185205
{
186206
name: 'test-role-1',
187207
elasticsearch: { cluster: [], indices: [], run_as: [] },
@@ -198,6 +218,11 @@ describe('<RolesGridPage />', () => {
198218
kibana: [{ base: [], spaces: [], feature: {} }],
199219
transient_metadata: { enabled: false },
200220
},
221+
{
222+
name: 'special%chars%role',
223+
elasticsearch: { cluster: [], indices: [], run_as: [] },
224+
kibana: [{ base: [], spaces: [], feature: {} }],
225+
},
201226
{
202227
name: 'test-role-1',
203228
elasticsearch: { cluster: [], indices: [], run_as: [] },

x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ interface State {
5858
}
5959

6060
const getRoleManagementHref = (action: 'edit' | 'clone', roleName?: string) => {
61-
return `/${action}${roleName ? `/${roleName}` : ''}`;
61+
return `/${action}${roleName ? `/${encodeURIComponent(roleName)}` : ''}`;
6262
};
6363

6464
export class RolesGridPage extends Component<Props, State> {

x-pack/plugins/security/public/management/users/users_grid/users_grid_page.test.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,38 @@ describe('UsersGridPage', () => {
7171
expect(findTestSubject(wrapper, 'userDisabled')).toHaveLength(0);
7272
});
7373

74+
it('generates valid links when usernames contain special characters', async () => {
75+
const apiClientMock = userAPIClientMock.create();
76+
apiClientMock.getUsers.mockImplementation(() => {
77+
return Promise.resolve<User[]>([
78+
{
79+
username: 'username with some fun characters!@#$%^&*()',
80+
81+
full_name: 'foo bar',
82+
roles: ['kibana_user'],
83+
enabled: true,
84+
},
85+
]);
86+
});
87+
88+
const wrapper = mountWithIntl(
89+
<UsersGridPage
90+
userAPIClient={apiClientMock}
91+
rolesAPIClient={rolesAPIClientMock.create()}
92+
notifications={coreStart.notifications}
93+
history={history}
94+
navigateToApp={coreStart.application.navigateToApp}
95+
/>
96+
);
97+
98+
await waitForRender(wrapper);
99+
100+
const link = findTestSubject(wrapper, 'userRowUserName');
101+
expect(link.props().href).toMatchInlineSnapshot(
102+
`"/edit/username%20with%20some%20fun%20characters!%40%23%24%25%5E%26*()"`
103+
);
104+
});
105+
74106
it('renders a forbidden message if user is not authorized', async () => {
75107
const apiClient = userAPIClientMock.create();
76108
apiClient.getUsers.mockRejectedValue({ body: { statusCode: 403 } });

x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export class UsersGridPage extends Component<Props, State> {
112112
render: (username: string) => (
113113
<EuiLink
114114
data-test-subj="userRowUserName"
115-
{...reactRouterNavigate(this.props.history, `/edit/${username}`)}
115+
{...reactRouterNavigate(this.props.history, `/edit/${encodeURIComponent(username)}`)}
116116
>
117117
{username}
118118
</EuiLink>

0 commit comments

Comments
 (0)