mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Expose userProfiles.getCurrent
and userProfiles.bulkGet
APIs via the start
contract on the client and server sides. (#136831)
This commit is contained in:
parent
39404ba558
commit
5107282b01
22 changed files with 667 additions and 127 deletions
|
@ -7,3 +7,4 @@
|
|||
|
||||
export { accountManagementApp } from './account_management_app';
|
||||
export { UserProfileAPIClient } from './user_profile/user_profile_api_client';
|
||||
export type { UserProfileBulkGetParams, UserProfileGetCurrentParams } from './user_profile';
|
||||
|
|
|
@ -8,3 +8,7 @@
|
|||
export { UserProfile } from './user_profile';
|
||||
|
||||
export type { UserProfileProps, UserProfileFormValues } from './user_profile';
|
||||
export type {
|
||||
UserProfileGetCurrentParams,
|
||||
UserProfileBulkGetParams,
|
||||
} from './user_profile_api_client';
|
||||
|
|
|
@ -20,16 +20,16 @@ describe('UserProfileAPIClient', () => {
|
|||
});
|
||||
|
||||
it('should get user profile without retrieving any user data', async () => {
|
||||
await apiClient.get();
|
||||
await apiClient.getCurrent();
|
||||
expect(coreStart.http.get).toHaveBeenCalledWith('/internal/security/user_profile', {
|
||||
query: { data: undefined },
|
||||
query: { dataPath: undefined },
|
||||
});
|
||||
});
|
||||
|
||||
it('should get user profile and user data', async () => {
|
||||
await apiClient.get('*');
|
||||
await apiClient.getCurrent({ dataPath: '*' });
|
||||
expect(coreStart.http.get).toHaveBeenCalledWith('/internal/security/user_profile', {
|
||||
query: { data: '*' },
|
||||
query: { dataPath: '*' },
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -10,9 +10,34 @@ import { Subject } from 'rxjs';
|
|||
|
||||
import type { HttpStart } from '@kbn/core/public';
|
||||
|
||||
import type { GetUserProfileResponse, UserProfileData } from '../../../common';
|
||||
import type { GetUserProfileResponse, UserProfile, UserProfileData } from '../../../common';
|
||||
|
||||
const USER_PROFILE_URL = '/internal/security/user_profile';
|
||||
/**
|
||||
* Parameters for the get user profile for the current user API.
|
||||
*/
|
||||
export interface UserProfileGetCurrentParams {
|
||||
/**
|
||||
* By default, get API returns user information, but does not return any user data. The optional "dataPath"
|
||||
* parameter can be used to return personal data for this user (within `kibana` namespace only).
|
||||
*/
|
||||
dataPath: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for the bulk get API.
|
||||
*/
|
||||
export interface UserProfileBulkGetParams {
|
||||
/**
|
||||
* List of user profile identifiers.
|
||||
*/
|
||||
uids: Set<string>;
|
||||
|
||||
/**
|
||||
* By default, suggest API returns user information, but does not return any user data. The optional "dataPath"
|
||||
* parameter can be used to return personal data for this user (within `kibana` namespace only).
|
||||
*/
|
||||
dataPath?: string;
|
||||
}
|
||||
|
||||
export class UserProfileAPIClient {
|
||||
private readonly internalDataUpdates$: Subject<UserProfileData> = new Subject();
|
||||
|
@ -26,12 +51,28 @@ export class UserProfileAPIClient {
|
|||
constructor(private readonly http: HttpStart) {}
|
||||
|
||||
/**
|
||||
* Retrieves the user profile of the current user.
|
||||
* @param dataPath By default `get()` returns user information, but does not return any user data. The optional "dataPath" parameter can be used to return personal data for this user.
|
||||
* Retrieves the user profile of the current user. If the profile isn't available, e.g. for the anonymous users or
|
||||
* users authenticated via authenticating proxies, the `null` value is returned.
|
||||
* @param [params] Get current user profile operation parameters.
|
||||
* @param params.dataPath By default `getCurrent()` returns user information, but does not return any user data. The
|
||||
* optional "dataPath" parameter can be used to return personal data for this user.
|
||||
*/
|
||||
public get<D extends UserProfileData>(dataPath?: string) {
|
||||
return this.http.get<GetUserProfileResponse<D>>(USER_PROFILE_URL, {
|
||||
query: { data: dataPath },
|
||||
public getCurrent<D extends UserProfileData>(params?: UserProfileGetCurrentParams) {
|
||||
return this.http.get<GetUserProfileResponse<D>>('/internal/security/user_profile', {
|
||||
query: { dataPath: params?.dataPath },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves multiple user profiles by their identifiers.
|
||||
* @param params Bulk get operation parameters.
|
||||
* @param params.uids List of user profile identifiers.
|
||||
* @param params.dataPath By default Elasticsearch returns user information, but does not return any user data. The
|
||||
* optional "dataPath" parameter can be used to return personal data for the requested user profiles.
|
||||
*/
|
||||
public bulkGet<D extends UserProfileData>(params: UserProfileBulkGetParams) {
|
||||
return this.http.post<Array<UserProfile<D>>>('/internal/security/user_profile/_bulk_get', {
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -40,8 +81,10 @@ export class UserProfileAPIClient {
|
|||
* @param data Application data to be written (merged with existing data).
|
||||
*/
|
||||
public update<D extends UserProfileData>(data: D) {
|
||||
return this.http.post(`${USER_PROFILE_URL}/_data`, { body: JSON.stringify(data) }).then(() => {
|
||||
this.internalDataUpdates$.next(data);
|
||||
});
|
||||
return this.http
|
||||
.post('/internal/security/user_profile/_data', { body: JSON.stringify(data) })
|
||||
.then(() => {
|
||||
this.internalDataUpdates$.next(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,5 +31,8 @@ export function useCurrentUser() {
|
|||
export function useUserProfile<T extends UserProfileData>(dataPath?: string) {
|
||||
const { userProfiles } = useSecurityApiClients();
|
||||
const dataUpdateState = useObservable(userProfiles.dataUpdates$);
|
||||
return useAsync(() => userProfiles.get<T>(dataPath), [userProfiles, dataUpdateState]);
|
||||
return useAsync(
|
||||
() => userProfiles.getCurrent<T>(dataPath ? { dataPath } : undefined),
|
||||
[userProfiles, dataUpdateState]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ export type { AuthenticatedUser } from '../common/model';
|
|||
export type { SecurityLicense, SecurityLicenseFeatures } from '../common/licensing';
|
||||
export type { UiApi, ChangePasswordProps, PersonalInfoProps } from './ui_api';
|
||||
export type { UserMenuLink, SecurityNavControlServiceStart } from './nav_control';
|
||||
export type { UserProfileBulkGetParams, UserProfileGetCurrentParams } from './account_management';
|
||||
|
||||
export type { AuthenticationServiceStart, AuthenticationServiceSetup } from './authentication';
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ function createStartMock() {
|
|||
return {
|
||||
authc: authenticationMock.createStart(),
|
||||
navControlService: navControlServiceMock.createStart(),
|
||||
userProfiles: { getCurrent: jest.fn(), bulkGet: jest.fn() },
|
||||
uiApi: getUiApiMock.createStart(),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -90,22 +90,28 @@ describe('Security Plugin', () => {
|
|||
dataViews: {} as DataViewsPublicPluginStart,
|
||||
features: {} as FeaturesPluginStart,
|
||||
})
|
||||
).toEqual({
|
||||
uiApi: {
|
||||
components: {
|
||||
getChangePassword: expect.any(Function),
|
||||
getPersonalInfo: expect.any(Function),
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"authc": Object {
|
||||
"areAPIKeysEnabled": [Function],
|
||||
"getCurrentUser": [Function],
|
||||
},
|
||||
},
|
||||
authc: {
|
||||
getCurrentUser: expect.any(Function),
|
||||
areAPIKeysEnabled: expect.any(Function),
|
||||
},
|
||||
navControlService: {
|
||||
getUserMenuLinks$: expect.any(Function),
|
||||
addUserMenuLinks: expect.any(Function),
|
||||
},
|
||||
});
|
||||
"navControlService": Object {
|
||||
"addUserMenuLinks": [Function],
|
||||
"getUserMenuLinks$": [Function],
|
||||
},
|
||||
"uiApi": Object {
|
||||
"components": Object {
|
||||
"getChangePassword": [Function],
|
||||
"getPersonalInfo": [Function],
|
||||
},
|
||||
},
|
||||
"userProfiles": Object {
|
||||
"bulkGet": [Function],
|
||||
"getCurrent": [Function],
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('starts Management Service if `management` plugin is available', () => {
|
||||
|
|
|
@ -21,13 +21,16 @@ import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/pu
|
|||
import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
|
||||
import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
|
||||
|
||||
import type { UserProfile, UserProfileData, UserProfileWithSecurity } from '../common';
|
||||
import type { SecurityLicense } from '../common/licensing';
|
||||
import { SecurityLicenseService } from '../common/licensing';
|
||||
import type { UserProfileBulkGetParams, UserProfileGetCurrentParams } from './account_management';
|
||||
import { accountManagementApp, UserProfileAPIClient } from './account_management';
|
||||
import { AnalyticsService } from './analytics';
|
||||
import { AnonymousAccessService } from './anonymous_access';
|
||||
import type { AuthenticationServiceSetup, AuthenticationServiceStart } from './authentication';
|
||||
import { AuthenticationService } from './authentication';
|
||||
import type { SecurityApiClients } from './components';
|
||||
import type { ConfigType } from './config';
|
||||
import { ManagementService, UserAPIClient } from './management';
|
||||
import type { SecurityNavControlServiceStart } from './nav_control';
|
||||
|
@ -71,6 +74,7 @@ export class SecurityPlugin
|
|||
private readonly anonymousAccessService = new AnonymousAccessService();
|
||||
private readonly analyticsService = new AnalyticsService();
|
||||
private authc!: AuthenticationServiceSetup;
|
||||
private securityApiClients!: SecurityApiClients;
|
||||
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {
|
||||
this.config = this.initializerContext.config.get<ConfigType>();
|
||||
|
@ -93,7 +97,7 @@ export class SecurityPlugin
|
|||
http: core.http,
|
||||
});
|
||||
|
||||
const securityApiClients = {
|
||||
this.securityApiClients = {
|
||||
userProfiles: new UserProfileAPIClient(core.http),
|
||||
users: new UserAPIClient(core.http),
|
||||
};
|
||||
|
@ -101,7 +105,7 @@ export class SecurityPlugin
|
|||
this.navControlService.setup({
|
||||
securityLicense: license,
|
||||
logoutUrl: getLogoutUrl(core.http),
|
||||
securityApiClients,
|
||||
securityApiClients: this.securityApiClients,
|
||||
});
|
||||
|
||||
this.analyticsService.setup({ securityLicense: license });
|
||||
|
@ -110,7 +114,7 @@ export class SecurityPlugin
|
|||
authc: this.authc,
|
||||
application: core.application,
|
||||
getStartServices: core.getStartServices,
|
||||
securityApiClients,
|
||||
securityApiClients: this.securityApiClients,
|
||||
});
|
||||
|
||||
if (management) {
|
||||
|
@ -181,6 +185,14 @@ export class SecurityPlugin
|
|||
uiApi: getUiApi({ core }),
|
||||
navControlService: this.navControlService.start({ core, authc: this.authc }),
|
||||
authc: this.authc as AuthenticationServiceStart,
|
||||
userProfiles: {
|
||||
getCurrent: this.securityApiClients.userProfiles.getCurrent.bind(
|
||||
this.securityApiClients.userProfiles
|
||||
),
|
||||
bulkGet: this.securityApiClients.userProfiles.bulkGet.bind(
|
||||
this.securityApiClients.userProfiles
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -217,6 +229,32 @@ export interface SecurityPluginStart {
|
|||
* Exposes authentication information about the currently logged in user.
|
||||
*/
|
||||
authc: AuthenticationServiceStart;
|
||||
/**
|
||||
* A set of methods to work with Kibana user profiles.
|
||||
*/
|
||||
userProfiles: {
|
||||
/**
|
||||
* Retrieves the user profile of the current user. If the profile isn't available, e.g. for the anonymous users or
|
||||
* users authenticated via authenticating proxies, the `null` value is returned.
|
||||
* @param [params] Get current user profile operation parameters.
|
||||
* @param params.dataPath By default `getCurrent()` returns user information, but does not return any user data. The
|
||||
* optional "dataPath" parameter can be used to return personal data for this user.
|
||||
*/
|
||||
getCurrent<D extends UserProfileData>(
|
||||
params?: UserProfileGetCurrentParams
|
||||
): Promise<UserProfileWithSecurity<D> | null>;
|
||||
/**
|
||||
* Retrieves multiple user profiles by their identifiers.
|
||||
* @param params Bulk get operation parameters.
|
||||
* @param params.uids List of user profile identifiers.
|
||||
* @param params.dataPath By default Elasticsearch returns user information, but does not return any user data. The
|
||||
* optional "dataPath" parameter can be used to return personal data for the requested user profiles.
|
||||
*/
|
||||
bulkGet<D extends UserProfileData>(
|
||||
params: UserProfileBulkGetParams
|
||||
): Promise<Array<UserProfile<D>>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Exposes UI components that will be loaded asynchronously.
|
||||
* @deprecated
|
||||
|
|
|
@ -40,6 +40,7 @@ export type {
|
|||
UserProfileBulkGetParams,
|
||||
UserProfileSuggestParams,
|
||||
UserProfileRequiredPrivileges,
|
||||
UserProfileGetCurrentParams,
|
||||
} from './user_profile';
|
||||
|
||||
export const config: PluginConfigDescriptor<TypeOf<typeof ConfigSchema>> = {
|
||||
|
|
|
@ -53,6 +53,7 @@ function createStartMock() {
|
|||
mode: mockAuthz.mode,
|
||||
},
|
||||
userProfiles: {
|
||||
getCurrent: mockUserProfiles.getCurrent,
|
||||
suggest: mockUserProfiles.suggest,
|
||||
bulkGet: mockUserProfiles.bulkGet,
|
||||
},
|
||||
|
|
|
@ -189,6 +189,7 @@ describe('Security Plugin', () => {
|
|||
},
|
||||
"userProfiles": Object {
|
||||
"bulkGet": [Function],
|
||||
"getCurrent": [Function],
|
||||
"suggest": [Function],
|
||||
},
|
||||
}
|
||||
|
|
|
@ -399,7 +399,7 @@ export class SecurityPlugin
|
|||
});
|
||||
this.session = session;
|
||||
|
||||
this.userProfileStart = this.userProfileService.start({ clusterClient });
|
||||
this.userProfileStart = this.userProfileService.start({ clusterClient, session });
|
||||
|
||||
const config = this.getConfig();
|
||||
this.authenticationStart = this.authenticationService.start({
|
||||
|
@ -444,6 +444,7 @@ export class SecurityPlugin
|
|||
mode: this.authorizationSetup!.mode,
|
||||
},
|
||||
userProfiles: {
|
||||
getCurrent: this.userProfileStart.getCurrent,
|
||||
bulkGet: this.userProfileStart.bulkGet,
|
||||
suggest: this.userProfileStart.suggest,
|
||||
},
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ObjectType } from '@kbn/config-schema';
|
||||
import type { RequestHandler, RouteConfig } from '@kbn/core/server';
|
||||
import { kibanaResponseFactory } from '@kbn/core/server';
|
||||
import { httpServerMock } from '@kbn/core/server/mocks';
|
||||
|
||||
import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock';
|
||||
import { userProfileMock } from '../../../common/model/user_profile.mock';
|
||||
import { authenticationServiceMock } from '../../authentication/authentication_service.mock';
|
||||
import type { SecurityRequestHandlerContext, SecurityRouter } from '../../types';
|
||||
import type { UserProfileServiceStartInternal } from '../../user_profile';
|
||||
import { userProfileServiceMock } from '../../user_profile/user_profile_service.mock';
|
||||
import { routeDefinitionParamsMock } from '../index.mock';
|
||||
import { defineGetCurrentUserProfileRoute } from './get_current';
|
||||
|
||||
function getMockContext() {
|
||||
return {
|
||||
licensing: {
|
||||
license: { check: jest.fn().mockReturnValue({ check: 'valid' }) },
|
||||
},
|
||||
} as unknown as SecurityRequestHandlerContext;
|
||||
}
|
||||
|
||||
describe('Get current user profile routes', () => {
|
||||
let router: jest.Mocked<SecurityRouter>;
|
||||
let userProfileService: jest.Mocked<UserProfileServiceStartInternal>;
|
||||
let authenticationService: ReturnType<typeof authenticationServiceMock.createStart>;
|
||||
beforeEach(() => {
|
||||
const routeParamsMock = routeDefinitionParamsMock.create();
|
||||
router = routeParamsMock.router;
|
||||
|
||||
userProfileService = userProfileServiceMock.createStart();
|
||||
routeParamsMock.getUserProfileService.mockReturnValue(userProfileService);
|
||||
|
||||
authenticationService = authenticationServiceMock.createStart();
|
||||
routeParamsMock.getAuthenticationService.mockReturnValue(authenticationService);
|
||||
|
||||
defineGetCurrentUserProfileRoute(routeParamsMock);
|
||||
});
|
||||
|
||||
describe('get profile for the currently authenticated user', () => {
|
||||
let routeHandler: RequestHandler<any, any, any, SecurityRequestHandlerContext>;
|
||||
let routeConfig: RouteConfig<any, any, any, any>;
|
||||
beforeEach(() => {
|
||||
const [updateRouteConfig, updateRouteHandler] = router.get.mock.calls.find(
|
||||
([{ path }]) => path === '/internal/security/user_profile'
|
||||
)!;
|
||||
|
||||
routeConfig = updateRouteConfig;
|
||||
routeHandler = updateRouteHandler;
|
||||
});
|
||||
|
||||
it('correctly defines route.', () => {
|
||||
const querySchema = (routeConfig.validate as any).query as ObjectType;
|
||||
expect(() => querySchema.validate(0)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"expected a plain object value, but found [number] instead."`
|
||||
);
|
||||
expect(() => querySchema.validate(null)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"expected a plain object value, but found [null] instead."`
|
||||
);
|
||||
|
||||
expect(querySchema.validate(undefined)).toEqual({});
|
||||
expect(querySchema.validate({})).toEqual({});
|
||||
expect(querySchema.validate({ dataPath: '*' })).toEqual({ dataPath: '*' });
|
||||
});
|
||||
|
||||
it('returns `404` if user is not available', async () => {
|
||||
authenticationService.getCurrentUser.mockReturnValue(null);
|
||||
|
||||
await expect(
|
||||
routeHandler(getMockContext(), httpServerMock.createKibanaRequest(), kibanaResponseFactory)
|
||||
).resolves.toEqual(expect.objectContaining({ status: 404 }));
|
||||
|
||||
expect(userProfileService.getCurrent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns `404` if profile is not available', async () => {
|
||||
const mockRequest = httpServerMock.createKibanaRequest();
|
||||
authenticationService.getCurrentUser.mockReturnValue(mockAuthenticatedUser());
|
||||
userProfileService.getCurrent.mockResolvedValue(null);
|
||||
|
||||
await expect(
|
||||
routeHandler(getMockContext(), mockRequest, kibanaResponseFactory)
|
||||
).resolves.toEqual(expect.objectContaining({ status: 404 }));
|
||||
|
||||
expect(userProfileService.getCurrent).toBeCalledTimes(1);
|
||||
expect(userProfileService.getCurrent).toBeCalledWith({ request: mockRequest });
|
||||
});
|
||||
|
||||
it('fails if `getCurrent` call fails.', async () => {
|
||||
const unhandledException = new Error('Something went wrong.');
|
||||
const mockRequest = httpServerMock.createKibanaRequest();
|
||||
authenticationService.getCurrentUser.mockReturnValue(mockAuthenticatedUser());
|
||||
userProfileService.getCurrent.mockRejectedValue(unhandledException);
|
||||
|
||||
await expect(
|
||||
routeHandler(getMockContext(), mockRequest, kibanaResponseFactory)
|
||||
).resolves.toEqual(expect.objectContaining({ status: 500, payload: unhandledException }));
|
||||
|
||||
expect(userProfileService.getCurrent).toBeCalledTimes(1);
|
||||
expect(userProfileService.getCurrent).toBeCalledWith({ request: mockRequest });
|
||||
});
|
||||
|
||||
it('returns user profile for the current user.', async () => {
|
||||
const mockRequest = httpServerMock.createKibanaRequest({ query: { dataPath: '*' } });
|
||||
|
||||
const mockUser = mockAuthenticatedUser();
|
||||
authenticationService.getCurrentUser.mockReturnValue(mockUser);
|
||||
|
||||
const mockProfile = userProfileMock.createWithSecurity({ uid: 'uid-1' });
|
||||
userProfileService.getCurrent.mockResolvedValue(mockProfile);
|
||||
|
||||
await expect(
|
||||
routeHandler(getMockContext(), mockRequest, kibanaResponseFactory)
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
status: 200,
|
||||
payload: {
|
||||
...mockProfile,
|
||||
user: {
|
||||
...mockProfile.user,
|
||||
authentication_provider: mockUser.authentication_provider,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(userProfileService.getCurrent).toBeCalledTimes(1);
|
||||
expect(userProfileService.getCurrent).toBeCalledWith({ request: mockRequest, dataPath: '*' });
|
||||
});
|
||||
});
|
||||
});
|
|
@ -8,48 +8,48 @@
|
|||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import type { RouteDefinitionParams } from '..';
|
||||
import type { GetUserProfileResponse } from '../../../common';
|
||||
import type { GetUserProfileResponse, UserProfileWithSecurity } from '../../../common';
|
||||
import { wrapIntoCustomErrorResponse } from '../../errors';
|
||||
import { getPrintableSessionId } from '../../session_management';
|
||||
import { createLicensedRouteHandler } from '../licensed_route_handler';
|
||||
|
||||
export function defineGetUserProfileRoute({
|
||||
export function defineGetCurrentUserProfileRoute({
|
||||
router,
|
||||
getSession,
|
||||
getUserProfileService,
|
||||
logger,
|
||||
getAuthenticationService,
|
||||
}: RouteDefinitionParams) {
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/security/user_profile',
|
||||
validate: {
|
||||
query: schema.object({ data: schema.maybe(schema.string()) }),
|
||||
query: schema.object({ dataPath: schema.maybe(schema.string()) }),
|
||||
},
|
||||
},
|
||||
createLicensedRouteHandler(async (context, request, response) => {
|
||||
const session = await getSession().get(request);
|
||||
if (!session) {
|
||||
const authenticationService = await getAuthenticationService();
|
||||
const currentUser = authenticationService.getCurrentUser(request);
|
||||
if (!currentUser) {
|
||||
return response.notFound();
|
||||
}
|
||||
|
||||
if (!session.userProfileId) {
|
||||
logger.warn(
|
||||
`User profile missing from current session. (sid: ${getPrintableSessionId(session.sid)})`
|
||||
);
|
||||
return response.notFound();
|
||||
}
|
||||
|
||||
const userProfileService = getUserProfileService();
|
||||
let profile: UserProfileWithSecurity | null;
|
||||
try {
|
||||
const profile = await userProfileService.get(session.userProfileId, request.query.data);
|
||||
const body: GetUserProfileResponse = {
|
||||
...profile,
|
||||
user: { ...profile.user, authentication_provider: session.provider },
|
||||
};
|
||||
return response.ok({ body });
|
||||
profile = await getUserProfileService().getCurrent({
|
||||
request,
|
||||
dataPath: request.query.dataPath,
|
||||
});
|
||||
} catch (error) {
|
||||
return response.customError(wrapIntoCustomErrorResponse(error));
|
||||
}
|
||||
|
||||
if (!profile) {
|
||||
return response.notFound();
|
||||
}
|
||||
|
||||
const body: GetUserProfileResponse = {
|
||||
...profile,
|
||||
user: { ...profile.user, authentication_provider: currentUser.authentication_provider },
|
||||
};
|
||||
return response.ok({ body });
|
||||
})
|
||||
);
|
||||
}
|
|
@ -7,11 +7,11 @@
|
|||
|
||||
import type { RouteDefinitionParams } from '..';
|
||||
import { defineBulkGetUserProfilesRoute } from './bulk_get';
|
||||
import { defineGetUserProfileRoute } from './get';
|
||||
import { defineGetCurrentUserProfileRoute } from './get_current';
|
||||
import { defineUpdateUserProfileDataRoute } from './update';
|
||||
|
||||
export function defineUserProfileRoutes(params: RouteDefinitionParams) {
|
||||
defineUpdateUserProfileDataRoute(params);
|
||||
defineGetUserProfileRoute(params);
|
||||
defineGetCurrentUserProfileRoute(params);
|
||||
defineBulkGetUserProfilesRoute(params);
|
||||
}
|
||||
|
|
|
@ -13,5 +13,6 @@ export type {
|
|||
UserProfileSuggestParams,
|
||||
UserProfileBulkGetParams,
|
||||
UserProfileRequiredPrivileges,
|
||||
UserProfileGetCurrentParams,
|
||||
} from './user_profile_service';
|
||||
export type { UserProfileGrant } from './user_profile_grant';
|
||||
|
|
|
@ -11,7 +11,7 @@ import { userProfileMock } from '../../common/model/user_profile.mock';
|
|||
export const userProfileServiceMock = {
|
||||
createStart: (): jest.Mocked<UserProfileServiceStartInternal> => ({
|
||||
activate: jest.fn().mockReturnValue(userProfileMock.createWithSecurity()),
|
||||
get: jest.fn(),
|
||||
getCurrent: jest.fn(),
|
||||
update: jest.fn(),
|
||||
suggest: jest.fn(),
|
||||
bulkGet: jest.fn(),
|
||||
|
|
|
@ -12,12 +12,18 @@ import type {
|
|||
SecuritySuggestUserProfilesResponse,
|
||||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
import { elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import {
|
||||
elasticsearchServiceMock,
|
||||
httpServerMock,
|
||||
loggingSystemMock,
|
||||
} from '@kbn/core/server/mocks';
|
||||
import { nextTick } from '@kbn/test-jest-helpers';
|
||||
|
||||
import type { UserProfileWithSecurity } from '../../common';
|
||||
import { userProfileMock } from '../../common/model/user_profile.mock';
|
||||
import { authorizationMock } from '../authorization/index.mock';
|
||||
import { securityMock } from '../mocks';
|
||||
import { sessionMock } from '../session_management/session.mock';
|
||||
import { UserProfileService } from './user_profile_service';
|
||||
|
||||
const logger = loggingSystemMock.createLogger();
|
||||
|
@ -26,11 +32,13 @@ const userProfileService = new UserProfileService(logger);
|
|||
describe('UserProfileService', () => {
|
||||
let mockStartParams: {
|
||||
clusterClient: ReturnType<typeof elasticsearchServiceMock.createClusterClient>;
|
||||
session: ReturnType<typeof sessionMock.create>;
|
||||
};
|
||||
let mockAuthz: ReturnType<typeof authorizationMock.create>;
|
||||
beforeEach(() => {
|
||||
mockStartParams = {
|
||||
clusterClient: elasticsearchServiceMock.createClusterClient(),
|
||||
session: sessionMock.create(),
|
||||
};
|
||||
mockAuthz = authorizationMock.create();
|
||||
|
||||
|
@ -47,53 +55,104 @@ describe('UserProfileService', () => {
|
|||
Object {
|
||||
"activate": [Function],
|
||||
"bulkGet": [Function],
|
||||
"get": [Function],
|
||||
"getCurrent": [Function],
|
||||
"suggest": [Function],
|
||||
"update": [Function],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
describe('#get', () => {
|
||||
describe('#getCurrent', () => {
|
||||
let mockUserProfile: UserProfileWithSecurity;
|
||||
let mockRequest: ReturnType<typeof httpServerMock.createKibanaRequest>;
|
||||
beforeEach(() => {
|
||||
const userProfile = userProfileMock.createWithSecurity({
|
||||
mockRequest = httpServerMock.createKibanaRequest();
|
||||
|
||||
mockUserProfile = userProfileMock.createWithSecurity({
|
||||
uid: 'UID',
|
||||
data: {
|
||||
kibana: {
|
||||
avatar: 'fun.gif',
|
||||
},
|
||||
other_app: {
|
||||
secret: 'data',
|
||||
},
|
||||
user: {
|
||||
username: 'user-1',
|
||||
display_name: 'display-name-1',
|
||||
full_name: 'full-name-1',
|
||||
realm_name: 'some-realm',
|
||||
realm_domain: 'some-domain',
|
||||
roles: ['role-1'],
|
||||
},
|
||||
});
|
||||
|
||||
mockStartParams.clusterClient.asInternalUser.security.getUserProfile.mockResolvedValue({
|
||||
[userProfile.uid]: userProfile,
|
||||
[mockUserProfile.uid]: mockUserProfile,
|
||||
} as unknown as SecurityGetUserProfileResponse);
|
||||
});
|
||||
|
||||
it('should get user profile', async () => {
|
||||
it('returns `null` if session is not available', async () => {
|
||||
mockStartParams.session.get.mockResolvedValue(null);
|
||||
|
||||
const startContract = userProfileService.start(mockStartParams);
|
||||
await expect(startContract.get('UID')).resolves.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"data": Object {
|
||||
"avatar": "fun.gif",
|
||||
},
|
||||
"enabled": true,
|
||||
"labels": Object {},
|
||||
"uid": "UID",
|
||||
"user": Object {
|
||||
"display_name": undefined,
|
||||
"email": "some@email",
|
||||
"full_name": undefined,
|
||||
"realm_domain": "some-realm-domain",
|
||||
"realm_name": "some-realm",
|
||||
"roles": Array [],
|
||||
"username": "some-username",
|
||||
},
|
||||
}
|
||||
`);
|
||||
await expect(startContract.getCurrent({ request: mockRequest })).resolves.toBeNull();
|
||||
|
||||
expect(mockStartParams.session.get).toHaveBeenCalledTimes(1);
|
||||
expect(mockStartParams.session.get).toHaveBeenCalledWith(mockRequest);
|
||||
|
||||
expect(
|
||||
mockStartParams.clusterClient.asInternalUser.security.getUserProfile
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns `null` if session available, but not user profile id', async () => {
|
||||
mockStartParams.session.get.mockResolvedValue(
|
||||
sessionMock.createValue({ userProfileId: undefined })
|
||||
);
|
||||
|
||||
const startContract = userProfileService.start(mockStartParams);
|
||||
await expect(startContract.getCurrent({ request: mockRequest })).resolves.toBeNull();
|
||||
|
||||
expect(mockStartParams.session.get).toHaveBeenCalledTimes(1);
|
||||
expect(mockStartParams.session.get).toHaveBeenCalledWith(mockRequest);
|
||||
|
||||
expect(
|
||||
mockStartParams.clusterClient.asInternalUser.security.getUserProfile
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('fails if session retrieval fails', async () => {
|
||||
const failureReason = new errors.ResponseError(
|
||||
securityMock.createApiResponse({ statusCode: 500, body: 'some message' })
|
||||
);
|
||||
mockStartParams.session.get.mockRejectedValue(failureReason);
|
||||
|
||||
const startContract = userProfileService.start(mockStartParams);
|
||||
await expect(startContract.getCurrent({ request: mockRequest })).rejects.toBe(failureReason);
|
||||
|
||||
expect(mockStartParams.session.get).toHaveBeenCalledTimes(1);
|
||||
expect(mockStartParams.session.get).toHaveBeenCalledWith(mockRequest);
|
||||
|
||||
expect(
|
||||
mockStartParams.clusterClient.asInternalUser.security.getUserProfile
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('fails if profile retrieval fails', async () => {
|
||||
mockStartParams.session.get.mockResolvedValue(
|
||||
sessionMock.createValue({ userProfileId: mockUserProfile.uid })
|
||||
);
|
||||
|
||||
const failureReason = new errors.ResponseError(
|
||||
securityMock.createApiResponse({ statusCode: 500, body: 'some message' })
|
||||
);
|
||||
mockStartParams.clusterClient.asInternalUser.security.getUserProfile.mockRejectedValue(
|
||||
failureReason
|
||||
);
|
||||
|
||||
const startContract = userProfileService.start(mockStartParams);
|
||||
await expect(startContract.getCurrent({ request: mockRequest })).rejects.toBe(failureReason);
|
||||
|
||||
expect(mockStartParams.session.get).toHaveBeenCalledTimes(1);
|
||||
expect(mockStartParams.session.get).toHaveBeenCalledWith(mockRequest);
|
||||
|
||||
expect(
|
||||
mockStartParams.clusterClient.asInternalUser.security.getUserProfile
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
mockStartParams.clusterClient.asInternalUser.security.getUserProfile
|
||||
).toHaveBeenCalledWith({
|
||||
|
@ -101,18 +160,61 @@ describe('UserProfileService', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should handle errors when get user profile fails', async () => {
|
||||
mockStartParams.clusterClient.asInternalUser.security.getUserProfile.mockRejectedValue(
|
||||
new Error('Fail')
|
||||
it('properly parses returned profile', async () => {
|
||||
mockStartParams.session.get.mockResolvedValue(
|
||||
sessionMock.createValue({ userProfileId: mockUserProfile.uid })
|
||||
);
|
||||
|
||||
const startContract = userProfileService.start(mockStartParams);
|
||||
await expect(startContract.get('UID')).rejects.toMatchInlineSnapshot(`[Error: Fail]`);
|
||||
expect(logger.error).toHaveBeenCalled();
|
||||
await expect(startContract.getCurrent({ request: mockRequest })).resolves
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"data": Object {},
|
||||
"enabled": true,
|
||||
"labels": Object {},
|
||||
"uid": "UID",
|
||||
"user": Object {
|
||||
"display_name": "display-name-1",
|
||||
"email": undefined,
|
||||
"full_name": "full-name-1",
|
||||
"realm_domain": "some-domain",
|
||||
"realm_name": "some-realm",
|
||||
"roles": Array [
|
||||
"role-1",
|
||||
],
|
||||
"username": "user-1",
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
expect(mockStartParams.session.get).toHaveBeenCalledTimes(1);
|
||||
expect(mockStartParams.session.get).toHaveBeenCalledWith(mockRequest);
|
||||
|
||||
expect(
|
||||
mockStartParams.clusterClient.asInternalUser.security.getUserProfile
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
mockStartParams.clusterClient.asInternalUser.security.getUserProfile
|
||||
).toHaveBeenCalledWith({
|
||||
uid: 'UID',
|
||||
});
|
||||
});
|
||||
|
||||
it('should get user profile and application data scoped to Kibana', async () => {
|
||||
mockStartParams.session.get.mockResolvedValue(
|
||||
sessionMock.createValue({ userProfileId: mockUserProfile.uid })
|
||||
);
|
||||
|
||||
mockStartParams.clusterClient.asInternalUser.security.getUserProfile.mockResolvedValue({
|
||||
[mockUserProfile.uid]: userProfileMock.createWithSecurity({
|
||||
...mockUserProfile,
|
||||
data: { kibana: { avatar: 'fun.gif' }, other_app: { secret: 'data' } },
|
||||
}),
|
||||
} as unknown as SecurityGetUserProfileResponse);
|
||||
|
||||
const startContract = userProfileService.start(mockStartParams);
|
||||
await expect(startContract.get('UID', '*')).resolves.toMatchInlineSnapshot(`
|
||||
await expect(startContract.getCurrent({ request: mockRequest, dataPath: '*' })).resolves
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"data": Object {
|
||||
"avatar": "fun.gif",
|
||||
|
@ -121,16 +223,25 @@ describe('UserProfileService', () => {
|
|||
"labels": Object {},
|
||||
"uid": "UID",
|
||||
"user": Object {
|
||||
"display_name": undefined,
|
||||
"email": "some@email",
|
||||
"full_name": undefined,
|
||||
"realm_domain": "some-realm-domain",
|
||||
"display_name": "display-name-1",
|
||||
"email": undefined,
|
||||
"full_name": "full-name-1",
|
||||
"realm_domain": "some-domain",
|
||||
"realm_name": "some-realm",
|
||||
"roles": Array [],
|
||||
"username": "some-username",
|
||||
"roles": Array [
|
||||
"role-1",
|
||||
],
|
||||
"username": "user-1",
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
expect(mockStartParams.session.get).toHaveBeenCalledTimes(1);
|
||||
expect(mockStartParams.session.get).toHaveBeenCalledWith(mockRequest);
|
||||
|
||||
expect(
|
||||
mockStartParams.clusterClient.asInternalUser.security.getUserProfile
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
mockStartParams.clusterClient.asInternalUser.security.getUserProfile
|
||||
).toHaveBeenCalledWith({
|
||||
|
|
|
@ -11,12 +11,20 @@ import type {
|
|||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
import type { SecurityUserProfile } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
|
||||
import type { IClusterClient, Logger } from '@kbn/core/server';
|
||||
import type { IClusterClient, KibanaRequest, Logger } from '@kbn/core/server';
|
||||
import type { PublicMethodsOf } from '@kbn/utility-types';
|
||||
|
||||
import type { UserProfile, UserProfileData, UserProfileWithSecurity } from '../../common';
|
||||
import type {
|
||||
UserProfile,
|
||||
UserProfileData,
|
||||
UserProfileLabels,
|
||||
UserProfileWithSecurity,
|
||||
} from '../../common';
|
||||
import type { AuthorizationServiceSetupInternal } from '../authorization';
|
||||
import type { CheckUserProfilesPrivilegesResponse } from '../authorization/types';
|
||||
import { getDetailedErrorMessage, getErrorStatusCode } from '../errors';
|
||||
import type { Session } from '../session_management';
|
||||
import { getPrintableSessionId } from '../session_management';
|
||||
import type { UserProfileGrant } from './user_profile_grant';
|
||||
|
||||
const KIBANA_DATA_ROOT = 'kibana';
|
||||
|
@ -30,6 +38,18 @@ const MIN_SUGGESTIONS_FOR_PRIVILEGES_CHECK = 10;
|
|||
* A set of methods to work with Kibana user profiles.
|
||||
*/
|
||||
export interface UserProfileServiceStart {
|
||||
/**
|
||||
* Retrieves a user profile for the current user extracted from the specified request. If the profile isn't available,
|
||||
* e.g. for the anonymous users or users authenticated via authenticating proxies, the `null` value is returned.
|
||||
* @param params Get current user profile operation parameters.
|
||||
* @param params.request User request instance to get user profile for.
|
||||
* @param params.dataPath By default Elasticsearch returns user information, but does not return any user data. The
|
||||
* optional "dataPath" parameter can be used to return personal data for the requested user profiles.
|
||||
*/
|
||||
getCurrent<D extends UserProfileData, L extends UserProfileLabels>(
|
||||
params: UserProfileGetCurrentParams
|
||||
): Promise<UserProfileWithSecurity<D, L> | null>;
|
||||
|
||||
/**
|
||||
* Retrieves multiple user profiles by their identifiers.
|
||||
* @param params Bulk get operation parameters.
|
||||
|
@ -61,17 +81,6 @@ export interface UserProfileServiceStartInternal extends UserProfileServiceStart
|
|||
*/
|
||||
activate(grant: UserProfileGrant): Promise<UserProfileWithSecurity>;
|
||||
|
||||
/**
|
||||
* Retrieves a single user profile by its identifier.
|
||||
* @param uid User profile identifier.
|
||||
* @param dataPath By default Elasticsearch returns user information, but does not return any user data. The optional
|
||||
* "dataPath" parameter can be used to return personal data for the requested user profile.
|
||||
*/
|
||||
get<D extends UserProfileData>(
|
||||
uid: string,
|
||||
dataPath?: string
|
||||
): Promise<UserProfileWithSecurity<D>>;
|
||||
|
||||
/**
|
||||
* Updates user preferences by identifier.
|
||||
* @param uid User ID
|
||||
|
@ -86,6 +95,7 @@ export interface UserProfileServiceSetupParams {
|
|||
|
||||
export interface UserProfileServiceStartParams {
|
||||
clusterClient: IClusterClient;
|
||||
session: PublicMethodsOf<Session>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,6 +113,22 @@ export interface UserProfileRequiredPrivileges {
|
|||
privileges: { kibana: string[] };
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for the get user profile for the current user API.
|
||||
*/
|
||||
export interface UserProfileGetCurrentParams {
|
||||
/**
|
||||
* User request instance to get user profile for.
|
||||
*/
|
||||
request: KibanaRequest;
|
||||
|
||||
/**
|
||||
* By default, get API returns user information, but does not return any user data. The optional "dataPath"
|
||||
* parameter can be used to return personal data for this user (within `kibana` namespace only).
|
||||
*/
|
||||
dataPath?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for the bulk get API.
|
||||
*/
|
||||
|
@ -194,10 +220,10 @@ export class UserProfileService {
|
|||
this.authz = authz;
|
||||
}
|
||||
|
||||
start({ clusterClient }: UserProfileServiceStartParams) {
|
||||
start({ clusterClient, session }: UserProfileServiceStartParams) {
|
||||
return {
|
||||
activate: this.activate.bind(this, clusterClient),
|
||||
get: this.get.bind(this, clusterClient),
|
||||
getCurrent: this.getCurrent.bind(this, clusterClient, session),
|
||||
bulkGet: this.bulkGet.bind(this, clusterClient),
|
||||
update: this.update.bind(this, clusterClient),
|
||||
suggest: this.suggest.bind(this, clusterClient),
|
||||
|
@ -261,29 +287,52 @@ export class UserProfileService {
|
|||
}
|
||||
|
||||
/**
|
||||
* See {@link UserProfileServiceStartInternal} for documentation.
|
||||
* See {@link UserProfileServiceStart} for documentation.
|
||||
*/
|
||||
private async get<D extends UserProfileData>(
|
||||
private async getCurrent<D extends UserProfileData>(
|
||||
clusterClient: IClusterClient,
|
||||
uid: string,
|
||||
dataPath?: string
|
||||
session: PublicMethodsOf<Session>,
|
||||
{ request, dataPath }: UserProfileGetCurrentParams
|
||||
) {
|
||||
let userSession;
|
||||
try {
|
||||
userSession = await session.get(request);
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to retrieve user session: ${getDetailedErrorMessage(error)}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!userSession) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!userSession.userProfileId) {
|
||||
this.logger.debug(
|
||||
`User profile missing from the current session [sid=${getPrintableSessionId(
|
||||
userSession.sid
|
||||
)}].`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await clusterClient.asInternalUser.security.getUserProfile({
|
||||
uid,
|
||||
uid: userSession.userProfileId,
|
||||
data: dataPath ? `${KIBANA_DATA_ROOT}.${dataPath}` : undefined,
|
||||
});
|
||||
return parseUserProfileWithSecurity<D>(body[uid]!);
|
||||
return parseUserProfileWithSecurity<D>(body[userSession.userProfileId]!);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to retrieve user profile [uid=${uid}]: ${getDetailedErrorMessage(error)}`
|
||||
`Failed to retrieve user profile for the current user [sid=${getPrintableSessionId(
|
||||
userSession.sid
|
||||
)}]: ${getDetailedErrorMessage(error)}`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link UserProfileServiceStartInternal} for documentation.
|
||||
* See {@link UserProfileServiceStart} for documentation.
|
||||
*/
|
||||
private async bulkGet<D extends UserProfileData>(
|
||||
clusterClient: IClusterClient,
|
||||
|
@ -357,7 +406,7 @@ export class UserProfileService {
|
|||
}
|
||||
|
||||
/**
|
||||
* See {@link UserProfileServiceStartInternal} for documentation.
|
||||
* See {@link UserProfileServiceStart} for documentation.
|
||||
*/
|
||||
private async suggest<D extends UserProfileData>(
|
||||
clusterClient: IClusterClient,
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { parse as parseCookie } from 'tough-cookie';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||
const security = getService('security');
|
||||
|
||||
describe('Getting user profile for the current user', () => {
|
||||
const testUserName = 'user_with_profile';
|
||||
|
||||
async function login() {
|
||||
const response = await supertestWithoutAuth
|
||||
.post('/internal/security/login')
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.send({
|
||||
providerType: 'basic',
|
||||
providerName: 'basic',
|
||||
currentURL: '/',
|
||||
params: { username: testUserName, password: 'changeme' },
|
||||
})
|
||||
.expect(200);
|
||||
return parseCookie(response.headers['set-cookie'][0])!;
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
await security.user.create(testUserName, {
|
||||
password: 'changeme',
|
||||
roles: [`viewer`],
|
||||
full_name: 'User With Profile',
|
||||
email: 'user_with_profile@elastic.co',
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await security.user.delete(testUserName);
|
||||
});
|
||||
|
||||
it('can get user profile for the current user', async () => {
|
||||
const sessionCookie = await login();
|
||||
|
||||
await supertestWithoutAuth
|
||||
.post('/internal/security/user_profile/_data')
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.set('Cookie', sessionCookie.cookieString())
|
||||
.send({ some: 'data', another: 'another-data' })
|
||||
.expect(200);
|
||||
|
||||
const { body: profileWithoutData } = await supertestWithoutAuth
|
||||
.get('/internal/security/user_profile')
|
||||
.set('Cookie', sessionCookie.cookieString())
|
||||
.expect(200);
|
||||
const { body: profileWithAllData } = await supertestWithoutAuth
|
||||
.get('/internal/security/user_profile?dataPath=*')
|
||||
.set('Cookie', sessionCookie.cookieString())
|
||||
.expect(200);
|
||||
const { body: profileWithSomeData } = await supertestWithoutAuth
|
||||
.get('/internal/security/user_profile?dataPath=some')
|
||||
.set('Cookie', sessionCookie.cookieString())
|
||||
.expect(200);
|
||||
|
||||
// Profile UID is supposed to be stable.
|
||||
expectSnapshot(profileWithoutData).toMatchInline(`
|
||||
Object {
|
||||
"data": Object {},
|
||||
"enabled": true,
|
||||
"labels": Object {},
|
||||
"uid": "u_K1WXIRQbRoHiuJylXp842IEhAO_OdqT7SDHrJSzUIjU_0",
|
||||
"user": Object {
|
||||
"authentication_provider": Object {
|
||||
"name": "basic",
|
||||
"type": "basic",
|
||||
},
|
||||
"email": "user_with_profile@elastic.co",
|
||||
"full_name": "User With Profile",
|
||||
"realm_name": "default_native",
|
||||
"roles": Array [
|
||||
"viewer",
|
||||
],
|
||||
"username": "user_with_profile",
|
||||
},
|
||||
}
|
||||
`);
|
||||
expectSnapshot(profileWithAllData).toMatchInline(`
|
||||
Object {
|
||||
"data": Object {
|
||||
"another": "another-data",
|
||||
"some": "data",
|
||||
},
|
||||
"enabled": true,
|
||||
"labels": Object {},
|
||||
"uid": "u_K1WXIRQbRoHiuJylXp842IEhAO_OdqT7SDHrJSzUIjU_0",
|
||||
"user": Object {
|
||||
"authentication_provider": Object {
|
||||
"name": "basic",
|
||||
"type": "basic",
|
||||
},
|
||||
"email": "user_with_profile@elastic.co",
|
||||
"full_name": "User With Profile",
|
||||
"realm_name": "default_native",
|
||||
"roles": Array [
|
||||
"viewer",
|
||||
],
|
||||
"username": "user_with_profile",
|
||||
},
|
||||
}
|
||||
`);
|
||||
expectSnapshot(profileWithSomeData).toMatchInline(`
|
||||
Object {
|
||||
"data": Object {
|
||||
"some": "data",
|
||||
},
|
||||
"enabled": true,
|
||||
"labels": Object {},
|
||||
"uid": "u_K1WXIRQbRoHiuJylXp842IEhAO_OdqT7SDHrJSzUIjU_0",
|
||||
"user": Object {
|
||||
"authentication_provider": Object {
|
||||
"name": "basic",
|
||||
"type": "basic",
|
||||
},
|
||||
"email": "user_with_profile@elastic.co",
|
||||
"full_name": "User With Profile",
|
||||
"realm_name": "default_native",
|
||||
"roles": Array [
|
||||
"viewer",
|
||||
],
|
||||
"username": "user_with_profile",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -11,5 +11,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
|||
describe('security APIs - User Profiles', function () {
|
||||
loadTestFile(require.resolve('./suggest'));
|
||||
loadTestFile(require.resolve('./bulk_get'));
|
||||
loadTestFile(require.resolve('./get_current'));
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue