diff --git a/CHANGELOG.md b/CHANGELOG.md index 02e3c3793a..5ba48d92e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to the Wazuh app project will be documented in this file. - Added a title to the agent name input of the deploy a new agent section. [#5429](https://github.com/wazuh/wazuh-kibana-app/pull/5429) - Added callout below the agent name entry of the deploy a new agent section. [#5429](https://github.com/wazuh/wazuh-kibana-app/pull/5429) - Added new CLI to generate API data from specification file [#5519](https://github.com/wazuh/wazuh-kibana-app/pull/5519) +- Added specific RBAC permissions to Security section [#5551](https://github.com/wazuh/wazuh-kibana-app/pull/5551) ### Changed diff --git a/docker/imposter/security/security-actions.json b/docker/imposter/security/security-actions.json new file mode 100644 index 0000000000..88ba661fa8 --- /dev/null +++ b/docker/imposter/security/security-actions.json @@ -0,0 +1,716 @@ +{ + "data": { + "active-response:command": { + "description": "Execute active response commands in the agents", + "resources": ["agent:id", "agent:group"], + "example": { + "actions": ["active-response:command"], + "resources": ["agent:id:001", "agent:group:atlantic"], + "effect": "allow" + }, + "related_endpoints": ["PUT /active-response"] + }, + "agent:delete": { + "description": "Delete agents", + "resources": ["agent:id", "agent:group"], + "example": { + "actions": ["agent:delete"], + "resources": ["agent:id:010", "agent:group:pacific"], + "effect": "allow" + }, + "related_endpoints": ["DELETE /agents"] + }, + "agent:read": { + "description": "Access agents information (id, name, group, last keep alive, etc)", + "resources": ["agent:id", "agent:group"], + "example": { + "actions": ["agent:read"], + "resources": ["agent:id:*"], + "effect": "allow" + }, + "related_endpoints": [ + "GET /agents", + "GET /agents/{agent_id}/config/{component}/{configuration}", + "GET /agents/{agent_id}/group/is_sync", + "GET /agents/{agent_id}/key", + "GET /agents/{agent_id}/daemons/stats", + "GET /agents/{agent_id}/stats/{component}", + "GET /groups/{group_id}/agents", + "GET /agents/no_group", + "GET /agents/outdated", + "GET /agents/stats/distinct", + "GET /agents/summary/os", + "GET /agents/summary/status", + "GET /overview/agents" + ] + }, + "agent:create": { + "description": "Create new agents", + "resources": ["*:*"], + "example": { + "actions": ["agent:create"], + "resources": ["*:*:*"], + "effect": "allow" + }, + "related_endpoints": [ + "POST /agents", + "POST /agents/insert", + "POST /agents/insert/quick" + ] + }, + "agent:modify_group": { + "description": "Change the group of agents", + "resources": ["agent:id", "agent:group"], + "example": { + "actions": ["agent:modify_group"], + "resources": ["agent:id:004", "agent:group:us-east"], + "effect": "allow" + }, + "related_endpoints": [ + "DELETE /agents/{agent_id}/group", + "DELETE /agents/{agent_id}/group/{group_id}", + "PUT /agents/{agent_id}/group/{group_id}", + "DELETE /agents/group", + "PUT /agents/group" + ] + }, + "group:modify_assignments": { + "description": "Change the agents assigned to the group", + "resources": ["group:id"], + "example": { + "actions": ["group:modify_assignments"], + "resources": ["group:id:*"], + "effect": "allow" + }, + "related_endpoints": [ + "DELETE /agents/{agent_id}/group", + "DELETE /agents/{agent_id}/group/{group_id}", + "PUT /agents/{agent_id}/group/{group_id}", + "DELETE /agents/group", + "PUT /agents/group" + ] + }, + "agent:restart": { + "description": "Restart agents", + "resources": ["agent:id", "agent:group"], + "example": { + "actions": ["agent:restart"], + "resources": ["agent:id:050", "agent:id:049"], + "effect": "deny" + }, + "related_endpoints": [ + "PUT /agents/{agent_id}/restart", + "PUT /agents/group/{group_id}/restart", + "PUT /agents/node/{node_id}/restart", + "PUT /agents/restart" + ] + }, + "agent:upgrade": { + "description": "Upgrade the version of the agents", + "resources": ["agent:id", "agent:group"], + "example": { + "actions": ["agent:upgrade"], + "resources": ["agent:id:001", "agent:group:mediterranean"], + "effect": "allow" + }, + "related_endpoints": [ + "PUT /agents/upgrade", + "PUT /agents/upgrade_custom", + "GET /agents/upgrade_result" + ] + }, + "group:delete": { + "description": "Delete agent groups", + "resources": ["group:id"], + "example": { + "actions": ["group:delete"], + "resources": ["group:id:*"], + "effect": "allow" + }, + "related_endpoints": ["DELETE /groups"] + }, + "group:read": { + "description": "Access agent groups information (id, name, agents, etc)", + "resources": ["group:id"], + "example": { + "actions": ["group:create"], + "resources": ["group:id:*"], + "effect": "allow" + }, + "related_endpoints": [ + "GET /groups", + "GET /groups/{group_id}/agents", + "GET /groups/{group_id}/configuration", + "GET /groups/{group_id}/files", + "GET /groups/{group_id}/files/{file_name}/json", + "GET /groups/{group_id}/files/{file_name}/xml", + "GET /overview/agents" + ] + }, + "group:create": { + "description": "Create new agent groups", + "resources": ["*:*"], + "example": { + "actions": ["group:create"], + "resources": ["*:*:*"], + "effect": "allow" + }, + "related_endpoints": ["POST /groups"] + }, + "group:update_config": { + "description": "Change the configuration of agent groups", + "resources": ["group:id"], + "example": { + "actions": ["group:update_config"], + "resources": ["group:id:*"], + "effect": "deny" + }, + "related_endpoints": ["PUT /groups/{group_id}/configuration"] + }, + "cluster:read": { + "description": "Read Wazuh's cluster nodes configuration", + "resources": ["node:id"], + "example": { + "actions": ["cluster:read"], + "resources": ["node:id:worker1", "node:id:worker3"], + "effect": "deny" + }, + "related_endpoints": [ + "PUT /agents/node/{node_id}/restart", + "GET /cluster/local/info", + "GET /cluster/nodes", + "GET /cluster/healthcheck", + "GET /cluster/ruleset/synchronization", + "GET /cluster/local/config", + "GET /cluster/{node_id}/status", + "GET /cluster/{node_id}/info", + "GET /cluster/{node_id}/configuration", + "GET /cluster/{node_id}/daemons/stats", + "GET /cluster/{node_id}/stats", + "GET /cluster/{node_id}/stats/hourly", + "GET /cluster/{node_id}/stats/weekly", + "GET /cluster/{node_id}/stats/analysisd", + "GET /cluster/{node_id}/stats/remoted", + "GET /cluster/{node_id}/logs", + "GET /cluster/{node_id}/logs/summary", + "PUT /cluster/restart", + "GET /cluster/configuration/validation", + "GET /cluster/{node_id}/configuration/{component}/{configuration}" + ] + }, + "agent:reconnect": { + "description": "Force reconnect agents", + "resources": ["agent:id", "agent:group"], + "example": { + "actions": ["agent:reconnect"], + "resources": ["agent:id:050", "agent:id:049"], + "effect": "deny" + }, + "related_endpoints": ["PUT /agents/reconnect"] + }, + "ciscat:read": { + "description": "Access CIS-CAT results for agents", + "resources": ["agent:id", "agent:group"], + "example": { + "actions": ["ciscat:read"], + "resources": ["agent:id:001", "agent:id:003", "agent:group:default"], + "effect": "deny" + }, + "related_endpoints": [ + "GET /ciscat/{agent_id}/results", + "GET /experimental/ciscat/results" + ] + }, + "cluster:status": { + "description": "Check Wazuh's cluster general status", + "resources": ["*:*"], + "example": { + "actions": ["cluster:status"], + "resources": ["*:*:*"], + "effect": "allow" + }, + "related_endpoints": ["GET /cluster/status"] + }, + "cluster:read_api_config": { + "description": "Check Wazuh's cluster nodes API configuration", + "resources": ["*:*"], + "example": { + "actions": ["cluster:read_api_config"], + "resources": ["node:id:worker1", "node:id:worker3"], + "effect": "allow" + }, + "related_endpoints": ["GET /cluster/api/config"] + }, + "cluster:update_config": { + "description": "Change the Wazuh's cluster node configuration", + "resources": ["node:id"], + "example": { + "actions": ["cluster:update_config"], + "resources": ["node:id:worker1"], + "effect": "allow" + }, + "related_endpoints": ["PUT /cluster/{node_id}/configuration"] + }, + "cluster:restart": { + "description": "Restart Wazuh's cluster nodes", + "resources": ["node:id"], + "example": { + "actions": ["cluster:restart"], + "resources": ["node:id:worker1"], + "effect": "allow" + }, + "related_endpoints": ["PUT /cluster/restart"] + }, + "lists:read": { + "description": "Read cdb lists files", + "resources": ["list:file"], + "example": { + "actions": ["lists:read"], + "resources": ["list:file:audit-keys"], + "effect": "deny" + }, + "related_endpoints": [ + "GET /lists", + "GET /lists/files/{filename}", + "GET /lists/files" + ] + }, + "lists:update": { + "description": "Update or upload cdb lists files", + "resources": ["*:*"], + "example": { + "actions": ["lists:update"], + "resources": ["*:*:*"], + "effect": "allow" + }, + "related_endpoints": ["PUT /lists/files/{filename}"] + }, + "lists:delete": { + "description": "Delete cdb lists files", + "resources": ["list:file"], + "example": { + "actions": ["lists:delete"], + "resources": ["list:file:audit-keys"], + "effect": "deny" + }, + "related_endpoints": [ + "PUT /lists/files/{filename}", + "DELETE /lists/files/{filename}" + ] + }, + "logtest:run": { + "description": "Run logtest tool or end a logtest session", + "resources": ["*:*"], + "example": { + "actions": ["logtest:run"], + "resources": ["*:*:*"], + "effect": "allow" + }, + "related_endpoints": ["PUT /logtest", "DELETE /logtest/sessions/{token}"] + }, + "manager:read": { + "description": "Read Wazuh manager configuration", + "resources": ["*:*"], + "example": { + "actions": ["manager:read"], + "resources": ["*:*:*"], + "effect": "allow" + }, + "related_endpoints": [ + "GET /manager/status", + "GET /manager/info", + "GET /manager/configuration", + "GET /manager/daemons/stats", + "GET /manager/stats", + "GET /manager/stats/hourly", + "GET /manager/stats/weekly", + "GET /manager/stats/analysisd", + "GET /manager/stats/remoted", + "GET /manager/logs", + "GET /manager/logs/summary", + "PUT /manager/restart", + "GET /manager/configuration/validation", + "GET /manager/configuration/{component}/{configuration}" + ] + }, + "manager:update_config": { + "description": "Update current Wazuh manager configuration", + "resources": ["*:*"], + "example": { + "actions": ["manager:update_config"], + "resources": ["*:*:*"], + "effect": "allow" + }, + "related_endpoints": ["PUT /manager/configuration"] + }, + "manager:read_api_config": { + "description": "Read Wazuh manager API configuration", + "resources": ["*:*"], + "example": { + "actions": ["manager:read_api_config"], + "resources": ["*:*:*"], + "effect": "allow" + }, + "related_endpoints": ["GET /manager/api/config"] + }, + "manager:restart": { + "description": "Restart Wazuh managers", + "resources": ["*:*"], + "example": { + "actions": ["manager:restart"], + "resources": ["*:*:*"], + "effect": "deny" + }, + "related_endpoints": ["PUT /manager/restart"] + }, + "mitre:read": { + "description": "Access information from MITRE database", + "resources": ["*:*"], + "example": { + "actions": ["mitre:read"], + "resources": ["*:*:*"], + "effect": "allow" + }, + "related_endpoints": [ + "GET /mitre/groups", + "GET /mitre/metadata", + "GET /mitre/mitigations", + "GET /mitre/references", + "GET /mitre/software", + "GET /mitre/tactics", + "GET /mitre/techniques" + ] + }, + "rootcheck:run": { + "description": "Run agents rootcheck scan", + "resources": ["agent:id", "agent:group"], + "example": { + "actions": ["rootcheck:run"], + "resources": ["agent:id:*"], + "effect": "allow" + }, + "related_endpoints": ["PUT /rootcheck"] + }, + "rootcheck:read": { + "description": "Access information from agents rootcheck database", + "resources": ["agent:id", "agent:group"], + "example": { + "actions": ["rootcheck:read"], + "resources": ["agent:id:011"], + "effect": "allow" + }, + "related_endpoints": [ + "GET /rootcheck/{agent_id}", + "GET /rootcheck/{agent_id}/last_scan" + ] + }, + "rootcheck:clear": { + "description": "Clear the agents rootcheck database", + "resources": ["agent:id", "agent:group"], + "example": { + "actions": ["rootcheck:clear"], + "resources": ["agent:id:*"], + "effect": "deny" + }, + "related_endpoints": [ + "DELETE /rootcheck/{agent_id}", + "DELETE /experimental/rootcheck" + ] + }, + "rules:read": { + "description": "Read rules files", + "resources": ["rule:file"], + "example": { + "actions": ["rules:read"], + "resources": ["rule:file:0610-win-ms_logs_rules.xml"], + "effect": "allow" + }, + "related_endpoints": [ + "GET /rules", + "GET /rules/groups", + "GET /rules/requirement/{requirement}", + "GET /rules/files", + "GET /rules/files/{filename}" + ] + }, + "rules:update": { + "description": "Update or upload custom rule files", + "resources": ["*:*"], + "example": { + "actions": ["rules:update"], + "resources": ["*:*:*"], + "effect": "allow" + }, + "related_endpoints": ["PUT /rules/files/{filename}"] + }, + "rules:delete": { + "description": "Delete custom rule files", + "resources": ["rule:file"], + "example": { + "actions": ["rules:delete"], + "resources": ["rule:file:0610-win-ms_logs_rules.xml"], + "effect": "allow" + }, + "related_endpoints": [ + "PUT /rules/files/{filename}", + "DELETE /rules/files/{filename}" + ] + }, + "sca:read": { + "description": "Access agents security configuration assessment", + "resources": ["agent:id", "agent:group"], + "example": { + "actions": ["sca:read"], + "resources": ["agent:id:*"], + "effect": "allow" + }, + "related_endpoints": [ + "GET /sca/{agent_id}", + "GET /sca/{agent_id}/checks/{policy_id}" + ] + }, + "syscheck:run": { + "description": "Run agents syscheck scan", + "resources": ["agent:id", "agent:group"], + "example": { + "actions": ["syscheck:run"], + "resources": ["agent:id:*"], + "effect": "allow" + }, + "related_endpoints": ["PUT /syscheck"] + }, + "syscheck:read": { + "description": "Access information from agents syscheck database", + "resources": ["agent:id", "agent:group"], + "example": { + "actions": ["syscheck:read"], + "resources": ["agent:id:011", "agent:group:us-west"], + "effect": "allow" + }, + "related_endpoints": [ + "GET /syscheck/{agent_id}", + "GET /syscheck/{agent_id}/last_scan" + ] + }, + "syscheck:clear": { + "description": "Clear the agents syscheck database", + "resources": ["agent:id", "agent:group"], + "example": { + "actions": ["syscheck:clear"], + "resources": ["agent:id:*"], + "effect": "deny" + }, + "related_endpoints": [ + "DELETE /syscheck/{agent_id}", + "DELETE /experimental/syscheck" + ] + }, + "decoders:read": { + "description": "Read decoders files", + "resources": ["decoder:file"], + "example": { + "actions": ["decoders:read"], + "resources": ["decoder:file:*"], + "effect": "allow" + }, + "related_endpoints": [ + "GET /decoders", + "GET /decoders/files", + "GET /decoders/files/{filename}", + "GET /decoders/parents" + ] + }, + "decoders:update": { + "description": "Update or upload custom decoder files", + "resources": ["*:*"], + "example": { + "actions": ["decoders:update"], + "resources": ["*:*:*"], + "effect": "allow" + }, + "related_endpoints": ["PUT /decoders/files/{filename}"] + }, + "decoders:delete": { + "description": "Delete custom decoder files", + "resources": ["decoder:file"], + "example": { + "actions": ["decoders:delete"], + "resources": ["decoder:file:local_decoder.xml"], + "effect": "allow" + }, + "related_endpoints": [ + "PUT /decoders/files/{filename}", + "DELETE /decoders/files/{filename}" + ] + }, + "syscollector:read": { + "description": "Access agents syscollector information", + "resources": ["agent:id", "agent:group"], + "example": { + "actions": ["syscollector:read"], + "resources": ["agent:id:*"], + "effect": "allow" + }, + "related_endpoints": [ + "GET /experimental/syscollector/hardware", + "GET /experimental/syscollector/netaddr", + "GET /experimental/syscollector/netiface", + "GET /experimental/syscollector/netproto", + "GET /experimental/syscollector/os", + "GET /experimental/syscollector/packages", + "GET /experimental/syscollector/ports", + "GET /experimental/syscollector/processes", + "GET /experimental/syscollector/hotfixes", + "GET /syscollector/{agent_id}/hardware", + "GET /syscollector/{agent_id}/hotfixes", + "GET /syscollector/{agent_id}/netaddr", + "GET /syscollector/{agent_id}/netiface", + "GET /syscollector/{agent_id}/netproto", + "GET /syscollector/{agent_id}/os", + "GET /syscollector/{agent_id}/packages", + "GET /syscollector/{agent_id}/ports", + "GET /syscollector/{agent_id}/processes" + ] + }, + "security:edit_run_as": { + "description": "Change the value of the allow_run_as flag for a user", + "resources": ["*:*"], + "example": { + "actions": ["security:edit_run_as"], + "resources": ["*:*:*"], + "effect": "allow" + }, + "related_endpoints": ["PUT /security/users/{user_id}/run_as"] + }, + "security:read": { + "description": "Access information about system security resources", + "resources": ["policy:id", "role:id", "user:id", "rule:id"], + "example": { + "actions": ["security:read"], + "resources": ["policy:id:*", "role:id:2", "user:id:5", "rule:id:3"], + "effect": "allow" + }, + "related_endpoints": [ + "GET /security/users", + "GET /security/roles", + "GET /security/rules", + "GET /security/policies" + ] + }, + "security:create_user": { + "description": "Create new system users", + "resources": ["*:*"], + "example": { + "actions": ["security:create_user"], + "resources": ["*:*:*"], + "effect": "allow" + }, + "related_endpoints": ["POST /security/users"] + }, + "security:delete": { + "description": "Delete system security resources", + "resources": ["policy:id", "role:id", "user:id", "rule:id"], + "example": { + "actions": ["security:update"], + "resources": ["policy:id:*", "role:id:3", "user:id:4", "rule:id:2"], + "effect": "deny" + }, + "related_endpoints": [ + "DELETE /security/users", + "DELETE /security/roles", + "DELETE /security/rules", + "DELETE /security/policies", + "DELETE /security/users/{user_id}/roles", + "DELETE /security/roles/{role_id}/policies", + "DELETE /security/roles/{role_id}/rules" + ] + }, + "security:update": { + "description": "Update the information of system security resources", + "resources": ["policy:id", "role:id", "user:id", "rule:id"], + "example": { + "actions": ["security:update"], + "resources": ["policy:id:*", "role:id:4", "user:id:3", "rule:id:4"], + "effect": "deny" + }, + "related_endpoints": [ + "PUT /security/users/{user_id}", + "PUT /security/roles/{role_id}", + "PUT /security/rules/{rule_id}", + "PUT /security/policies/{policy_id}", + "POST /security/users/{user_id}/roles", + "POST /security/roles/{role_id}/policies", + "POST /security/roles/{role_id}/rules" + ] + }, + "security:create": { + "description": "Create new system security resources", + "resources": ["*:*"], + "example": { + "actions": ["security:create"], + "resources": ["*:*:*"], + "effect": "deny" + }, + "related_endpoints": [ + "POST /security/roles", + "POST /security/rules", + "POST /security/policies" + ] + }, + "security:read_config": { + "description": "Read current system security configuration", + "resources": ["*:*"], + "example": { + "actions": ["security:read_config"], + "resources": ["*:*:*"], + "effect": "allow" + }, + "related_endpoints": ["GET /security/config"] + }, + "security:update_config": { + "description": "Update current system security configuration", + "resources": ["*:*"], + "example": { + "actions": ["security:update_config"], + "resources": ["*:*:*"], + "effect": "allow" + }, + "related_endpoints": ["PUT /security/config", "DELETE /security/config"] + }, + "task:status": { + "description": "Access task's status information", + "resources": ["*:*"], + "example": { + "actions": ["task:status"], + "resources": ["*:*:*"], + "effect": "deny" + }, + "related_endpoints": ["GET /tasks/status"] + }, + "vulnerability:run": { + "description": "Allow running a vulnerability detector scan", + "resources": ["*:*"], + "example": { + "actions": ["vulnerability:run"], + "resources": ["*:*:*"], + "effect": "allow" + }, + "related_endpoints": ["PUT /vulnerability"] + }, + "vulnerability:read": { + "description": "Allow reading agents' vulnerabilities information", + "resources": ["agent:id", "agent:group"], + "example": { + "actions": ["vulnerability:read"], + "resources": ["agent:id:011", "agent:group:us-west"], + "effect": "allow" + }, + "related_endpoints": [ + "GET /vulnerability/{agent_id}", + "GET /vulnerability/{agent_id}/last_scan", + "GET /vulnerability/{agent_id}/summary/{field}" + ] + } + }, + "error": 0 +} diff --git a/docker/imposter/security/security-me-policies.js b/docker/imposter/security/security-me-policies.js new file mode 100644 index 0000000000..90af38c452 --- /dev/null +++ b/docker/imposter/security/security-me-policies.js @@ -0,0 +1,349 @@ +var userPoliciesMap = { + PLUGIN_SECURITY_USERS_ALL: { + 'security:read': { + 'user:id:*': 'allow', + 'role:id:*': 'allow', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_USERS_DENY_USERS: { + 'security:read': { + 'user:id:*': 'deny', + 'role:id:*': 'allow', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_USERS_DENY_ROLES: { + 'security:read': { + 'user:id:*': 'allow', + 'role:id:*': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_USERS_DENY_USERS_ROLES: { + 'security:read': { + 'user:id:*': 'deny', + 'role:id:*': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_USERS_DENY_CREATE_USER: { + 'security:read': { + 'user:id:*': 'allow', + 'role:id:*': 'allow', + }, + 'security:create_user': { + '*:*:*': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_USERS_ALLOW_CREATE_USER_DENY_EDIT_RUN_AS: { + 'security:read': { + 'user:id:*': 'allow', + 'role:id:*': 'allow', + }, + 'security:create_user': { + '*:*:*': 'allow', + }, + 'security:edit_run_as': { + '*:*:*': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_USERS_ALLOW_EDIT_USER: { + 'security:read': { + 'user:id:*': 'allow', + 'role:id:*': 'allow', + }, + 'security:create_user': { + '*:*:*': 'allow', + }, + 'security:update': { + 'user:id:*': 'allow', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_USERS_ALLOW_EDIT_USER_ID_101: { + 'security:read': { + 'user:id:*': 'allow', + 'role:id:*': 'allow', + }, + 'security:create_user': { + '*:*:*': 'allow', + }, + 'security:update': { + 'user:id:101': 'deny', + 'user:id:102': 'allow', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_USERS_ALLOW_CREATE_USER_DENY_UPDATE_USER: { + 'security:read': { + 'user:id:*': 'allow', + 'role:id:*': 'allow', + }, + 'security:create_user': { + '*:*:*': 'allow', + }, + 'security:update': { + 'user:id:*': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_USERS_ALLOW_CREATE_USER_ALLOW_UPDATE_USER_DENY_EDIT_RUN_AS: { + 'security:read': { + 'user:id:*': 'allow', + 'role:id:*': 'allow', + }, + 'security:create_user': { + '*:*:*': 'allow', + }, + 'security:update': { + 'user:id:*': 'allow', + }, + 'security:edit_run_as': { + 'user:id:*': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_USERS_DENY_EDIT_USER: { + 'security:read': { + 'user:id:*': 'allow', + 'role:id:*': 'allow', + }, + 'security:create_user': { + '*:*:*': 'allow', + }, + 'security:update': { + 'user:id:*': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_USERS_DENY_DELETE_USER_101: { + 'security:read': { + 'user:id:*': 'allow', + 'role:id:*': 'allow', + }, + 'security:create_user': { + '*:*:*': 'allow', + }, + 'security:delete': { + 'user:id:100': 'allow', + 'user:id:101': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_ROLES_ALLOW_READ_ROLES_POLICIES: { + 'security:read': { + 'role:id:*': 'allow', + 'policy:id:*': 'allow', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_ROLES_ALLOW_READ_POLICIES_DENY_READ_ROLE: { + 'security:read': { + 'role:id:*': 'deny', + 'policy:id:*': 'allow', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_ROLES_DENY_READ_POLICIES_ALLOW_READ_ROLE: { + 'security:read': { + 'role:id:*': 'allow', + 'policy:id:*': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_ROLES_DENY_READ_POLICIES_DENY_READ_ROLE: { + 'security:read': { + 'role:id:*': 'deny', + 'policy:id:*': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_ROLES_DENY_CREATE_POLICY: { + 'security:read': { + 'role:id:*': 'allow', + 'policy:id:*': 'allow', + }, + 'security:create': { + '*:*:*': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_ROLES_ALL_DENY_DELETE_ROLE_101_ALLOW_ROLE_102: { + 'security:read': { + 'role:id:*': 'allow', + 'policy:id:*': 'allow', + }, + 'security:delete': { + 'role:id:101': 'deny', + 'role:id:102': 'allow', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_ROLES_DENY_UPDATE_ROLE_101_ALLOW_UPDATE_ROLE_102: { + 'security:read': { + 'role:id:*': 'allow', + 'policy:id:*': 'allow', + }, + 'security:update': { + 'role:id:101': 'deny', + 'role:id:102': 'allow', + }, + 'security:delete': { + 'role:id:101': 'deny', + 'role:id:102': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_POLICIES_ALLOW_READ_POLICIES: { + 'security:read': { + 'policy:id:*': 'allow', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_POLICIES_DENY_READ_POLICIES: { + 'security:read': { + 'policy:id:*': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_POLICIES_ALLOW_READ_POLICIES_DENY_DELETE_101_ALLOW_DELETE_102: + { + 'security:read': { + 'policy:id:*': 'allow', + }, + 'security:delete': { + 'policy:id:101': 'deny', + 'policy:id:102': 'allow', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_POLICIES_DENY_CREATE_POLICY: { + 'security:read': { + 'policy:id:*': 'allow', + }, + 'security:create': { + '*:*:*': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_POLICIES_DENY_UPDATE_POLICY: { + 'security:read': { + 'policy:id:*': 'allow', + }, + 'security:update': { + 'policy:id:100': 'deny', + 'policy:id:101': 'allow', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_POLICIES_ALLOW_CREATE_POLICY: { + 'security:read': { + 'policy:id:*': 'allow', + }, + 'security:create': { + '*:*:*': 'allow', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_ROLES_MAPPING_ALLOW_READ_ROLES_MAPPING: { + 'security:read': { + 'rule:id:*': 'allow', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_ROLES_MAPPING_DENY_READ_ROLES_MAPPING: { + 'security:read': { + 'role:id:*': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_ROLES_MAPPING_DENY_READ_RULES_MAPPING: { + 'security:read': { + 'rule:id:*': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_ROLES_MAPPING_DENY_READ_ROLE_DENY_READ_RULES_MAPPING: { + 'security:read': { + 'role:id:*': 'deny', + 'rule:id:*': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_ROLES_MAPPING_ALLOW_READ_ROLES_MAPPING_DENY_DELETE_ROLE_MAPPING_101: + { + 'security:read': { + 'rule:id:*': 'allow', + }, + 'security:delete': { + 'rule:id:101': 'deny', + 'rule:id:102': 'allow', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_ROLES_MAPPING_ALLOW_READ_ROLES_MAPPING_DENY_CREATE_ROLE_MAPPING: + { + 'security:read': { + 'rule:id:*': 'allow', + }, + 'security:create': { + '*:*:*:': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_ROLES_MAPPING_ALLOW_READ_ROLES_MAPPING_ALLOW_CREATE_ROLE_MAPPING: + { + 'security:read': { + 'rule:id:*': 'allow', + }, + 'security:create': { + '*:*:*': 'allow', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_ROLES_MAPPING_ALLOW_READ_ROLES_MAPPING_ALLOW_CREATE_ROLE_MAPPING_ALLOW_UPDATE_ROLE_MAPPING_DENY_DELETE_ROLE_MAPPING: + { + 'security:read': { + 'rule:id:*': 'allow', + }, + 'security:create': { + '*:*:*': 'allow', + }, + 'security:update': { + 'rule:id:*': 'allow', + }, + 'security:delete': { + 'rule:id:*': 'deny', + }, + rbac_mode: 'black', + }, + PLUGIN_SECURITY_ROLES_MAPPING_ALLOW_READ_ROLES_MAPPING_ALLOW_CREATE_ROLE_MAPPING_DENY_UPDATE_ROLE_MAPPING_ALLOW_DELETE_ROLE_MAPPING: + { + 'security:read': { + 'rule:id:*': 'allow', + }, + 'security:create': { + '*:*:*': 'allow', + }, + 'security:update': { + 'rule:id:*': 'deny', + }, + 'security:delete': { + 'rule:id:*': 'allow', + }, + rbac_mode: 'black', + }, +}; + +var selectedUserPolicy = userPoliciesMap['PLUGIN_SECURITY_USERS_ALL']; + +var response = { + data: selectedUserPolicy, + message: 'Current user processed policies information was returned', + error: 0, +}; + +respond().withStatusCode(200).withData(JSON.stringify(response)); diff --git a/docker/imposter/security/security_policies.json b/docker/imposter/security/security-policies.json similarity index 95% rename from docker/imposter/security/security_policies.json rename to docker/imposter/security/security-policies.json index 565a90284c..928b625433 100644 --- a/docker/imposter/security/security_policies.json +++ b/docker/imposter/security/security-policies.json @@ -402,6 +402,26 @@ "effect": "deny" }, "roles": [100] + }, + { + "id": 101, + "name": "custom_manager_deny_read", + "policy": { + "actions": ["manager:read", "cluster:read"], + "resources": ["*:*:*", "node:id:*"], + "effect": "deny" + }, + "roles": [100] + }, + { + "id": 102, + "name": "custom_manager_deny_read2", + "policy": { + "actions": ["manager:read", "cluster:read"], + "resources": ["*:*:*", "node:id:*"], + "effect": "deny" + }, + "roles": [100] } ], "total_affected_items": 36, diff --git a/docker/imposter/security/security_roles.json b/docker/imposter/security/security-roles.json similarity index 82% rename from docker/imposter/security/security_roles.json rename to docker/imposter/security/security-roles.json index a6ea88acb3..02ad41c2ae 100644 --- a/docker/imposter/security/security_roles.json +++ b/docker/imposter/security/security-roles.json @@ -52,6 +52,20 @@ "policies": [29, 30], "users": [], "rules": [] + }, + { + "id": 101, + "name": "custom_role", + "policies": [29, 30], + "users": [], + "rules": [] + }, + { + "id": 102, + "name": "custom_role2", + "policies": [29, 30], + "users": [], + "rules": [] } ], "total_affected_items": 8, diff --git a/docker/imposter/security/security-rules.json b/docker/imposter/security/security-rules.json new file mode 100644 index 0000000000..91fa8308bc --- /dev/null +++ b/docker/imposter/security/security-rules.json @@ -0,0 +1,57 @@ +{ + "data": { + "affected_items": [ + { + "id": 1, + "name": "wui_elastic_admin", + "rule": { + "FIND": { + "username": "elastic" + } + }, + "roles": [ + 1 + ] + }, + { + "id": 2, + "name": "wui_opendistro_admin", + "rule": { + "FIND": { + "username": "admin" + } + }, + "roles": [ + 1 + ] + }, + { + "id": 101, + "name": "custom_rule1", + "rule": { + "FIND": { + "username": "admin" + } + }, + "roles": [ + 1 + ] + }, + { + "id": 102, + "name": "custom_rule2", + "rule": { + "FIND": { + "username": "admin" + } + }, + "roles": [ + 2 + ] + } + ], + "total_affected_items": 4, + "total_failed_items": 0, + "failed_items": [] + } +} diff --git a/docker/imposter/security/users.json b/docker/imposter/security/users.json new file mode 100644 index 0000000000..8292e68ef1 --- /dev/null +++ b/docker/imposter/security/users.json @@ -0,0 +1,63 @@ +{ + "data": { + "affected_items": [ + { + "id": 1, + "username": "wazuh", + "allow_run_as": true, + "roles": [1] + }, + { + "id": 2, + "username": "wazuh-wui", + "allow_run_as": true, + "roles": [] + }, + { + "id": 3, + "username": "administrator", + "allow_run_as": true, + "roles": [2] + }, + { + "id": 4, + "username": "guest", + "allow_run_as": false, + "roles": [] + }, + { + "id": 5, + "username": "normal", + "allow_run_as": false, + "roles": [4, 5, 6] + }, + { + "id": 6, + "username": "ossec", + "allow_run_as": true, + "roles": [2, 5] + }, + { + "id": 7, + "username": "rbac", + "allow_run_as": false, + "roles": [3, 4, 5] + }, + { + "id": 100, + "username": "python", + "allow_run_as": true, + "roles": [] + }, + { + "id": 101, + "username": "custom_user", + "allow_run_as": true, + "roles": [] + } + ], + "total_affected_items": 8, + "total_failed_items": 0, + "failed_items": [] + } +} diff --git a/docker/imposter/wazuh-config.yml b/docker/imposter/wazuh-config.yml index 94298f0363..4b56023218 100755 --- a/docker/imposter/wazuh-config.yml +++ b/docker/imposter/wazuh-config.yml @@ -649,6 +649,9 @@ resources: # Get current user processed policies - method: GET path: /security/users/me/policies + response: + statusCode: 200 + scriptFile: security/security-me-policies.js # Revoke JWT tokens - method: PUT @@ -661,6 +664,9 @@ resources: # List RBAC actions - method: GET path: /security/actions + response: + statusCode: 200 + staticFile: security/security-actions.json # List RBAC resources - method: GET @@ -669,6 +675,9 @@ resources: # List users - method: GET path: /security/users + response: + statusCode: 200 + staticFile: security/users.json # Add users - method: POST @@ -687,7 +696,7 @@ resources: path: /security/roles response: statusCode: 200 - staticFile: security/security_roles.json + staticFile: security/security-roles.json # Add role - method: POST @@ -704,6 +713,9 @@ resources: # List security rules - method: GET path: /security/rules + response: + statusCode: 200 + staticFile: security/security-rules.json # Add security rule - method: POST @@ -722,7 +734,7 @@ resources: path: /security/policies response: statusCode: 200 - staticFile: security/security_policies.json + staticFile: security/security-policies.json # Add policy - method: POST diff --git a/plugins/main/common/constants.ts b/plugins/main/common/constants.ts index e01586d620..426d72d2ea 100644 --- a/plugins/main/common/constants.ts +++ b/plugins/main/common/constants.ts @@ -105,6 +105,10 @@ export const WAZUH_CONFIGURATION_CACHE_TIME = 10000; // time in ms; // Reserved ids for Users/Role mapping export const WAZUH_API_RESERVED_ID_LOWER_THAN = 100; +export const WAZUH_API_RESERVED_WUI_SECURITY_RULES = [ + 1, + 2 +]; // Wazuh data path const WAZUH_DATA_PLUGIN_PLATFORM_BASE_PATH = 'data'; diff --git a/plugins/main/public/components/security/main.tsx b/plugins/main/public/components/security/main.tsx index be1f8713a6..fff3f9d4f8 100644 --- a/plugins/main/public/components/security/main.tsx +++ b/plugins/main/public/components/security/main.tsx @@ -6,9 +6,7 @@ import { EuiFlexItem, EuiTabs, EuiTab, - EuiPanel, EuiCallOut, - EuiEmptyPrompt, EuiSpacer, } from '@elastic/eui'; import { Users } from './users/users'; @@ -17,18 +15,18 @@ import { Policies } from './policies/policies'; import { GenericRequest } from '../../react-services/generic-request'; import { API_USER_STATUS_RUN_AS } from '../../../server/lib/cache-api-user-has-run-as'; import { AppState } from '../../react-services/app-state'; -import { ErrorHandler } from '../../react-services/error-handler'; import { RolesMapping } from './roles-mapping/roles-mapping'; import { withReduxProvider, withGlobalBreadcrumb, - withUserAuthorizationPrompt, withErrorBoundary, } from '../common/hocs'; import { compose } from 'redux'; -import { WAZUH_ROLE_ADMINISTRATOR_NAME, PLUGIN_PLATFORM_NAME } from '../../../common/constants'; +import { + PLUGIN_PLATFORM_NAME, + UI_LOGGER_LEVELS, +} from '../../../common/constants'; import { updateSecuritySection } from '../../redux/actions/securityActions'; -import { UI_LOGGER_LEVELS } from '../../../common/constants'; import { UI_ERROR_SEVERITIES } from '../../react-services/error-orchestrator/types'; import { getErrorOrchestrator } from '../../react-services/common-services'; import { getPluginDataPath } from '../../../common/plugin'; @@ -60,12 +58,13 @@ export const WzSecurity = compose( withErrorBoundary, withReduxProvider, withGlobalBreadcrumb([{ text: '' }, { text: 'Security' }]), - withUserAuthorizationPrompt(null, [WAZUH_ROLE_ADMINISTRATOR_NAME]) )(() => { const dispatch = useDispatch(); // Get the initial tab when the component is initiated - const securityTabRegExp = new RegExp(`tab=(${tabs.map((tab) => tab.id).join('|')})`); + const securityTabRegExp = new RegExp( + `tab=(${tabs.map(tab => tab.id).join('|')})`, + ); const tab = window.location.href.match(securityTabRegExp); const selectedTabId = (tab && tab[1]) || 'users'; @@ -73,7 +72,11 @@ export const WzSecurity = compose( const checkRunAsUser = async () => { const currentApi = AppState.getCurrentAPI(); try { - const ApiCheck = await GenericRequest.request('POST', '/api/check-api', currentApi); + const ApiCheck = await GenericRequest.request( + 'POST', + '/api/check-api', + currentApi, + ); return ApiCheck.data.allow_run_as; } catch (error) { throw new Error(error); @@ -107,8 +110,11 @@ export const WzSecurity = compose( dispatch(updateSecuritySection(selectedTabId)); }, []); - const onSelectedTabChanged = (id) => { - window.location.href = window.location.href.replace(`tab=${selectedTabId}`, `tab=${id}`); + const onSelectedTabChanged = id => { + window.location.href = window.location.href.replace( + `tab=${selectedTabId}`, + `tab=${id}`, + ); }; const renderTabs = () => { @@ -125,20 +131,22 @@ export const WzSecurity = compose( )); }; - const isNotRunAs = (allowRunAs) => { + const isNotRunAs = allowRunAs => { let runAsWarningTxt = ''; switch (allowRunAs) { case API_USER_STATUS_RUN_AS.HOST_DISABLED: - runAsWarningTxt = - `For the role mapping to take effect, enable run_as in ${getPluginDataPath('config/wazuh.yml')} configuration file, restart the ${PLUGIN_PLATFORM_NAME} service and clear your browser cache and cookies.`; + runAsWarningTxt = `For the role mapping to take effect, enable run_as in ${getPluginDataPath( + 'config/wazuh.yml', + )} configuration file, restart the ${PLUGIN_PLATFORM_NAME} service and clear your browser cache and cookies.`; break; case API_USER_STATUS_RUN_AS.USER_NOT_ALLOWED: runAsWarningTxt = 'The role mapping has no effect because the current Wazuh API user has allow_run_as disabled.'; break; case API_USER_STATUS_RUN_AS.ALL_DISABLED: - runAsWarningTxt = - `For the role mapping to take effect, enable run_as in ${getPluginDataPath('config/wazuh.yml')} configuration file and set the current Wazuh API user allow_run_as to true. Restart the ${PLUGIN_PLATFORM_NAME} service and clear your browser cache and cookies.`; + runAsWarningTxt = `For the role mapping to take effect, enable run_as in ${getPluginDataPath( + 'config/wazuh.yml', + )} configuration file and set the current Wazuh API user allow_run_as to true. Restart the ${PLUGIN_PLATFORM_NAME} service and clear your browser cache and cookies.`; break; default: runAsWarningTxt = @@ -149,7 +157,11 @@ export const WzSecurity = compose( return ( - + @@ -161,7 +173,7 @@ export const WzSecurity = compose( {renderTabs()} - + {selectedTabId === 'users' && } {selectedTabId === 'roles' && } {selectedTabId === 'policies' && } diff --git a/plugins/main/public/components/security/policies/create-policy.tsx b/plugins/main/public/components/security/policies/create-policy.tsx index 078448db98..65f2cbf086 100644 --- a/plugins/main/public/components/security/policies/create-policy.tsx +++ b/plugins/main/public/components/security/policies/create-policy.tsx @@ -22,6 +22,7 @@ import { UI_LOGGER_LEVELS } from '../../../../common/constants'; import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; import { getErrorOrchestrator } from '../../../react-services/common-services'; import { WzFlyout } from '../../common/flyouts'; +import { WzButtonPermissions } from '../../common/permissions/button'; export const CreatePolicyFlyout = ({ closeFlyout }) => { const [isModalVisible, setIsModalVisible] = useState(false); @@ -54,7 +55,7 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { type: 'icon', color: 'danger', icon: 'trash', - onClick: (action) => removeAction(action), + onClick: action => removeAction(action), }, ], }, @@ -76,7 +77,7 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { type: 'icon', color: 'danger', icon: 'trash', - onClick: (resource) => removeResource(resource), + onClick: resource => removeResource(resource), }, ], }, @@ -94,8 +95,16 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { ]; async function getData() { - const resourcesRequest = await WzRequest.apiReq('GET', '/security/resources', {}); - const actionsRequest = await WzRequest.apiReq('GET', '/security/actions', {}); + const resourcesRequest = await WzRequest.apiReq( + 'GET', + '/security/resources', + {}, + ); + const actionsRequest = await WzRequest.apiReq( + 'GET', + '/security/actions', + {}, + ); const resourcesData = resourcesRequest?.data?.data || {}; setAvailableResources(resourcesData); @@ -109,8 +118,10 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { dropdownDisplay: ( <> {x} - -

{actionsData[x].description}

+ +

+ {actionsData[x].description} +

), @@ -121,7 +132,7 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { const loadResources = () => { let allResources = []; - addedActions.forEach((x) => { + addedActions.forEach(x => { const res = (availableActions[x.action] || {})['resources']; allResources = allResources.concat(res); }); @@ -134,8 +145,10 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { dropdownDisplay: ( <> {x} - -

{availableResources[x].description}

+ +

+ {availableResources[x].description} +

), @@ -144,24 +157,20 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { setResources(resources); }; - const removeAction = (action) => { - setAddedActions(addedActions.filter((x) => x !== action)); + const removeAction = action => { + setAddedActions(addedActions.filter(x => x !== action)); }; const createPolicy = async () => { try { - const result = await WzRequest.apiReq( - 'POST', - '/security/policies', - { - name: policyName, - policy: { - actions: addedActions.map((x) => x.action), - resources: addedResources.map((x) => x.resource), - effect: effectValue, - }, - } - ); + const result = await WzRequest.apiReq('POST', '/security/policies', { + name: policyName, + policy: { + actions: addedActions.map(x => x.action), + resources: addedResources.map(x => x.resource), + effect: effectValue, + }, + }); const resultData = (result.data || {}).data; if (resultData.failed_items && resultData.failed_items.length) { return; @@ -195,10 +204,11 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { const addResource = () => { if ( - !addedResources.filter((x) => x.resource === `${resourceValue}:${resourceIdentifierValue}`) - .length + !addedResources.filter( + x => x.resource === `${resourceValue}:${resourceIdentifierValue}`, + ).length ) { - setAddedResources((addedResources) => [ + setAddedResources(addedResources => [ ...addedResources, { resource: `${resourceValue}:${resourceIdentifierValue}` }, ]); @@ -207,34 +217,37 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { }; const addAction = () => { - if (!addedActions.filter((x) => x.action === actionValue).length) { - setAddedActions((addedActions) => [...addedActions, { action: actionValue }]); + if (!addedActions.filter(x => x.action === actionValue).length) { + setAddedActions(addedActions => [ + ...addedActions, + { action: actionValue }, + ]); } setActionValue(''); }; - const removeResource = (resource) => { - setAddedResources(addedResources.filter((x) => x !== resource)); + const removeResource = resource => { + setAddedResources(addedResources.filter(x => x !== resource)); }; - const onChangePolicyName = (e) => { + const onChangePolicyName = e => { setPolicyName(e.target.value); }; - const onChangeResourceValue = async (value) => { + const onChangeResourceValue = async value => { setResourceValue(value); setResourceIdentifierValue(''); }; - const onChangeActionValue = async (value) => { + const onChangeActionValue = async value => { setActionValue(value); }; - const onEffectValueChange = (value) => { + const onEffectValueChange = value => { setEffectValue(value); }; - const onChangeResourceIdentifierValue = async (e) => { + const onChangeResourceIdentifierValue = async e => { setResourceIdentifierValue(e.target.value); }; @@ -243,7 +256,7 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { modal = ( { setIsModalVisible(false); closeFlyout(false); @@ -251,7 +264,7 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { }} onCancel={() => setIsModalVisible(false)} cancelButtonText="No, don't do it" - confirmButtonText="Yes, do it" + confirmButtonText='Yes, do it' >

There are unsaved changes. Are you sure you want to proceed? @@ -291,34 +304,37 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { <> - +

New policy

- - + + onChangePolicyName(e)} - aria-label="" + onChange={e => onChangePolicyName(e)} + aria-label='' /> onChangeActionValue(value)} - itemLayoutAlign="top" + onChange={value => onChangeActionValue(value)} + itemLayoutAlign='top' hasDividers /> @@ -327,9 +343,9 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { addAction()} - iconType="plusInCircle" + iconType='plusInCircle' disabled={!actionValue} > Add @@ -339,10 +355,13 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { {!!addedActions.length && ( <> - + - + @@ -351,15 +370,15 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { onChangeResourceValue(value)} - itemLayoutAlign="top" + onChange={value => onChangeResourceValue(value)} + itemLayoutAlign='top' hasDividers disabled={!addedActions.length} /> @@ -367,14 +386,14 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { onChangeResourceIdentifierValue(e)} + onChange={e => onChangeResourceIdentifierValue(e)} disabled={!resourceValue} /> @@ -382,9 +401,9 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { addResource()} - iconType="plusInCircle" + iconType='plusInCircle' disabled={!resourceIdentifierValue} > Add @@ -394,27 +413,39 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { {!!addedResources.length && ( <> - + - + )} - + onEffectValueChange(value)} + onChange={value => onEffectValueChange(value)} /> - { createPolicy(); @@ -422,7 +453,7 @@ export const CreatePolicyFlyout = ({ closeFlyout }) => { fill > Create policy - + diff --git a/plugins/main/public/components/security/policies/edit-policy.tsx b/plugins/main/public/components/security/policies/edit-policy.tsx index 2830589dc4..13e2e50c79 100644 --- a/plugins/main/public/components/security/policies/edit-policy.tsx +++ b/plugins/main/public/components/security/policies/edit-policy.tsx @@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react'; import { EuiButton, EuiTitle, - EuiFlyout, EuiFlyoutHeader, EuiFlyoutBody, EuiForm, @@ -17,7 +16,6 @@ import { EuiFieldText, EuiConfirmModal, EuiOverlayMask, - EuiOutsideClickDetector, } from '@elastic/eui'; import { WzRequest } from '../../../react-services/wz-request'; import { ErrorHandler } from '../../../react-services/error-handler'; @@ -27,6 +25,7 @@ import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/ import { getErrorOrchestrator } from '../../../react-services/common-services'; import _ from 'lodash'; import { WzFlyout } from '../../common/flyouts'; +import { WzButtonPermissions } from '../../common/permissions/button'; export const EditPolicyFlyout = ({ policy, closeFlyout }) => { const isReserved = WzAPIUtils.isReservedID(policy.id); @@ -59,21 +58,27 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { const updatePolicy = async () => { try { - const actions = addedActions.map((item) => item.action); - const resources = addedResources.map((item) => item.resource); - const response = await WzRequest.apiReq('PUT', `/security/policies/${policy.id}`, { - policy: { - actions: actions, - resources: resources, - effect: effectValue, + const actions = addedActions.map(item => item.action); + const resources = addedResources.map(item => item.resource); + const response = await WzRequest.apiReq( + 'PUT', + `/security/policies/${policy.id}`, + { + policy: { + actions: actions, + resources: resources, + effect: effectValue, + }, }, - }); + ); const data = (response.data || {}).data; if (data.failed_items && data.failed_items.length) { return; } - ErrorHandler.info('Role was successfully updated with the selected policies'); + ErrorHandler.info( + 'Role was successfully updated with the selected policies', + ); closeFlyout(); } catch (error) { const options = { @@ -92,8 +97,16 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { }; async function getData() { - const resources_request = await WzRequest.apiReq('GET', '/security/resources', {}); - const actions_request = await WzRequest.apiReq('GET', '/security/actions', {}); + const resources_request = await WzRequest.apiReq( + 'GET', + '/security/resources', + {}, + ); + const actions_request = await WzRequest.apiReq( + 'GET', + '/security/actions', + {}, + ); const resources_data = ((resources_request || {}).data || {}).data || {}; setAvailableResources(resources_data); @@ -107,8 +120,10 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { dropdownDisplay: ( <> {x} - -

{actions_data[x].description}

+ +

+ {actions_data[x].description} +

), @@ -119,7 +134,7 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { const loadResources = () => { let allResources = []; - addedActions.forEach((x) => { + addedActions.forEach(x => { const res = (availableActions[x.action] || {})['resources']; allResources = allResources.concat(res); }); @@ -132,8 +147,10 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { dropdownDisplay: ( <> {x} - -

{(availableResources[x] || {}).description}

+ +

+ {(availableResources[x] || {}).description} +

), @@ -144,14 +161,14 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { const initData = () => { const policies = ((policy || {}).policy || {}).actions || []; - const initPolicies = policies.map((item) => { + const initPolicies = policies.map(item => { return { action: item }; }); setAddedActions(initPolicies); setInitialAddedActions(initPolicies); const resources = ((policy || {}).policy || {}).resources || []; - const initResources = resources.map((item) => { + const initResources = resources.map(item => { return { resource: item }; }); setAddedResources(initResources); @@ -161,7 +178,7 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { setInitialEffectValue(policy.policy.effect); }; - const onEffectValueChange = (value) => { + const onEffectValueChange = value => { setEffectValue(value); }; @@ -176,19 +193,22 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { }, ]; - const onChangeActionValue = async (value) => { + const onChangeActionValue = async value => { setActionValue(value); }; const addAction = () => { - if (!addedActions.filter((x) => x.action === actionValue).length) { - setAddedActions((addedActions) => [...addedActions, { action: actionValue }]); + if (!addedActions.filter(x => x.action === actionValue).length) { + setAddedActions(addedActions => [ + ...addedActions, + { action: actionValue }, + ]); } setActionValue(''); }; - const removeAction = (action) => { - setAddedActions(addedActions.filter((x) => x !== action)); + const removeAction = action => { + setAddedActions(addedActions.filter(x => x !== action)); }; const actions_columns = [ @@ -208,7 +228,7 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { enabled: () => !isReserved, color: 'danger', icon: 'trash', - onClick: (action) => removeAction(action), + onClick: action => removeAction(action), }, ], }, @@ -231,13 +251,13 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { color: 'danger', enabled: () => !isReserved, icon: 'trash', - onClick: (resource) => removeResource(resource), + onClick: resource => removeResource(resource), }, ], }, ]; - const onChangeResourceValue = async (value) => { + const onChangeResourceValue = async value => { setResourceValue(value); setResourceIdentifierValue(''); }; @@ -247,20 +267,21 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { return (keys[resourceValue] || ':').split(':')[1]; }; - const onChangeResourceIdentifierValue = async (e) => { + const onChangeResourceIdentifierValue = async e => { setResourceIdentifierValue(e.target.value); }; - const removeResource = (resource) => { - setAddedResources(addedResources.filter((x) => x !== resource)); + const removeResource = resource => { + setAddedResources(addedResources.filter(x => x !== resource)); }; const addResource = () => { if ( - !addedResources.filter((x) => x.resource === `${resourceValue}:${resourceIdentifierValue}`) - .length + !addedResources.filter( + x => x.resource === `${resourceValue}:${resourceIdentifierValue}`, + ).length ) { - setAddedResources((addedResources) => [ + setAddedResources(addedResources => [ ...addedResources, { resource: `${resourceValue}:${resourceIdentifierValue}` }, ]); @@ -273,14 +294,14 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { modal = ( { setIsModalVisible(false); closeFlyout(false); }} onCancel={() => setIsModalVisible(false)} cancelButtonText="No, don't do it" - confirmButtonText="Yes, do it" + confirmButtonText='Yes, do it' >

There are unsaved changes. Are you sure you want to proceed? @@ -311,38 +332,41 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { <> - +

Edit policy {policy.name}   - {isReserved && Reserved} + {isReserved && Reserved}

- - + + {}} - aria-label="" + aria-label='' /> onChangeActionValue(value)} - itemLayoutAlign="top" + onChange={value => onChangeActionValue(value)} + itemLayoutAlign='top' hasDividers /> @@ -352,7 +376,7 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { addAction()} - iconType="plusInCircle" + iconType='plusInCircle' disabled={!actionValue || isReserved} > Add @@ -362,10 +386,13 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { {!!addedActions.length && ( <> - + - + @@ -374,14 +401,14 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { onChangeResourceValue(value)} - itemLayoutAlign="top" + onChange={value => onChangeResourceValue(value)} + itemLayoutAlign='top' hasDividers disabled={!addedActions.length || isReserved} /> @@ -389,13 +416,13 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { onChangeResourceIdentifierValue(e)} + onChange={e => onChangeResourceIdentifierValue(e)} disabled={!resourceValue || isReserved} /> @@ -404,7 +431,7 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { addResource()} - iconType="plusInCircle" + iconType='plusInCircle' disabled={!resourceIdentifierValue || isReserved} > Add @@ -414,26 +441,40 @@ export const EditPolicyFlyout = ({ policy, closeFlyout }) => { {!!addedResources.length && ( <> - + - + )} - + onEffectValueChange(value)} + onChange={value => onEffectValueChange(value)} /> - + Apply - + diff --git a/plugins/main/public/components/security/policies/policies-table.tsx b/plugins/main/public/components/security/policies/policies-table.tsx index 3b9f3df383..7f5d5fea2a 100644 --- a/plugins/main/public/components/security/policies/policies-table.tsx +++ b/plugins/main/public/components/security/policies/policies-table.tsx @@ -1,15 +1,20 @@ -import React, { useState, useEffect } from 'react'; -import { EuiInMemoryTable, EuiBadge, EuiToolTip, EuiButtonIcon } from '@elastic/eui'; +import React from 'react'; +import { EuiInMemoryTable, EuiBadge } from '@elastic/eui'; import { WzRequest } from '../../../react-services/wz-request'; import { ErrorHandler } from '../../../react-services/error-handler'; import { WzAPIUtils } from '../../../react-services/wz-api-utils'; -import { WzButtonModalConfirm } from '../../common/buttons'; +import { WzButtonPermissionsModalConfirm } from '../../common/buttons'; import { UI_LOGGER_LEVELS } from '../../../../common/constants'; import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; import { getErrorOrchestrator } from '../../../react-services/common-services'; -export const PoliciesTable = ({ policies, loading, editPolicy, updatePolicies }) => { - const getRowProps = (item) => { +export const PoliciesTable = ({ + policies, + loading, + editPolicy, + updatePolicies, +}) => { + const getRowProps = item => { const { id } = item; return { 'data-test-subj': `row-${id}`, @@ -19,14 +24,18 @@ export const PoliciesTable = ({ policies, loading, editPolicy, updatePolicies }) }; }; - const confirmDeletePolicy = (item) => { + const confirmDeletePolicy = item => { return async () => { try { - const response = await WzRequest.apiReq('DELETE', `/security/policies/`, { - params: { - policy_ids: item.id, + const response = await WzRequest.apiReq( + 'DELETE', + `/security/policies/`, + { + params: { + policy_ids: item.id, + }, }, - }); + ); const data = (response.data || {}).data; if (data.failed_items && data.failed_items.length) { return; @@ -48,7 +57,7 @@ export const PoliciesTable = ({ policies, loading, editPolicy, updatePolicies }) getErrorOrchestrator().handleError(options); } }; - } + }; const columns = [ { @@ -68,7 +77,7 @@ export const PoliciesTable = ({ policies, loading, editPolicy, updatePolicies }) field: 'policy.actions', name: 'Actions', sortable: true, - render: (actions) => { + render: actions => { return (actions || []).join(', '); }, truncateText: true, @@ -88,8 +97,12 @@ export const PoliciesTable = ({ policies, loading, editPolicy, updatePolicies }) { field: 'id', name: 'Status', - render: (item) => { - return WzAPIUtils.isReservedID(item) && Reserved; + render: item => { + return ( + WzAPIUtils.isReservedID(item) && ( + Reserved + ) + ); }, width: 150, sortable: false, @@ -98,10 +111,13 @@ export const PoliciesTable = ({ policies, loading, editPolicy, updatePolicies }) align: 'right', width: '5%', name: 'Actions', - render: (item) => ( -
ev.stopPropagation()}> - ( +
ev.stopPropagation()}> +
), diff --git a/plugins/main/public/components/security/policies/policies.tsx b/plugins/main/public/components/security/policies/policies.tsx index a2718d5e5e..92b8da437a 100644 --- a/plugins/main/public/components/security/policies/policies.tsx +++ b/plugins/main/public/components/security/policies/policies.tsx @@ -5,15 +5,18 @@ import { EuiPageContentHeaderSection, EuiPageContentBody, EuiButton, - EuiTitle + EuiTitle, } from '@elastic/eui'; import { PoliciesTable } from './policies-table'; import { WzRequest } from '../../../react-services/wz-request'; import { EditPolicyFlyout } from './edit-policy'; import { CreatePolicyFlyout } from './create-policy'; +import { withUserAuthorizationPrompt } from '../../common/hocs'; +import { WzButtonPermissions } from '../../common/permissions/button'; - -export const Policies = () => { +export const Policies = withUserAuthorizationPrompt([ + { action: 'security:read', resource: 'policy:id:*' }, +])(() => { const [policies, setPolicies] = useState(''); const [loading, setLoading] = useState(false); const [isCreatingPolicy, setIsCreatingPolicy] = useState(false); @@ -22,11 +25,7 @@ export const Policies = () => { const getPolicies = async () => { setLoading(true); - const request = await WzRequest.apiReq( - 'GET', - '/security/policies', - {} - ); + const request = await WzRequest.apiReq('GET', '/security/policies', {}); const policies = request?.data?.data?.affected_items || []; setPolicies(policies); setLoading(false); @@ -36,7 +35,7 @@ export const Policies = () => { getPolicies(); }, []); - const editPolicy = (item) => { + const editPolicy = item => { setEditingPolicy(item); setIsEditingPolicy(true); }; @@ -51,18 +50,18 @@ export const Policies = () => { await getPolicies(); }; - let editFlyout; if (isEditingPolicy) { editFlyout = ( - + ); } let flyout; if (isCreatingPolicy) { - flyout = ( - - ); + flyout = ; } return ( @@ -74,18 +73,19 @@ export const Policies = () => { - { - !loading - && -
- setIsCreatingPolicy(true)}> - Create policy - - {flyout} - {editFlyout} -
- } + {!loading && ( +
+ setIsCreatingPolicy(true)} + > + Create policy + + {flyout} + {editFlyout} +
+ )}
@@ -98,4 +98,4 @@ export const Policies = () => { ); -}; +}); diff --git a/plugins/main/public/components/security/roles-mapping/components/roles-mapping-create.tsx b/plugins/main/public/components/security/roles-mapping/components/roles-mapping-create.tsx index 7b8d980041..068d105787 100644 --- a/plugins/main/public/components/security/roles-mapping/components/roles-mapping-create.tsx +++ b/plugins/main/public/components/security/roles-mapping/components/roles-mapping-create.tsx @@ -41,16 +41,16 @@ export const RolesMappingCreate = ({ const [isModalVisible, setIsModalVisible] = useState(false); const [hasChanges, setHasChanges] = useState(false); const getRolesList = () => { - const list = roles.map((item) => { + const list = roles.map(item => { return { label: rolesEquivalences[item.id], id: item.id }; }); return list; }; - const createRule = async (toSaveRule) => { + const createRule = async toSaveRule => { try { setIsLoading(true); - const formattedRoles = selectedRoles.map((item) => { + const formattedRoles = selectedRoles.map(item => { return item.id; }); const newRule = await RulesServices.CreateRule({ @@ -58,7 +58,9 @@ export const RolesMappingCreate = ({ rule: toSaveRule, }); await Promise.all( - formattedRoles.map(async (role) => await RolesServices.AddRoleRules(role, [newRule.id])) + formattedRoles.map( + async role => await RolesServices.AddRoleRules(role, [newRule.id]), + ), ); ErrorHandler.info('Role mapping was successfully created'); } catch (error) { @@ -84,7 +86,7 @@ export const RolesMappingCreate = ({ modal = ( { setIsModalVisible(false); closeFlyout(false); @@ -92,7 +94,7 @@ export const RolesMappingCreate = ({ }} onCancel={() => setIsModalVisible(false)} cancelButtonText="No, don't do it" - confirmButtonText="Yes, do it" + confirmButtonText='Yes, do it' >

There are unsaved changes. Are you sure you want to proceed? @@ -122,40 +124,40 @@ export const RolesMappingCreate = ({ <> - +

Create new role mapping  

- + setRuleName(e.target.value)} + onChange={e => setRuleName(e.target.value)} /> { + onChange={roles => { setSelectedRoles(roles); }} isClearable={true} - data-test-subj="demoComboBox" + data-test-subj='demoComboBox' /> @@ -163,13 +165,24 @@ export const RolesMappingCreate = ({ createRule(rule)} + save={rule => createRule(rule)} + saveButtonPermissions={[ + { action: 'security:create', resource: '*:*:*' }, + ...(selectedRoles.length > 0 + ? [ + { + action: 'security:update', + resource: 'rule:id:*', + }, + ] + : []), + ]} initialRule={false} isReserved={false} isLoading={isLoading} internalUsers={internalUsers} currentPlatform={currentPlatform} - onFormChange={(hasChange) => { + onFormChange={hasChange => { setHasChangeMappingRules(hasChange); }} > diff --git a/plugins/main/public/components/security/roles-mapping/components/roles-mapping-edit.tsx b/plugins/main/public/components/security/roles-mapping/components/roles-mapping-edit.tsx index 6f44f3f2e4..4b11df21f1 100644 --- a/plugins/main/public/components/security/roles-mapping/components/roles-mapping-edit.tsx +++ b/plugins/main/public/components/security/roles-mapping/components/roles-mapping-edit.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react'; import { EuiTitle, - EuiFlyout, EuiFlyoutHeader, EuiFlyoutBody, EuiForm, @@ -12,7 +11,6 @@ import { EuiBadge, EuiComboBox, EuiOverlayMask, - EuiOutsideClickDetector, EuiConfirmModal, EuiFieldText, } from '@elastic/eui'; @@ -36,31 +34,33 @@ export const RolesMappingEdit = ({ onSave, currentPlatform, }) => { - const getEquivalences = (roles) => { - const list = roles.map((item) => { + const getEquivalences = roles => { + const list = roles.map(item => { return { label: rolesEquivalences[item], id: item }; }); return list; }; - const [selectedRoles, setSelectedRoles] = useState(getEquivalences(rule.roles)); + const [selectedRoles, setSelectedRoles] = useState( + getEquivalences(rule.roles), + ); const [ruleName, setRuleName] = useState(rule.name); const [isLoading, setIsLoading] = useState(false); const [hasChangeMappingRules, setHasChangeMappingRules] = useState(false); const [isModalVisible, setIsModalVisible] = useState(false); const [hasChanges, setHasChanges] = useState(false); - const getRolesList = (roles) => { - const list = roles.map((item) => { + const getRolesList = roles => { + const list = roles.map(item => { return { label: rolesEquivalences[item.id], id: item.id }; }); return list; }; - const editRule = async (toSaveRule) => { + const editRule = async toSaveRule => { try { setIsLoading(true); - const formattedRoles = selectedRoles.map((item) => { + const formattedRoles = selectedRoles.map(item => { return item.id; }); @@ -69,18 +69,20 @@ export const RolesMappingEdit = ({ rule: toSaveRule, }); - const toAdd = formattedRoles.filter((value) => !rule.roles.includes(value)); - const toRemove = rule.roles.filter((value) => !formattedRoles.includes(value)); + const toAdd = formattedRoles.filter(value => !rule.roles.includes(value)); + const toRemove = rule.roles.filter( + value => !formattedRoles.includes(value), + ); await Promise.all( - toAdd.map(async (role) => { + toAdd.map(async role => { return RolesServices.AddRoleRules(role, [rule.id]); - }) + }), ); await Promise.all( - toRemove.map(async (role) => { + toRemove.map(async role => { return RolesServices.RemoveRoleRules(role, [rule.id]); - }) + }), ); ErrorHandler.info('Role mapping was successfully updated'); @@ -108,7 +110,7 @@ export const RolesMappingEdit = ({ modal = ( { setIsModalVisible(false); closeFlyout(false); @@ -116,8 +118,8 @@ export const RolesMappingEdit = ({ }} onCancel={() => setIsModalVisible(false)} cancelButtonText="No, don't do it" - confirmButtonText="Yes, do it" - defaultFocusedButton="confirm" + confirmButtonText='Yes, do it' + defaultFocusedButton='confirm' >

There are unsaved changes. Are you sure you want to proceed? @@ -129,7 +131,11 @@ export const RolesMappingEdit = ({ useEffect(() => { const initialRoles = getEquivalences(rule.roles); - if (rule.name != ruleName || !_.isEqual(initialRoles, selectedRoles) || hasChangeMappingRules) { + if ( + rule.name != ruleName || + !_.isEqual(initialRoles, selectedRoles) || + hasChangeMappingRules + ) { setHasChanges(true); } else { setHasChanges(false); @@ -144,45 +150,47 @@ export const RolesMappingEdit = ({ <> - +

Edit {rule.name}   - {WzAPIUtils.isReservedID(rule.id) && Reserved} + {WzAPIUtils.isReservedID(rule.id) && ( + Reserved + )}

- + setRuleName(e.target.value)} - aria-label="" + onChange={e => setRuleName(e.target.value)} + aria-label='' /> { + onChange={roles => { setSelectedRoles(roles); }} isClearable={true} - data-test-subj="demoComboBox" + data-test-subj='demoComboBox' /> @@ -190,13 +198,38 @@ export const RolesMappingEdit = ({ editRule(rule)} + save={rule => editRule(rule)} + saveButtonPermissions={[ + // Require security:update:{rule_id} if some roles were added + ...(selectedRoles + .map(item => item.id) + .filter(value => !rule.roles.includes(value)).length > 0 + ? [ + { + action: 'security:update', + resource: `rule:id:${rule.id}`, + }, + ] + : []), + // Require security:delete:{rule_id} if some roles were removed + ...(rule.roles.filter( + value => + !selectedRoles.map(item => item.id).includes(value), + ) > 0 + ? [ + { + action: 'security:delete', + resource: `rule:id:${rule.id}`, + }, + ] + : []), + ]} initialRule={rule.rule} isLoading={isLoading} isReserved={WzAPIUtils.isReservedID(rule.id)} internalUsers={internalUsers} currentPlatform={currentPlatform} - onFormChange={(hasChange) => setHasChangeMappingRules(hasChange)} + onFormChange={hasChange => setHasChangeMappingRules(hasChange)} > diff --git a/plugins/main/public/components/security/roles-mapping/components/roles-mapping-table.tsx b/plugins/main/public/components/security/roles-mapping/components/roles-mapping-table.tsx index 3a04543a39..5efe9cbb14 100644 --- a/plugins/main/public/components/security/roles-mapping/components/roles-mapping-table.tsx +++ b/plugins/main/public/components/security/roles-mapping/components/roles-mapping-table.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { - EuiSpacer, EuiToolTip, EuiInMemoryTable, EuiBadge, @@ -10,14 +9,20 @@ import { SortDirection, } from '@elastic/eui'; import { ErrorHandler } from '../../../../react-services/error-handler'; -import { WzButtonModalConfirm } from '../../../common/buttons'; +import { WzButtonPermissionsModalConfirm } from '../../../common/buttons'; import { WzAPIUtils } from '../../../../react-services/wz-api-utils'; import RulesServices from '../../rules/services'; -import { UI_LOGGER_LEVELS } from '../../../../../common/constants'; +import { UI_LOGGER_LEVELS, WAZUH_API_RESERVED_WUI_SECURITY_RULES } from '../../../../../common/constants'; import { UI_ERROR_SEVERITIES } from '../../../../react-services/error-orchestrator/types'; import { getErrorOrchestrator } from '../../../../react-services/common-services'; -export const RolesMappingTable = ({ rolesEquivalences, rules, loading, editRule, updateRules }) => { +export const RolesMappingTable = ({ + rolesEquivalences, + rules, + loading, + editRule, + updateRules, +}) => { const getRowProps = item => { const { id } = item; return { @@ -26,7 +31,7 @@ export const RolesMappingTable = ({ rolesEquivalences, rules, loading, editRule, }; }; - const onDeleteRoleMapping = (item) => { + const onDeleteRoleMapping = item => { return async () => { try { await RulesServices.DeleteRules([item.id]); @@ -47,7 +52,7 @@ export const RolesMappingTable = ({ rolesEquivalences, rules, loading, editRule, getErrorOrchestrator().handleError(options); } }; - } + }; const columns: EuiBasicTableColumn[] = [ { @@ -71,12 +76,12 @@ export const RolesMappingTable = ({ rolesEquivalences, rules, loading, editRule, const tmpRoles = item.map((role, idx) => { return ( - {rolesEquivalences[role]} + {rolesEquivalences[role]} ); }); return ( - + {tmpRoles} ); @@ -86,20 +91,23 @@ export const RolesMappingTable = ({ rolesEquivalences, rules, loading, editRule, { field: 'id', name: 'Status', - render (item, obj){ - if(WzAPIUtils.isReservedID(item)){ - if( (obj.id === 1 || obj.id === 2)){ - return( + render(item, obj) { + if (WzAPIUtils.isReservedID(item)) { + if (WAZUH_API_RESERVED_WUI_SECURITY_RULES.includes(obj.id)) { + return ( - Reserved - - wazuh-wui + Reserved + + + wazuh-wui + ); - } - else - return Reserved; + } else return Reserved; } }, width: '300', @@ -111,22 +119,26 @@ export const RolesMappingTable = ({ rolesEquivalences, rules, loading, editRule, name: 'Actions', render: item => (
ev.stopPropagation()}> -
), diff --git a/plugins/main/public/components/security/roles-mapping/components/rule-editor.tsx b/plugins/main/public/components/security/roles-mapping/components/rule-editor.tsx index bd2fdc77ea..0e4cab31b0 100644 --- a/plugins/main/public/components/security/roles-mapping/components/rule-editor.tsx +++ b/plugins/main/public/components/security/roles-mapping/components/rule-editor.tsx @@ -2,7 +2,6 @@ import React, { useState, useEffect, Fragment } from 'react'; import { EuiToolTip, EuiButtonIcon, - EuiButton, EuiTitle, EuiFormRow, EuiFlexGroup, @@ -30,11 +29,21 @@ import { WAZUH_SECURITY_PLUGIN_OPEN_DISTRO_FOR_ELASTICSEARCH } from '../../../.. import 'brace/mode/json'; import 'brace/snippets/json'; import 'brace/ext/language_tools'; -import "brace/ext/searchbox"; +import 'brace/ext/searchbox'; import _ from 'lodash'; import { webDocumentationLink } from '../../../../../common/services/web_documentation'; +import { WzButtonPermissions } from '../../../common/permissions/button'; -export const RuleEditor = ({ save, initialRule, isLoading, isReserved, internalUsers, currentPlatform,onFormChange }) => { +export const RuleEditor = ({ + save, + initialRule, + isLoading, + isReserved, + internalUsers, + currentPlatform, + onFormChange, + saveButtonPermissions = [], +}) => { const [logicalOperator, setLogicalOperator] = useState('OR'); const [isLogicalPopoverOpen, setIsLogicalPopoverOpen] = useState(false); const [isJsonEditor, setIsJsonEditor] = useState(false); @@ -42,13 +51,19 @@ export const RuleEditor = ({ save, initialRule, isLoading, isReserved, internalU const [hasWrongFormat, setHasWrongFormat] = useState(false); const [rules, setRules] = useState([]); const [initialRules, setInitialRules] = useState([]); - const [initialInternalUserRules, setInitialInternalUserRules] = useState([]); + const [initialInternalUserRules, setInitialInternalUserRules] = useState< + any[] + >([]); const [internalUserRules, setInternalUserRules] = useState([]); - const [internalUsersOptions, setInternalUsersOptions] = useState[]>( - [] - ); - const [selectedUsers, setSelectedUsers] = useState[]>([]); - const [initialSelectedUsers, setInitialSelectedUsers] = useState[]>([]); + const [internalUsersOptions, setInternalUsersOptions] = useState< + EuiComboBoxOptionOption[] + >([]); + const [selectedUsers, setSelectedUsers] = useState< + EuiComboBoxOptionOption[] + >([]); + const [initialSelectedUsers, setInitialSelectedUsers] = useState< + EuiComboBoxOptionOption[] + >([]); const [initialLogicalOperator, setInitialLogicalOperator] = useState('OR'); const searchOperationOptions = [ @@ -57,14 +72,23 @@ export const RuleEditor = ({ save, initialRule, isLoading, isReserved, internalU { value: 'MATCH', text: 'MATCH' }, { value: 'MATCH$', text: 'MATCH$' }, ]; - const default_user_field = currentPlatform === WAZUH_SECURITY_PLUGIN_OPEN_DISTRO_FOR_ELASTICSEARCH ? 'user_name' : 'username'; - const default_rule = { user_field: default_user_field, searchOperation: 'FIND', value: 'wazuh' }; + const default_user_field = + currentPlatform === WAZUH_SECURITY_PLUGIN_OPEN_DISTRO_FOR_ELASTICSEARCH + ? 'user_name' + : 'username'; + const default_rule = { + user_field: default_user_field, + searchOperation: 'FIND', + value: 'wazuh', + }; useEffect(() => { if (initialRule) { setStateFromRule(JSON.stringify(initialRule)); const rulesResult = getRulesFromJson(JSON.stringify(initialRule)); - const _selectedUsers = getSelectedUsersFromRules(rulesResult.internalUsersRules); + const _selectedUsers = getSelectedUsersFromRules( + rulesResult.internalUsersRules, + ); setInitialLogicalOperator(rulesResult.logicalOperator); setInitialRules(rulesResult.customRules); setInitialInternalUserRules(rulesResult.internalUsersRules); @@ -74,7 +98,10 @@ export const RuleEditor = ({ save, initialRule, isLoading, isReserved, internalU useEffect(() => { if (internalUsers.length) { - const users = internalUsers.map(user => ({ label: user.user, id: user.user })); + const users = internalUsers.map(user => ({ + label: user.user, + id: user.user, + })); setInternalUsersOptions(users); } }, [internalUsers]); @@ -84,7 +111,9 @@ export const RuleEditor = ({ save, initialRule, isLoading, isReserved, internalU if (!rulesResult.wrongFormat) { setRules(rulesResult.customRules); setInternalUserRules(rulesResult.internalUsersRules); - const _selectedUsers = getSelectedUsersFromRules(rulesResult.internalUsersRules); + const _selectedUsers = getSelectedUsersFromRules( + rulesResult.internalUsersRules, + ); setSelectedUsers(_selectedUsers); setIsJsonEditor(false); } else { @@ -126,13 +155,11 @@ export const RuleEditor = ({ save, initialRule, isLoading, isReserved, internalU setRules(rulesTmp); }; - const getRulesFromJson = (jsonRule) => { + const getRulesFromJson = jsonRule => { if (jsonRule !== '{}' && jsonRule !== '') { // empty json is valid - const { customRules, internalUsersRules, wrongFormat, logicalOperator } = decodeJsonRule( - jsonRule, - internalUsers - ); + const { customRules, internalUsersRules, wrongFormat, logicalOperator } = + decodeJsonRule(jsonRule, internalUsers); setLogicalOperator(logicalOperator); setHasWrongFormat(wrongFormat); @@ -156,36 +183,36 @@ export const RuleEditor = ({ save, initialRule, isLoading, isReserved, internalU - + updateUserField(e, idx)} - aria-label="" + aria-label='' /> - + onSelectorChange(e, idx)} - aria-label="Use aria labels when no actual label is in use" + aria-label='Use aria labels when no actual label is in use' /> - + updateValueField(e, idx)} - aria-label="" + aria-label='' /> @@ -193,15 +220,15 @@ export const RuleEditor = ({ save, initialRule, isLoading, isReserved, internalU removeRule(idx)} - iconType="trash" - color="danger" - aria-label="Remove rule" + iconType='trash' + color='danger' + aria-label='Remove rule' />
- + @@ -218,7 +245,11 @@ export const RuleEditor = ({ save, initialRule, isLoading, isReserved, internalU }; const openJsonEditor = () => { - const ruleObject = getJsonFromRule(internalUserRules, rules, logicalOperator); + const ruleObject = getJsonFromRule( + internalUserRules, + rules, + logicalOperator, + ); setRuleJson(JSON.stringify(ruleObject, undefined, 2)); setIsJsonEditor(true); @@ -236,9 +267,12 @@ export const RuleEditor = ({ save, initialRule, isLoading, isReserved, internalU const getSwitchVisualButton = () => { if (hasWrongFormat) { return ( - + openVisualEditor()} > @@ -248,7 +282,7 @@ export const RuleEditor = ({ save, initialRule, isLoading, isReserved, internalU ); } else { return ( - openVisualEditor()}> + openVisualEditor()}> Switch to visual editor ); @@ -270,7 +304,11 @@ export const RuleEditor = ({ save, initialRule, isLoading, isReserved, internalU const onChangeSelectedUsers = selectedUsers => { setSelectedUsers(selectedUsers); const tmpInternalUsersRules = selectedUsers.map(user => { - return { user_field: default_user_field, searchOperation: 'FIND', value: user.id }; + return { + user_field: default_user_field, + searchOperation: 'FIND', + value: user.id, + }; }); setInternalUserRules(tmpInternalUsersRules); }; @@ -279,11 +317,11 @@ export const RuleEditor = ({ save, initialRule, isLoading, isReserved, internalU if ( !_.isEqual(initialSelectedUsers, selectedUsers) || !_.isEqual(initialRules, rules) || - !_.isEqual(initialInternalUserRules, internalUserRules)|| + !_.isEqual(initialInternalUserRules, internalUserRules) || !_.isEqual(initialLogicalOperator, logicalOperator) - ){ - onFormChange(true) - } else{ + ) { + onFormChange(true); + } else { onFormChange(false); } }, [selectedUsers, rules, internalUserRules, logicalOperator]); @@ -299,10 +337,12 @@ export const RuleEditor = ({ save, initialRule, isLoading, isReserved, internalU Assign roles to users who match these rules. Learn more @@ -316,96 +356,104 @@ export const RuleEditor = ({ save, initialRule, isLoading, isReserved, internalU )) || ( - - -

Map internal users

-
- - - - - -

Custom rules

-
- - {logicalOperator === 'AND' ? 'All are true' : 'Any are true'} -
- } - isOpen={isLogicalPopoverOpen} - closePopover={closeLogicalPopover} - anchorPosition="downCenter" - > -
- - - selectOperator('AND')} - > - {logicalOperator === 'AND' && }All are true + + +

Map internal users

+
+ + + + + +

Custom rules

+
+ + {logicalOperator === 'AND' + ? 'All are true' + : 'Any are true'} +
+ } + isOpen={isLogicalPopoverOpen} + closePopover={closeLogicalPopover} + anchorPosition='downCenter' + > +
+ + + selectOperator('AND')} + > + {logicalOperator === 'AND' && ( + + )} + All are true - - - - - selectOperator('OR')} - > - {logicalOperator === 'OR' && }Any are true + + + + + selectOperator('OR')} + > + {logicalOperator === 'OR' && ( + + )} + Any are true - - -
- - {printRules()} +
+
+
+ + {printRules()} - addNewRule()} - > - Add new rule + addNewRule()} + > + Add new rule - - )} + + )} {(isJsonEditor && getSwitchVisualButton()) || ( - openJsonEditor()}> + openJsonEditor()}> Switch to JSON editor )} @@ -415,9 +463,16 @@ export const RuleEditor = ({ save, initialRule, isLoading, isReserved, internalU - saveRule()}> + saveRule()} + > Save role mapping - + diff --git a/plugins/main/public/components/security/roles-mapping/roles-mapping.tsx b/plugins/main/public/components/security/roles-mapping/roles-mapping.tsx index bf20790523..7bf8deaefb 100644 --- a/plugins/main/public/components/security/roles-mapping/roles-mapping.tsx +++ b/plugins/main/public/components/security/roles-mapping/roles-mapping.tsx @@ -4,16 +4,7 @@ import { EuiPageContentHeader, EuiPageContentHeaderSection, EuiPageContentBody, - EuiButton, EuiTitle, - EuiOverlayMask, - EuiSpacer, - EuiText, - EuiModal, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, } from '@elastic/eui'; import { RolesMappingTable } from './components/roles-mapping-table'; import { RolesMappingEdit } from './components/roles-mapping-edit'; @@ -29,17 +20,27 @@ import { useSelector } from 'react-redux'; import { UI_LOGGER_LEVELS } from '../../../../common/constants'; import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; import { getErrorOrchestrator } from '../../../react-services/common-services'; +import { withUserAuthorizationPrompt } from '../../common/hocs'; +import { WzButtonPermissions } from '../../common/permissions/button'; -export const RolesMapping = () => { +export const RolesMapping = withUserAuthorizationPrompt([ + { action: 'security:read', resource: 'role:id:*' }, + { action: 'security:read', resource: 'rule:id:*' }, +])(() => { const [isEditingRule, setIsEditingRule] = useState(false); const [isCreatingRule, setIsCreatingRule] = useState(false); const [rules, setRules] = useState([]); const [loadingTable, setLoadingTable] = useState(true); const [selectedRule, setSelectedRule] = useState({}); const [rolesEquivalences, setRolesEquivalences] = useState({}); - const [rolesLoading, roles, rolesError] = useApiService(RolesServices.GetRoles, {}); + const [rolesLoading, roles, rolesError] = useApiService( + RolesServices.GetRoles, + {}, + ); const [internalUsers, setInternalUsers] = useState([]); - const currentPlatform = useSelector((state: any) => state.appStateReducers.currentPlatform); + const currentPlatform = useSelector( + (state: any) => state.appStateReducers.currentPlatform, + ); useEffect(() => { initData(); @@ -49,7 +50,7 @@ export const RolesMapping = () => { if (!rolesLoading && (roles || [])) { const _rolesObject = (roles || []).reduce( (rolesObj, role) => ({ ...rolesObj, [role.id]: role.name }), - {} + {}, ); setRolesEquivalences(_rolesObject); } @@ -57,7 +58,7 @@ export const RolesMapping = () => { ErrorHandler.handle('There was an error loading roles'); } }, [rolesLoading]); - + const getInternalUsers = async () => { try { const wazuhSecurity = new WazuhSecurity(); @@ -122,13 +123,13 @@ export const RolesMapping = () => { const updateRoles = async () => { await getRules(); }; - + let editFlyout; if (isEditingRule) { editFlyout = ( { + closeFlyout={isVisible => { setIsEditingRule(isVisible); initData(); }} @@ -142,9 +143,9 @@ export const RolesMapping = () => { } let createFlyout; if (isCreatingRule) { - editFlyout = ( + createFlyout = ( { + closeFlyout={isVisible => { setIsCreatingRule(isVisible); initData(); }} @@ -161,19 +162,21 @@ export const RolesMapping = () => { -

Role mapping

+

Roles mapping

{!loadingTable && (
- { setIsCreatingRule(true); }} > Create Role mapping - + {createFlyout} {editFlyout}
@@ -185,7 +188,7 @@ export const RolesMapping = () => { rolesEquivalences={rolesEquivalences} loading={loadingTable || rolesLoading} rules={rules} - editRule={(item) => { + editRule={item => { setSelectedRule(item); setIsEditingRule(true); }} @@ -194,4 +197,4 @@ export const RolesMapping = () => { ); -}; +}); diff --git a/plugins/main/public/components/security/roles/create-role.tsx b/plugins/main/public/components/security/roles/create-role.tsx index 7ddb27780e..832954df28 100644 --- a/plugins/main/public/components/security/roles/create-role.tsx +++ b/plugins/main/public/components/security/roles/create-role.tsx @@ -1,14 +1,11 @@ import React, { useState, useEffect } from 'react'; import { - EuiButton, EuiTitle, - EuiFlyout, EuiFlyoutHeader, EuiFlyoutBody, EuiForm, EuiFieldText, EuiOverlayMask, - EuiOutsideClickDetector, EuiFormRow, EuiSpacer, EuiComboBox, @@ -17,8 +14,8 @@ import { import { WzRequest } from '../../../react-services/wz-request'; import { ErrorHandler } from '../../../react-services/error-handler'; -import { WzOverlayMask } from '../../common/util'; import { WzFlyout } from '../../common/flyouts'; +import { WzButtonPermissions } from '../../common/permissions/button'; export const CreateRole = ({ closeFlyout }) => { const [policies, setPolicies] = useState([]); @@ -32,12 +29,16 @@ export const CreateRole = ({ closeFlyout }) => { const [hasChanges, setHasChanges] = useState(false); async function getData() { - const policies_request = await WzRequest.apiReq('GET', '/security/policies', {}); - const policies = ((((policies_request || {}).data || {}).data || {}).affected_items || []).map( - (x) => { - return { label: x.name, id: x.id }; - } + const policies_request = await WzRequest.apiReq( + 'GET', + '/security/policies', + {}, ); + const policies = ( + (((policies_request || {}).data || {}).data || {}).affected_items || [] + ).map(x => { + return { label: x.name, id: x.id }; + }); setPolicies(policies); } @@ -71,31 +72,37 @@ export const CreateRole = ({ closeFlyout }) => { if (data.affected_items && data.affected_items) { roleId = data.affected_items[0].id; } - const policiesId = selectedPolicies.map((policy) => { + const policiesId = selectedPolicies.map(policy => { return policy.id; }); - const policyResult = await WzRequest.apiReq('POST', `/security/roles/${roleId}/policies`, { - params: { - policy_ids: policiesId.toString(), + const policyResult = await WzRequest.apiReq( + 'POST', + `/security/roles/${roleId}/policies`, + { + params: { + policy_ids: policiesId.toString(), + }, }, - }); + ); const policiesData = (policyResult.data || {}).data; if (policiesData.failed_items && policiesData.failed_items.length) { return; } - ErrorHandler.info('Role was successfully created with the selected policies'); + ErrorHandler.info( + 'Role was successfully created with the selected policies', + ); } catch (error) { ErrorHandler.handle(error, 'There was an error'); } closeFlyout(false); }; - const onChangeRoleName = (e) => { + const onChangeRoleName = e => { setRoleName(e.target.value); }; - const onChangePolicies = (selectedPolicies) => { + const onChangePolicies = selectedPolicies => { setSelectedPolicies(selectedPolicies); }; @@ -104,7 +111,7 @@ export const CreateRole = ({ closeFlyout }) => { modal = ( { setIsModalVisible(false); closeFlyout(false); @@ -112,7 +119,7 @@ export const CreateRole = ({ closeFlyout }) => { }} onCancel={() => setIsModalVisible(false)} cancelButtonText="No, don't do it" - confirmButtonText="Yes, do it" + confirmButtonText='Yes, do it' >

There are unsaved changes. Are you sure you want to proceed? @@ -123,7 +130,10 @@ export const CreateRole = ({ closeFlyout }) => { } useEffect(() => { - if (initialSelectedPolies.length != selectedPolicies.length || initialRoleName != roleName) { + if ( + initialSelectedPolies.length != selectedPolicies.length || + initialRoleName != roleName + ) { setHasChanges(true); } else { setHasChanges(false); @@ -138,44 +148,52 @@ export const CreateRole = ({ closeFlyout }) => { <> - +

New role

- + onChangeRoleName(e)} - aria-label="" + onChange={e => onChangeRoleName(e)} + aria-label='' /> - + Create role - + diff --git a/plugins/main/public/components/security/roles/edit-role-table.tsx b/plugins/main/public/components/security/roles/edit-role-table.tsx index c75bc893ab..f12fba2967 100644 --- a/plugins/main/public/components/security/roles/edit-role-table.tsx +++ b/plugins/main/public/components/security/roles/edit-role-table.tsx @@ -1,122 +1,137 @@ -import React, { useState, useEffect } from 'react'; -import { - EuiBasicTable, - EuiButtonIcon, - EuiDescriptionList, -} from '@elastic/eui'; +import React, { useState } from 'react'; +import { EuiBasicTable, EuiButtonIcon, EuiDescriptionList } from '@elastic/eui'; import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; import { WzRequest } from '../../../react-services/wz-request'; import { ErrorHandler } from '../../../react-services/error-handler'; +import { WzButtonPermissions } from '../../common/permissions/button'; +export const EditRolesTable = ({ + policies, + role, + onChange, + isDisabled, + loading, +}) => { + const [isLoading, setIsLoading] = useState(false); + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(10); + const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState({}); + const onTableChange = ({ page = {}, sort = {} }) => { + const { index: pageIndex, size: pageSize } = page; + setPageIndex(pageIndex); + setPageSize(pageSize); + }; -export const EditRolesTable = ({ policies, role, onChange, isDisabled, loading}) => { - const [isLoading, setIsLoading] = useState(false); - const [pageIndex, setPageIndex] = useState(0); - const [pageSize, setPageSize] = useState(10); - const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState({}); - - const onTableChange = ({ page = {}, sort = {} }) => { - const { index: pageIndex, size: pageSize } = page; - setPageIndex(pageIndex); - setPageSize(pageSize); - }; + const formatPolicies = policiesArray => { + return policiesArray.map(policy => { + return policies.find(item => item.id === policy); + }); + }; - const formatPolicies = (policiesArray) => { - return policiesArray.map(policy => { - return policies.find(item => item.id === policy) - }) - } + const getItems = () => { + if (loading) return { pageOfItems: [], totalItemCount: 0 }; + const items = formatPolicies( + role.policies.slice( + pageIndex * pageSize, + pageIndex * pageSize + pageSize, + ), + ); + return { pageOfItems: items, totalItemCount: role.policies.length }; + }; - const getItems = () => { - if(loading) return { pageOfItems: [], totalItemCount: 0}; - const items = formatPolicies(role.policies.slice(pageIndex*pageSize, pageIndex*pageSize + pageSize)); - return { pageOfItems: items, totalItemCount: role.policies.length} - } + const { pageOfItems, totalItemCount } = getItems(); - const { pageOfItems, totalItemCount } = getItems(); - - const toggleDetails = item => { - const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; - if (itemIdToExpandedRowMapValues[item.id]) { - delete itemIdToExpandedRowMapValues[item.id]; - } else { - - const listItems = [ - { - title: 'Actions', - description: `${item.policy.actions}`, - }, - { - title: 'Resources', - description: `${item.policy.resources}`, - }, - { - title: 'Effect', - description: `${item.policy.effect}`, - }, - ]; - itemIdToExpandedRowMapValues[item.id] = ( - - ); - } - setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); - }; - const columns = [ + const toggleDetails = item => { + const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; + if (itemIdToExpandedRowMapValues[item.id]) { + delete itemIdToExpandedRowMapValues[item.id]; + } else { + const listItems = [ { - field: 'label', - name: 'Policies', - sortable: false, - truncateText: true + title: 'Actions', + description: `${item.policy.actions}`, }, { - name: 'Actions', - actions: [ - { - name: 'Remove', - description: 'Remove', - type: 'icon', - color: 'danger', - icon: 'trash', - enabled : () => !isDisabled && !isLoading, - onClick: async(item) => { - try{ - setIsLoading(true); - const response = await WzRequest.apiReq( - 'DELETE', - `/security/roles/${role.id}/policies`, - { - params: { - policy_ids: item.id - } - } - ); - const removePolicy = (response.data || {}).data; - if (removePolicy.failed_items && removePolicy.failed_items.length) { - setIsLoading(false); - return; - } - ErrorHandler.info(`Policy was successfully removed from role ${role.name}`); - await onChange(); - }catch(err){ } - setIsLoading(false); - }, - }, - ], + title: 'Resources', + description: `${item.policy.resources}`, }, { - align: RIGHT_ALIGNMENT, - width: '40px', - isExpander: true, + title: 'Effect', + description: `${item.policy.effect}`, + }, + ]; + itemIdToExpandedRowMapValues[item.id] = ( + + ); + } + setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); + }; + const columns = [ + { + field: 'label', + name: 'Policies', + sortable: false, + truncateText: true, + }, + { + name: 'Actions', + actions: [ + { render: item => ( - toggleDetails(item)} - aria-label={itemIdToExpandedRowMap[item.id] ? 'Collapse' : 'Expand'} - iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'} + { + try { + setIsLoading(true); + const response = await WzRequest.apiReq( + 'DELETE', + `/security/roles/${role.id}/policies`, + { + params: { + policy_ids: item.id, + }, + }, + ); + const removePolicy = (response.data || {}).data; + if ( + removePolicy.failed_items && + removePolicy.failed_items.length + ) { + setIsLoading(false); + return; + } + ErrorHandler.info( + `Policy was successfully removed from role ${role.name}`, + ); + await onChange(); + } catch (err) {} + setIsLoading(false); + }} /> ), }, - ]; + ], + }, + { + align: RIGHT_ALIGNMENT, + width: '40px', + isExpander: true, + render: item => ( + toggleDetails(item)} + aria-label={itemIdToExpandedRowMap[item.id] ? 'Collapse' : 'Expand'} + iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'} + /> + ), + }, + ]; const pagination = { pageIndex: pageIndex, @@ -129,7 +144,7 @@ export const EditRolesTable = ({ policies, role, onChange, isDisabled, loading}) <> { const [isLoading, setIsLoading] = useState(true); const [currentRole, setCurrentRole] = useState({}); - const [isReserved, setIsReserved] = useState(reservedRoles.includes(role.name)); + const [isReserved, setIsReserved] = useState( + reservedRoles.includes(role.name), + ); const [policies, setPolicies] = useState([]); const [selectedPolicies, setSelectedPolicies] = useState([]); const [selectedPoliciesError, setSelectedPoliciesError] = useState(false); @@ -50,27 +49,41 @@ export const EditRole = ({ role, closeFlyout }) => { async function getData() { try { setIsLoading(true); - const roleDataResponse = await WzRequest.apiReq('GET', '/security/roles', { - params: { - role_ids: role.id, + const roleDataResponse = await WzRequest.apiReq( + 'GET', + '/security/roles', + { + params: { + role_ids: role.id, + }, }, - }); - const roleData = (((roleDataResponse.data || {}).data || {}).affected_items || [])[0]; + ); + const roleData = (((roleDataResponse.data || {}).data || {}) + .affected_items || [])[0]; setCurrentRole(roleData); - const policies_request = await WzRequest.apiReq('GET', '/security/policies', {}); + const policies_request = await WzRequest.apiReq( + 'GET', + '/security/policies', + {}, + ); const selectedPoliciesCopy = []; - const policies = ( - (((policies_request || {}).data || {}).data || {}).affected_items || [] - ).map((x) => { - const currentPolicy = { label: x.name, id: x.id, roles: x.roles, policy: x.policy }; - if (roleData.policies.includes(x.id)) { - selectedPoliciesCopy.push(currentPolicy); - return false; - } else { - return currentPolicy; - } - }); - const filteredPolicies = policies.filter((item) => item !== false); + const policies = (policies_request?.data?.data?.affected_items || []).map( + x => { + const currentPolicy = { + label: x.name, + id: x.id, + roles: x.roles, + policy: x.policy, + }; + if (roleData.policies.includes(x.id)) { + selectedPoliciesCopy.push(currentPolicy); + return false; + } else { + return currentPolicy; + } + }, + ); + const filteredPolicies = policies.filter(item => item !== false); setAssignedPolicies(selectedPoliciesCopy); setPolicies(filteredPolicies); } catch (error) { @@ -94,20 +107,26 @@ export const EditRole = ({ role, closeFlyout }) => { try { let roleId = currentRole.id; - const policiesId = selectedPolicies.map((policy) => { + const policiesId = selectedPolicies.map(policy => { return policy.id; }); - const policyResult = await WzRequest.apiReq('POST', `/security/roles/${roleId}/policies`, { - params: { - policy_ids: policiesId.toString(), + const policyResult = await WzRequest.apiReq( + 'POST', + `/security/roles/${roleId}/policies`, + { + params: { + policy_ids: policiesId.toString(), + }, }, - }); + ); const policiesData = (policyResult.data || {}).data; if (policiesData.failed_items && policiesData.failed_items.length) { return; } - ErrorHandler.info('Role was successfully updated with the selected policies'); + ErrorHandler.info( + 'Role was successfully updated with the selected policies', + ); setSelectedPolicies([]); await update(); } catch (error) { @@ -130,7 +149,7 @@ export const EditRole = ({ role, closeFlyout }) => { await getData(); }; - const onChangePolicies = (selectedPolicies) => { + const onChangePolicies = selectedPolicies => { setSelectedPolicies(selectedPolicies); }; @@ -139,14 +158,14 @@ export const EditRole = ({ role, closeFlyout }) => { modal = ( { setIsModalVisible(false); closeFlyout(false); }} onCancel={() => setIsModalVisible(false)} cancelButtonText="No, don't do it" - confirmButtonText="Yes, do it" + confirmButtonText='Yes, do it' >

There are unsaved changes. Are you sure you want to proceed? @@ -164,45 +183,52 @@ export const EditRole = ({ role, closeFlyout }) => { return ( <> - + - +

Edit {role.name} role   - {isReserved && Reserved} + {isReserved && Reserved}

- + - - + Add policy - +
diff --git a/plugins/main/public/components/security/roles/roles-table.tsx b/plugins/main/public/components/security/roles/roles-table.tsx index 597a8d029a..20a66adcc4 100644 --- a/plugins/main/public/components/security/roles/roles-table.tsx +++ b/plugins/main/public/components/security/roles/roles-table.tsx @@ -5,20 +5,25 @@ import { EuiFlexGroup, EuiFlexItem, EuiToolTip, - EuiButtonIcon, EuiSpacer, EuiLoadingSpinner, } from '@elastic/eui'; import { WzRequest } from '../../../react-services/wz-request'; import { ErrorHandler } from '../../../react-services/error-handler'; -import { WzButtonModalConfirm } from '../../common/buttons'; +import { WzButtonPermissionsModalConfirm } from '../../common/buttons'; import { WzAPIUtils } from '../../../react-services/wz-api-utils'; import { UI_LOGGER_LEVELS } from '../../../../common/constants'; import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; import { getErrorOrchestrator } from '../../../react-services/common-services'; -export const RolesTable = ({ roles, policiesData, loading, editRole, updateRoles }) => { - const getRowProps = (item) => { +export const RolesTable = ({ + roles, + policiesData, + loading, + editRole, + updateRoles, +}) => { + const getRowProps = item => { const { id } = item; return { 'data-test-subj': `row-${id}`, @@ -26,7 +31,7 @@ export const RolesTable = ({ roles, policiesData, loading, editRole, updateRoles }; }; - const onConfirmDeleteRole = (item) => { + const onConfirmDeleteRole = item => { return async () => { try { const response = await WzRequest.apiReq('DELETE', `/security/roles/`, { @@ -55,7 +60,7 @@ export const RolesTable = ({ roles, policiesData, loading, editRole, updateRoles getErrorOrchestrator().handleError(options); } }; - } + }; const columns = [ { @@ -75,32 +80,37 @@ export const RolesTable = ({ roles, policiesData, loading, editRole, updateRoles { field: 'policies', name: 'Policies', - render: (policies) => { + render: policies => { return ( (policiesData && ( - - {policies.map((policy) => { - const data = (policiesData || []).find((x) => x.id === policy) || {}; + + {policies.map(policy => { + const data = + (policiesData || []).find(x => x.id === policy) || {}; return ( data.name && ( Actions -

{((data.policy || {}).actions || []).join(', ')}

- +

+ {((data.policy || {}).actions || []).join(', ')} +

+ Resources -

{((data.policy || {}).resources || []).join(', ')}

- +

+ {((data.policy || {}).resources || []).join(', ')} +

+ Effect

{(data.policy || {}).effect}

} > {}} onClickAriaLabel={`${data.name} policy`} title={null} @@ -113,7 +123,7 @@ export const RolesTable = ({ roles, policiesData, loading, editRole, updateRoles ); })}
- )) || + )) || ); }, sortable: true, @@ -121,8 +131,12 @@ export const RolesTable = ({ roles, policiesData, loading, editRole, updateRoles { field: 'id', name: 'Status', - render: (item) => { - return WzAPIUtils.isReservedID(item) && Reserved; + render: item => { + return ( + WzAPIUtils.isReservedID(item) && ( + Reserved + ) + ); }, width: 150, sortable: false, @@ -131,10 +145,13 @@ export const RolesTable = ({ roles, policiesData, loading, editRole, updateRoles align: 'right', width: '5%', name: 'Actions', - render: (item) => ( -
ev.stopPropagation()}> - ( +
ev.stopPropagation()}> +
), diff --git a/plugins/main/public/components/security/roles/roles.tsx b/plugins/main/public/components/security/roles/roles.tsx index f181f1bb77..fc2dc63107 100644 --- a/plugins/main/public/components/security/roles/roles.tsx +++ b/plugins/main/public/components/security/roles/roles.tsx @@ -4,47 +4,39 @@ import { EuiPageContentHeader, EuiPageContentHeaderSection, EuiPageContentBody, - EuiButton, EuiTitle, - EuiFlyout, - EuiOverlayMask, - EuiFlyoutHeader, - EuiFlyoutBody, - EuiForm, - EuiFormRow, - EuiSpacer, - EuiFieldText, - EuiComboBox } from '@elastic/eui'; import { RolesTable } from './roles-table'; -import { WzRequest } from '../../../react-services/wz-request' +import { WzRequest } from '../../../react-services/wz-request'; import { CreateRole } from './create-role'; import { EditRole } from './edit-role'; +import { withUserAuthorizationPrompt } from '../../common/hocs'; +import { WzButtonPermissions } from '../../common/permissions/button'; -export const Roles = () => { +export const Roles = withUserAuthorizationPrompt([ + { action: 'security:read', resource: 'role:id:*' }, + { action: 'security:read', resource: 'policy:id:*' }, +])(() => { const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); const [isEditFlyoutVisible, setIsEditFlyoutVisible] = useState(false); const [editingRole, setEditingRole] = useState(false); - const [roles, setRoles] = useState([]) - const [policiesData, setPoliciesData] = useState([]) + const [roles, setRoles] = useState([]); + const [policiesData, setPoliciesData] = useState([]); const [loadingTable, setLoadingTable] = useState(false); - async function getData() { setLoadingTable(true); - const roles_request = await WzRequest.apiReq( - 'GET', - '/security/roles', - {} - ); - const roles = (((roles_request || {}).data || {}).data || {}).affected_items || []; + const roles_request = await WzRequest.apiReq('GET', '/security/roles', {}); + const roles = + (((roles_request || {}).data || {}).data || {}).affected_items || []; setRoles(roles); const policies_request = await WzRequest.apiReq( 'GET', '/security/policies', - {} + {}, ); - const policiesData = (((policies_request || {}).data || {}).data || {}).affected_items || []; + const policiesData = + (((policies_request || {}).data || {}).data || {}).affected_items || []; setPoliciesData(policiesData); setLoadingTable(false); } @@ -56,26 +48,30 @@ export const Roles = () => { let flyout; if (isFlyoutVisible) { flyout = ( - { + { setIsFlyoutVisible(isVisible); await getData(); - }} /> + }} + /> ); } - const editRole = (item) => { + const editRole = item => { setEditingRole(item); setIsEditFlyoutVisible(true); - } + }; let editFlyout; if (isEditFlyoutVisible) { editFlyout = ( - { + { setIsEditFlyoutVisible(isVisible); await getData(); - }} /> + }} + /> ); } @@ -88,23 +84,30 @@ export const Roles = () => { - { - !loadingTable - && -
- setIsFlyoutVisible(true)}> - Create role - - {flyout} - {editFlyout} -
- } + {!loadingTable && ( +
+ setIsFlyoutVisible(true)} + > + Create role + + {flyout} + {editFlyout} +
+ )}
- + ); -}; \ No newline at end of file +}); diff --git a/plugins/main/public/components/security/users/components/create-user.tsx b/plugins/main/public/components/security/users/components/create-user.tsx index 9ff0a3a072..2e4bc9288b 100644 --- a/plugins/main/public/components/security/users/components/create-user.tsx +++ b/plugins/main/public/components/security/users/components/create-user.tsx @@ -1,8 +1,6 @@ import React, { useEffect, useRef, useState } from 'react'; import { - EuiButton, EuiTitle, - EuiFlyout, EuiFlyoutHeader, EuiFlyoutBody, EuiForm, @@ -14,7 +12,6 @@ import { EuiFieldPassword, EuiFieldText, EuiOverlayMask, - EuiOutsideClickDetector, EuiPanel, EuiConfirmModal, } from '@elastic/eui'; @@ -34,9 +31,12 @@ import { WzFlyout } from '../../../common/flyouts'; export const CreateUser = ({ closeFlyout }) => { const [selectedRoles, setSelectedRole] = useState([]); - const [rolesLoading, roles, rolesError] = useApiService(RolesServices.GetRoles, {}); + const [rolesLoading, roles, rolesError] = useApiService( + RolesServices.GetRoles, + {}, + ); const rolesOptions: any = roles - ? roles.map((item) => { + ? roles.map(item => { return { label: item.name, id: item.id }; }) : []; @@ -65,7 +65,7 @@ export const CreateUser = ({ closeFlyout }) => { else userNameRef.current = true; }, 300, - [userName] + [userName], ); const passwordRef = useRef(false); @@ -75,7 +75,7 @@ export const CreateUser = ({ closeFlyout }) => { else passwordRef.current = true; }, 300, - [password] + [password], ); const confirmPasswordRef = useRef(false); @@ -85,7 +85,7 @@ export const CreateUser = ({ closeFlyout }) => { else confirmPasswordRef.current = true; }, 300, - [confirmPassword] + [confirmPassword], ); useDebouncedEffect( @@ -93,13 +93,18 @@ export const CreateUser = ({ closeFlyout }) => { setShowApply(isValidForm(false)); }, 300, - [userName, password, confirmPassword] + [userName, password, confirmPassword], ); const validations = { userName: [ { fn: () => (userName.trim() === '' ? 'The user name is required' : '') }, - { fn: () => (userName.trim().includes(' ') ? 'The user name cannot contain spaces' : '') }, + { + fn: () => + userName.trim().includes(' ') + ? 'The user name cannot contain spaces' + : '', + }, { fn: () => !userName.match(/^.{4,20}$/) @@ -111,21 +116,29 @@ export const CreateUser = ({ closeFlyout }) => { { fn: () => (password === '' ? 'The password is required' : '') }, { fn: () => - !password.match(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,64}$/) + !password.match( + /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,64}$/, + ) ? 'The password must contain a length between 8 and 64 characters, and must contain at least one upper and lower case letter, a number and a symbol.' : '', }, ], confirmPassword: [ - { fn: () => (confirmPassword === '' ? 'The confirm password is required' : '') }, - { fn: () => (confirmPassword !== password ? `Passwords don't match.` : '') }, + { + fn: () => + confirmPassword === '' ? 'The confirm password is required' : '', + }, + { + fn: () => + confirmPassword !== password ? `Passwords don't match.` : '', + }, ], }; const validateFields = (fields, showErrors = true) => { const _formErrors = { ...formErrors }; let isValid = true; - fields.forEach((field) => { + fields.forEach(field => { const error = validations[field].reduce((currentError, validation) => { return !!currentError ? currentError : validation.fn(); }, ''); @@ -157,7 +170,8 @@ export const CreateUser = ({ closeFlyout }) => { try { const user = await UsersServices.CreateUser(userData); await addRoles(user.id); - if (allowRunAsData) await UsersServices.UpdateAllowRunAs(user.id, allowRunAsData); + if (allowRunAsData) + await UsersServices.UpdateAllowRunAs(user.id, allowRunAsData); ErrorHandler.info('User was successfully created'); closeFlyout(true); @@ -178,30 +192,31 @@ export const CreateUser = ({ closeFlyout }) => { } }; - const addRoles = async (userId) => { - const formattedRoles = selectedRoles.map((item) => { + const addRoles = async userId => { + const formattedRoles = selectedRoles.map(item => { return item.id; }); - if (formattedRoles.length > 0) await UsersServices.AddUserRoles(userId, formattedRoles); + if (formattedRoles.length > 0) + await UsersServices.AddUserRoles(userId, formattedRoles); }; - const onChangeRoles = (selectedRoles) => { + const onChangeRoles = selectedRoles => { setSelectedRole(selectedRoles); }; - const onChangeUserName = (e) => { + const onChangeUserName = e => { setUserName(e.target.value); }; - const onChangePassword = (e) => { + const onChangePassword = e => { setPassword(e.target.value); }; - const onChangeConfirmPassword = (e) => { + const onChangeConfirmPassword = e => { setConfirmPassword(e.target.value); }; - const onChangeAllowRunAs = (e) => { + const onChangeAllowRunAs = e => { setAllowRunAs(e.target.checked); }; @@ -210,7 +225,7 @@ export const CreateUser = ({ closeFlyout }) => { modal = ( { setIsModalVisible(false); closeFlyout(false); @@ -218,7 +233,7 @@ export const CreateUser = ({ closeFlyout }) => { }} onCancel={() => setIsModalVisible(false)} cancelButtonText="No, don't do it" - confirmButtonText="Yes, do it" + confirmButtonText='Yes, do it' >

There are unsaved changes. Are you sure you want to proceed? @@ -250,94 +265,114 @@ export const CreateUser = ({ closeFlyout }) => { <> - +

Create new user

- + - +

User data

onChangeUserName(e)} - aria-label="" + onChange={e => onChangeUserName(e)} + aria-label='' isInvalid={!!formErrors.userName} /> onChangePassword(e)} - aria-label="" + onChange={e => onChangePassword(e)} + aria-label='' isInvalid={!!formErrors.password} /> onChangeConfirmPassword(e)} - aria-label="" + onChange={e => onChangeConfirmPassword(e)} + aria-label='' isInvalid={!!formErrors.confirmPassword} /> - + onChangeAllowRunAs(e)} - aria-label="" + permissions={[ + { action: 'security:edit_run_as', resource: '*:*:*' }, + ]} + onChange={e => onChangeAllowRunAs(e)} + aria-label='' />
- +

User roles

- +
- + Apply - +
diff --git a/plugins/main/public/components/security/users/components/edit-user.tsx b/plugins/main/public/components/security/users/components/edit-user.tsx index 2f6123df25..12ced6d42b 100644 --- a/plugins/main/public/components/security/users/components/edit-user.tsx +++ b/plugins/main/public/components/security/users/components/edit-user.tsx @@ -1,6 +1,5 @@ import React, { useRef, useState, useEffect } from 'react'; import { - EuiButton, EuiTitle, EuiFlyoutHeader, EuiFlyoutBody, @@ -35,12 +34,15 @@ import { WzFlyout } from '../../../common/flyouts'; export const EditUser = ({ currentUser, closeFlyout, rolesObject }) => { const userRolesFormatted = currentUser.roles && currentUser.roles.length - ? currentUser.roles.map((item) => ({ label: rolesObject[item], id: item })) + ? currentUser.roles.map(item => ({ label: rolesObject[item], id: item })) : []; const [selectedRoles, setSelectedRole] = useState(userRolesFormatted); - const [rolesLoading, roles, rolesError] = useApiService(RolesServices.GetRoles, {}); + const [rolesLoading, roles, rolesError] = useApiService( + RolesServices.GetRoles, + {}, + ); const rolesOptions: any = roles - ? roles.map((item) => { + ? roles.map(item => { return { label: item.name, id: item.id }; }) : []; @@ -50,7 +52,9 @@ export const EditUser = ({ currentUser, closeFlyout, rolesObject }) => { const [confirmPassword, setConfirmPassword] = useState(''); const [initialPassword] = useState(''); const [hasChanges, setHasChanges] = useState(false); - const [allowRunAs, setAllowRunAs] = useState(currentUser.allow_run_as); + const [allowRunAs, setAllowRunAs] = useState( + currentUser.allow_run_as, + ); const [isModalVisible, setIsModalVisible] = useState(false); const [formErrors, setFormErrors] = useState({ password: '', @@ -65,7 +69,7 @@ export const EditUser = ({ currentUser, closeFlyout, rolesObject }) => { else passwordRef.current = true; }, 300, - [password] + [password], ); const confirmPasswordRef = useRef(false); @@ -75,7 +79,7 @@ export const EditUser = ({ currentUser, closeFlyout, rolesObject }) => { else confirmPasswordRef.current = true; }, 300, - [confirmPassword] + [confirmPassword], ); useDebouncedEffect( @@ -84,12 +88,12 @@ export const EditUser = ({ currentUser, closeFlyout, rolesObject }) => { isValidForm(false) && (allowRunAs !== currentUser.allow_run_as || password !== '' || - Object.values(getRolesDiff()).some((i) => i.length)); + Object.values(getRolesDiff()).some(i => i.length)); setShowApply(_showApply); }, 300, - [password, confirmPassword, allowRunAs, selectedRoles] + [password, confirmPassword, allowRunAs, selectedRoles], ); const validations = { @@ -97,18 +101,25 @@ export const EditUser = ({ currentUser, closeFlyout, rolesObject }) => { { fn: () => password !== '' && - !password.match(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,64}$/) + !password.match( + /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,64}$/, + ) ? 'The password must contain a length between 8 and 64 characters, and must contain at least one upper and lower case letter, a number and a symbol.' : '', }, ], - confirmPassword: [{ fn: () => (confirmPassword !== password ? `Passwords don't match.` : '') }], + confirmPassword: [ + { + fn: () => + confirmPassword !== password ? `Passwords don't match.` : '', + }, + ], }; const validateFields = (fields, showErrors = true) => { const _formErrors = { ...formErrors }; let isValid = true; - fields.forEach((field) => { + fields.forEach(field => { const error = validations[field].reduce((currentError, validation) => { return !!currentError ? currentError : validation.fn(); }, ''); @@ -136,7 +147,9 @@ export const EditUser = ({ currentUser, closeFlyout, rolesObject }) => { const allowRunAsData: boolean = allowRunAs; if (allowRunAsData != currentUser.allow_run_as) - userPromises.push(UsersServices.UpdateAllowRunAs(currentUser.id, allowRunAsData)); + userPromises.push( + UsersServices.UpdateAllowRunAs(currentUser.id, allowRunAsData), + ); if (password) { userData.password = password; @@ -167,32 +180,37 @@ export const EditUser = ({ currentUser, closeFlyout, rolesObject }) => { }; const getRolesDiff = () => { - const formattedRoles = selectedRoles.map((item) => item.id); - const _userRolesFormatted = userRolesFormatted.map((role) => role.id); - const toAdd = formattedRoles.filter((value) => !_userRolesFormatted.includes(value)); - const toRemove = _userRolesFormatted.filter((value) => !formattedRoles.includes(value)); + const formattedRoles = selectedRoles.map(item => item.id); + const _userRolesFormatted = userRolesFormatted.map(role => role.id); + const toAdd = formattedRoles.filter( + value => !_userRolesFormatted.includes(value), + ); + const toRemove = _userRolesFormatted.filter( + value => !formattedRoles.includes(value), + ); return { toAdd, toRemove }; }; const updateRoles = async () => { const { toAdd, toRemove } = getRolesDiff(); if (toAdd.length) await UsersServices.AddUserRoles(currentUser.id, toAdd); - if (toRemove.length) await UsersServices.RemoveUserRoles(currentUser.id, toRemove); + if (toRemove.length) + await UsersServices.RemoveUserRoles(currentUser.id, toRemove); }; - const onChangeRoles = (selectedRoles) => { + const onChangeRoles = selectedRoles => { setSelectedRole(selectedRoles); }; - const onChangePassword = (e) => { + const onChangePassword = e => { setPassword(e.target.value); }; - const onChangeConfirmPassword = (e) => { + const onChangeConfirmPassword = e => { setConfirmPassword(e.target.value); }; - const onChangeAllowRunAs = (e) => { + const onChangeAllowRunAs = e => { setAllowRunAs(e.target.checked); }; @@ -201,7 +219,7 @@ export const EditUser = ({ currentUser, closeFlyout, rolesObject }) => { modal = ( { setIsModalVisible(false); closeFlyout(false); @@ -209,7 +227,7 @@ export const EditUser = ({ currentUser, closeFlyout, rolesObject }) => { }} onCancel={() => setIsModalVisible(false)} cancelButtonText="No, don't do it" - confirmButtonText="Yes, do it" + confirmButtonText='Yes, do it' >

There are unsaved changes. Are you sure you want to proceed? @@ -221,8 +239,10 @@ export const EditUser = ({ currentUser, closeFlyout, rolesObject }) => { useEffect(() => { if ( - initialPassword != password || initialPassword != confirmPassword || - !_.isEqual(userRolesFormatted, selectedRoles) || allowRunAs != currentUser.allow_run_as + initialPassword != password || + initialPassword != confirmPassword || + !_.isEqual(userRolesFormatted, selectedRoles) || + allowRunAs != currentUser.allow_run_as ) { setHasChanges(true); } else { @@ -238,65 +258,70 @@ export const EditUser = ({ currentUser, closeFlyout, rolesObject }) => { <> - +

Edit {currentUser.username} user     {WzAPIUtils.isReservedID(currentUser.id) && ( - Reserved + Reserved )}

- + - +

Run as

- + onChangeAllowRunAs(e)} - aria-label="" + permissions={[ + { action: 'security:edit_run_as', resource: '*:*:*' }, + ]} + onChange={e => onChangeAllowRunAs(e)} + aria-label='' disabled={WzAPIUtils.isReservedID(currentUser.id)} />
- +

Password

onChangePassword(e)} - aria-label="" + onChange={e => onChangePassword(e)} + aria-label='' isInvalid={!!formErrors.password} disabled={WzAPIUtils.isReservedID(currentUser.id)} /> onChangeConfirmPassword(e)} - aria-label="" + onChange={e => onChangeConfirmPassword(e)} + aria-label='' isInvalid={!!formErrors.confirmPassword} disabled={WzAPIUtils.isReservedID(currentUser.id)} /> @@ -304,18 +329,18 @@ export const EditUser = ({ currentUser, closeFlyout, rolesObject }) => {
- +

Roles

- + @@ -324,14 +349,31 @@ export const EditUser = ({ currentUser, closeFlyout, rolesObject }) => { - Apply - +
diff --git a/plugins/main/public/components/security/users/components/users-table.tsx b/plugins/main/public/components/security/users/components/users-table.tsx index b0add823bb..9f9bb51f91 100644 --- a/plugins/main/public/components/security/users/components/users-table.tsx +++ b/plugins/main/public/components/security/users/components/users-table.tsx @@ -8,7 +8,7 @@ import { EuiBasicTableColumn, SortDirection, } from '@elastic/eui'; -import { WzButtonModalConfirm } from '../../../common/buttons'; +import { WzButtonPermissionsModalConfirm } from '../../../common/buttons'; import UsersServices from '../services'; import { ErrorHandler } from '../../../../react-services/error-handler'; import { WzAPIUtils } from '../../../../react-services/wz-api-utils'; @@ -16,7 +16,13 @@ import { UI_LOGGER_LEVELS } from '../../../../../common/constants'; import { UI_ERROR_SEVERITIES } from '../../../../react-services/error-orchestrator/types'; import { getErrorOrchestrator } from '../../../../react-services/common-services'; -export const UsersTable = ({ users, editUserFlyover, rolesLoading, roles, onSave }) => { +export const UsersTable = ({ + users, + editUserFlyover, + rolesLoading, + roles, + onSave, +}) => { const getRowProps = item => { const { id } = item; return { @@ -25,7 +31,7 @@ export const UsersTable = ({ users, editUserFlyover, rolesLoading, roles, onSave }; }; - const onConfirmDeleteUser = (item) => { + const onConfirmDeleteUser = item => { return async () => { try { await UsersServices.DeleteUsers([item.id]); @@ -67,18 +73,18 @@ export const UsersTable = ({ users, editUserFlyover, rolesLoading, roles, onSave dataType: 'boolean', render: userRoles => { if (rolesLoading) { - return ; + return ; } if (!userRoles || !userRoles.length) return <>; const tmpRoles = userRoles.map((userRole, idx) => { return ( - {roles[userRole]} + {roles[userRole]} ); }); return ( - + {tmpRoles} ); @@ -91,21 +97,26 @@ export const UsersTable = ({ users, editUserFlyover, rolesLoading, roles, onSave name: 'Actions', render: item => (
ev.stopPropagation()}> -
), diff --git a/plugins/main/public/components/security/users/users.tsx b/plugins/main/public/components/security/users/users.tsx index f837cc6d0c..4672e4c18e 100644 --- a/plugins/main/public/components/security/users/users.tsx +++ b/plugins/main/public/components/security/users/users.tsx @@ -4,9 +4,7 @@ import { EuiPageContentHeader, EuiPageContentHeaderSection, EuiPageContentBody, - EuiButton, EuiTitle, - EuiOverlayMask, EuiEmptyPrompt, } from '@elastic/eui'; import { UsersTable } from './components/users-table'; @@ -20,13 +18,21 @@ import { Role } from '../roles/types/role.type'; import { UI_LOGGER_LEVELS } from '../../../../common/constants'; import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; import { getErrorOrchestrator } from '../../../react-services/common-services'; +import { withUserAuthorizationPrompt } from '../../common/hocs'; +import { WzButtonPermissions } from '../../common/permissions/button'; -export const Users = () => { +export const Users = withUserAuthorizationPrompt([ + { action: 'security:read', resource: 'user:id:*' }, + { action: 'security:read', resource: 'role:id:*' }, +])(() => { const [isEditFlyoutVisible, setIsEditFlyoutVisible] = useState(false); const [isCreateFlyoutVisible, setIsCreateFlyoutVisible] = useState(false); const [editingUser, setEditingUser] = useState({}); const [users, setUsers] = useState([] as User[]); - const [rolesLoading, roles, rolesError] = useApiService(RolesServices.GetRoles, {}); + const [rolesLoading, roles, rolesError] = useApiService( + RolesServices.GetRoles, + {}, + ); const [securityError, setSecurityError] = useState(false); const [rolesObject, setRolesObject] = useState({}); @@ -56,7 +62,7 @@ export const Users = () => { if (!rolesLoading && (roles || []).length) { const _rolesObject = (roles || []).reduce( (rolesObj, role) => ({ ...rolesObj, [role.id]: role.name }), - {} + {}, ); setRolesObject(_rolesObject); } @@ -75,7 +81,7 @@ export const Users = () => { setIsEditFlyoutVisible(false); }; - const closeCreateFlyout = async (refresh) => { + const closeCreateFlyout = async refresh => { if (refresh) await getUsers(); setIsCreateFlyoutVisible(false); }; @@ -83,7 +89,7 @@ export const Users = () => { if (securityError) { return ( You need permission to manage users} body={

Contact your system administrator.

} /> @@ -91,18 +97,16 @@ export const Users = () => { } if (isEditFlyoutVisible) { editFlyout = ( - + ); } if (isCreateFlyoutVisible) { - createFlyout = ( - - ); + createFlyout = ; } const showEditFlyover = item => { @@ -119,14 +123,20 @@ export const Users = () => { - { - !rolesLoading - && + {!rolesLoading && (
- setIsCreateFlyoutVisible(true)}>Create user + setIsCreateFlyoutVisible(true)} + > + Create user + {createFlyout}
- } + )}
@@ -141,4 +151,4 @@ export const Users = () => { {editFlyout} ); -}; +});