diff --git a/x-pack/plugins/security/common/constants.ts b/x-pack/plugins/security/common/constants.ts index 48a020210596560..aab0c9d6c438b11 100644 --- a/x-pack/plugins/security/common/constants.ts +++ b/x-pack/plugins/security/common/constants.ts @@ -92,3 +92,8 @@ export const SESSION_EXTENSION_THROTTLE_MS = 60 * 1000; * Route to get session info and extend session expiration */ export const SESSION_ROUTE = '/internal/security/session'; + +/** + * Allowed image file types for uploading an image as avatar + */ +export const IMAGE_FILE_TYPES = ['image/svg+xml', 'image/jpeg', 'image/png', 'image/gif']; diff --git a/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx b/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx index 04ac8cc4fcfe9af..601e13c8bb16e10 100644 --- a/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx +++ b/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx @@ -42,8 +42,9 @@ import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import type { DarkModeValue, UserProfileData } from '@kbn/user-profile-components'; import { UserAvatar, useUpdateUserProfile } from '@kbn/user-profile-components'; -import { createImageHandler, getRandomColor, IMAGE_FILE_TYPES, VALID_HEX_COLOR } from './utils'; +import { createImageHandler, getRandomColor, VALID_HEX_COLOR } from './utils'; import type { AuthenticatedUser } from '../../../common'; +import { IMAGE_FILE_TYPES } from '../../../common/constants'; import { canUserChangeDetails, canUserChangePassword, diff --git a/x-pack/plugins/security/public/account_management/user_profile/utils.ts b/x-pack/plugins/security/public/account_management/user_profile/utils.ts index fd1535028849305..2621ba0b65f1fd9 100644 --- a/x-pack/plugins/security/public/account_management/user_profile/utils.ts +++ b/x-pack/plugins/security/public/account_management/user_profile/utils.ts @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { IMAGE_FILE_TYPES } from '../../../common/constants'; -export const IMAGE_FILE_TYPES = ['image/svg+xml', 'image/jpeg', 'image/png', 'image/gif']; export const MAX_IMAGE_SIZE = 64; export function readFile(data: File) { diff --git a/x-pack/plugins/security/server/routes/user_profile/update.ts b/x-pack/plugins/security/server/routes/user_profile/update.ts index 205f6a6d68a4a35..9a550ada52adc18 100644 --- a/x-pack/plugins/security/server/routes/user_profile/update.ts +++ b/x-pack/plugins/security/server/routes/user_profile/update.ts @@ -8,6 +8,7 @@ import { schema } from '@kbn/config-schema'; import type { RouteDefinitionParams } from '..'; +import { IMAGE_FILE_TYPES } from '../../../common/constants'; import { wrapIntoCustomErrorResponse } from '../../errors'; import { flattenObject } from '../../lib'; import { getPrintableSessionId } from '../../session_management'; @@ -47,7 +48,28 @@ export function defineUpdateUserProfileDataRoute({ } const currentUser = getAuthenticationService().getCurrentUser(request); + const userProfileData = request.body; + const imageDataUrl = userProfileData.avatar?.imageUrl; + if (imageDataUrl && typeof imageDataUrl === 'string') { + const matches = imageDataUrl.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/); + if (!matches || matches.length !== 3) { + return response.customError({ + body: 'Unsupported media type', + statusCode: 415, + }); + } + + const [, mimeType] = matches; + + if (!IMAGE_FILE_TYPES.includes(mimeType)) { + return response.customError({ + body: 'Unsupported media type', + statusCode: 415, + }); + } + } + const keysToUpdate = Object.keys(flattenObject(userProfileData)); if (currentUser?.elastic_cloud_user) { diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/user_actions_get_users.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/user_actions_get_users.ts index e49c48fbf2347e6..c1edd4280be85e6 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/user_actions_get_users.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/user_actions_get_users.ts @@ -43,6 +43,10 @@ export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); const kibanaServer = getService('kibanaServer'); + // Use simple image data URL to match server side validation of image type + const IMAGE_URL_TEST = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAIBJREFUeF7t14ERACAMAjG6/9A6yMcROEuOe9tb+J0A/AAnoAPCHTglSAEKUIACFAgngEEMYhCDGAwjYAxhEIMYxCAGMRhOAIMYxCAGMRhGwBrEIAYxiEEMYjCcAAYxiEEMYjCMgDWIQQxiEIMYxGA4AQxiEIMYxGAYAWsQg3UGPw2Yf8EFsz4JAAAAAElFTkSuQmCC'; + describe('user_actions_get_users', () => { afterEach(async () => { await deleteAllCaseItems(es); @@ -160,7 +164,7 @@ export default ({ getService }: FtrProviderContext): void => { req: { initials: 'ES', color: '#6092C0', - imageUrl: 'my-image', + imageUrl: IMAGE_URL_TEST, }, headers: superUserHeaders, });