diff --git a/packages/hawtio/src/plugins/rbac/rbac-service.test.ts b/packages/hawtio/src/plugins/rbac/rbac-service.test.ts index 33adc2cc..79493fbb 100644 --- a/packages/hawtio/src/plugins/rbac/rbac-service.test.ts +++ b/packages/hawtio/src/plugins/rbac/rbac-service.test.ts @@ -14,7 +14,7 @@ describe('RBACService', () => { userService.isLogin = jest.fn(async () => true) jolokiaService.search = jest.fn(async () => []) - await expect(rbacService.getACLMBean()).resolves.toBe('') + await expect(rbacService.getACLMBean()).resolves.toBeNull() }) test('there is one ACLMBean', async () => { diff --git a/packages/hawtio/src/plugins/rbac/rbac-service.ts b/packages/hawtio/src/plugins/rbac/rbac-service.ts index 564db40c..5dbd2a72 100644 --- a/packages/hawtio/src/plugins/rbac/rbac-service.ts +++ b/packages/hawtio/src/plugins/rbac/rbac-service.ts @@ -6,18 +6,18 @@ import { log } from './globals' const ACL_MBEAN_PATTERN = '*:type=security,area=jmx,*' interface IRBACService { - getACLMBean(): Promise reset(): void + getACLMBean(): Promise } class RBACService implements IRBACService { - private aclMBean?: Promise + private aclMBean?: Promise reset() { this.aclMBean = undefined } - getACLMBean(): Promise { + getACLMBean(): Promise { if (this.aclMBean) { return this.aclMBean } @@ -27,7 +27,7 @@ class RBACService implements IRBACService { return this.aclMBean } - private async fetchACLMBean(): Promise { + private async fetchACLMBean(): Promise { if (!(await userService.isLogin())) { throw new Error('User needs to have logged in to run RBAC plugin') } @@ -37,7 +37,7 @@ class RBACService implements IRBACService { if (mbeans.length === 0) { log.info("Didn't discover any ACL MBeans; client-side RBAC is disabled") - return '' + return null } const mbean = mbeans[0] @@ -50,7 +50,7 @@ class RBACService implements IRBACService { const chosen = mbeans.find(mbean => !mbean.includes('HawtioDummy')) if (!chosen || isBlank(chosen)) { log.info("Didn't discover any effective ACL MBeans; client-side RBAC is disabled") - return '' + return null } log.info('Use MBean', chosen, 'for client-side RBAC') return chosen diff --git a/packages/hawtio/src/plugins/rbac/tree-processor.ts b/packages/hawtio/src/plugins/rbac/tree-processor.ts index 6d078d65..47d53dc0 100644 --- a/packages/hawtio/src/plugins/rbac/tree-processor.ts +++ b/packages/hawtio/src/plugins/rbac/tree-processor.ts @@ -8,7 +8,6 @@ import { } from '@hawtiosrc/plugins/shared' import { operationToString } from '@hawtiosrc/util/jolokia' import { isString } from '@hawtiosrc/util/objects' -import { isBlank } from '@hawtiosrc/util/strings' import { Request, Response } from 'jolokia.js' import { log } from './globals' import { rbacService } from './rbac-service' @@ -36,7 +35,7 @@ export const rbacTreeProcessor: TreeProcessor = async (tree: MBeanTree) => { log.debug('Processing tree:', tree) const aclMBean = await rbacService.getACLMBean() - if (isBlank(aclMBean)) { + if (!aclMBean) { /* * Some implementations of jolokia provision, eg. running with java -javaagent * do not provide an acl mbean or implement server-side RBAC so need to skip diff --git a/packages/hawtio/src/plugins/shared/attributes/AttributeModal.tsx b/packages/hawtio/src/plugins/shared/attributes/AttributeModal.tsx index 288b19f6..f6f957b7 100644 --- a/packages/hawtio/src/plugins/shared/attributes/AttributeModal.tsx +++ b/packages/hawtio/src/plugins/shared/attributes/AttributeModal.tsx @@ -1,68 +1,103 @@ -import { - Button, - ClipboardCopy, - Form, - FormGroup, - Modal, - ModalVariant, - TextArea, - TextInput, -} from '@patternfly/react-core' -import React, { useEffect, useState, useContext } from 'react' -import { attributeService } from './attribute-service' import { PluginNodeSelectionContext } from '@hawtiosrc/plugins/context' +import { Button, ClipboardCopy, Form, FormGroup, Modal, TextArea, TextInput } from '@patternfly/react-core' +import React, { useContext, useEffect, useState } from 'react' +import { attributeService } from './attribute-service' +import { log } from '../globals' +import { eventService } from '@hawtiosrc/core' -export interface AttributeModalProps { +export const AttributeModal: React.FunctionComponent<{ isOpen: boolean onClose: () => void + onUpdate: () => void input: { name: string; value: string } -} - -export const AttributeModal: React.FunctionComponent = props => { +}> = ({ isOpen, onClose, onUpdate, input }) => { const { selectedNode } = useContext(PluginNodeSelectionContext) - const { isOpen, onClose, input } = props - const { name, value } = input + const attributeName = input.name + const [attributeValue, setAttributeValue] = useState('') const [jolokiaUrl, setJolokiaUrl] = useState('Loading...') + const [isWritable, setIsWritable] = useState(false) useEffect(() => { - if (!selectedNode || !selectedNode.objectName) { + if (!selectedNode || !selectedNode.objectName || !selectedNode.mbean) { return } - const mbean = selectedNode.objectName + const { mbean, objectName } = selectedNode + + const attribute = mbean.attr?.[attributeName] + if (!attribute) { + return + } + + setAttributeValue(input.value) + + // Update Jolokia URL const buildUrl = async () => { - const url = await attributeService.buildUrl(mbean, name) + const url = await attributeService.buildUrl(objectName, attributeName) setJolokiaUrl(url) } buildUrl() - }, [selectedNode, name]) - if (!selectedNode || !selectedNode.mbean || !selectedNode.objectName) { + // Check RBAC on the selected attribute + if (attribute.rw) { + // For writable attribute, we need to check RBAC + const canInvoke = async () => { + const canInvoke = await attributeService.canInvoke(objectName, attributeName, attribute.type) + log.debug('Attribute', attributeName, 'canInvoke:', canInvoke) + setIsWritable(canInvoke) + } + canInvoke() + } else { + setIsWritable(false) + } + }, [selectedNode, attributeName, input]) + + if (!selectedNode || !selectedNode.objectName || !selectedNode.mbean) { return null } - const attribute = selectedNode.mbean.attr?.[name] + const { mbean, objectName } = selectedNode + + const attribute = mbean.attr?.[attributeName] if (!attribute) { return null } - const modalTitle = `Attribute: ${input.name}` + const updateAttribute = async () => { + if (attributeValue === input.value) { + eventService.notify({ type: 'info', message: 'The attribute value has not changed' }) + } else { + await attributeService.update(objectName, attributeName, attributeValue) + onUpdate() + } + onClose() + } + + const modalTitle = `Attribute: ${attributeName}` + + const modalActions = [ + , + ] + if (isWritable) { + modalActions.push( + , + ) + } return ( - - Close - , - ]} - > +
- +