From 3a78575b9a44ae9e22e2fcf7e7bda07cc0598ac0 Mon Sep 17 00:00:00 2001 From: Matt Gilman Date: Tue, 21 May 2024 08:42:19 -0400 Subject: [PATCH] NIFI-12968: Simplify login sequence (#8843) * NIFI-12968: - Remove usage of Access-Token-Expiration - No longer attempt SPNEGO auth - Leverage authentication configuration to drive log in/out URIs - Remove Login/Logout servlet filters - Remove usage of access configuration and access status - Fixing broken unit tests * NIFI-12968: - Only rendering the user identity when the user is not anonymous. - Fixing an issue where the fallback route would render when redirecting the user to an external SSO log in. - Using the login supported flag to render the log in link. * NIFI-12968: - Addressing review feedback. This closes #8843 --- .../apache/nifi/web/filter/LoginFilter.java | 87 ---------- .../apache/nifi/web/filter/LogoutFilter.java | 91 ---------- .../main/nifi/src/app/app-routing.module.ts | 5 + .../src/main/nifi/src/app/app.module.ts | 28 +-- .../pages/error/feature/error.component.ts | 9 +- .../change-color-dialog.component.html | 4 +- .../ui/canvas/header/header.component.spec.ts | 6 + .../edit-label/edit-label.component.spec.ts | 6 +- .../create-port/create-port.component.spec.ts | 11 +- .../edit-port/edit-port.component.spec.ts | 6 +- .../create-process-group.component.spec.ts | 11 +- .../create-processor.component.spec.ts | 11 +- ...ate-remote-process-group.component.spec.ts | 11 +- .../pages/login/feature/login.component.html | 40 ++--- .../pages/login/feature/login.component.ts | 29 +++- .../app/pages/login/feature/login.module.ts | 2 + .../login/state/access/access.actions.ts | 17 +- .../login/state/access/access.effects.ts | 123 ++++--------- .../login/state/access/access.reducer.ts | 35 +--- .../login/state/access/access.selectors.ts | 4 +- .../src/app/pages/login/state/access/index.ts | 25 +-- .../ui/login-form/login-form.component.html | 4 +- .../login-form/login-form.component.spec.ts | 9 +- .../ui/login-form/login-form.component.ts | 32 ++-- .../feature/route-not-found.component.spec.ts | 12 +- .../user-access-policies.component.spec.ts | 10 +- .../user-table/user-table.component.spec.ts | 3 +- .../src/app/service/auth-storage.service.ts | 75 -------- .../main/nifi/src/app/service/auth.service.ts | 82 +-------- .../src/app/service/error-helper.service.ts | 5 +- .../app/service/guard/authentication.guard.ts | 164 +++++++----------- .../guard/login-configuration.guard.ts | 78 +++++++++ .../service/interceptors/auth.interceptor.ts | 103 ++++++----- .../interceptors/loading.interceptor.ts | 22 +-- .../interceptors/polling.interceptor.ts | 37 ++-- .../current-user/current-user.actions.ts | 8 + .../current-user/current-user.effects.ts | 51 +++++- .../current-user/current-user.reducer.ts | 6 +- .../current-user/current-user.selectors.ts | 7 +- .../nifi/src/app/state/current-user/index.ts | 1 + .../nifi/src/app/state/error/error.actions.ts | 5 + .../nifi/src/app/state/error/error.reducer.ts | 15 +- .../src/app/state/error/error.selectors.ts | 5 + .../main/nifi/src/app/state/error/index.ts | 1 + .../src/main/nifi/src/app/state/index.ts | 4 + .../login-configuration/index.ts} | 27 +-- .../login-configuration.actions.ts} | 21 +-- .../login-configuration.effects.ts | 61 +++++++ .../login-configuration.reducer.ts} | 41 ++--- .../login-configuration.selectors.ts} | 32 ++-- .../advanced-ui/advanced-ui.component.spec.ts | 6 + .../extension-creation.component.spec.ts | 9 +- .../navigation/navigation.component.html | 6 +- .../navigation/navigation.component.spec.ts | 6 + .../common/navigation/navigation.component.ts | 25 +-- .../page-content/page-content.component.html | 4 +- .../page-content.component.spec.ts | 12 +- .../page-content/page-content.component.ts | 24 +-- .../status-history.component.spec.ts | 11 +- ...ystem-diagnostics-dialog.component.spec.ts | 9 +- .../src/main/webapp/WEB-INF/web.xml | 41 ----- 61 files changed, 734 insertions(+), 901 deletions(-) delete mode 100644 nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/java/org/apache/nifi/web/filter/LoginFilter.java delete mode 100644 nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/java/org/apache/nifi/web/filter/LogoutFilter.java delete mode 100644 nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/auth-storage.service.ts create mode 100644 nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/login-configuration.guard.ts rename nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/{service/interceptors/loading.interceptor.spec.ts => state/login-configuration/index.ts} (63%) rename nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/{service/auth-storage.service.spec.ts => state/login-configuration/login-configuration.actions.ts} (67%) create mode 100644 nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/login-configuration/login-configuration.effects.ts rename nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/{service/interceptors/auth.interceptor.spec.ts => state/login-configuration/login-configuration.reducer.ts} (54%) rename nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/{service/interceptors/polling.interceptor.spec.ts => state/login-configuration/login-configuration.selectors.ts} (50%) diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/java/org/apache/nifi/web/filter/LoginFilter.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/java/org/apache/nifi/web/filter/LoginFilter.java deleted file mode 100644 index ea262bfca1..0000000000 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/java/org/apache/nifi/web/filter/LoginFilter.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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. - */ -package org.apache.nifi.web.filter; - -import org.apache.nifi.web.util.RequestUriBuilder; - -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.FilterConfig; -import jakarta.servlet.ServletContext; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.net.URI; - -/** - * Filter for determining appropriate login location. - */ -public class LoginFilter implements Filter { - private static final String OAUTH2_AUTHORIZATION_PATH = "/nifi-api/oauth2/authorization/consumer"; - - private static final String SAML2_AUTHENTICATE_FILTER_PATH = "/nifi-api/saml2/authenticate/consumer"; - - private static final String KNOX_REQUEST_PATH = "/nifi-api/access/knox/request"; - - private static final String NIFI_LOGIN_PATH = "/nf/"; - private static final String NIFI_LOGIN_FRAGMENT = "/login"; - - private ServletContext servletContext; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - servletContext = filterConfig.getServletContext(); - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { - final boolean supportsOidc = Boolean.parseBoolean(servletContext.getInitParameter("oidc-supported")); - final boolean supportsKnoxSso = Boolean.parseBoolean(servletContext.getInitParameter("knox-supported")); - final boolean supportsSAML = Boolean.parseBoolean(servletContext.getInitParameter("saml-supported")); - - final HttpServletRequest httpServletRequest = (HttpServletRequest) request; - final RequestUriBuilder requestUriBuilder = RequestUriBuilder.fromHttpServletRequest(httpServletRequest); - - if (supportsKnoxSso) { - final URI redirectUri = requestUriBuilder.path(KNOX_REQUEST_PATH).build(); - sendRedirect(response, redirectUri); - } else if (supportsOidc) { - final URI redirectUri = requestUriBuilder.path(OAUTH2_AUTHORIZATION_PATH).build(); - // Redirect to authorization URL defined in Spring Security OAuth2AuthorizationRequestRedirectFilter - sendRedirect(response, redirectUri); - } else if (supportsSAML) { - final URI redirectUri = requestUriBuilder.path(SAML2_AUTHENTICATE_FILTER_PATH).build(); - // Redirect to request consumer URL defined in Spring Security OpenSamlAuthenticationRequestResolver.requestMatcher - sendRedirect(response, redirectUri); - } else { - final URI redirectUri = requestUriBuilder.path(NIFI_LOGIN_PATH).fragment(NIFI_LOGIN_FRAGMENT).build(); - sendRedirect(response, redirectUri); - } - } - - @Override - public void destroy() { - } - - private void sendRedirect(final ServletResponse response, final URI redirectUri) throws IOException { - final HttpServletResponse httpServletResponse = (HttpServletResponse) response; - httpServletResponse.sendRedirect(redirectUri.toString()); - } -} diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/java/org/apache/nifi/web/filter/LogoutFilter.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/java/org/apache/nifi/web/filter/LogoutFilter.java deleted file mode 100644 index 8c6bdea56f..0000000000 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/java/org/apache/nifi/web/filter/LogoutFilter.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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. - */ -package org.apache.nifi.web.filter; - -import org.apache.nifi.web.util.RequestUriBuilder; - -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.FilterConfig; -import jakarta.servlet.ServletContext; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.net.URI; - -/** - * Filter for determining appropriate logout location. - */ -public class LogoutFilter implements Filter { - - private static final String OIDC_LOGOUT_URL = "/nifi-api/access/oidc/logout"; - - private static final String SAML_LOCAL_LOGOUT_URL = "/nifi-api/access/saml/local-logout/request"; - - private static final String SAML_SINGLE_LOGOUT_URL = "/nifi-api/access/saml/single-logout/request"; - - private static final String KNOX_LOGOUT_URL = "/nifi-api/access/knox/logout"; - - private static final String LOGOUT_COMPLETE_URL = "/nifi-api/access/logout/complete"; - - private ServletContext servletContext; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - servletContext = filterConfig.getServletContext(); - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { - final boolean supportsOidc = Boolean.parseBoolean(servletContext.getInitParameter("oidc-supported")); - final boolean supportsKnoxSso = Boolean.parseBoolean(servletContext.getInitParameter("knox-supported")); - final boolean supportsSaml = Boolean.parseBoolean(servletContext.getInitParameter("saml-supported")); - final boolean supportsSamlSingleLogout = Boolean.parseBoolean(servletContext.getInitParameter("saml-single-logout-supported")); - - // NOTE: This filter runs in the web-ui module and is bound to /nifi/logout. Currently the front-end first makes an ajax call - // to issue a DELETE to /nifi-api/access/logout. After successful completion it sets the browser location to /nifi/logout - // which triggers this filter. Since this request was made from setting window.location, the JWT will never be sent which - // means there will be no logged in user or Authorization header when forwarding to any of the URLs below. Instead the - // /access/logout end-point sets a Cookie with a logout request identifier which can be used by the end-points below - // to retrieve information about the user logging out. - - if (supportsOidc) { - sendRedirect(OIDC_LOGOUT_URL, request, response); - } else if (supportsKnoxSso) { - sendRedirect(KNOX_LOGOUT_URL, request, response); - } else if (supportsSaml) { - final String logoutUrl = supportsSamlSingleLogout ? SAML_SINGLE_LOGOUT_URL : SAML_LOCAL_LOGOUT_URL; - sendRedirect(logoutUrl, request, response); - } else { - sendRedirect(LOGOUT_COMPLETE_URL, request, response); - } - } - - @Override - public void destroy() { - } - - private void sendRedirect(final String logoutUrl, final ServletRequest request, final ServletResponse response) throws IOException { - final HttpServletRequest httpServletRequest = (HttpServletRequest) request; - final URI targetUri = RequestUriBuilder.fromHttpServletRequest(httpServletRequest).path(logoutUrl).build(); - final HttpServletResponse httpServletResponse = (HttpServletResponse) response; - httpServletResponse.sendRedirect(targetUri.toString()); - } -} diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app-routing.module.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app-routing.module.ts index 8e38042532..27c206e141 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app-routing.module.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app-routing.module.ts @@ -19,10 +19,15 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { authenticationGuard } from './service/guard/authentication.guard'; import { RouteNotFound } from './pages/route-not-found/feature/route-not-found.component'; +import { checkLoginConfiguration } from './service/guard/login-configuration.guard'; +import { LoginConfiguration } from './state/login-configuration'; const routes: Routes = [ { path: 'login', + canMatch: [ + checkLoginConfiguration((loginConfiguration: LoginConfiguration) => loginConfiguration.loginSupported) + ], loadChildren: () => import('./pages/login/feature/login.module').then((m) => m.LoginModule) }, { diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts index 50a5a81715..637f4cb1c5 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts @@ -24,15 +24,14 @@ import { StoreModule } from '@ngrx/store'; import { EffectsModule } from '@ngrx/effects'; import { StoreDevtoolsModule } from '@ngrx/store-devtools'; import { environment } from './environments/environment'; -import { HTTP_INTERCEPTORS, HttpClientModule, HttpClientXsrfModule } from '@angular/common/http'; +import { HttpClientModule, HttpClientXsrfModule, provideHttpClient, withInterceptors } from '@angular/common/http'; import { NavigationActionTiming, RouterState, StoreRouterConnectingModule } from '@ngrx/router-store'; import { rootReducers } from './state'; import { CurrentUserEffects } from './state/current-user/current-user.effects'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; -import { LoadingInterceptor } from './service/interceptors/loading.interceptor'; -import { AuthInterceptor } from './service/interceptors/auth.interceptor'; +import { authInterceptor } from './service/interceptors/auth.interceptor'; import { ExtensionTypesEffects } from './state/extension-types/extension-types.effects'; -import { PollingInterceptor } from './service/interceptors/polling.interceptor'; +import { pollingInterceptor } from './service/interceptors/polling.interceptor'; import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field'; import { MatNativeDateModule } from '@angular/material/core'; import { AboutEffects } from './state/about/about.effects'; @@ -47,6 +46,8 @@ import { MatSnackBarModule } from '@angular/material/snack-bar'; import { PipesModule } from './pipes/pipes.module'; import { DocumentationEffects } from './state/documentation/documentation.effects'; import { ClusterSummaryEffects } from './state/cluster-summary/cluster-summary.effects'; +import { loadingInterceptor } from './service/interceptors/loading.interceptor'; +import { LoginConfigurationEffects } from './state/login-configuration/login-configuration.effects'; @NgModule({ declarations: [AppComponent], @@ -70,6 +71,7 @@ import { ClusterSummaryEffects } from './state/cluster-summary/cluster-summary.e ExtensionTypesEffects, AboutEffects, FlowConfigurationEffects, + LoginConfigurationEffects, StatusHistoryEffects, ControllerServiceStateEffects, SystemDiagnosticsEffects, @@ -89,22 +91,8 @@ import { ClusterSummaryEffects } from './state/cluster-summary/cluster-summary.e PipesModule ], providers: [ - { - provide: HTTP_INTERCEPTORS, - useClass: LoadingInterceptor, - multi: true - }, - { - provide: HTTP_INTERCEPTORS, - useClass: AuthInterceptor, - multi: true - }, - { - provide: HTTP_INTERCEPTORS, - useClass: PollingInterceptor, - multi: true - }, - { provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { appearance: 'outline' } } + { provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { appearance: 'outline' } }, + provideHttpClient(withInterceptors([authInterceptor, loadingInterceptor, pollingInterceptor])) ], bootstrap: [AppComponent] }) diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/error/feature/error.component.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/error/feature/error.component.ts index 2bfa544afd..5defce4d29 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/error/feature/error.component.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/error/feature/error.component.ts @@ -15,18 +15,23 @@ * limitations under the License. */ -import { Component } from '@angular/core'; +import { Component, OnDestroy } from '@angular/core'; import { Store } from '@ngrx/store'; import { selectFullScreenError } from '../../../state/error/error.selectors'; import { NiFiState } from '../../../state'; +import { resetErrorState } from '../../../state/error/error.actions'; @Component({ selector: 'error', templateUrl: './error.component.html', styleUrls: ['./error.component.scss'] }) -export class Error { +export class Error implements OnDestroy { errorDetail$ = this.store.select(selectFullScreenError); constructor(private store: Store) {} + + ngOnDestroy(): void { + this.store.dispatch(resetErrorState()); + } } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/change-color-dialog/change-color-dialog.component.html b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/change-color-dialog/change-color-dialog.component.html index 883e587dad..7f8de7c74b 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/change-color-dialog/change-color-dialog.component.html +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/change-color-dialog/change-color-dialog.component.html @@ -34,9 +34,7 @@
Processor Name
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/header/header.component.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/header/header.component.spec.ts index d4323c86b0..450b2e1be4 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/header/header.component.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/header/header.component.spec.ts @@ -36,6 +36,8 @@ import { selectCurrentUser } from '../../../../../state/current-user/current-use import * as fromUser from '../../../../../state/current-user/current-user.reducer'; import { selectFlowConfiguration } from '../../../../../state/flow-configuration/flow-configuration.selectors'; import * as fromFlowConfiguration from '../../../../../state/flow-configuration/flow-configuration.reducer'; +import { selectLoginConfiguration } from '../../../../../state/login-configuration/login-configuration.selectors'; +import * as fromLoginConfiguration from '../../../../../state/login-configuration/login-configuration.reducer'; describe('HeaderComponent', () => { let component: HeaderComponent; @@ -120,6 +122,10 @@ describe('HeaderComponent', () => { { selector: selectFlowConfiguration, value: fromFlowConfiguration.initialState.flowConfiguration + }, + { + selector: selectLoginConfiguration, + value: fromLoginConfiguration.initialState.loginConfiguration } ] }) diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/label/edit-label/edit-label.component.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/label/edit-label/edit-label.component.spec.ts index 67007081c6..b23e76b07d 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/label/edit-label/edit-label.component.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/label/edit-label/edit-label.component.spec.ts @@ -19,7 +19,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { EditLabel } from './edit-label.component'; import { EditComponentDialogRequest } from '../../../../../state/flow'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ComponentType } from '../../../../../../../state/shared'; import { provideMockStore } from '@ngrx/store/testing'; import { initialState } from '../../../../../state/flow/flow.reducer'; @@ -79,6 +79,10 @@ describe('EditLabel', () => { useValue: { isDisconnectionAcknowledged: jest.fn() } + }, + { + provide: MatDialogRef, + useValue: null } ] }); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/port/create-port/create-port.component.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/port/create-port/create-port.component.spec.ts index 84698813fe..56be6c3ffd 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/port/create-port/create-port.component.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/port/create-port/create-port.component.spec.ts @@ -20,7 +20,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CreatePort } from './create-port.component'; import { CreateComponentRequest } from '../../../../../state/flow'; import { ComponentType } from '../../../../../../../state/shared'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { provideMockStore } from '@ngrx/store/testing'; import { initialState } from '../../../../../state/flow/flow.reducer'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; @@ -44,7 +44,14 @@ describe('CreatePort', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [CreatePort, NoopAnimationsModule], - providers: [{ provide: MAT_DIALOG_DATA, useValue: data }, provideMockStore({ initialState })] + providers: [ + { provide: MAT_DIALOG_DATA, useValue: data }, + provideMockStore({ initialState }), + { + provide: MatDialogRef, + useValue: null + } + ] }); fixture = TestBed.createComponent(CreatePort); component = fixture.componentInstance; diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/port/edit-port/edit-port.component.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/port/edit-port/edit-port.component.spec.ts index b77298bf16..ed5de99cc3 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/port/edit-port/edit-port.component.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/port/edit-port/edit-port.component.spec.ts @@ -19,7 +19,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { EditPort } from './edit-port.component'; import { EditComponentDialogRequest } from '../../../../../state/flow'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ComponentType } from '../../../../../../../state/shared'; import { provideMockStore } from '@ngrx/store/testing'; import { initialState } from '../../../../../state/flow/flow.reducer'; @@ -105,6 +105,10 @@ describe('EditPort', () => { useValue: { isDisconnectionAcknowledged: jest.fn() } + }, + { + provide: MatDialogRef, + useValue: null } ] }); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/create-process-group/create-process-group.component.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/create-process-group/create-process-group.component.spec.ts index 6da8b69a1c..ff6695a011 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/create-process-group/create-process-group.component.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/create-process-group/create-process-group.component.spec.ts @@ -19,7 +19,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CreateProcessGroup } from './create-process-group.component'; import { CreateProcessGroupDialogRequest } from '../../../../../state/flow'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ComponentType } from '../../../../../../../state/shared'; import { provideMockStore } from '@ngrx/store/testing'; import { initialState } from '../../../../../state/flow/flow.reducer'; @@ -156,7 +156,14 @@ describe('CreateProcessGroup', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [CreateProcessGroup, NoopAnimationsModule], - providers: [{ provide: MAT_DIALOG_DATA, useValue: data }, provideMockStore({ initialState })] + providers: [ + { provide: MAT_DIALOG_DATA, useValue: data }, + provideMockStore({ initialState }), + { + provide: MatDialogRef, + useValue: null + } + ] }); fixture = TestBed.createComponent(CreateProcessGroup); component = fixture.componentInstance; diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/processor/create-processor/create-processor.component.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/processor/create-processor/create-processor.component.spec.ts index 16cdaf4c3b..22e5dba3fa 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/processor/create-processor/create-processor.component.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/processor/create-processor/create-processor.component.spec.ts @@ -19,7 +19,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CreateProcessor } from './create-processor.component'; import { CreateProcessorDialogRequest } from '../../../../../state/flow'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { provideMockStore } from '@ngrx/store/testing'; import { initialState } from '../../../../../../../state/extension-types/extension-types.reducer'; import { ComponentType } from '../../../../../../../state/shared'; @@ -60,7 +60,14 @@ describe('CreateProcessor', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [CreateProcessor, NoopAnimationsModule], - providers: [{ provide: MAT_DIALOG_DATA, useValue: data }, provideMockStore({ initialState })] + providers: [ + { provide: MAT_DIALOG_DATA, useValue: data }, + provideMockStore({ initialState }), + { + provide: MatDialogRef, + useValue: null + } + ] }); fixture = TestBed.createComponent(CreateProcessor); component = fixture.componentInstance; diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/remote-process-group/create-remote-process-group/create-remote-process-group.component.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/remote-process-group/create-remote-process-group/create-remote-process-group.component.spec.ts index 3e827447f5..55420fe53c 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/remote-process-group/create-remote-process-group/create-remote-process-group.component.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/remote-process-group/create-remote-process-group/create-remote-process-group.component.spec.ts @@ -18,7 +18,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CreateRemoteProcessGroup } from './create-remote-process-group.component'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ComponentType } from '../../../../../../../state/shared'; import { provideMockStore } from '@ngrx/store/testing'; import { initialState } from '../../../../../state/flow/flow.reducer'; @@ -44,7 +44,14 @@ describe('CreateRemoteProcessGroup', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [CreateRemoteProcessGroup, NoopAnimationsModule], - providers: [{ provide: MAT_DIALOG_DATA, useValue: data }, provideMockStore({ initialState })] + providers: [ + { provide: MAT_DIALOG_DATA, useValue: data }, + provideMockStore({ initialState }), + { + provide: MatDialogRef, + useValue: null + } + ] }); fixture = TestBed.createComponent(CreateRemoteProcessGroup); component = fixture.componentInstance; diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/feature/login.component.html b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/feature/login.component.html index eb263fea7d..e0439d9acd 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/feature/login.component.html +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/feature/login.component.html @@ -15,32 +15,22 @@ ~ limitations under the License. --> - +} diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/feature/login.component.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/feature/login.component.ts index 68e2149aee..590d3b9d41 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/feature/login.component.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/feature/login.component.ts @@ -15,23 +15,36 @@ * limitations under the License. */ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { LoginState } from '../state'; -import { selectAccess } from '../state/access/access.selectors'; -import { loadAccess } from '../state/access/access.actions'; +import { selectCurrentUserState } from '../../../state/current-user/current-user.selectors'; +import { take } from 'rxjs'; +import { selectLoginConfiguration } from '../../../state/login-configuration/login-configuration.selectors'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { isDefinedAndNotNull } from '../../../state/shared'; @Component({ selector: 'login', templateUrl: './login.component.html', styleUrls: ['./login.component.scss'] }) -export class Login implements OnInit { - access$ = this.store.select(selectAccess); +export class Login { + currentUserState$ = this.store.select(selectCurrentUserState).pipe(take(1)); + loginConfiguration = this.store.selectSignal(selectLoginConfiguration); - constructor(private store: Store) {} + loading: boolean = true; - ngOnInit(): void { - this.store.dispatch(loadAccess()); + constructor(private store: Store) { + this.store + .select(selectLoginConfiguration) + .pipe(isDefinedAndNotNull(), takeUntilDestroyed()) + .subscribe((loginConfiguration) => { + if (loginConfiguration.externalLoginRequired) { + window.location.href = loginConfiguration.loginUri; + } else { + this.loading = false; + } + }); } } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/feature/login.module.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/feature/login.module.ts index 9d317caa47..ff2b49e911 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/feature/login.module.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/feature/login.module.ts @@ -30,6 +30,7 @@ import { AccessEffects } from '../state/access/access.effects'; import { LoginForm } from '../ui/login-form/login-form.component'; import { PageContent } from '../../../ui/common/page-content/page-content.component'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; @NgModule({ declarations: [Login, LoginForm], @@ -45,6 +46,7 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; MatInputModule, MatButtonModule, NgxSkeletonLoaderModule, + MatProgressSpinnerModule, PageContent ] }) diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/access.actions.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/access.actions.ts index 310de9c135..f6451e8979 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/access.actions.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/access.actions.ts @@ -16,21 +16,12 @@ */ import { createAction, props } from '@ngrx/store'; -import { AccessApiError, LoadAccessResponse, LoginRequest } from './index'; - -export const loadAccess = createAction('[Access] Load Access'); - -export const loadAccessSuccess = createAction( - '[Access] Load Access Success', - props<{ response: LoadAccessResponse }>() -); - -export const accessApiError = createAction('[Access] Load Access Error', props<{ error: AccessApiError }>()); +import { LoginRequest } from './index'; export const login = createAction('[Access] Login', props<{ request: LoginRequest }>()); -export const loginFailure = createAction('[Access] Login Failure', props<{ failure: string }>()); +export const loginSuccess = createAction('[Access] Login Success'); -export const verifyAccess = createAction('[Access] Verify Access'); +export const loginFailure = createAction('[Access] Login Failure', props<{ loginFailure: string }>()); -export const verifyAccessSuccess = createAction('[Access] Verify Access Success'); +export const resetLoginFailure = createAction('[Access] Reset Login Failure'); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/access.effects.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/access.effects.ts index dedb73a945..78957a0728 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/access.effects.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/access.effects.ts @@ -18,134 +18,75 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import * as AccessActions from './access.actions'; -import { catchError, combineLatest, from, map, of, switchMap, tap } from 'rxjs'; +import { catchError, from, map, of, switchMap, tap } from 'rxjs'; import { AuthService } from '../../../../service/auth.service'; -import { AuthStorage } from '../../../../service/auth-storage.service'; import { Router } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; import { OkDialog } from '../../../../ui/common/ok-dialog/ok-dialog.component'; import { MEDIUM_DIALOG } from '../../../../index'; import { ErrorHelper } from '../../../../service/error-helper.service'; import { HttpErrorResponse } from '@angular/common/http'; +import { Store } from '@ngrx/store'; +import { NiFiState } from '../../../../state'; +import { resetLoginFailure } from './access.actions'; @Injectable() export class AccessEffects { constructor( private actions$: Actions, + private store: Store, private authService: AuthService, - private authStorage: AuthStorage, private router: Router, private dialog: MatDialog, private errorHelper: ErrorHelper ) {} - loadAccess$ = createEffect(() => - this.actions$.pipe( - ofType(AccessActions.loadAccess), - switchMap(() => - combineLatest([this.authService.accessConfig(), this.authService.accessStatus()]).pipe( - map(([accessConfig, accessStatus]) => - AccessActions.loadAccessSuccess({ - response: { - accessConfig: accessConfig.config, - accessStatus: accessStatus.accessStatus - } - }) - ), - catchError((errorResponse: HttpErrorResponse) => - of( - AccessActions.accessApiError({ - error: { - title: 'Unable to check Access Status', - message: this.errorHelper.getErrorString(errorResponse) - } - }) - ) - ) - ) - ) - ) - ); - login$ = createEffect(() => this.actions$.pipe( ofType(AccessActions.login), map((action) => action.request), switchMap((request) => from(this.authService.login(request.username, request.password)).pipe( - map((jwt) => { - const sessionExpiration: string | null = this.authService.getSessionExpiration(jwt); - if (sessionExpiration) { - this.authStorage.setToken(sessionExpiration); - } - return AccessActions.verifyAccess(); - }), + map(() => AccessActions.loginSuccess()), catchError((errorResponse: HttpErrorResponse) => - of(AccessActions.loginFailure({ failure: this.errorHelper.getErrorString(errorResponse) })) + of(AccessActions.loginFailure({ loginFailure: this.errorHelper.getErrorString(errorResponse) })) ) ) ) ) ); - loginFailure$ = createEffect( + loginSuccess$ = createEffect( () => this.actions$.pipe( - ofType(AccessActions.loginFailure), - map((action) => action.failure), - tap((failure) => { - this.dialog.open(OkDialog, { - ...MEDIUM_DIALOG, - data: { - title: 'Login', - message: failure - } - }); - }) - ), - { dispatch: false } - ); - - verifyAccess$ = createEffect(() => - this.actions$.pipe( - ofType(AccessActions.verifyAccess), - switchMap(() => - from(this.authService.accessStatus()).pipe( - map((response) => { - if (response.accessStatus.status === 'ACTIVE') { - return AccessActions.verifyAccessSuccess(); - } else { - return AccessActions.accessApiError({ - error: { - title: 'Unable to log in', - message: response.accessStatus.message - } - }); - } - }), - catchError((errorResponse: HttpErrorResponse) => - of( - AccessActions.accessApiError({ - error: { - title: 'Unable to log in', - message: this.errorHelper.getErrorString(errorResponse) - } - }) - ) - ) - ) - ) - ) - ); - - verifyAccessSuccess$ = createEffect( - () => - this.actions$.pipe( - ofType(AccessActions.verifyAccessSuccess), + ofType(AccessActions.loginSuccess), tap(() => { this.router.navigate(['/']); }) ), { dispatch: false } ); + + loginFailure$ = createEffect( + () => + this.actions$.pipe( + ofType(AccessActions.loginFailure), + map((action) => action.loginFailure), + tap((loginFailure) => { + this.dialog + .open(OkDialog, { + ...MEDIUM_DIALOG, + data: { + title: 'Login', + message: loginFailure + } + }) + .afterClosed() + .subscribe(() => { + this.store.dispatch(resetLoginFailure()); + }); + }) + ), + { dispatch: false } + ); } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/access.reducer.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/access.reducer.ts index eab1d531bc..c27056989f 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/access.reducer.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/access.reducer.ts @@ -16,42 +16,21 @@ */ import { createReducer, on } from '@ngrx/store'; -import { Access, AccessConfig, AccessStatus } from './index'; -import { accessApiError, loadAccess, loadAccessSuccess } from './access.actions'; - -export const INITIAL_STATUS: AccessStatus = { - identity: '', - status: 'UNKNOWN', - message: '' -}; - -export const INITIAL_CONFIG: AccessConfig = { - supportsLogin: false -}; +import { Access } from './index'; +import { loginFailure, resetLoginFailure } from './access.actions'; export const initialState: Access = { - accessStatus: INITIAL_STATUS, - accessConfig: INITIAL_CONFIG, - error: null, - status: 'pending' + loginFailure: null }; export const accessReducer = createReducer( initialState, - on(loadAccess, (state) => ({ + on(loginFailure, (state, { loginFailure }) => ({ ...state, - status: 'loading' as const + loginFailure })), - on(loadAccessSuccess, (state, { response }) => ({ + on(resetLoginFailure, (state) => ({ ...state, - accessStatus: response.accessStatus, - accessConfig: response.accessConfig, - error: null, - status: 'success' as const - })), - on(accessApiError, (state, { error }) => ({ - ...state, - error, - status: 'error' as const + loginFailure: null })) ); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/access.selectors.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/access.selectors.ts index 34d7c60d2f..14f329af33 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/access.selectors.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/access.selectors.ts @@ -17,6 +17,8 @@ import { createSelector } from '@ngrx/store'; import { LoginState, selectLoginState } from '../index'; -import { accessFeatureKey } from './index'; +import { Access, accessFeatureKey } from './index'; export const selectAccess = createSelector(selectLoginState, (state: LoginState) => state[accessFeatureKey]); + +export const selectLoginFailure = createSelector(selectAccess, (access: Access) => access.loginFailure); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/index.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/index.ts index 20d59f19a7..3b3824efd5 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/index.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/state/access/index.ts @@ -22,29 +22,6 @@ export interface LoginRequest { password: string; } -export interface LoadAccessResponse { - accessStatus: AccessStatus; - accessConfig: AccessConfig; -} - -export interface AccessStatus { - identity: string; - status: string; - message: string; -} - -export interface AccessConfig { - supportsLogin: boolean; -} - -export interface AccessApiError { - title: string; - message: string; -} - export interface Access { - accessStatus: AccessStatus; - accessConfig: AccessConfig; - error: AccessApiError | null; - status: 'pending' | 'loading' | 'error' | 'success'; + loginFailure: string | null; } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/ui/login-form/login-form.component.html b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/ui/login-form/login-form.component.html index de8255952b..2fd8ca24bf 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/ui/login-form/login-form.component.html +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/ui/login-form/login-form.component.html @@ -19,10 +19,10 @@

Log In

- @if (hasToken()) { + @if (logoutSupported()) { log out } - home + home
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/ui/login-form/login-form.component.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/ui/login-form/login-form.component.spec.ts index 9c79abc6ec..bd581ef2c6 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/ui/login-form/login-form.component.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/ui/login-form/login-form.component.spec.ts @@ -27,6 +27,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { currentUserFeatureKey } from '../../../../state/current-user'; describe('LoginForm', () => { let component: LoginForm; @@ -45,7 +46,13 @@ describe('LoginForm', () => { ReactiveFormsModule, MatInputModule ], - providers: [provideMockStore({ initialState })] + providers: [ + provideMockStore({ + initialState: { + [currentUserFeatureKey]: initialState + } + }) + ] }); fixture = TestBed.createComponent(LoginForm); component = fixture.componentInstance; diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/ui/login-form/login-form.component.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/ui/login-form/login-form.component.ts index c639cf8326..ddb122769f 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/ui/login-form/login-form.component.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/login/ui/login-form/login-form.component.ts @@ -17,11 +17,14 @@ import { Component } from '@angular/core'; import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; -import { AuthStorage } from '../../../../service/auth-storage.service'; import { Store } from '@ngrx/store'; -import { LoginState } from '../../state'; import { login } from '../../state/access/access.actions'; -import { AuthService } from '../../../../service/auth.service'; +import { selectLoginFailure } from '../../state/access/access.selectors'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { selectLogoutSupported } from '../../../../state/current-user/current-user.selectors'; +import { NiFiState } from '../../../../state'; +import { setRoutedToFullScreenError } from '../../../../state/error/error.actions'; +import { logout } from '../../../../state/current-user/current-user.actions'; @Component({ selector: 'login-form', @@ -29,27 +32,36 @@ import { AuthService } from '../../../../service/auth.service'; styleUrls: ['./login-form.component.scss'] }) export class LoginForm { + logoutSupported = this.store.selectSignal(selectLogoutSupported); + loginForm: FormGroup; constructor( private formBuilder: FormBuilder, - private store: Store, - private authStorage: AuthStorage, - private authService: AuthService + private store: Store ) { // build the form this.loginForm = this.formBuilder.group({ username: new FormControl('', Validators.required), password: new FormControl('', Validators.required) }); - } - hasToken(): boolean { - return this.authStorage.hasToken(); + this.store + .select(selectLoginFailure) + .pipe(takeUntilDestroyed()) + .subscribe((loginFailure) => { + if (loginFailure) { + this.loginForm.get('password')?.setValue(''); + } + }); } logout(): void { - this.authService.logout(); + this.store.dispatch(logout()); + } + + resetRoutedToFullScreenError(): void { + this.store.dispatch(setRoutedToFullScreenError({ routedToFullScreenError: false })); } login() { diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/route-not-found/feature/route-not-found.component.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/route-not-found/feature/route-not-found.component.spec.ts index 5ec9e86776..84524ba1bd 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/route-not-found/feature/route-not-found.component.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/route-not-found/feature/route-not-found.component.spec.ts @@ -21,6 +21,9 @@ import { RouteNotFound } from './route-not-found.component'; import { PageContent } from '../../../ui/common/page-content/page-content.component'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { RouterTestingModule } from '@angular/router/testing'; +import { provideMockStore } from '@ngrx/store/testing'; +import { currentUserFeatureKey } from '../../../state/current-user'; +import { initialState } from '../../../state/current-user/current-user.reducer'; describe('RouteNotFound', () => { let component: RouteNotFound; @@ -29,7 +32,14 @@ describe('RouteNotFound', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [RouteNotFound], - imports: [PageContent, HttpClientTestingModule, RouterTestingModule] + imports: [PageContent, HttpClientTestingModule, RouterTestingModule], + providers: [ + provideMockStore({ + initialState: { + [currentUserFeatureKey]: initialState + } + }) + ] }).compileComponents(); fixture = TestBed.createComponent(RouteNotFound); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.spec.ts index 51e9f899a0..52e780f036 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.spec.ts @@ -20,7 +20,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { UserAccessPolicies } from './user-access-policies.component'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { UserAccessPoliciesDialogRequest } from '../../../state/user-listing'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; describe('UserAccessPolicies', () => { let component: UserAccessPolicies; @@ -53,7 +53,13 @@ describe('UserAccessPolicies', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [UserAccessPolicies, NoopAnimationsModule], - providers: [{ provide: MAT_DIALOG_DATA, useValue: data }] + providers: [ + { provide: MAT_DIALOG_DATA, useValue: data }, + { + provide: MatDialogRef, + useValue: null + } + ] }); fixture = TestBed.createComponent(UserAccessPolicies); component = fixture.componentInstance; diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.spec.ts index c1ce87e440..c4258373bb 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.spec.ts @@ -167,7 +167,8 @@ describe('UserTable', () => { } } ], - canVersionFlows: false + canVersionFlows: false, + logoutSupported: true }; beforeEach(() => { diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/auth-storage.service.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/auth-storage.service.ts deleted file mode 100644 index f953992018..0000000000 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/auth-storage.service.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 { Injectable } from '@angular/core'; - -@Injectable({ - providedIn: 'root' -}) -export class AuthStorage { - private static readonly TOKEN_ITEM_KEY: string = 'Access-Token-Expiration'; - - private static readonly REQUEST_TOKEN_PATTERN: RegExp = new RegExp('Request-Token=([^;]+)'); - - /** - * Get Request Token from document cookies - * - * @return Request Token string or null when not found - */ - public getRequestToken(): string | null { - const requestTokenMatcher = AuthStorage.REQUEST_TOKEN_PATTERN.exec(document.cookie); - if (requestTokenMatcher) { - return requestTokenMatcher[1]; - } - return null; - } - - /** - * Get Token from Session Storage - * - * @return Bearer Token string - */ - public getToken(): string | null { - return sessionStorage.getItem(AuthStorage.TOKEN_ITEM_KEY); - } - - /** - * Has Token returns the status of whether Session Storage contains the Token - * - * @return Boolean status of whether Session Storage contains the Token - */ - public hasToken(): boolean { - return typeof this.getToken() === 'string'; - } - - /** - * Remove Token from Session Storage - * - */ - public removeToken(): void { - sessionStorage.removeItem(AuthStorage.TOKEN_ITEM_KEY); - } - - /** - * Set Token in Session Storage - * - * @param token Token String - */ - public setToken(token: string): void { - sessionStorage.setItem(AuthStorage.TOKEN_ITEM_KEY, token); - } -} diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/auth.service.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/auth.service.ts index c000e69c1d..e39d8cb475 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/auth.service.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/auth.service.ts @@ -16,33 +16,17 @@ */ import { Injectable } from '@angular/core'; -import { Observable, take } from 'rxjs'; +import { Observable } from 'rxjs'; import { HttpClient, HttpParams } from '@angular/common/http'; -import { AuthStorage } from './auth-storage.service'; @Injectable({ providedIn: 'root' }) export class AuthService { private static readonly API: string = '../nifi-api'; - constructor( - private httpClient: HttpClient, - private authStorage: AuthStorage - ) {} + constructor(private httpClient: HttpClient) {} - public kerberos(): Observable { - return this.httpClient.post(`${AuthService.API}/access/kerberos`, null); - } - - public ticketExpiration(): Observable { - return this.httpClient.get(`${AuthService.API}/access/token/expiration`); - } - - public accessConfig(): Observable { - return this.httpClient.get(`${AuthService.API}/access/config`); - } - - public accessStatus(): Observable { - return this.httpClient.get(`${AuthService.API}/access`); + public getLoginConfiguration(): Observable { + return this.httpClient.get(`${AuthService.API}/authentication/configuration`); } public login(username: string, password: string): Observable { @@ -53,61 +37,7 @@ export class AuthService { }); } - public logout(): void { - this.httpClient - .delete(`${AuthService.API}/access/logout`) - .pipe(take(1)) - .subscribe(() => { - this.authStorage.removeToken(); - window.location.href = './logout'; - }); - } - - /** - * Extracts the subject from the specified jwt. If the jwt is not as expected - * an empty string is returned. - * - * @param {string} jwt - * @returns {string} - */ - public getJwtPayload(jwt: string): any { - if (jwt) { - const segments: string[] = jwt.split(/\./); - if (segments.length !== 3) { - return null; - } - - const rawPayload: string = atob(segments[1]); - return JSON.parse(rawPayload); - } - - return null; - } - - /** - * Get Session Expiration from JSON Web Token Payload exp claim - * - * @param {string} jwt - * @return {string} - */ - public getSessionExpiration(jwt: string): string | null { - const jwtPayload = this.getJwtPayload(jwt); - if (jwtPayload) { - return jwtPayload['exp']; - } - - return null; - } - - /** - * Get Default Session Expiration based on current time plus 12 hours as seconds - * - * @return {string} - */ - public getDefaultExpiration(): string { - const now: Date = new Date(); - const expiration: number = now.getTime() + 43200000; - const expirationSeconds: number = Math.round(expiration / 1000); - return expirationSeconds.toString(); + public logout(): Observable { + return this.httpClient.delete(`${AuthService.API}/access/logout`); } } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/error-helper.service.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/error-helper.service.ts index 82b2c856e6..07398f3b2f 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/error-helper.service.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/error-helper.service.ts @@ -19,12 +19,9 @@ import { Injectable } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; import * as ErrorActions from '../state/error/error.actions'; import { Action } from '@ngrx/store'; -import { NiFiCommon } from './nifi-common.service'; @Injectable({ providedIn: 'root' }) export class ErrorHelper { - constructor(private nifiCommon: NiFiCommon) {} - fullScreenError(errorResponse: HttpErrorResponse, skipReplaceUrl?: boolean): Action { let title: string; let message: string; @@ -51,6 +48,8 @@ export class ErrorHelper { if (errorResponse.status === 0 || !errorResponse.error) { message = 'An error occurred communicating with NiFi. Please check the logs and fix any configuration issues before restarting.'; + } else if (errorResponse.status === 401) { + message = 'Your session has expired. Please navigate home to log in again.'; } else { message = this.getErrorString(errorResponse); } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/authentication.guard.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/authentication.guard.ts index 7de1050c56..33c54a59fe 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/authentication.guard.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/authentication.guard.ts @@ -18,118 +18,88 @@ import { CanMatchFn } from '@angular/router'; import { inject } from '@angular/core'; import { AuthService } from '../auth.service'; -import { AuthStorage } from '../auth-storage.service'; -import { take } from 'rxjs'; +import { catchError, from, map, of, switchMap, take, tap } from 'rxjs'; import { CurrentUserService } from '../current-user.service'; import { Store } from '@ngrx/store'; import { CurrentUserState } from '../../state/current-user'; import { loadCurrentUserSuccess } from '../../state/current-user/current-user.actions'; import { selectCurrentUserState } from '../../state/current-user/current-user.selectors'; +import { HttpErrorResponse } from '@angular/common/http'; +import { fullScreenError } from '../../state/error/error.actions'; +import { ErrorHelper } from '../error-helper.service'; +import { selectLoginConfiguration } from '../../state/login-configuration/login-configuration.selectors'; +import { loadLoginConfigurationSuccess } from '../../state/login-configuration/login-configuration.actions'; export const authenticationGuard: CanMatchFn = () => { - const authStorage: AuthStorage = inject(AuthStorage); const authService: AuthService = inject(AuthService); const userService: CurrentUserService = inject(CurrentUserService); + const errorHelper: ErrorHelper = inject(ErrorHelper); const store: Store = inject(Store); - const handleAuthentication: Promise = new Promise((resolve) => { - if (authStorage.hasToken()) { - resolve(true); - } else { - authService - .kerberos() - .pipe(take(1)) - .subscribe({ - next: (jwt: string) => { - // Use Expiration from JWT for tracking authentication status - const sessionExpiration: string | null = authService.getSessionExpiration(jwt); - if (sessionExpiration) { - authStorage.setToken(sessionExpiration); - } - - resolve(true); - }, - error: () => { - authService - .ticketExpiration() - .pipe(take(1)) - .subscribe({ - next: (accessTokenExpirationEntity: any) => { - const accessTokenExpiration: any = - accessTokenExpirationEntity.accessTokenExpiration; - // Convert ISO 8601 string to session expiration in seconds - const expiration: number = Date.parse(accessTokenExpiration.expiration); - const expirationSeconds: number = expiration / 1000; - const sessionExpiration: number = Math.round(expirationSeconds); - authStorage.setToken(String(sessionExpiration)); - - resolve(true); - }, - error: () => { - resolve(false); + const getAuthenticationConfig = store.select(selectLoginConfiguration).pipe( + take(1), + switchMap((loginConfiguration) => { + if (loginConfiguration) { + return of(loginConfiguration); + } else { + return from(authService.getLoginConfiguration()).pipe( + tap((response) => { + store.dispatch( + loadLoginConfigurationSuccess({ + response: { + loginConfiguration: response.authenticationConfiguration } - }); - } - }); - } - }); + }) + ); + }) + ); + } + }) + ); - return new Promise((resolve) => { - handleAuthentication.finally(() => { - store - .select(selectCurrentUserState) - .pipe(take(1)) - .subscribe((userState) => { + return getAuthenticationConfig.pipe( + switchMap((authConfigResponse) => { + return store.select(selectCurrentUserState).pipe( + take(1), + switchMap((userState) => { if (userState.status == 'success') { - resolve(true); + return of(true); } else { - userService - .getUser() - .pipe(take(1)) - .subscribe({ - next: (response) => { - // store the loaded user - store.dispatch( - loadCurrentUserSuccess({ - response: { - user: response - } - }) - ); - - if (authStorage.hasToken()) { - resolve(true); - } else { - authService - .accessConfig() - .pipe(take(1)) - .subscribe({ - next: (response) => { - if (response.config.supportsLogin) { - // Set default expiration when authenticated to enable logout status - const expiration: string = authService.getDefaultExpiration(); - authStorage.setToken(expiration); - } - resolve(true); - }, - error: () => { - window.location.href = './login'; - resolve(false); - } - }); - } - }, - error: (error) => { - // there is no anonymous access and we don't know this user - open the login page which handles login - if (error.status === 401) { - authStorage.removeToken(); - window.location.href = './login'; - } - resolve(false); + return from(userService.getUser()).pipe( + tap((response) => { + store.dispatch( + loadCurrentUserSuccess({ + response: { + user: response + } + }) + ); + }), + map(() => true), + catchError((errorResponse: HttpErrorResponse) => { + if (errorResponse.status !== 401 || authConfigResponse.loginSupported) { + store.dispatch(errorHelper.fullScreenError(errorResponse)); } - }); + + return of(false); + }) + ); } - }); - }); - }); + }) + ); + }), + catchError(() => { + store.dispatch( + fullScreenError({ + errorDetail: { + title: 'Unauthorized', + message: + 'Unable to load authentication configuration. Please contact your system administrator.' + } + }) + ); + + return of(false); + }) + ); }; diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/login-configuration.guard.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/login-configuration.guard.ts new file mode 100644 index 0000000000..0cac94dc74 --- /dev/null +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/login-configuration.guard.ts @@ -0,0 +1,78 @@ +/* + * 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 { CanMatchFn } from '@angular/router'; +import { inject } from '@angular/core'; +import { catchError, map, of, switchMap, tap } from 'rxjs'; +import { Store } from '@ngrx/store'; +import { fullScreenError } from '../../state/error/error.actions'; +import { HttpErrorResponse } from '@angular/common/http'; +import { ErrorHelper } from '../error-helper.service'; +import { selectLoginConfiguration } from '../../state/login-configuration/login-configuration.selectors'; +import { AuthService } from '../auth.service'; +import { loadLoginConfigurationSuccess } from '../../state/login-configuration/login-configuration.actions'; +import { LoginConfiguration, LoginConfigurationState } from '../../state/login-configuration'; + +export const checkLoginConfiguration = ( + loginConfigurationCheck: (loginConfiguration: LoginConfiguration) => boolean +): CanMatchFn => { + return () => { + const store: Store = inject(Store); + const authService: AuthService = inject(AuthService); + const errorHelper: ErrorHelper = inject(ErrorHelper); + + return store.select(selectLoginConfiguration).pipe( + switchMap((loginConfiguration) => { + if (loginConfiguration) { + return of(loginConfiguration); + } else { + return authService.getLoginConfiguration().pipe( + tap((response) => + store.dispatch( + loadLoginConfigurationSuccess({ + response: { + loginConfiguration: response.authenticationConfiguration + } + }) + ) + ) + ); + } + }), + map((loginConfiguration) => { + if (loginConfigurationCheck(loginConfiguration)) { + return true; + } + + store.dispatch( + fullScreenError({ + skipReplaceUrl: true, + errorDetail: { + title: 'Unable to load', + message: 'Login configuration check failed' + } + }) + ); + return false; + }), + catchError((errorResponse: HttpErrorResponse) => { + store.dispatch(errorHelper.fullScreenError(errorResponse, true)); + return of(false); + }) + ); + }; +}; diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.ts index c5607237f9..7b003d886d 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.ts @@ -15,61 +15,58 @@ * limitations under the License. */ -import { Injectable } from '@angular/core'; -import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; -import { Observable, tap } from 'rxjs'; -import { AuthStorage } from '../auth-storage.service'; +import { inject } from '@angular/core'; +import { HttpErrorResponse, HttpHandlerFn, HttpInterceptorFn, HttpRequest } from '@angular/common/http'; +import { catchError, map, take, combineLatest, tap } from 'rxjs'; import { Store } from '@ngrx/store'; import { NiFiState } from '../../state'; -import { fullScreenError } from '../../state/error/error.actions'; -import { NiFiCommon } from '../nifi-common.service'; +import { fullScreenError, setRoutedToFullScreenError } from '../../state/error/error.actions'; +import { selectCurrentUserState } from '../../state/current-user/current-user.selectors'; +import { navigateToLogIn, resetCurrentUser } from '../../state/current-user/current-user.actions'; +import { selectRoutedToFullScreenError } from '../../state/error/error.selectors'; +import { selectLoginConfiguration } from '../../state/login-configuration/login-configuration.selectors'; -@Injectable({ - providedIn: 'root' -}) -export class AuthInterceptor implements HttpInterceptor { - routedToFullScreenError = false; +export const authInterceptor: HttpInterceptorFn = (request: HttpRequest, next: HttpHandlerFn) => { + const store: Store = inject(Store); - constructor( - private authStorage: AuthStorage, - private store: Store, - private nifiCommon: NiFiCommon - ) {} - - intercept(request: HttpRequest, next: HttpHandler): Observable> { - return next.handle(request).pipe( - tap({ - error: (errorResponse) => { - if (errorResponse instanceof HttpErrorResponse) { - if (errorResponse.status === 401) { - if (this.authStorage.hasToken()) { - this.routedToFullScreenError = true; - - this.authStorage.removeToken(); - - let message: string = errorResponse.error; - if (this.nifiCommon.isBlank(message)) { - message = 'Your session has expired. Please navigate home to log in again.'; - } else { - message += '. Please navigate home to log in again.'; - } - - this.store.dispatch( - fullScreenError({ - errorDetail: { - title: 'Unauthorized', - message - } - }) - ); - } else if (!this.routedToFullScreenError) { - // the user has never logged in, redirect them to do so - window.location.href = './login'; - } + return next(request).pipe( + catchError((errorResponse) => { + if (errorResponse instanceof HttpErrorResponse && errorResponse.status === 401) { + return combineLatest([ + store.select(selectCurrentUserState).pipe( + take(1), + tap(() => store.dispatch(resetCurrentUser())) + ), + store.select(selectLoginConfiguration).pipe(take(1)), + store.select(selectRoutedToFullScreenError).pipe( + take(1), + tap(() => store.dispatch(setRoutedToFullScreenError({ routedToFullScreenError: true }))) + ) + ]).pipe( + map(([currentUserState, loginConfiguration, routedToFullScreenError]) => { + if ( + currentUserState.status === 'pending' && + loginConfiguration?.loginSupported && + !routedToFullScreenError + ) { + store.dispatch(navigateToLogIn()); + } else { + store.dispatch( + fullScreenError({ + errorDetail: { + title: 'Unauthorized', + message: 'Your session has expired. Please navigate home to log in again.' + } + }) + ); } - } - } - }) - ); - } -} + + throw errorResponse; + }) + ); + } else { + throw errorResponse; + } + }) + ); +}; diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/loading.interceptor.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/loading.interceptor.ts index 0286ee089e..91d0741cef 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/loading.interceptor.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/loading.interceptor.ts @@ -15,20 +15,14 @@ * limitations under the License. */ -import { Injectable } from '@angular/core'; -import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; -import { finalize, Observable } from 'rxjs'; +import { inject } from '@angular/core'; +import { HttpHandlerFn, HttpInterceptorFn, HttpRequest } from '@angular/common/http'; +import { finalize } from 'rxjs'; import { LoadingService } from '../loading.service'; -@Injectable({ - providedIn: 'root' -}) -export class LoadingInterceptor implements HttpInterceptor { - constructor(private loadingService: LoadingService) {} +export const loadingInterceptor: HttpInterceptorFn = (request: HttpRequest, next: HttpHandlerFn) => { + const loadingService: LoadingService = inject(LoadingService); + loadingService.set(true, request.url); - intercept(request: HttpRequest, next: HttpHandler): Observable> { - this.loadingService.set(true, request.url); - - return next.handle(request).pipe(finalize(() => this.loadingService.set(false, request.url))); - } -} + return next(request).pipe(finalize(() => loadingService.set(false, request.url))); +}; diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/polling.interceptor.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/polling.interceptor.ts index 0868390668..c444c2caa0 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/polling.interceptor.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/polling.interceptor.ts @@ -15,32 +15,27 @@ * limitations under the License. */ -import { Injectable } from '@angular/core'; -import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; -import { Observable, tap } from 'rxjs'; +import { inject } from '@angular/core'; +import { HttpErrorResponse, HttpHandlerFn, HttpInterceptorFn, HttpRequest } from '@angular/common/http'; +import { tap } from 'rxjs'; import { NiFiState } from '../../state'; import { Store } from '@ngrx/store'; import { stopCurrentUserPolling } from '../../state/current-user/current-user.actions'; import { stopProcessGroupPolling } from '../../pages/flow-designer/state/flow/flow.actions'; import { stopClusterSummaryPolling } from '../../state/cluster-summary/cluster-summary.actions'; -@Injectable({ - providedIn: 'root' -}) -export class PollingInterceptor implements HttpInterceptor { - constructor(private store: Store) {} +export const pollingInterceptor: HttpInterceptorFn = (request: HttpRequest, next: HttpHandlerFn) => { + const store: Store = inject(Store); - intercept(request: HttpRequest, next: HttpHandler): Observable> { - return next.handle(request).pipe( - tap({ - error: (error) => { - if (error instanceof HttpErrorResponse && error.status === 0) { - this.store.dispatch(stopCurrentUserPolling()); - this.store.dispatch(stopProcessGroupPolling()); - this.store.dispatch(stopClusterSummaryPolling()); - } + return next(request).pipe( + tap({ + error: (error) => { + if (error instanceof HttpErrorResponse && error.status === 0) { + store.dispatch(stopCurrentUserPolling()); + store.dispatch(stopProcessGroupPolling()); + store.dispatch(stopClusterSummaryPolling()); } - }) - ); - } -} + } + }) + ); +}; diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.actions.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.actions.ts index 8add0796ee..0467b52504 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.actions.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.actions.ts @@ -28,3 +28,11 @@ export const loadCurrentUserSuccess = createAction( export const startCurrentUserPolling = createAction('[Current User] Start Current User Polling'); export const stopCurrentUserPolling = createAction('[Current User] Stop Current User Polling'); + +export const resetCurrentUser = createAction('[Current User] Reset Current User'); + +export const navigateToLogIn = createAction('[Current User] Navigate To Log In'); + +export const logout = createAction('[Current User] Log Out'); + +export const navigateToLogOut = createAction('[Current User] Navigate To Log Out'); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.effects.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.effects.ts index f24379ff24..e27c153ac9 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.effects.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.effects.ts @@ -18,15 +18,25 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import * as UserActions from './current-user.actions'; -import { asyncScheduler, catchError, from, interval, map, of, switchMap, takeUntil } from 'rxjs'; +import { asyncScheduler, catchError, from, interval, map, of, switchMap, takeUntil, tap } from 'rxjs'; import { CurrentUserService } from '../../service/current-user.service'; import { ErrorHelper } from '../../service/error-helper.service'; +import { concatLatestFrom } from '@ngrx/operators'; +import { Store } from '@ngrx/store'; +import { NiFiState } from '../index'; +import { selectLogoutUri } from '../login-configuration/login-configuration.selectors'; +import { Router } from '@angular/router'; +import { AuthService } from '../../service/auth.service'; +import { HttpErrorResponse } from '@angular/common/http'; @Injectable() export class CurrentUserEffects { constructor( private actions$: Actions, + private store: Store, + private router: Router, private userService: CurrentUserService, + private authService: AuthService, private errorHelper: ErrorHelper ) {} @@ -61,4 +71,43 @@ export class CurrentUserEffects { switchMap(() => of(UserActions.loadCurrentUser())) ) ); + + navigateToLogIn$ = createEffect( + () => + this.actions$.pipe( + ofType(UserActions.navigateToLogIn), + tap(() => { + this.router.navigate(['/login']); + }) + ), + { dispatch: false } + ); + + logout$ = createEffect(() => + this.actions$.pipe( + ofType(UserActions.logout), + switchMap(() => + from(this.authService.logout()).pipe( + map(() => UserActions.navigateToLogOut()), + catchError((errorResponse: HttpErrorResponse) => + of(this.errorHelper.fullScreenError(errorResponse)) + ) + ) + ) + ) + ); + + navigateToLogOut$ = createEffect( + () => + this.actions$.pipe( + ofType(UserActions.navigateToLogOut), + concatLatestFrom(() => this.store.select(selectLogoutUri)), + tap(([, logoutUri]) => { + if (logoutUri) { + window.location.href = logoutUri; + } + }) + ), + { dispatch: false } + ); } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.reducer.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.reducer.ts index a2fe0d4267..a63740fe39 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.reducer.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.reducer.ts @@ -18,7 +18,7 @@ import { createReducer, on } from '@ngrx/store'; import { CurrentUserState } from './index'; import { Permissions } from '../shared'; -import { loadCurrentUser, loadCurrentUserSuccess } from './current-user.actions'; +import { loadCurrentUser, loadCurrentUserSuccess, resetCurrentUser } from './current-user.actions'; export const NO_PERMISSIONS: Permissions = { canRead: false, @@ -30,6 +30,7 @@ export const initialState: CurrentUserState = { identity: '', anonymous: true, canVersionFlows: false, + logoutSupported: false, controllerPermissions: NO_PERMISSIONS, countersPermissions: NO_PERMISSIONS, parameterContextPermissions: NO_PERMISSIONS, @@ -53,5 +54,8 @@ export const currentUserReducer = createReducer( ...state, user: response.user, status: 'success' as const + })), + on(resetCurrentUser, () => ({ + ...initialState })) ); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.selectors.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.selectors.ts index 1dca25eff8..dee7161790 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.selectors.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/current-user.selectors.ts @@ -16,8 +16,13 @@ */ import { createFeatureSelector, createSelector } from '@ngrx/store'; -import { currentUserFeatureKey, CurrentUserState } from './index'; +import { CurrentUser, currentUserFeatureKey, CurrentUserState } from './index'; export const selectCurrentUserState = createFeatureSelector(currentUserFeatureKey); export const selectCurrentUser = createSelector(selectCurrentUserState, (state: CurrentUserState) => state.user); + +export const selectLogoutSupported = createSelector( + selectCurrentUser, + (currentUser: CurrentUser) => currentUser.logoutSupported +); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/index.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/index.ts index 1b9b0e3d07..ea683f857c 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/index.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/current-user/index.ts @@ -32,6 +32,7 @@ export interface CurrentUser { identity: string; anonymous: boolean; canVersionFlows: boolean; + logoutSupported: boolean; provenancePermissions: Permissions; countersPermissions: Permissions; tenantsPermissions: Permissions; diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.actions.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.actions.ts index da4ff5a42c..7c1d861a63 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.actions.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.actions.ts @@ -30,3 +30,8 @@ export const addBannerError = createAction('[Error] Add Banner Error', props<{ e export const clearBannerErrors = createAction('[Error] Clear Banner Errors'); export const resetErrorState = createAction('[Error] Reset Error State'); + +export const setRoutedToFullScreenError = createAction( + '[Error] Set Routed To Full Screen Error', + props<{ routedToFullScreenError: boolean }>() +); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.reducer.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.reducer.ts index abce7a8bb3..7b92ceb54d 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.reducer.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.reducer.ts @@ -17,12 +17,19 @@ import { createReducer, on } from '@ngrx/store'; import { ErrorState } from './index'; -import { resetErrorState, fullScreenError, addBannerError, clearBannerErrors } from './error.actions'; +import { + resetErrorState, + fullScreenError, + addBannerError, + clearBannerErrors, + setRoutedToFullScreenError +} from './error.actions'; import { produce } from 'immer'; export const initialState: ErrorState = { bannerErrors: null, - fullScreenError: null + fullScreenError: null, + routedToFullScreenError: false }; export const errorReducer = createReducer( @@ -44,6 +51,10 @@ export const errorReducer = createReducer( ...state, bannerErrors: null })), + on(setRoutedToFullScreenError, (state, { routedToFullScreenError }) => ({ + ...state, + routedToFullScreenError + })), on(resetErrorState, () => ({ ...initialState })) diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.selectors.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.selectors.ts index 0887a5d3de..62485f8069 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.selectors.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.selectors.ts @@ -23,3 +23,8 @@ export const selectErrorState = createFeatureSelector(errorFeatureKe export const selectFullScreenError = createSelector(selectErrorState, (state: ErrorState) => state.fullScreenError); export const selectBannerErrors = createSelector(selectErrorState, (state: ErrorState) => state.bannerErrors); + +export const selectRoutedToFullScreenError = createSelector( + selectErrorState, + (state: ErrorState) => state.routedToFullScreenError +); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/index.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/index.ts index b4febc851b..7f3e3ddb10 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/index.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/index.ts @@ -25,4 +25,5 @@ export interface ErrorDetail { export interface ErrorState { bannerErrors: string[] | null; fullScreenError: ErrorDetail | null; + routedToFullScreenError: boolean; } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts index 1824d08269..7f2aee6f27 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts @@ -39,6 +39,8 @@ import { documentationFeatureKey, DocumentationState } from './documentation'; import { documentationReducer } from './documentation/documentation.reducer'; import { clusterSummaryFeatureKey, ClusterSummaryState } from './cluster-summary'; import { clusterSummaryReducer } from './cluster-summary/cluster-summary.reducer'; +import { loginConfigurationFeatureKey, LoginConfigurationState } from './login-configuration'; +import { loginConfigurationReducer } from './login-configuration/login-configuration.reducer'; export interface NiFiState { [DEFAULT_ROUTER_FEATURENAME]: RouterReducerState; @@ -47,6 +49,7 @@ export interface NiFiState { [extensionTypesFeatureKey]: ExtensionTypesState; [aboutFeatureKey]: AboutState; [flowConfigurationFeatureKey]: FlowConfigurationState; + [loginConfigurationFeatureKey]: LoginConfigurationState; [statusHistoryFeatureKey]: StatusHistoryState; [controllerServiceStateFeatureKey]: ControllerServiceState; [systemDiagnosticsFeatureKey]: SystemDiagnosticsState; @@ -62,6 +65,7 @@ export const rootReducers: ActionReducerMap = { [extensionTypesFeatureKey]: extensionTypesReducer, [aboutFeatureKey]: aboutReducer, [flowConfigurationFeatureKey]: flowConfigurationReducer, + [loginConfigurationFeatureKey]: loginConfigurationReducer, [statusHistoryFeatureKey]: statusHistoryReducer, [controllerServiceStateFeatureKey]: controllerServiceStateReducer, [systemDiagnosticsFeatureKey]: systemDiagnosticsReducer, diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/loading.interceptor.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/login-configuration/index.ts similarity index 63% rename from nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/loading.interceptor.spec.ts rename to nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/login-configuration/index.ts index 38c79a5e7c..e5ef90b327 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/loading.interceptor.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/login-configuration/index.ts @@ -15,19 +15,20 @@ * limitations under the License. */ -import { TestBed } from '@angular/core/testing'; +export const loginConfigurationFeatureKey = 'loginConfiguration'; -import { LoadingInterceptor } from './loading.interceptor'; +export interface LoadLoginConfigurationResponse { + loginConfiguration: LoginConfiguration; +} -describe('LoadingInterceptor', () => { - let service: LoadingInterceptor; +export interface LoginConfiguration { + loginSupported: boolean; + externalLoginRequired: boolean; + loginUri: string; + logoutUri: string; +} - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(LoadingInterceptor); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); +export interface LoginConfigurationState { + loginConfiguration: LoginConfiguration | null; + status: 'pending' | 'loading' | 'success'; +} diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/auth-storage.service.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/login-configuration/login-configuration.actions.ts similarity index 67% rename from nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/auth-storage.service.spec.ts rename to nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/login-configuration/login-configuration.actions.ts index 24bea8e75c..26a5117256 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/auth-storage.service.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/login-configuration/login-configuration.actions.ts @@ -15,19 +15,12 @@ * limitations under the License. */ -import { TestBed } from '@angular/core/testing'; +import { createAction, props } from '@ngrx/store'; +import { LoadLoginConfigurationResponse } from './index'; -import { AuthStorage } from './auth-storage.service'; +export const loadLoginConfiguration = createAction('[Login Configuration] Load Login Configuration'); -describe('AuthStorage', () => { - let service: AuthStorage; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(AuthStorage); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); +export const loadLoginConfigurationSuccess = createAction( + '[Login Configuration] Load Login Configuration Success', + props<{ response: LoadLoginConfigurationResponse }>() +); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/login-configuration/login-configuration.effects.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/login-configuration/login-configuration.effects.ts new file mode 100644 index 0000000000..8a0d33cdf0 --- /dev/null +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/login-configuration/login-configuration.effects.ts @@ -0,0 +1,61 @@ +/* + * 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 { Injectable } from '@angular/core'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; +import * as LoginConfigurationActions from './login-configuration.actions'; +import { catchError, from, map, of, switchMap } from 'rxjs'; +import * as ErrorActions from '../error/error.actions'; +import { HttpErrorResponse } from '@angular/common/http'; +import { ErrorHelper } from '../../service/error-helper.service'; +import { AuthService } from '../../service/auth.service'; + +@Injectable() +export class LoginConfigurationEffects { + constructor( + private actions$: Actions, + private authService: AuthService, + private errorHelper: ErrorHelper + ) {} + + loadLoginConfiguration$ = createEffect(() => + this.actions$.pipe( + ofType(LoginConfigurationActions.loadLoginConfiguration), + switchMap(() => { + return from( + this.authService.getLoginConfiguration().pipe( + map((response) => + LoginConfigurationActions.loadLoginConfigurationSuccess({ + response + }) + ), + catchError((errorResponse: HttpErrorResponse) => + of( + ErrorActions.snackBarError({ + error: this.errorHelper.getErrorString( + errorResponse, + 'Failed to load Login Configuration.' + ) + }) + ) + ) + ) + ); + }) + ) + ); +} diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/login-configuration/login-configuration.reducer.ts similarity index 54% rename from nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.spec.ts rename to nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/login-configuration/login-configuration.reducer.ts index 1ce007609f..b8f89c6ea6 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/login-configuration/login-configuration.reducer.ts @@ -15,27 +15,24 @@ * limitations under the License. */ -import { TestBed } from '@angular/core/testing'; +import { createReducer, on } from '@ngrx/store'; +import { LoginConfigurationState } from './index'; +import { loadLoginConfiguration, loadLoginConfigurationSuccess } from './login-configuration.actions'; -import { AuthInterceptor } from './auth.interceptor'; -import { provideMockStore } from '@ngrx/store/testing'; -import { initialState } from '../../state/error/error.reducer'; +export const initialState: LoginConfigurationState = { + loginConfiguration: null, + status: 'pending' +}; -describe('AuthInterceptor', () => { - let service: AuthInterceptor; - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - provideMockStore({ - initialState - }) - ] - }); - service = TestBed.inject(AuthInterceptor); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); +export const loginConfigurationReducer = createReducer( + initialState, + on(loadLoginConfiguration, (state) => ({ + ...state, + status: 'loading' as const + })), + on(loadLoginConfigurationSuccess, (state, { response }) => ({ + ...state, + loginConfiguration: response.loginConfiguration, + status: 'success' as const + })) +); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/polling.interceptor.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/login-configuration/login-configuration.selectors.ts similarity index 50% rename from nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/polling.interceptor.spec.ts rename to nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/login-configuration/login-configuration.selectors.ts index 0d496d452f..124d8be859 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/polling.interceptor.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/login-configuration/login-configuration.selectors.ts @@ -15,23 +15,23 @@ * limitations under the License. */ -import { TestBed } from '@angular/core/testing'; +import { createFeatureSelector, createSelector } from '@ngrx/store'; +import { LoginConfiguration, LoginConfigurationState, loginConfigurationFeatureKey } from './index'; -import { PollingInterceptor } from './polling.interceptor'; -import { provideMockStore } from '@ngrx/store/testing'; -import { initialState } from '../../state/current-user/current-user.reducer'; +export const selectLoginConfigurationState = + createFeatureSelector(loginConfigurationFeatureKey); -describe('PollingInterceptor', () => { - let service: PollingInterceptor; +export const selectLoginConfiguration = createSelector( + selectLoginConfigurationState, + (state: LoginConfigurationState) => state.loginConfiguration +); - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [provideMockStore({ initialState })] - }); - service = TestBed.inject(PollingInterceptor); - }); +export const selectLoginUri = createSelector( + selectLoginConfiguration, + (loginConfiguration: LoginConfiguration | null) => loginConfiguration?.loginUri +); - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); +export const selectLogoutUri = createSelector( + selectLoginConfiguration, + (loginConfiguration: LoginConfiguration | null) => loginConfiguration?.logoutUri +); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/advanced-ui/advanced-ui.component.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/advanced-ui/advanced-ui.component.spec.ts index a388bb594b..8291b351ab 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/advanced-ui/advanced-ui.component.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/advanced-ui/advanced-ui.component.spec.ts @@ -29,6 +29,8 @@ import { selectClusterSummary } from '../../../state/cluster-summary/cluster-sum import * as fromClusterSummary from '../../../state/cluster-summary/cluster-summary.reducer'; import { selectFlowConfiguration } from '../../../state/flow-configuration/flow-configuration.selectors'; import * as fromFlowConfiguration from '../../../state/flow-configuration/flow-configuration.reducer'; +import { selectLoginConfiguration } from '../../../state/login-configuration/login-configuration.selectors'; +import * as fromLoginConfiguration from '../../../state/login-configuration/login-configuration.reducer'; describe('AdvancedUi', () => { let component: AdvancedUi; @@ -59,6 +61,10 @@ describe('AdvancedUi', () => { { selector: selectFlowConfiguration, value: fromFlowConfiguration.initialState.flowConfiguration + }, + { + selector: selectLoginConfiguration, + value: fromLoginConfiguration.initialState.loginConfiguration } ] }) diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.spec.ts index b4b52f004e..c7f44d5336 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.spec.ts @@ -19,6 +19,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ExtensionCreation } from './extension-creation.component'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { MatDialogRef } from '@angular/material/dialog'; describe('ExtensionCreation', () => { let component: ExtensionCreation; @@ -26,7 +27,13 @@ describe('ExtensionCreation', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ExtensionCreation, NoopAnimationsModule] + imports: [ExtensionCreation, NoopAnimationsModule], + providers: [ + { + provide: MatDialogRef, + useValue: null + } + ] }); fixture = TestBed.createComponent(ExtensionCreation); component = fixture.componentInstance; diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.html b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.html index 6043279f53..b37870f0e6 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.html +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.html @@ -32,11 +32,13 @@ @if (currentUser(); as user) {
-
{{ user.identity }}
+ @if (!user.anonymous) { +
{{ user.identity }}
+ } @if (allowLogin(user)) { log in } - @if (hasToken()) { + @if (user.logoutSupported) { log out }
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.spec.ts index 05ea83f294..175914c37f 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.spec.ts @@ -28,6 +28,8 @@ import { selectClusterSummary } from '../../../state/cluster-summary/cluster-sum import * as fromClusterSummary from '../../../state/cluster-summary/cluster-summary.reducer'; import { selectFlowConfiguration } from '../../../state/flow-configuration/flow-configuration.selectors'; import * as fromFlowConfiguration from '../../../state/flow-configuration/flow-configuration.reducer'; +import { selectLoginConfiguration } from '../../../state/login-configuration/login-configuration.selectors'; +import * as fromLoginConfiguration from '../../../state/login-configuration/login-configuration.reducer'; describe('Navigation', () => { let component: Navigation; @@ -51,6 +53,10 @@ describe('Navigation', () => { { selector: selectFlowConfiguration, value: fromFlowConfiguration.initialState.flowConfiguration + }, + { + selector: selectLoginConfiguration, + value: fromLoginConfiguration.initialState.loginConfiguration } ] }) diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.ts index 8faba641b7..8abc3014a8 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.ts @@ -23,8 +23,6 @@ import { MatMenuModule } from '@angular/material/menu'; import { getNodeStatusHistoryAndOpenDialog } from '../../../state/status-history/status-history.actions'; import { getSystemDiagnosticsAndOpenDialog } from '../../../state/system-diagnostics/system-diagnostics.actions'; import { Store } from '@ngrx/store'; -import { AuthStorage } from '../../../service/auth-storage.service'; -import { AuthService } from '../../../service/auth.service'; import { CurrentUser } from '../../../state/current-user'; import { RouterLink } from '@angular/router'; import { selectCurrentUser } from '../../../state/current-user/current-user.selectors'; @@ -35,7 +33,11 @@ import { Storage } from '../../../service/storage.service'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { DARK_THEME, LIGHT_THEME, OS_SETTING, ThemingService } from '../../../service/theming.service'; import { loadFlowConfiguration } from '../../../state/flow-configuration/flow-configuration.actions'; -import { startCurrentUserPolling, stopCurrentUserPolling } from '../../../state/current-user/current-user.actions'; +import { + logout, + startCurrentUserPolling, + stopCurrentUserPolling +} from '../../../state/current-user/current-user.actions'; import { loadAbout, openAboutDialog } from '../../../state/about/about.actions'; import { loadClusterSummary, @@ -43,6 +45,7 @@ import { stopClusterSummaryPolling } from '../../../state/cluster-summary/cluster-summary.actions'; import { selectClusterSummary } from '../../../state/cluster-summary/cluster-summary.selectors'; +import { selectLoginConfiguration } from '../../../state/login-configuration/login-configuration.selectors'; @Component({ selector: 'navigation', @@ -69,12 +72,11 @@ export class Navigation implements OnInit, OnDestroy { OS_SETTING: string = OS_SETTING; currentUser = this.store.selectSignal(selectCurrentUser); flowConfiguration = this.store.selectSignal(selectFlowConfiguration); + loginConfiguration = this.store.selectSignal(selectLoginConfiguration); clusterSummary = this.store.selectSignal(selectClusterSummary); constructor( private store: Store, - private authStorage: AuthStorage, - private authService: AuthService, private storage: Storage, private themingService: ThemingService ) { @@ -104,15 +106,16 @@ export class Navigation implements OnInit, OnDestroy { } allowLogin(user: CurrentUser): boolean { - return user.anonymous && location.protocol === 'https:'; - } - - hasToken(): boolean { - return this.authStorage.hasToken(); + const loginConfig = this.loginConfiguration(); + if (loginConfig) { + return user.anonymous && loginConfig.loginSupported; + } else { + return false; + } } logout(): void { - this.authService.logout(); + this.store.dispatch(logout()); } viewNodeStatusHistory(): void { diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/page-content/page-content.component.html b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/page-content/page-content.component.html index 9ba4d7f092..92726ece57 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/page-content/page-content.component.html +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/page-content/page-content.component.html @@ -19,10 +19,10 @@

{{ title }}

- @if (hasToken()) { + @if (logoutSupported()) { log out } - home + home
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/page-content/page-content.component.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/page-content/page-content.component.spec.ts index b61aeaa042..d2b06358b3 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/page-content/page-content.component.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/page-content/page-content.component.spec.ts @@ -21,6 +21,9 @@ import { PageContent } from './page-content.component'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { RouterModule } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; +import { provideMockStore } from '@ngrx/store/testing'; +import { currentUserFeatureKey } from '../../../state/current-user'; +import { initialState } from '../../../state/current-user/current-user.reducer'; describe('PageContent', () => { let component: PageContent; @@ -28,7 +31,14 @@ describe('PageContent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [PageContent, HttpClientTestingModule, RouterModule, RouterTestingModule] + imports: [PageContent, HttpClientTestingModule, RouterModule, RouterTestingModule], + providers: [ + provideMockStore({ + initialState: { + [currentUserFeatureKey]: initialState + } + }) + ] }); fixture = TestBed.createComponent(PageContent); component = fixture.componentInstance; diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/page-content/page-content.component.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/page-content/page-content.component.ts index 038a1fe4a0..4644ad1ef0 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/page-content/page-content.component.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/page-content/page-content.component.ts @@ -16,9 +16,12 @@ */ import { Component, Input } from '@angular/core'; -import { AuthStorage } from '../../../service/auth-storage.service'; -import { AuthService } from '../../../service/auth.service'; import { RouterLink } from '@angular/router'; +import { selectLogoutSupported } from '../../../state/current-user/current-user.selectors'; +import { Store } from '@ngrx/store'; +import { NiFiState } from '../../../state'; +import { setRoutedToFullScreenError } from '../../../state/error/error.actions'; +import { logout } from '../../../state/current-user/current-user.actions'; @Component({ selector: 'page-content', @@ -30,16 +33,15 @@ import { RouterLink } from '@angular/router'; export class PageContent { @Input() title = ''; - constructor( - private authStorage: AuthStorage, - private authService: AuthService - ) {} + logoutSupported = this.store.selectSignal(selectLogoutSupported); + + constructor(private store: Store) {} + + resetRoutedToFullScreenError(): void { + this.store.dispatch(setRoutedToFullScreenError({ routedToFullScreenError: false })); + } logout(): void { - this.authService.logout(); - } - - hasToken(): boolean { - return this.authStorage.hasToken(); + this.store.dispatch(logout()); } } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/status-history/status-history.component.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/status-history/status-history.component.spec.ts index e9140a8221..1a042ea63d 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/status-history/status-history.component.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/status-history/status-history.component.spec.ts @@ -21,7 +21,7 @@ import { StatusHistory } from './status-history.component'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { provideMockStore } from '@ngrx/store/testing'; import { initialState } from '../../../state/extension-types/extension-types.reducer'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; describe('StatusHistory', () => { let component: StatusHistory; @@ -30,7 +30,14 @@ describe('StatusHistory', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], - providers: [{ provide: MAT_DIALOG_DATA, useValue: {} }, provideMockStore({ initialState })] + providers: [ + { provide: MAT_DIALOG_DATA, useValue: {} }, + provideMockStore({ initialState }), + { + provide: MatDialogRef, + useValue: null + } + ] }); fixture = TestBed.createComponent(StatusHistory); component = fixture.componentInstance; diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/system-diagnostics-dialog/system-diagnostics-dialog.component.spec.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/system-diagnostics-dialog/system-diagnostics-dialog.component.spec.ts index 5c22267700..1e270ef183 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/system-diagnostics-dialog/system-diagnostics-dialog.component.spec.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/system-diagnostics-dialog/system-diagnostics-dialog.component.spec.ts @@ -20,6 +20,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { SystemDiagnosticsDialog } from './system-diagnostics-dialog.component'; import { provideMockStore } from '@ngrx/store/testing'; import { initialSystemDiagnosticsState } from '../../../state/system-diagnostics/system-diagnostics.reducer'; +import { MatDialogRef } from '@angular/material/dialog'; describe('SystemDiagnosticsDialog', () => { let component: SystemDiagnosticsDialog; @@ -28,7 +29,13 @@ describe('SystemDiagnosticsDialog', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [SystemDiagnosticsDialog], - providers: [provideMockStore({ initialState: initialSystemDiagnosticsState })] + providers: [ + provideMockStore({ initialState: initialSystemDiagnosticsState }), + { + provide: MatDialogRef, + useValue: null + } + ] }); fixture = TestBed.createComponent(SystemDiagnosticsDialog); component = fixture.componentInstance; diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/webapp/WEB-INF/web.xml b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/webapp/WEB-INF/web.xml index e6072b4d77..f4d7a0c0ed 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/webapp/WEB-INF/web.xml +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/webapp/WEB-INF/web.xml @@ -19,47 +19,6 @@ version="6.0"> nifi-web-frontend - - - - - - - - - - - - - - - - - - - - - - - LoginFilter - org.apache.nifi.web.filter.LoginFilter - - - LoginFilter - /login - - - - - LogoutFilter - org.apache.nifi.web.filter.LogoutFilter - - - LogoutFilter - /logout - - - SanitizeContextPathFilter org.apache.nifi.web.filter.SanitizeContextPathFilter