diff --git a/CHANGELOG.md b/CHANGELOG.md index f227ebda87..12b7f67a9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to the Wazuh app project will be documented in this file. - Support for Wazuh 5.0.0 - Added creation of report definition when creating dashboard by reference and the button to reset the report [#7091](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7091) - Added a frontend http client to core plugin [#7000](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7000) +- Added serverSecurity service to core plugin [#7026](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7026) - Added an initilization service to core plugin to run the initilization tasks related to user scope [#7145](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7145) ### Removed diff --git a/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx b/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx index 98aa57da9d..b4d7764f66 100644 --- a/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx @@ -154,12 +154,10 @@ export const PromptCheckIndex = (props: { ); }; -const mapStateToProps = state => { - return { - vulnerabilitiesStatesindexPatternID: - state.appConfig.data['vulnerabilities.pattern'], - }; -}; +const mapStateToProps = state => ({ + vulnerabilitiesStatesindexPatternID: + state.appConfig.data['vulnerabilities.pattern'], +}); export const withVulnerabilitiesStateDataSource = compose( connect(mapStateToProps), diff --git a/plugins/wazuh-core/common/services/initialization/constants.ts b/plugins/wazuh-core/common/services/initialization/constants.ts index 336fb7f41e..42f427a5ea 100644 --- a/plugins/wazuh-core/common/services/initialization/constants.ts +++ b/plugins/wazuh-core/common/services/initialization/constants.ts @@ -1,4 +1,4 @@ -export const initializationTask = { +export const INITIALIZATION_TASK = { RUN_STATUS: { NOT_STARTED: 'not_started', RUNNING: 'running', diff --git a/plugins/wazuh-core/common/services/initialization/types.ts b/plugins/wazuh-core/common/services/initialization/types.ts index 0118a181de..93894a003e 100644 --- a/plugins/wazuh-core/common/services/initialization/types.ts +++ b/plugins/wazuh-core/common/services/initialization/types.ts @@ -1,13 +1,13 @@ -import { initializationTask } from './constants'; +import { INITIALIZATION_TASK } from './constants'; -type RunStatusEnum = (typeof initializationTask)['RUN_STATUS']; +type RunStatusEnum = (typeof INITIALIZATION_TASK)['RUN_STATUS']; export type InitializationTaskRunStatus = RunStatusEnum[keyof RunStatusEnum]; -type RunResultEnum = (typeof initializationTask)['RUN_RESULT']; +type RunResultEnum = (typeof INITIALIZATION_TASK)['RUN_RESULT']; export type InitializationTaskRunResult = RunResultEnum[keyof RunResultEnum]; -type ContextEnum = (typeof initializationTask)['CONTEXT']; +type ContextEnum = (typeof INITIALIZATION_TASK)['CONTEXT']; export type InitializationTaskContext = ContextEnum[keyof ContextEnum]; diff --git a/plugins/wazuh-core/docs/README.md b/plugins/wazuh-core/docs/README.md index c1bdc0b37f..42a33475ac 100644 --- a/plugins/wazuh-core/docs/README.md +++ b/plugins/wazuh-core/docs/README.md @@ -15,6 +15,8 @@ This plugin provides some core services: ## Frontend -- Configuration: manage the plugins configuration -- Utils - Constants +- Utils +- Configuration: manage the plugins configuration +- Dashboard Security: manage the security related to Wazuh dashboard +- Server Security: manage the security related to Wazuh server diff --git a/plugins/wazuh-core/public/plugin.ts b/plugins/wazuh-core/public/plugin.ts index 8b9135fc44..df8e36184b 100644 --- a/plugins/wazuh-core/public/plugin.ts +++ b/plugins/wazuh-core/public/plugin.ts @@ -10,8 +10,9 @@ import { setChrome, setCore, setUiSettings } from './plugin-services'; import * as utils from './utils'; import * as uiComponents from './components'; import { ConfigurationStore } from './utils/configuration-store'; -import { DashboardSecurity } from './utils/dashboard-security'; +import { DashboardSecurity } from './services/dashboard-security'; import * as hooks from './hooks'; +import { CoreServerSecurity } from './services'; import { CoreHTTPClient } from './services/http/http-client'; const noop = () => {}; @@ -19,7 +20,7 @@ const noop = () => {}; export class WazuhCorePlugin implements Plugin { - runtime = { setup: {} }; + runtime: Record = { setup: {}, start: {} }; internal: Record = {}; services: Record = {}; @@ -55,6 +56,8 @@ export class WazuhCorePlugin // Create dashboardSecurity this.services.dashboardSecurity = new DashboardSecurity(logger, core.http); + this.services.serverSecurity = new CoreServerSecurity(logger); + // Create http this.services.http = new CoreHTTPClient(logger, { getTimeout: async () => @@ -66,16 +69,37 @@ export class WazuhCorePlugin }); // Setup services - await this.services.dashboardSecurity.setup(); + this.runtime.setup.dashboardSecurity = + await this.services.dashboardSecurity.setup({ + updateData$: this.services.http.server.auth$, + }); this.runtime.setup.http = await this.services.http.setup({ core }); + this.runtime.setup.serverSecurity = this.services.serverSecurity.setup({ + useDashboardSecurityAccount: + this.runtime.setup.dashboardSecurity.hooks.useDashboardSecurityIsAdmin, + auth$: this.services.http.server.auth$, + useLoadingLogo: () => + this.runtime.start.serverSecurityDeps.chrome.logos.AnimatedMark, + }); + return { ...this.services, utils, API_USER_STATUS_RUN_AS, + hooks: { + ...hooks, + ...this.runtime.setup.dashboardSecurity.hooks, + ...this.runtime.setup.serverSecurity.hooks, + }, + hocs: { + ...this.runtime.setup.dashboardSecurity.hocs, + ...this.runtime.setup.serverSecurity.hocs, + }, ui: { ...uiComponents, ...this.runtime.setup.http.ui, + ...this.runtime.setup.serverSecurity.ui, }, }; } @@ -90,14 +114,27 @@ export class WazuhCorePlugin await this.services.dashboardSecurity.start(); await this.services.http.start(); + this.runtime.start.serverSecurityDeps = { + chrome: core.chrome, + }; + return { ...this.services, utils, API_USER_STATUS_RUN_AS, - hooks, + hooks: { + ...hooks, + ...this.runtime.setup.dashboardSecurity.hooks, + ...this.runtime.setup.serverSecurity.hooks, + }, + hocs: { + ...this.runtime.setup.dashboardSecurity.hocs, + ...this.runtime.setup.serverSecurity.hocs, + }, ui: { ...uiComponents, ...this.runtime.setup.http.ui, + ...this.runtime.setup.serverSecurity.ui, }, }; } diff --git a/plugins/wazuh-core/public/services/dashboard-security/README.md b/plugins/wazuh-core/public/services/dashboard-security/README.md new file mode 100644 index 0000000000..6302d85618 --- /dev/null +++ b/plugins/wazuh-core/public/services/dashboard-security/README.md @@ -0,0 +1,82 @@ +# Dashboard security + +The `dashboardSecurity` service is created in the core plugin and manage the security related to the Wazuh dashboard. + +- Fetch data about the security platform (Wazuh dashboard security enabled or disabled) +- Store information about the current user account data + - administrator + - administrator requirements +- Expose hooks and HOCs for using with ReactJS + +## Account data + +```ts +export interface DashboardSecurityServiceAccount { + administrator: boolean; // user is considered as administrator of Wazuh dashboard. This can be used for some Wazuh plugin features with no dependency of Wazuh indexer permissions + administrator_requirements: string | null; // display a message about the requirements to be administrator if the user has not an administrator +} +``` + +## Get account data + +See the [account data](#account-data). + +### Using the service + +```ts +plugins.wazuhCore.dashboardSecurity.account; +``` + +### In ReactJS components + +- hook + +```ts +const MyComponent = props => { + const [dashboardSecurityAccount, setDashboardSecurityAccount] = + getWazuhCorePlugin().hooks.useDashboardSecurityAccount(); +}; +``` + +- HOC + +```ts +const MyComponent = getWazuhCorePlugin().hocs.withDashboardSecurityAccount( + ({ dashboardSecurityAccount }) => { + // dashboardSecurityAccount contains the dashboard account data + }, +); +``` + +## Get if the user is an administrator + +Get if the user is considered as an administrator for Wazuh plugins. + +> NOTE: this consideration is not related to Wazuh indexer permissions. + +### Using the service + +```ts +plugins.wazuhCore.dashboardSecurity.account.administrator; +``` + +### In ReactJS components + +- hook + +```ts +const MyComponent = props => { + const dashboardSecurityAccountAdmin = + getWazuhCorePlugin().hooks.useDashboardSecurityAccountAdmin(); +}; +``` + +- HOC + +```ts +const MyComponent = getWazuhCorePlugin().hocs.withDashboardSecurityAccountAdmin( + ({ dashboardSecurityAccountAdmin }) => { + // dashboardSecurityAccountAdmin contains if the user is admin or not + }, +); +``` diff --git a/plugins/wazuh-core/public/services/dashboard-security/dashboard-security.ts b/plugins/wazuh-core/public/services/dashboard-security/dashboard-security.ts new file mode 100644 index 0000000000..3ac207a9c4 --- /dev/null +++ b/plugins/wazuh-core/public/services/dashboard-security/dashboard-security.ts @@ -0,0 +1,121 @@ +import { BehaviorSubject } from 'rxjs'; +import jwtDecode from 'jwt-decode'; +import { Logger } from '../../../common/services/configuration'; +import { WAZUH_ROLE_ADMINISTRATOR_ID } from '../../../common/constants'; +import { createDashboardSecurityHooks } from './ui/hooks/creator'; +import { createDashboardSecurityHOCs } from './ui/hocs/creator'; +import { + DashboardSecurityServiceAccount, + DashboardSecurityService, + DashboardSecurityServiceSetupDeps, + DashboardSecurityServiceSetupReturn, +} from './types'; + +export class DashboardSecurity implements DashboardSecurityService { + private _securityPlatform = ''; + public account$: BehaviorSubject; + + constructor( + private readonly logger: Logger, + private readonly http: { get: (path: string) => any }, + ) { + this.account$ = new BehaviorSubject({ + administrator: false, + administrator_requirements: null, + }); + } + + get securityPlatform() { + return this._securityPlatform; + } + + private async fetchCurrentPlatform() { + try { + this.logger.debug('Fetching the security platform'); + + const response = await this.http.get( + '/elastic/security/current-platform', + ); + + this._securityPlatform = response.platform; + this.logger.debug(`Security platform: ${this._securityPlatform}`); + + return this.securityPlatform; + } catch (error) { + this.logger.error(error.message); + throw error; + } + } + + get account() { + return this.account$.getValue(); + } + + async setup({ + updateData$, + }: DashboardSecurityServiceSetupDeps): Promise { + this.logger.debug('Setup'); + + let hooks, hocs; + + try { + this.logger.debug('Creating the UI utilities'); + + this.logger.debug('Creating hooks'); + hooks = createDashboardSecurityHooks({ + account$: this.account$, + }); + this.logger.debug('Created hooks'); + + this.logger.debug('Creating HOCs'); + hocs = createDashboardSecurityHOCs(hooks); + this.logger.debug('Created HOCs'); + this.logger.debug('Created the UI utilities'); + } catch (error) { + this.logger.error(`Error creating the UI utilities: ${error.message}`); + throw error; + } + + try { + this.logger.debug('Getting security platform'); + await this.fetchCurrentPlatform(); + } catch (error) { + this.logger.error( + `Error fetching the current platform: ${error.message}`, + ); + } + + // Update the dashboard security account information based on server API token + updateData$.subscribe(({ token }: { token: string }) => { + const jwtPayload: { + rbac_roles?: number[]; + } | null = token ? jwtDecode(token) : null; + + this.account$.next(this.getAccountFromJWTAPIDecodedToken(jwtPayload)); + }); + + return { + hooks, + hocs, + }; + } + + async start() {} + + async stop() {} + + private getAccountFromJWTAPIDecodedToken(decodedToken: { + rbac_roles?: number[]; + }) { + const isAdministrator = decodedToken?.rbac_roles?.some?.( + role => role === WAZUH_ROLE_ADMINISTRATOR_ID, + ); + + return { + administrator: isAdministrator, + administrator_requirements: isAdministrator + ? null + : 'User has no administrator role in the selected API connection.', + }; + } +} diff --git a/plugins/wazuh-core/public/services/dashboard-security/index.ts b/plugins/wazuh-core/public/services/dashboard-security/index.ts new file mode 100644 index 0000000000..8587beb86c --- /dev/null +++ b/plugins/wazuh-core/public/services/dashboard-security/index.ts @@ -0,0 +1,2 @@ +export * from './dashboard-security'; +export * from './types'; diff --git a/plugins/wazuh-core/public/services/dashboard-security/types.ts b/plugins/wazuh-core/public/services/dashboard-security/types.ts new file mode 100644 index 0000000000..0239b78b09 --- /dev/null +++ b/plugins/wazuh-core/public/services/dashboard-security/types.ts @@ -0,0 +1,44 @@ +import React from 'react'; +import { BehaviorSubject } from 'rxjs'; +import { WAZUH_SECURITY_PLUGIN_OPENSEARCH_DASHBOARDS_SECURITY } from '../../../common/constants'; + +export interface DashboardSecurityServiceAccount { + administrator: boolean; + administrator_requirements: string | null; +} + +export interface DashboardSecurityServiceSetupReturn { + hooks: { + useDashboardSecurityAccount: () => [ + DashboardSecurityServiceAccount, + (accountData: any) => void, + ]; + useDashboardSecurityIsAdmin: () => boolean; + }; + hocs: { + // FIXME: enhance typing + withDashboardSecurityAccount: ( + WrappedComponent: React.ElementType, + ) => (props: any) => React.ElementRef; + withDashboardSecurityAccountAdmin: ( + WrappedComponent: React.ElementType, + ) => (props: any) => React.ElementRef; + }; +} + +export interface DashboardSecurityServiceSetupDeps { + updateData$: BehaviorSubject; +} + +export interface DashboardSecurityService { + securityPlatform: + | typeof WAZUH_SECURITY_PLUGIN_OPENSEARCH_DASHBOARDS_SECURITY + | ''; + account: DashboardSecurityServiceAccount; + account$: BehaviorSubject; + setup: ( + deps: DashboardSecurityServiceSetupDeps, + ) => Promise; + start: () => void; + stop: () => void; +} diff --git a/plugins/wazuh-core/public/services/dashboard-security/ui/hocs/creator.tsx b/plugins/wazuh-core/public/services/dashboard-security/ui/hocs/creator.tsx new file mode 100644 index 0000000000..001142a9cc --- /dev/null +++ b/plugins/wazuh-core/public/services/dashboard-security/ui/hocs/creator.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { DashboardSecurityServiceSetupReturn } from '../../types'; + +export function createDashboardSecurityHOCs({ + useDashboardSecurityAccount, + useDashboardSecurityIsAdmin, +}: DashboardSecurityServiceSetupReturn['hooks']): DashboardSecurityServiceSetupReturn['hocs'] { + const withDashboardSecurityAccount = (WrappedComponent: React.ElementType) => + function WithDashboardSecurityAccount(props: any) { + const dashboardSecurityAccount = useDashboardSecurityAccount(); + + return ( + + ); + }; + + const withDashboardSecurityAccountAdmin = ( + WrappedComponent: React.ElementType, + ) => + function WithDashboardSecurityAccountAdmin(props: any) { + const dashboardSecurityAccountAdmin = useDashboardSecurityIsAdmin(); + + return ( + + ); + }; + + return { + withDashboardSecurityAccount, + withDashboardSecurityAccountAdmin, + }; +} diff --git a/plugins/wazuh-core/public/services/dashboard-security/ui/hooks/creator.ts b/plugins/wazuh-core/public/services/dashboard-security/ui/hooks/creator.ts new file mode 100644 index 0000000000..15c9a7c0e5 --- /dev/null +++ b/plugins/wazuh-core/public/services/dashboard-security/ui/hooks/creator.ts @@ -0,0 +1,23 @@ +import { + DashboardSecurityService, + DashboardSecurityServiceSetupReturn, +} from '../../types'; + +export function createDashboardSecurityHooks({ + account$, +}: { + account$: DashboardSecurityService['account$']; +}): DashboardSecurityServiceSetupReturn['hooks'] { + const setAccount = data => { + account$.next(data); + }; + + return { + useDashboardSecurityAccount: function useDashboardSecurityAccount() { + return [account$.getValue(), setAccount]; + }, + useDashboardSecurityIsAdmin: function useDashboardSecurityIsAdmin() { + return account$.getValue()?.administrator; + }, + }; +} diff --git a/plugins/wazuh-core/public/services/http/README.md b/plugins/wazuh-core/public/services/http/README.md index 96b14e9807..b155aa7b8d 100644 --- a/plugins/wazuh-core/public/services/http/README.md +++ b/plugins/wazuh-core/public/services/http/README.md @@ -18,7 +18,7 @@ This client provides a method to run the request that injects some properties re #### Request ```ts -plugins.wazuhCore.http.request('GET', '/api/check-api', {}); +plugins.wazuhCore.http.generic.request('GET', '/api/check-api', {}); ``` ## Server @@ -34,50 +34,48 @@ This client provides: #### Authentication ```ts -plugins.wazuhCore.http.auth(); +plugins.wazuhCore.http.server.auth(); ``` #### Unauthentication ```ts -plugins.wazuhCore.http.unauth(); +plugins.wazuhCore.http.server.unauth(); ``` #### Request ```ts -plugins.wazuhCore.http.request('GET', '/agents', {}); +plugins.wazuhCore.http.server.request('GET', '/agents', {}); ``` #### CSV ```ts -plugins.wazuhCore.http.csv('GET', '/agents', {}); +plugins.wazuhCore.http.server.csv('GET', '/agents', {}); ``` #### Check API id ```ts -plugins.wazuhCore.http.checkApiById('api-host-id'); +plugins.wazuhCore.http.server.checkApiById('api-host-id'); ``` #### Check API ```ts -plugins.wazuhCore.http.checkApi(apiHostData); +plugins.wazuhCore.http.server.checkApi(apiHostData); ``` -#### Get user data +#### Get authentication data -```ts -plugins.wazuhCore.http.getUserData(); -``` +The changes in the user authentication can be retrieved through the `auth$` observable. -The changes in the user data can be retrieved thourgh the `userData$` observable. +> This is used by the `serverSecurity` and `dashboardSecurity` services to update own states. ```ts -plugins.wazuhCore.http.userData$.subscribe(userData => { - // do something with the data +plugins.wazuhCore.http.server.auth$.subscribe(authenticationData => { + // do something with the authentication data }); ``` diff --git a/plugins/wazuh-core/public/services/http/server-client.test.ts b/plugins/wazuh-core/public/services/http/server-client.test.ts index 878e30a82f..0d1b200cec 100644 --- a/plugins/wazuh-core/public/services/http/server-client.test.ts +++ b/plugins/wazuh-core/public/services/http/server-client.test.ts @@ -50,40 +50,24 @@ function createClient() { } describe('Create client', () => { - it('Ensure the initial userData value', done => { + it('Authentication', done => { const { client } = createClient(); - client.userData$.subscribe(userData => { + client.auth$.subscribe(userData => { expect(userData).toEqual({ - logged: false, - token: null, - account: null, - policies: null, + token: USER_TOKEN, + policies: {}, + logged: true, }); done(); }); - }); - - it('Authentication', done => { - const { client } = createClient(); client.auth().then(data => { expect(data).toEqual({ token: USER_TOKEN, policies: {}, - account: null, logged: true, }); - - client.userData$.subscribe(userData => { - expect(userData).toEqual({ - token: USER_TOKEN, - policies: {}, - account: null, - logged: true, - }); - done(); - }); }); }); diff --git a/plugins/wazuh-core/public/services/http/server-client.ts b/plugins/wazuh-core/public/services/http/server-client.ts index 69faac8c77..7b18739119 100644 --- a/plugins/wazuh-core/public/services/http/server-client.ts +++ b/plugins/wazuh-core/public/services/http/server-client.ts @@ -9,8 +9,7 @@ * * Find more information about this on the LICENSE file. */ -import jwtDecode from 'jwt-decode'; -import { BehaviorSubject } from 'rxjs'; +import { Subject } from 'rxjs'; import { Logger } from '../../../common/services/configuration'; import { HTTPClientServer, @@ -40,20 +39,13 @@ export class WzRequest implements HTTPClientServer { overwriteHeaders?: any; }, ) => Promise; - private userData: HTTPClientServerUserData; - userData$: BehaviorSubject; + auth$: Subject; constructor( private readonly logger: Logger, private readonly services: WzRequestServices, ) { - this.userData = { - logged: false, - token: null, - account: null, - policies: null, - }; - this.userData$ = new BehaviorSubject(this.userData); + this.auth$ = new Subject(); } /** @@ -66,7 +58,7 @@ export class WzRequest implements HTTPClientServer { method: HTTPVerb, path: string, payload: any = null, - options: RequestInternalOptions, + options?: RequestInternalOptions, ): Promise { const { shouldRetry, checkCurrentApiIsUp, overwriteHeaders } = { shouldRetry: true, @@ -166,7 +158,7 @@ export class WzRequest implements HTTPClientServer { method: HTTPVerb, path: string, body: any, - options: RequestOptions, + options?: RequestOptions, ): Promise> { const { checkCurrentApiIsUp, @@ -312,28 +304,15 @@ export class WzRequest implements HTTPClientServer { // Decode token and get expiration time // eslint-disable-next-line @typescript-eslint/no-unused-vars - const jwtPayload = jwtDecode(token); // Get user Policies const userPolicies = await this.getUserPolicies(); - // Dispatch actions to set permissions and administrator consideration - // TODO: implement - // store.dispatch(updateUserPermissions(userPolicies)); - // store.dispatch( - // updateUserAccount( - // getWazuhCorePlugin().dashboardSecurity.getAccountFromJWTAPIDecodedToken( - // jwtPayload, - // ), - // ), - // ); - // store.dispatch(updateWithUserLogged(true)); const data = { token, policies: userPolicies, - account: null, // TODO: implement logged: true, }; - this.updateUserData(data); + this.auth$.next(data); return data; } catch (error) { @@ -349,19 +328,11 @@ export class WzRequest implements HTTPClientServer { // }, // }; // getErrorOrchestrator().handleError(options); - // store.dispatch( - // updateUserAccount( - // getWazuhCorePlugin().dashboardSecurity.getAccountFromJWTAPIDecodedToken( - // {}, // This value should cause the user is not considered as an administrator - // ), - // ), - // ); - // store.dispatch(updateWithUserLogged(true)); - this.updateUserData({ + this.auth$.next({ token: null, policies: null, - account: null, // TODO: implement logged: true, + error, }); throw error; } @@ -419,15 +390,6 @@ export class WzRequest implements HTTPClientServer { } } - /** - * Update the internal user data and emit the value to the subscribers of userData$ - * @param data - */ - private updateUserData(data: HTTPClientServerUserData) { - this.userData = data; - this.userData$.next(this.getUserData()); - } - async checkAPIById(serverHostId: string, idChanged = false) { try { const timeout = await this.services.getTimeout(); diff --git a/plugins/wazuh-core/public/services/http/types.ts b/plugins/wazuh-core/public/services/http/types.ts index ce0e47f558..976f02aca6 100644 --- a/plugins/wazuh-core/public/services/http/types.ts +++ b/plugins/wazuh-core/public/services/http/types.ts @@ -22,7 +22,6 @@ export interface HTTPClientGeneric { export interface HTTPClientServerUserData { token: string | null; policies: any | null; - account: any | null; logged: boolean; } @@ -39,7 +38,7 @@ export interface HTTPClientServer { csv: (path: string, filters: any) => Promise; auth: (force: boolean) => Promise; unauth: (force: boolean) => Promise; - userData$: BehaviorSubject; + auth$: BehaviorSubject; getUserData: () => HTTPClientServerUserData; } diff --git a/plugins/wazuh-core/public/services/index.ts b/plugins/wazuh-core/public/services/index.ts new file mode 100644 index 0000000000..fac2bc12b4 --- /dev/null +++ b/plugins/wazuh-core/public/services/index.ts @@ -0,0 +1 @@ +export * from './server-security'; diff --git a/plugins/wazuh-core/public/services/server-security/README.md b/plugins/wazuh-core/public/services/server-security/README.md new file mode 100644 index 0000000000..609943a8ae --- /dev/null +++ b/plugins/wazuh-core/public/services/server-security/README.md @@ -0,0 +1,55 @@ +# Server security + +The `serverSecurity` service is created in the core plugin and manage the security related to the Wazuh server. + +- Permissions + +## Features + +### Service + +- Expose methods to check the missing permission for the current user or a generic method + +### Others + +The service creates in the `setup` lifecycle method the following resources: + +- hooks + - useServerUserPermissions: the permissions of the logged user + ```tsx + const userPermissions = useServerUserPermissions(); + ``` + - useServerUserPermissionsRequirements: the missing permissions of the required permissions for the logged user + ```tsx + const [missingPermissions, userPermissions] = + useServerUserPermissionsRequirements(requiredPermissions); + ``` + - useServerUserPermissionsIsAdminRequirements: the missing requirements for "administrator users" + ```tsx + const [administratorRequirements, userSession] = + useServerUserPermissionsIsAdminRequirements(); + ``` + - useServerUserLogged: user is logged status + ```tsx + const useIsLogged = useServerUserLogged(); + ``` +- HOCs + - withServerUserAuthorizationPromptChanged: + ```tsx + withServerUserAuthorizationPromptChanged(permissions, { + isAdmininistrator: true, + })(WrappedComponent); + ``` + - withServerUserLogged: when the user is not logged, display a loading + ```tsx + withServerUserLogged(WrappedComponent); + ``` + - withServerUserAuthorizationPrompt: + ```tsx + withServerUserAuthorizationPrompt(permissions, { isAdmininistrator: true })( + WrappedComponent, + ); + ``` +- UI components + - ServerButtonPermissions + - ServerElementPermissions diff --git a/plugins/wazuh-core/public/services/server-security/index.ts b/plugins/wazuh-core/public/services/server-security/index.ts new file mode 100644 index 0000000000..771c8730ca --- /dev/null +++ b/plugins/wazuh-core/public/services/server-security/index.ts @@ -0,0 +1,2 @@ +export * from './types'; +export { CoreServerSecurity } from './server-security'; diff --git a/plugins/wazuh-core/public/services/server-security/server-security.ts b/plugins/wazuh-core/public/services/server-security/server-security.ts new file mode 100644 index 0000000000..390b7d3b6d --- /dev/null +++ b/plugins/wazuh-core/public/services/server-security/server-security.ts @@ -0,0 +1,92 @@ +import { BehaviorSubject } from 'rxjs'; +import { Logger } from '../../../common/services/configuration'; +import { checkMissingUserPermissions } from './wz-user-permissions'; +import { + ServerSecurity, + ServerSecurityCombinedPermission, + ServerSecuritySetupDeps, + ServerSecuritySetupReturn, +} from './types'; +import { createServerSecurityHooks } from './ui/hooks/creator'; +import { createServerSecurityHOCS } from './ui/hocs/creator'; +import { createServerSecurityUI } from './ui/components/creator'; +import { LoadingServerUserLogging } from './ui/components/loading'; +import { WzEmptyPromptNoPermissions } from './ui/components/prompt'; + +export class CoreServerSecurity implements ServerSecurity { + public serverSecurityUserData$: BehaviorSubject<{ + logged: boolean; + policies: any; + token: string; + }>; + + constructor(private readonly logger: Logger) { + this.serverSecurityUserData$ = new BehaviorSubject({ + logged: false, + policies: null, + token: null, + }); + } + + setup(deps: ServerSecuritySetupDeps): ServerSecuritySetupReturn { + this.logger.debug('Setup'); + + this.logger.debug('Creating runtime hooks'); + + // Update the user server security information based on the authentication + deps.auth$.subscribe(data => this.serverSecurityUserData$.next(data)); + + const hooks = createServerSecurityHooks({ + ...deps, + serverSecurityUserData$: this.serverSecurityUserData$, + checkMissingUserPermissions: this.checkMissingUserPermissions, + }); + + this.logger.debug('Created runtime hooks'); + + this.logger.debug('Creating runtime HOCs'); + + const hocs = createServerSecurityHOCS({ + ...deps, + ...hooks, + LoadingServerUserLogging, + PromptNoPermissions: WzEmptyPromptNoPermissions, + }); + + this.logger.debug('Created runtime HOCs'); + + this.logger.debug('Creating UI components'); + + const ui = createServerSecurityUI(hooks); + + this.logger.debug('Creating UI components'); + + this.logger.debug('Setup finished'); + + return { + hooks, + hocs, + ui, + }; + } + + start() {} + + stop() {} + + checkMissingUserPermissions( + requiredPermissions: ServerSecurityCombinedPermission[], + userPermissions: any, + ) { + return checkMissingUserPermissions(requiredPermissions, userPermissions); + } + + getMissingUserPermissions( + requiredPermissions: ServerSecurityCombinedPermission[], + ) { + return checkMissingUserPermissions( + requiredPermissions, + this.serverSecurityUserData$.getValue(), + ); + } +} diff --git a/plugins/wazuh-core/public/services/server-security/types.ts b/plugins/wazuh-core/public/services/server-security/types.ts new file mode 100644 index 0000000000..79b3e94e41 --- /dev/null +++ b/plugins/wazuh-core/public/services/server-security/types.ts @@ -0,0 +1,66 @@ +import React from 'react'; +import { BehaviorSubject, Subject } from 'rxjs'; + +export interface ServerSecurityPermission { + action: string; + resource: string; +} + +export type ServerSecurityCombinedPermission = + | ServerSecurityPermission + | ServerSecurityPermission[]; + +export type ServerSecurityCombinedPermissionWithFunction = + | ServerSecurityCombinedPermission + | ((props: any) => ServerSecurityPermission); + +export interface ServerSecuritySetupDeps { + auth$: Subject; + useDashboardSecurityAccount: () => { + administrator_requirements: string | null; + }; + useLoadingLogo: () => { url: string; type: string }; +} + +export interface ServerSecuritySetupReturn { + hooks: { + useServerUserLogged: () => boolean; + useServerUserPermissions: () => any; + useServerUserPermissionsRequirements: ( + permissions: ServerSecurityCombinedPermissionWithFunction, + ) => [ServerSecurityCombinedPermission, any]; + }; + hocs: { + withServerUserAuthorizationPrompt: ( + permissions: ServerSecurityCombinedPermissionWithFunction | null, + otherPermissions: { isAdmininistrator: boolean | null }, + ) => (WrappedComponent: React.ComponentType) => React.ReactElement; + withServerUserLogged: ( + WrappedComponent: React.ComponentType, + ) => React.ReactElement; + }; + ui: { + ServerButtonPermissions: React.ComponentType; + ServerElementPermissions: React.ComponentType; + }; +} + +export interface ServerSecurityUserData { + logged: boolean; + policies: any; + token: string; +} + +export interface ServerSecurity { + serverSecurityUserData$: BehaviorSubject; + setup: (deps: ServerSecuritySetupDeps) => ServerSecuritySetupReturn; + start: () => void; + stop: () => void; + checkMissingUserPermissions: ( + requiredPermissions: ServerSecurityCombinedPermission[], + userPermissions: any, + ) => ServerSecurityCombinedPermission[] | false; + getMissingUserPermissions: ( + requiredPermissions: ServerSecurityCombinedPermission[], + ) => ServerSecurityCombinedPermission[] | false; +} diff --git a/plugins/wazuh-core/public/services/server-security/ui/components/button.tsx b/plugins/wazuh-core/public/services/server-security/ui/components/button.tsx new file mode 100644 index 0000000000..c57188b5c4 --- /dev/null +++ b/plugins/wazuh-core/public/services/server-security/ui/components/button.tsx @@ -0,0 +1,93 @@ +/* + * Wazuh app - Button with Wazuh API permissions and/or roles required to be useful + * Copyright (C) 2015-2022 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + */ + +import React from 'react'; +import { + EuiSwitch, + EuiButton, + EuiButtonEmpty, + EuiButtonIcon, + EuiLink, +} from '@elastic/eui'; +import { + ServerElementPermissionsProps, + ServerElementPermissions, +} from './element'; + +enum SERVER_BUTTON_PERMISSIONS_TYPES { + DEFAULT = 'default', + EMPTY = 'empty', + ICON = 'icon', + LINK = 'link', + SWITCH = 'switch', +} + +interface IServerButtonPermissionsProps + extends Omit< + ServerElementPermissionsProps, + 'children' | 'additionalPropsFunction' + > { + buttonType?: SERVER_BUTTON_PERMISSIONS_TYPES; + rest: any; +} + +const ButtonsMap = { + [SERVER_BUTTON_PERMISSIONS_TYPES.DEFAULT]: EuiButton, + [SERVER_BUTTON_PERMISSIONS_TYPES.EMPTY]: EuiButtonEmpty, + [SERVER_BUTTON_PERMISSIONS_TYPES.ICON]: EuiButtonIcon, + [SERVER_BUTTON_PERMISSIONS_TYPES.LINK]: EuiLink, + [SERVER_BUTTON_PERMISSIONS_TYPES.SWITCH]: EuiSwitch, +}; + +export const ServerButtonPermissions = ({ + buttonType = SERVER_BUTTON_PERMISSIONS_TYPES.DEFAULT, + permissions, + administrator, + tooltip, + ...rest +}: IServerButtonPermissionsProps) => { + const Button = ButtonsMap[buttonType] || ButtonsMap['default']; + + return ( + { + const additionalProps = { + ...([ + SERVER_BUTTON_PERMISSIONS_TYPES.LINK, + SERVER_BUTTON_PERMISSIONS_TYPES.SWITCH, + ].includes(buttonType) + ? { disabled } + : { isDisabled: disabled }), + onClick: disabled || !rest.onClick ? undefined : rest.onClick, + onChange: + !disabled || + rest.onChange || + buttonType === SERVER_BUTTON_PERMISSIONS_TYPES.SWITCH + ? rest.onChange + : undefined, + }; + + if (buttonType === SERVER_BUTTON_PERMISSIONS_TYPES.SWITCH) { + delete additionalProps.onClick; + } + + return additionalProps; + }} + {...rest} + > +