mcgilman commented on code in PR #8225: URL: https://github.com/apache/nifi/pull/8225#discussion_r1450463616
########## nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.ts: ########## @@ -0,0 +1,427 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; +import { + createAccessPolicy, + openAddTenantToPolicyDialog, + promptDeleteAccessPolicy, + promptRemoveTenantFromPolicy, + reloadAccessPolicy, + resetAccessPolicyState, + selectComponentAccessPolicy, + setAccessPolicy +} from '../../state/access-policy/access-policy.actions'; +import { AccessPolicyState, RemoveTenantFromPolicyRequest } from '../../state/access-policy'; +import { initialState } from '../../state/access-policy/access-policy.reducer'; +import { + selectAccessPolicyState, + selectComponentResourceActionFromRoute +} from '../../state/access-policy/access-policy.selectors'; +import { filter } from 'rxjs'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { NiFiCommon } from '../../../../service/nifi-common.service'; +import { ComponentType, SelectOption, TextTipInput } from '../../../../state/shared'; +import { TextTip } from '../../../../ui/common/tooltips/text-tip/text-tip.component'; +import { AccessPolicyEntity, Action, PolicyStatus, ResourceAction } from '../../state/shared'; +import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions'; +import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors'; +import { loadTenants, resetTenantsState } from '../../state/tenants/tenants.actions'; +import { loadPolicyComponent, resetPolicyComponentState } from '../../state/policy-component/policy-component.actions'; +import { selectPolicyComponentState } from '../../state/policy-component/policy-component.selectors'; +import { PolicyComponentState } from '../../state/policy-component'; + +@Component({ + selector: 'global-access-policies', + templateUrl: './component-access-policies.component.html', + styleUrls: ['./component-access-policies.component.scss'] +}) +export class ComponentAccessPolicies implements OnInit, OnDestroy { + flowConfiguration$ = this.store.select(selectFlowConfiguration); + accessPolicyState$ = this.store.select(selectAccessPolicyState); + policyComponentState$ = this.store.select(selectPolicyComponentState); + currentUser$ = this.store.select(selectCurrentUser); + + protected readonly TextTip = TextTip; + protected readonly Action = Action; + protected readonly PolicyStatus = PolicyStatus; + protected readonly ComponentType = ComponentType; + + policyForm: FormGroup; + policyActionOptions: SelectOption[] = [ + { + text: 'view the component', + value: 'read-component', + description: 'Allows users to view component configuration details' + }, + { + text: 'modify the component', + value: 'write-component', + description: 'Allows users to modify component configuration details' + }, + { + text: 'operate the component', + value: 'write-operation', + description: + 'Allows users to operate components by changing component run status (start/stop/enable/disable), remote port transmission status, or terminating processor threads' + }, + { + text: 'view provenance', + value: 'read-provenance-data', + description: 'Allows users to view provenance events generated by this component' + }, + { + text: 'view the data', + value: 'read-data', + description: + 'Allows users to view metadata and content for this component in flowfile queues in outbound connections and through provenance events' + }, + { + text: 'modify the data', + value: 'write-data', + description: + 'Allows users to empty flowfile queues in outbound connections and submit replays through provenance events' + }, + { + text: 'receive data via site-to-site', + value: 'write-receive-data', + description: 'Allows this port to receive data from these NiFi instances', + disabled: true + }, + { + text: 'send data via site-to-site', + value: 'write-send-data', + description: 'Allows this port to send data to these NiFi instances', + disabled: true + }, + { + text: 'view the policies', + value: 'read-policies', + description: 'Allows users to view the list of users who can view/modify this component' + }, + { + text: 'modify the policies', + value: 'write-policies', + description: 'Allows users to modify the list of users who can view/modify this component' + } + ]; + + action!: Action; + resource!: string; + policy!: string; + resourceIdentifier!: string; + + @ViewChild('inheritedFromPolicies') inheritedFromPolicies!: TemplateRef<any>; + @ViewChild('inheritedFromController') inheritedFromController!: TemplateRef<any>; + @ViewChild('inheritedFromGlobalParameterContexts') inheritedFromGlobalParameterContexts!: TemplateRef<any>; + @ViewChild('inheritedFromProcessGroup') inheritedFromProcessGroup!: TemplateRef<any>; + + constructor( + private store: Store<AccessPolicyState>, + private formBuilder: FormBuilder, + private nifiCommon: NiFiCommon + ) { + this.policyForm = this.formBuilder.group({ + policyAction: new FormControl(this.policyActionOptions[0].value, Validators.required) + }); + + this.store + .select(selectComponentResourceActionFromRoute) + .pipe( + filter((resourceAction) => resourceAction != null), + takeUntilDestroyed() + ) + .subscribe((componentResourceAction) => { + if (componentResourceAction) { + this.action = componentResourceAction.action; + this.policy = componentResourceAction.policy; + this.resource = componentResourceAction.resource; + this.resourceIdentifier = componentResourceAction.resourceIdentifier; + + // data transfer policies for site to site are presented different in the form so + // we need to distinguish by type + let policyForResource: string = this.policy; + if (this.policy === 'data-transfer') { + if (this.resource === 'input-ports') { + policyForResource = 'receive-data'; + } else { + policyForResource = 'send-data'; + } + } + + this.policyForm.get('policyAction')?.setValue(`${this.action}-${policyForResource}`); + + // component policies are presented simply as '/processors/1234' while non-component policies + // like viewing provenance for a specific component is presented as `/provenance-data/processors/1234` + let resourceToLoad: string = this.resource; + if (componentResourceAction.policy !== 'component') { + resourceToLoad = `${this.policy}/${this.resource}`; + } + + const resourceAction: ResourceAction = { + action: this.action, + resource: resourceToLoad, + resourceIdentifier: this.resourceIdentifier + }; + + this.store.dispatch( + loadPolicyComponent({ + request: { + componentResourceAction + } + }) + ); + this.store.dispatch( + setAccessPolicy({ + request: { + resourceAction + } + }) + ); + } + }); + } + + ngOnInit(): void { + this.store.dispatch(loadFlowConfiguration()); + this.store.dispatch(loadTenants()); + } + + isInitialLoading(state: AccessPolicyState): boolean { + return state.loadedTimestamp == initialState.loadedTimestamp; + } + + isComponentPolicy(option: SelectOption, policyComponentState: PolicyComponentState): boolean { + // consider the type of component to override which policies shouldn't be supported + + if (policyComponentState.resource === 'process-groups') { + switch (option.value) { + case 'write-send-data': + case 'write-receive-data': + return false; + } + } else if ( + policyComponentState.resource === 'controller-services' || + policyComponentState.resource === 'reporting-tasks' + ) { + switch (option.value) { + case 'read-data': + case 'write-data': + case 'write-send-data': + case 'write-receive-data': + case 'read-provenance-data': + return false; + } + } else if ( + policyComponentState.resource === 'parameter-contexts' || + policyComponentState.resource === 'parameter-providers' + ) { + switch (option.value) { + case 'read-data': + case 'write-data': + case 'write-send-data': + case 'write-receive-data': + case 'read-provenance-data': + case 'write-operation': + return false; + } + } else if (policyComponentState.resource === 'labels') { + switch (option.value) { + case 'write-operation': + case 'read-data': + case 'write-data': + case 'write-send-data': + case 'write-receive-data': + return false; + } + } else if (policyComponentState.resource === 'input-ports' && policyComponentState.allowRemoteAccess) { + // if input ports allow remote access, disable send data. if input ports do not allow remote + // access it will fall through to the else block where both send and receive data will be disabled + switch (option.value) { + case 'write-send-data': + return false; + } + } else if (policyComponentState.resource === 'output-ports' && policyComponentState.allowRemoteAccess) { + // if output ports allow remote access, disable receive data. if output ports do not allow remote + // access it will fall through to the else block where both send and receive data will be disabled + switch (option.value) { + case 'write-receive-data': + return false; + } + } else { + switch (option.value) { + case 'write-send-data': + case 'write-receive-data': + return false; + } + } + + // enable all other options + return true; + } + + getSelectOptionTipData(option: SelectOption): TextTipInput { + return { + // @ts-ignore + text: option.description + }; + } + + getContextIcon(): string { + switch (this.resource) { + case 'processors': + return 'icon-processor'; + case 'input-ports': + return 'icon-port-in'; + case 'output-ports': + return 'icon-port-out'; + case 'funnels': + return 'icon-funnel'; + case 'labels': + return 'icon-label'; + case 'remote-process-groups': + return 'icon-group-remote'; + } + + return 'icon-group'; + } + + getContextType(): string { + switch (this.resource) { + case 'processors': + return 'Processor'; + case 'input-ports': + return 'Input Ports'; + case 'output-ports': + return 'Output Ports'; + case 'funnels': + return 'Funnel'; + case 'labels': + return 'Label'; + case 'remote-process-groups': + return 'Remote Process Group'; + } + + return 'Process Group'; Review Comment: Good catch! -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
