From 3f812ea25d352ec33d07f5ffa4c2aa2a77e8e793 Mon Sep 17 00:00:00 2001
From: "Lott, Christopher (cl778h)"
Date: Wed, 17 Jul 2019 14:28:11 -0400
Subject: [PATCH] Integrate EPSDK-FW library for auth and users
Change-Id: I8cba9e80e50b0e890783610d769e275091f942a7
Signed-off-by: Lott, Christopher (cl778h)
---
README.md | 45 +--
docs/release-notes.rst | 3 +-
webapp-backend/.gitignore | 2 +
webapp-backend/README.md | 120 ++++++-
webapp-backend/config/.gitignore | 2 +
webapp-backend/config/key.properties.template | 21 ++
webapp-backend/config/portal.properties.template | 34 ++
webapp-backend/pom.xml | 78 ++++-
.../ric/portal/dashboard/DashboardApplication.java | 15 +-
.../ric/portal/dashboard/DashboardConstants.java | 11 +
.../oransc/ric/portal/dashboard/LoginServlet.java | 116 +++++++
.../dashboard/config/SpringContextCache.java | 44 +++
.../dashboard/config/WebSecurityConfiguration.java | 172 +++++++++
.../dashboard/controller/AcXappController.java | 4 +
.../dashboard/controller/AdminController.java | 4 +
.../dashboard/controller/AnrXappController.java | 8 +
.../dashboard/controller/AppManagerController.java | 13 +
.../CustomResponseEntityExceptionHandler.java | 42 ++-
.../dashboard/controller/E2ManagerController.java | 9 +
.../portal/dashboard/model/EcompUserDetails.java | 85 +++++
.../ric/portal/dashboard/model/ErrorTransport.java | 12 +-
.../dashboard/portalapi/DashboardUserManager.java | 122 +++++++
.../dashboard/portalapi/IPortalSdkDecryptor.java | 41 +++
.../dashboard/portalapi/PortalAuthManager.java | 118 +++++++
.../portalapi/PortalAuthenticationFilter.java | 177 ++++++++++
.../portalapi/PortalRestCentralServiceImpl.java | 88 +++++
.../dashboard/portalapi/PortalSdkDecryptorAes.java | 32 ++
.../dashboard/portalapi/PortalSdkDecryptorPkc.java | 31 ++
webapp-backend/src/main/resources/ESAPI.properties | 385 +++++++++++++++++++++
.../src/main/resources/application.properties | 31 +-
webapp-backend/src/main/resources/logback.xml | 14 +-
.../src/main/resources/validation.properties | 19 +
.../config/A1MediatorMockConfiguration.java | 19 +-
.../config/AnrXappMockConfiguration.java | 58 +++-
.../config/AppManagerMockConfiguration.java | 75 ++--
.../config/E2ManagerMockConfiguration.java | 39 ++-
.../config/WebSecurityMockConfiguration.java | 171 +++++++++
.../controller/AbstractControllerTest.java | 13 +-
.../controller/AcXappControllerTest.java | 11 +-
.../{test => }/controller/AdminControllerTest.java | 16 +-
.../controller/AnrXappControllerTest.java | 14 +-
.../controller/AppManagerControllerTest.java | 22 +-
.../{test => }/controller/DefaultContextTest.java | 2 +-
.../controller/E2ManagerControllerTest.java | 22 +-
.../controller/PortalRestCentralServiceTest.java | 103 ++++++
webapp-backend/src/test/resources/key.properties | 22 ++
.../src/test/resources/portal.properties | 26 ++
.../src/app/anr-xapp/anr-xapp.datasource.ts | 6 +-
.../src/app/app-control/app-control.component.ts | 15 +-
.../src/app/app-control/app-control.datasource.ts | 6 +-
.../src/app/catalog/catalog.component.ts | 13 +-
.../src/app/catalog/catalog.datasource.ts | 6 +-
.../sidenav-list/sidenav-list.component.html | 2 +-
.../ran-control/ran-connection-dialog.component.ts | 9 +-
.../src/app/ran-control/ran-control.component.ts | 11 +-
.../src/app/ran-control/ran-control.datasource.ts | 6 +-
.../src/app/services/stats/stats.service.ts | 4 +-
webapp-frontend/src/app/user/user.datasource.ts | 6 +-
58 files changed, 2393 insertions(+), 202 deletions(-)
create mode 100644 webapp-backend/config/.gitignore
create mode 100644 webapp-backend/config/key.properties.template
create mode 100644 webapp-backend/config/portal.properties.template
create mode 100644 webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/LoginServlet.java
create mode 100644 webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/SpringContextCache.java
create mode 100644 webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/WebSecurityConfiguration.java
create mode 100644 webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/EcompUserDetails.java
create mode 100644 webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/DashboardUserManager.java
create mode 100644 webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/IPortalSdkDecryptor.java
create mode 100644 webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthManager.java
create mode 100644 webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthenticationFilter.java
create mode 100644 webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalRestCentralServiceImpl.java
create mode 100644 webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorAes.java
create mode 100644 webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorPkc.java
create mode 100644 webapp-backend/src/main/resources/ESAPI.properties
create mode 100644 webapp-backend/src/main/resources/validation.properties
rename webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/{test => }/config/A1MediatorMockConfiguration.java (85%)
rename webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/{test => }/config/AnrXappMockConfiguration.java (85%)
rename webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/{test => }/config/AppManagerMockConfiguration.java (79%)
rename webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/{test => }/config/E2ManagerMockConfiguration.java (84%)
create mode 100644 webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/WebSecurityMockConfiguration.java
rename webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/{test => }/controller/AbstractControllerTest.java (87%)
rename webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/{test => }/controller/AcXappControllerTest.java (88%)
rename webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/{test => }/controller/AdminControllerTest.java (80%)
rename webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/{test => }/controller/AnrXappControllerTest.java (88%)
rename webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/{test => }/controller/AppManagerControllerTest.java (84%)
rename webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/{test => }/controller/DefaultContextTest.java (96%)
rename webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/{test => }/controller/E2ManagerControllerTest.java (81%)
create mode 100644 webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/PortalRestCentralServiceTest.java
create mode 100644 webapp-backend/src/test/resources/key.properties
create mode 100644 webapp-backend/src/test/resources/portal.properties
diff --git a/README.md b/README.md
index 3cec2d60..e70617f5 100644
--- a/README.md
+++ b/README.md
@@ -1,46 +1,39 @@
# O-RAN-SC RIC Dashboard Web Application
-This webapp is built with Angular 7 and Spring-Boot 2.
+The O-RAN SC RIC Dashboard provides administrative and operator functions
+for a disaggregated radio access network (RAN) controller.
+The web app is built as a single-page app using an Angular 8 front end
+and a Spring-Boot 2 back end.
## Deployment configuration
-The application expects an application.properties file to be provided,
-probably mounted as a file from a Kubernetes configuration map, with
-the following content:
+The application expects the following configuration files,
+usually mounted as files from Kubernetes configuration maps:
- # A1 Mediator
- a1med.url = http://A1-URL
- # ANR xApp
- anrxapp.url = http://ANR-URL
- # E2 Manager
- e2mgr.url = http://E2-URL
- # Xapp Manager
- xappmgr.url = http://MGR-URL
+ application.properties (in launch directory)
+ key.properties (on classpath)
+ portal.properties (on classpath)
+
+Sample files are in directory src/main/resources and src/test/resources.
## Development guide
This section gives a quickstart guide for developers.
-### Check prerequisites
+### Prerequisites
-1. Java development kit (JDK), version 1.8 or later
+1. Java development kit (JDK), version 11 or later
2. Maven dependency-management tool, version 3.4 or later
### Build and launch the web app
- mvn -Ddocker.skip=true clean install
- cd webapp-backend
- mvn spring-boot:run
-
-Then open a browser on http://localhost:8080
-
-In addition to the above, you can run the Angular server
-for debugging the frontend and backend separately:
-
- cd webapp-frontend
- ./ng serve --proxy-config proxy.conf.json
+Instructions for launching a backend Sprint-Boot server
+are available in the webapp-backend README file.
+After launching, open a browser on http://localhost:8080
-Then open a browser on http://localhost:4200
+Instructions for launching a frontend Angular server (only for development)
+are available in the webapp-frontend README file.
+After launching, open a browser on http://localhost:4200
## License
diff --git a/docs/release-notes.rst b/docs/release-notes.rst
index b6eb3f20..10560391 100644
--- a/docs/release-notes.rst
+++ b/docs/release-notes.rst
@@ -20,7 +20,7 @@
RIC Dashboard Release Notes
===========================
-Version 1.2.0, 17 July 2019
+Version 1.2.0, 26 July 2019
---------------------------
* Split URL properties into prefix/suffix parts
* Add jacoco plugin to back-end for code coverage
@@ -29,6 +29,7 @@ Version 1.2.0, 17 July 2019
* Drop mock RAN names feature that supported R1 testing
* Extend mock endpoints to simulate delay seen in tests
* Move mock configuration classes into test area
+* Add EPSDK-FW user management and Portal security
Version 1.0.5, 5 July 2019
--------------------------
diff --git a/webapp-backend/.gitignore b/webapp-backend/.gitignore
index 0367d238..891095d0 100644
--- a/webapp-backend/.gitignore
+++ b/webapp-backend/.gitignore
@@ -30,3 +30,5 @@
/build/
/application-tlab2.properties
+/application.properties
+/users.json
diff --git a/webapp-backend/README.md b/webapp-backend/README.md
index 8a142d78..6751ef99 100644
--- a/webapp-backend/README.md
+++ b/webapp-backend/README.md
@@ -1,19 +1,123 @@
# RIC Dashboard Web Application Backend
-## Launch server
+The RIC Dashboard back-end provides REST services to the Dashboard
+front-end Typescript features running in the user's browser. For
+production use, this server also offers the Angular application files.
-Run `mvn -Dspring.config.name=application-abc spring-boot:run` to run a server configured
-by the file 'application-abc.properties' in the local directory.
+The server uses the ONAP Portal's "EPSDK-FW" library to support
+single-sign-on (SSO) feature, which requires users to authenticate
+at the ONAP Portal UI. Authentication features including SSO are
+excluded by Spring profiles when running the back-end as a development
+server, see below.
-## Development server
+## Launch production server
-Set an environment variable via JVM argument "-Dorg.oransc.ric.portal.dashboard=mock"
-and run the JUnit test case DashboardServerTest for a development server to run standalone
-with mock configuration and data that simulates the behavior of remote endpoints.
+This server requires several configuration files:
+
+ application.properties (in launch directory)
+ key.properties (on Java classpath)
+ portal.properties (on Java classpath)
+
+These steps are required:
+
+1. Check the set of properties files in the config folder, and create
+ files from templates as needed. E.g., copy
+ "key.properties.template" to "key.properties".
+2. Add the config folder to the Java classpath
+3a. Launch the server with this command-line invocation:
+
+ java -cp config:target/ric-dash-be-1.2.0-SNAPSHOT.jar \
+ -Dloader.main=org.oransc.ric.portal.dashboard.DashboardApplication \
+ org.springframework.boot.loader.PropertiesLauncher
+
+3b. To use the configuration in the "application-abc.properties" file, addd a
+key-value pair for "spring.config.name" and launch with an invocation like this:
+
+ java -cp config:target/ric-dash-be-1.2.0-SNAPSHOT.jar \
+ -Dspring.config.name=application-abc \
+ -Dloader.main=org.oransc.ric.portal.dashboard.DashboardApplication \
+ org.springframework.boot.loader.PropertiesLauncher
+
+### Production user authentication
+
+The regular server authenticates requests using cookies that are set
+by the ONAP Portal:
+
+ EPService
+ UserId
+
+The EPService value is not checked. The UserId value is decrypted
+using a secret key shared with the ONAP Portal to yield a user ID.
+That ID must match a user's loginId defined in the user manager.
+
+The regular server checks requests for the following granted
+authorities (role names), as defined in the DashboardConstants class.
+A standard user can read (GET) all methods but not make changes.
+An administrator can read (GET) and write (POST PUT DELETE) all data.
+
+ Standard_User
+ System_Administrator
+
+Use the following structure in a JSON file to publish a user for the
+user manager:
+
+ [
+ {
+ "orgId":null,
+ "managerId":null,
+ "firstName":"Demo",
+ "middleInitial":null,
+ "lastName":"User",
+ "phone":null,
+ "email":null,
+ "hrid":null,
+ "orgUserId":null,
+ "orgCode":null,
+ "orgManagerUserId":null,
+ "jobTitle":null,
+ "loginId":"demo",
+ "active":true,
+ "roles":[
+ {
+ "id":null,
+ "name":"Standard_User",
+ "roleFunctions":null
+ }
+ ]
+ }
+ ]
+
+
+## Launch development server
+
+The development server uses local configuration and serves mock data
+that simulates the behavior of remote endpoints. The directory
+src/main/resources contains usable versions of the required property
+files. These steps are required to launch:
+
+1. Set an environment variable via JVM argument: "-Dorg.oransc.ric.portal.dashboard=mock"
+2. Run the JUnit test case DashboardServerTest -- not exactly a "test" because it never finishes.
+
+Both steps can be done with this command-line invocation:
+
+ mvn -Dorg.oransc.ric.portal.dashboard=mock -Dtest=DashboardTestServer test
+
+### Development user authentication
+
+The development server requires basic HTTP user authentication for all requests. Like
+the production server, it requires HTTP headers with authentication for Portal API
+requests. The credentials are in constants in this Java class in the src/test/java
+folder:
+
+ org.oransc.ric.portal.dashboard.config.WebSecurityMockConfiguration
+
+Like the production server, the development server also performs role-based
+authentication on requests. The user name-role name associations are defined
+in the class shown above.
## Swagger API documentation
-View the server's API documentation at URL `http://localhost:8080/swagger-ui.html`.
+Both a regular and a development server publish API documentation at URL `http://localhost:8080/swagger-ui.html`.
## License
diff --git a/webapp-backend/config/.gitignore b/webapp-backend/config/.gitignore
new file mode 100644
index 00000000..edd66f16
--- /dev/null
+++ b/webapp-backend/config/.gitignore
@@ -0,0 +1,2 @@
+/key.properties
+/portal.properties
diff --git a/webapp-backend/config/key.properties.template b/webapp-backend/config/key.properties.template
new file mode 100644
index 00000000..6ba89b1e
--- /dev/null
+++ b/webapp-backend/config/key.properties.template
@@ -0,0 +1,21 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property and Nokia
+# %%
+# Licensed 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.
+# ========================LICENSE_END===================================
+
+# Template for the file that provides a secret key for the RIC Dashboard.
+
+cipher.enc.key =
diff --git a/webapp-backend/config/portal.properties.template b/webapp-backend/config/portal.properties.template
new file mode 100644
index 00000000..601793c2
--- /dev/null
+++ b/webapp-backend/config/portal.properties.template
@@ -0,0 +1,34 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property and Nokia
+# %%
+# Licensed 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.
+# ========================LICENSE_END===================================
+
+# Template for the file that provides properties for the EPSDK-FW library.
+# This file must be present on the Java classpath.
+
+# The following properties are the same in every deployment
+
+portal.api.impl.class = org.oransc.ric.portal.dashboard.portalapi.PortalRestCentralServiceImpl
+role_access_centralized = remote
+
+# The following properties are DIFFERENT in every deployment
+
+# URL of portal login screen
+ecomp_redirect_url = http://localhost/portal
+# URL of portal API
+ecomp_rest_url = http://localhost/portal
+# Value assigned by portal instance
+ueb_app_key = abcdef1234567890
diff --git a/webapp-backend/pom.xml b/webapp-backend/pom.xml
index e96777fe..40db7710 100644
--- a/webapp-backend/pom.xml
+++ b/webapp-backend/pom.xml
@@ -34,6 +34,13 @@ limitations under the License.
0
+
+
+ onap-releases
+ ONAP - Release Repository
+ https://nexus.onap.org/content/repositories/releases
+
+
@@ -57,6 +64,55 @@ limitations under the License.
e2-mgr-client
20190703-SNAPSHOT
+
+ org.onap.portal.sdk
+ epsdk-fw
+ 2.4.0
+
+
+ commons-logging
+ commons-logging
+
+
+ log4j
+ log4j
+
+
+ log4j
+ apache-log4j-extras
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ junit
+ junit
+
+
+ commons-fileupload
+ commons-fileupload
+
+
+ commons-beanutils
+ commons-beanutils
+
+
+
+ org.powermock
+ powermock-module-junit4
+
+
+
+ org.powermock
+ powermock-api-mockito
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
org.springframework.boot
spring-boot-starter-web
@@ -65,6 +121,11 @@ limitations under the License.
org.slf4j
slf4j-api
+
+
+ org.slf4j
+ jcl-over-slf4j
+
ch.qos.logback
logback-classic
@@ -230,12 +291,21 @@ limitations under the License.
- mkdir /maven/logs
- chmod -R 777 /maven
+ mkdir /logs
+ chmod -R 777 /logs
-
-
+
+
+ java
+ -cp
+ maven:maven/${project.artifactId}-${project.version}.${project.packaging}
+ -Dloader.main=org.oransc.ric.portal.dashboard.DashboardApplication
+ -Xms128m
+ -Xmx256m
+ -Djava.security.egd=file:/dev/./urandom
+ org.springframework.boot.loader.PropertiesLauncher
+
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardApplication.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardApplication.java
index 05778dc5..4819e345 100644
--- a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardApplication.java
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardApplication.java
@@ -19,6 +19,8 @@
*/
package org.oransc.ric.portal.dashboard;
+import java.io.IOException;
+import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import org.slf4j.Logger;
@@ -34,8 +36,19 @@ public class DashboardApplication {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
- public static void main(String[] args) {
+ // Unfortunately these names are not available as constants
+ private static final String[] propertyFiles = { "ESAPI.properties", "key.properties", "portal.properties",
+ "validation.properties" };
+
+ public static void main(String[] args) throws IOException {
SpringApplication.run(DashboardApplication.class, args);
+ for (String pf : propertyFiles) {
+ InputStream in = MethodHandles.lookup().lookupClass().getClassLoader().getResourceAsStream(pf);
+ if (in == null)
+ logger.warn("Failed to find property file on classpath: {}", pf);
+ else
+ in.close();
+ }
// Force this onto the console by using level WARN
logger.warn("main: version '{}' successful start",
getImplementationVersion(MethodHandles.lookup().lookupClass()));
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardConstants.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardConstants.java
index 9b80864f..bb093cda 100644
--- a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardConstants.java
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardConstants.java
@@ -27,7 +27,18 @@ public abstract class DashboardConstants {
public static final String ENDPOINT_PREFIX = "/api";
+ public static final String LOGIN_PAGE = "/login.html";
+
// Factor out method names used in multiple controllers
public static final String VERSION_METHOD = "version";
+ // The role names are defined by ONAP Portal.
+ // The prefix "ROLE_" is required by Spring.
+ // These are used in Java code annotations that require constants.
+ public static final String ROLE_NAME_STANDARD = "Standard_User";
+ public static final String ROLE_NAME_ADMIN = "System_Administrator";
+ private static final String ROLE_PREFIX = "ROLE_";
+ public static final String ROLE_ADMIN = ROLE_PREFIX + ROLE_NAME_ADMIN;
+ public static final String ROLE_STANDARD = ROLE_PREFIX + ROLE_NAME_STANDARD;
+
}
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/LoginServlet.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/LoginServlet.java
new file mode 100644
index 00000000..fe58e933
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/LoginServlet.java
@@ -0,0 +1,116 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property and Nokia
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.net.URLEncoder;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.onap.portalsdk.core.onboarding.util.PortalApiProperties;
+import org.oransc.ric.portal.dashboard.portalapi.PortalAuthenticationFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+
+/**
+ * Serves a login page that contains a link from configuration to ONAP Portal.
+ * This avoids the immediate redirect to Portal that is confusing to users and
+ * infuriating to developers.
+ *
+ * Basically this is do-it-yourself JSP :)
+ */
+public class LoginServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1191385178190976568L;
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ @Override
+ public void init(ServletConfig servletConfig) throws ServletException {
+ logger.debug("init");
+ super.init(servletConfig);
+ final String portalURL = PortalApiProperties.getProperty(PortalApiConstants.ECOMP_REDIRECT_URL);
+ if (portalURL == null || portalURL.length() == 0)
+ throw new ServletException("Failed to get property " + PortalApiConstants.ECOMP_REDIRECT_URL);
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException {
+ logger.debug("doGet {}", request.getRequestURI());
+ // The original page URL should arrive as a query parameter
+ String appUrl = request.getParameter(PortalAuthenticationFilter.REDIRECT_URL_KEY);
+ // If a user bookmarks the login page, then nothing arrives;
+ // use the original URL without the login suffix.
+ if (appUrl == null || appUrl.isEmpty()) {
+ String loginUrl = request.getRequestURL().toString();
+ int indexOfLogin = loginUrl.indexOf(DashboardConstants.LOGIN_PAGE);
+ appUrl = loginUrl.substring(0, indexOfLogin);
+ }
+ String encodedAppUrl = URLEncoder.encode(appUrl, "UTF-8");
+ String portalBaseUrl = PortalApiProperties.getProperty(PortalApiConstants.ECOMP_REDIRECT_URL);
+ String redirectUrl = portalBaseUrl + "?" + PortalAuthenticationFilter.REDIRECT_URL_KEY + "=" + encodedAppUrl;
+ String aHref = "";
+ // If only Java had "here" documents.
+ String body = String.join(//
+ System.getProperty("line.separator"), //
+ "", //
+ "", //
+ "RIC Dashboard ", //
+ "", //
+ "", //
+ "", //
+ "RIC Dashboard ", //
+ "Please log in. ", //
+ "", //
+ aHref, "Click here to authenticate at the ONAP Portal
", //
+ "
", //
+ "", //
+ "");
+ writeAndFlush(response, MediaType.TEXT_HTML_VALUE, body);
+ }
+
+ /**
+ * Sets the content type and writes the response.
+ *
+ * @param response
+ * @param contentType
+ * @param responseBody
+ * @throws IOException
+ */
+ private void writeAndFlush(HttpServletResponse response, String contentType, String responseBody)
+ throws IOException {
+ response.setContentType(contentType);
+ response.getWriter().print(responseBody);
+ response.getWriter().flush();
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/SpringContextCache.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/SpringContextCache.java
new file mode 100644
index 00000000..2f1d1f69
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/SpringContextCache.java
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property and Nokia
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.config;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * Allows non-Spring-managed classes to obtain the Spring application context.
+ */
+@Component
+public class SpringContextCache implements ApplicationContextAware {
+
+ private static ApplicationContext applicationContext = null;
+
+ @Override
+ public void setApplicationContext(final ApplicationContext appContext) throws BeansException {
+ applicationContext = appContext;
+ }
+
+ public static ApplicationContext getApplicationContext() {
+ return applicationContext;
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/WebSecurityConfiguration.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/WebSecurityConfiguration.java
new file mode 100644
index 00000000..9357a1cf
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/WebSecurityConfiguration.java
@@ -0,0 +1,172 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property and Nokia
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.config;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+
+import org.onap.portalsdk.core.onboarding.crossapi.PortalRestAPIProxy;
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.oransc.ric.portal.dashboard.DashboardConstants;
+import org.oransc.ric.portal.dashboard.LoginServlet;
+import org.oransc.ric.portal.dashboard.controller.AcXappController;
+import org.oransc.ric.portal.dashboard.controller.AdminController;
+import org.oransc.ric.portal.dashboard.controller.AnrXappController;
+import org.oransc.ric.portal.dashboard.controller.AppManagerController;
+import org.oransc.ric.portal.dashboard.controller.E2ManagerController;
+import org.oransc.ric.portal.dashboard.controller.SimpleErrorController;
+import org.oransc.ric.portal.dashboard.portalapi.DashboardUserManager;
+import org.oransc.ric.portal.dashboard.portalapi.PortalAuthManager;
+import org.oransc.ric.portal.dashboard.portalapi.PortalAuthenticationFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.web.servlet.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(securedEnabled = true)
+@Profile("!test")
+public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ // Although constructor arguments are recommended over field injection,
+ // this results in fewer lines of code.
+ @Value("${userfile}")
+ private String userFilePath;
+ @Value("${portalapi.appname}")
+ private String appName;
+ @Value("${portalapi.username}")
+ private String userName;
+ @Value("${portalapi.password}")
+ private String password;
+ @Value("${portalapi.decryptor}")
+ private String decryptor;
+ @Value("${portalapi.usercookie}")
+ private String userCookie;
+
+ protected void configure(HttpSecurity http) throws Exception {
+ logger.debug("configure");
+ // A chain of ".and()" always baffles me
+ http.authorizeRequests().anyRequest().authenticated();
+ // http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
+ http.addFilterBefore(portalAuthenticationFilterBean(), BasicAuthenticationFilter.class);
+ }
+
+ /**
+ * Resource paths that do not require authentication, especially including
+ * Swagger-generated documentation.
+ */
+ public static final String[] OPEN_PATHS = { //
+ "/v2/api-docs", //
+ "/swagger-resources/**", //
+ "/swagger-ui.html", //
+ "/webjars/**", //
+ PortalApiConstants.API_PREFIX + "/**", //
+ AcXappController.CONTROLLER_PATH + "/" + AcXappController.VERSION_METHOD, //
+ AdminController.CONTROLLER_PATH + "/" + AdminController.HEALTH_METHOD, //
+ AdminController.CONTROLLER_PATH + "/" + AdminController.VERSION_METHOD, //
+ AnrXappController.CONTROLLER_PATH + "/" + AnrXappController.HEALTH_ALIVE_METHOD, //
+ AnrXappController.CONTROLLER_PATH + "/" + AnrXappController.HEALTH_READY_METHOD, //
+ AnrXappController.CONTROLLER_PATH + "/" + AnrXappController.VERSION_METHOD, //
+ AppManagerController.CONTROLLER_PATH + "/" + AppManagerController.HEALTH_ALIVE_METHOD, //
+ AppManagerController.CONTROLLER_PATH + "/" + AppManagerController.HEALTH_READY_METHOD, //
+ AppManagerController.CONTROLLER_PATH + "/" + AppManagerController.VERSION_METHOD, //
+ E2ManagerController.CONTROLLER_PATH + "/" + E2ManagerController.HEALTH_METHOD, //
+ E2ManagerController.CONTROLLER_PATH + "/" + E2ManagerController.VERSION_METHOD, //
+ DashboardConstants.LOGIN_PAGE, //
+ SimpleErrorController.ERROR_PATH };
+
+ @Override
+ public void configure(WebSecurity web) throws Exception {
+ // This disables Spring security, but not the app's filter.
+ web.ignoring().antMatchers(OPEN_PATHS);
+ }
+
+ @Bean
+ public PortalAuthManager portalAuthManagerBean()
+ throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
+ return new PortalAuthManager(appName, userName, password, decryptor, userCookie);
+ }
+
+ @Bean
+ public DashboardUserManager dashboardUserManagerBean() throws IOException {
+ return new DashboardUserManager(userFilePath);
+ }
+
+ /*
+ * If this is annotated with @Bean, it is created automatically AND REGISTERED,
+ * and Spring processes annotations in the source of the class. However, the
+ * filter is added in the chain apparently in the wrong order. Alternately, with
+ * no @Bean and added to the chain up in the configure() method in the desired
+ * order, the ignoring() matcher pattern configured above causes Spring to
+ * bypass this filter, which seems to me means the filter participates
+ * correctly.
+ */
+ public PortalAuthenticationFilter portalAuthenticationFilterBean()
+ throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
+ PortalAuthenticationFilter portalAuthenticationFilter = new PortalAuthenticationFilter(portalAuthManagerBean(),
+ dashboardUserManagerBean());
+ return portalAuthenticationFilter;
+ }
+
+ /**
+ * Instantiates the EPSDK-FW servlet. Needed because this app is not configured
+ * to scan the EPSDK-FW packages; there's also a chance that Spring-Boot does
+ * not automatically process @WebServlet annotations.
+ *
+ * @return Servlet registration bean for the Portal Rest API proxy servlet.
+ */
+ @Bean
+ public ServletRegistrationBean portalApiProxyServletBean() {
+ PortalRestAPIProxy servlet = new PortalRestAPIProxy();
+ final ServletRegistrationBean servletBean = new ServletRegistrationBean<>(servlet,
+ PortalApiConstants.API_PREFIX + "/*");
+ servletBean.setName("PortalRestApiProxyServlet");
+ return servletBean;
+ }
+
+ /**
+ * Instantiates a trivial login servlet that serves a basic page with a link to
+ * authenticate at Portal. The login filter redirects to this page instead of
+ * Portal.
+ *
+ * @return Servlet registration bean for the Dashboard login servlet.
+ */
+ @Bean
+ public ServletRegistrationBean loginServletBean() {
+ LoginServlet servlet = new LoginServlet();
+ final ServletRegistrationBean servletBean = new ServletRegistrationBean<>(servlet,
+ DashboardConstants.LOGIN_PAGE);
+ servletBean.setName("LoginServlet");
+ return servletBean;
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/AcXappController.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/AcXappController.java
index cdb99b0b..655b47aa 100644
--- a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/AcXappController.java
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/AcXappController.java
@@ -31,6 +31,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
+import org.springframework.security.access.annotation.Secured;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
@@ -77,6 +78,7 @@ public class AcXappController {
@ApiOperation(value = "Gets the A1 client library MANIFEST.MF property Implementation-Version.", response = SuccessTransport.class)
@GetMapping(VERSION_METHOD)
+ // No role required
public SuccessTransport getA1MediatorClientVersion() {
return new SuccessTransport(200, DashboardApplication.getImplementationVersion(A1MediatorApi.class));
}
@@ -86,6 +88,7 @@ public class AcXappController {
*/
@ApiOperation(value = "Gets the admission control policy for AC xApp via the A1 Mediator")
@GetMapping(ADMCTRL_METHOD)
+ @Secured({ DashboardConstants.ROLE_ADMIN, DashboardConstants.ROLE_STANDARD })
public Object getAdmissionControlPolicy(HttpServletResponse response) {
logger.debug("getAdmissionControlPolicy");
response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED);
@@ -98,6 +101,7 @@ public class AcXappController {
*/
@ApiOperation(value = "Sets the admission control policy for AC xApp via the A1 Mediator")
@PutMapping(ADMCTRL_METHOD)
+ @Secured({ DashboardConstants.ROLE_ADMIN })
public void setAdmissionControlPolicy(@ApiParam(value = "Admission control policy") @RequestBody JsonNode acPolicy, //
HttpServletResponse response) {
logger.debug("setAdmissionControlPolicy {}", acPolicy);
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/AdminController.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/AdminController.java
index 86a77009..6f282543 100644
--- a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/AdminController.java
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/AdminController.java
@@ -28,6 +28,7 @@ import org.oransc.ric.portal.dashboard.model.SuccessTransport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
+import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -66,6 +67,7 @@ public class AdminController {
@ApiOperation(value = "Gets the Dashboard MANIFEST.MF property Implementation-Version.", response = SuccessTransport.class)
@GetMapping(VERSION_METHOD)
+ // No role required
public SuccessTransport getVersion() {
logger.debug("getVersion");
return new SuccessTransport(200,
@@ -74,6 +76,7 @@ public class AdminController {
@ApiOperation(value = "Checks the health of the application.", response = SuccessTransport.class)
@GetMapping(HEALTH_METHOD)
+ // No role required
public SuccessTransport getHealth() {
logger.debug("getHealth");
return new SuccessTransport(200, "Dashboard is healthy!");
@@ -81,6 +84,7 @@ public class AdminController {
@ApiOperation(value = "Gets the list of application users.", response = DashboardUser.class, responseContainer = "List")
@GetMapping(USER_METHOD)
+ @Secured({ DashboardConstants.ROLE_ADMIN })
public DashboardUser[] getUsers() {
logger.debug("getUsers");
return users;
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/AnrXappController.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/AnrXappController.java
index c6a6b90f..55b42124 100644
--- a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/AnrXappController.java
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/AnrXappController.java
@@ -36,6 +36,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
+import org.springframework.security.access.annotation.Secured;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@@ -93,12 +94,14 @@ public class AnrXappController {
@ApiOperation(value = "Gets the ANR client library MANIFEST.MF property Implementation-Version.", response = SuccessTransport.class)
@GetMapping(VERSION_METHOD)
+ // No role required
public SuccessTransport getClientVersion() {
return new SuccessTransport(200, DashboardApplication.getImplementationVersion(HealthApi.class));
}
@ApiOperation(value = "Performs a liveness probe on the ANR xApp, result expressed as the response code.")
@GetMapping(HEALTH_ALIVE_METHOD)
+ // No role required
public void getHealthAlive(HttpServletResponse response) {
logger.debug("getHealthAlive");
healthApi.getHealthAlive();
@@ -107,6 +110,7 @@ public class AnrXappController {
@ApiOperation(value = "Performs a readiness probe on the ANR xApp, result expressed as the response code.")
@GetMapping(HEALTH_READY_METHOD)
+ // No role required
public void getHealthReady(HttpServletResponse response) {
logger.debug("getHealthReady");
healthApi.getHealthReady();
@@ -115,6 +119,7 @@ public class AnrXappController {
@ApiOperation(value = "Returns list of gNodeB IDs based on NCRT in ANR", response = GgNodeBTable.class)
@GetMapping(GNODEBS_METHOD)
+ @Secured({ DashboardConstants.ROLE_ADMIN, DashboardConstants.ROLE_STANDARD })
public GgNodeBTable getGnodebs() {
logger.debug("getGnodebs");
return ncrtApi.getgNodeB();
@@ -122,6 +127,7 @@ public class AnrXappController {
@ApiOperation(value = "Returns neighbor cell relation table for all gNodeBs or based on query parameters", response = NeighborCellRelationTable.class)
@GetMapping(NCRT_METHOD)
+ @Secured({ DashboardConstants.ROLE_ADMIN, DashboardConstants.ROLE_STANDARD })
public NeighborCellRelationTable getNcrt( //
@RequestParam(name = QP_NODEB, required = false) String ggnbId, //
@RequestParam(name = QP_SERVING, required = false) String servingCellNrcgi, //
@@ -134,6 +140,7 @@ public class AnrXappController {
// /ncrt/servingcells/{servCellNrcgi}/neighborcells/{neighCellNrpci} :
@ApiOperation(value = "Modify neighbor cell relation based on Serving Cell NRCGI and Neighbor Cell NRPCI")
@PutMapping(NCRT_METHOD + "/" + PP_SERVING + "/{" + PP_SERVING + "}/" + PP_NEIGHBOR + "/{" + PP_NEIGHBOR + "}")
+ @Secured({ DashboardConstants.ROLE_ADMIN })
public void modifyNcrt(@PathVariable(PP_SERVING) String servingCellNrcgi, //
@PathVariable(PP_NEIGHBOR) String neighborCellNrpci, //
@RequestBody NeighborCellRelationMod ncrMod, HttpServletResponse response) {
@@ -145,6 +152,7 @@ public class AnrXappController {
@ApiOperation(value = "Delete neighbor cell relation based on Serving Cell NRCGI and Neighbor Cell NRPCI")
@DeleteMapping(NCRT_METHOD + "/" + PP_SERVING + "/{" + PP_SERVING + "}/" + PP_NEIGHBOR + "/{" + PP_NEIGHBOR + "}")
+ @Secured({ DashboardConstants.ROLE_ADMIN })
public void deleteNcrt(@PathVariable(PP_SERVING) String servingCellNrcgi, //
@PathVariable(PP_NEIGHBOR) String neighborCellNrpci, //
HttpServletResponse response) {
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/AppManagerController.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/AppManagerController.java
index 40e03dbf..f71c6bd9 100644
--- a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/AppManagerController.java
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/AppManagerController.java
@@ -42,6 +42,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
+import org.springframework.security.access.annotation.Secured;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@@ -95,12 +96,14 @@ public class AppManagerController {
@ApiOperation(value = "Gets the XApp manager client library MANIFEST.MF property Implementation-Version.", response = SuccessTransport.class)
@GetMapping(VERSION_METHOD)
+ // No role required
public SuccessTransport getClientVersion() {
return new SuccessTransport(200, DashboardApplication.getImplementationVersion(HealthApi.class));
}
@ApiOperation(value = "Health check of xApp Manager - Liveness probe.")
@GetMapping(HEALTH_ALIVE_METHOD)
+ // No role required
public void getHealth(HttpServletResponse response) {
logger.debug("getHealthAlive");
healthApi.getHealthAlive();
@@ -109,6 +112,7 @@ public class AppManagerController {
@ApiOperation(value = "Readiness check of xApp Manager - Readiness probe.")
@GetMapping(HEALTH_READY_METHOD)
+ // No role required
public void getHealthReady(HttpServletResponse response) {
logger.debug("getHealthReady");
healthApi.getHealthReady();
@@ -117,6 +121,7 @@ public class AppManagerController {
@ApiOperation(value = "Returns the configuration of all xapps.", response = AllXappConfig.class)
@GetMapping(CONFIG_METHOD)
+ @Secured({ DashboardConstants.ROLE_ADMIN, DashboardConstants.ROLE_STANDARD })
public AllXappConfig getAllXappConfig() {
logger.debug("getAllXappConfig");
return xappApi.getAllXappConfig();
@@ -124,6 +129,7 @@ public class AppManagerController {
@ApiOperation(value = "Create xApp config.", response = XAppConfig.class)
@PostMapping(CONFIG_METHOD)
+ @Secured({ DashboardConstants.ROLE_ADMIN })
public XAppConfig createXappConfig(@RequestBody XAppConfig xAppConfig) {
logger.debug("createXappConfig {}", xAppConfig);
return xappApi.createXappConfig(xAppConfig);
@@ -131,6 +137,7 @@ public class AppManagerController {
@ApiOperation(value = "Modify xApp config.", response = XAppConfig.class)
@PutMapping(CONFIG_METHOD)
+ @Secured({ DashboardConstants.ROLE_ADMIN })
public XAppConfig modifyXappConfig(@RequestBody XAppConfig xAppConfig) {
logger.debug("modifyXappConfig {}", xAppConfig);
return xappApi.modifyXappConfig(xAppConfig);
@@ -138,6 +145,7 @@ public class AppManagerController {
@ApiOperation(value = "Delete xApp configuration.")
@DeleteMapping(CONFIG_METHOD + "/{" + PP_XAPP_NAME + "}")
+ @Secured({ DashboardConstants.ROLE_ADMIN })
public void deleteXappConfig(@RequestBody ConfigMetadata configMetadata, HttpServletResponse response) {
logger.debug("deleteXappConfig {}", configMetadata);
xappApi.deleteXappConfig(configMetadata);
@@ -146,6 +154,7 @@ public class AppManagerController {
@ApiOperation(value = "Returns a list of deployable xapps.", response = DashboardDeployableXapps.class)
@GetMapping(XAPPS_LIST_METHOD)
+ @Secured({ DashboardConstants.ROLE_ADMIN, DashboardConstants.ROLE_STANDARD })
public Object getAvailableXapps() {
logger.debug("getAvailableXapps");
AllDeployableXapps appNames = xappApi.listAllXapps();
@@ -160,6 +169,7 @@ public class AppManagerController {
@ApiOperation(value = "Returns the status of all deployed xapps.", response = AllDeployedXapps.class)
@GetMapping(XAPPS_METHOD)
+ @Secured({ DashboardConstants.ROLE_ADMIN, DashboardConstants.ROLE_STANDARD })
public AllDeployedXapps getDeployedXapps() {
logger.debug("getDeployedXapps");
return xappApi.getAllXapps();
@@ -167,6 +177,7 @@ public class AppManagerController {
@ApiOperation(value = "Returns the status of a given xapp.", response = Xapp.class)
@GetMapping(XAPPS_METHOD + "/{" + PP_XAPP_NAME + "}")
+ @Secured({ DashboardConstants.ROLE_ADMIN, DashboardConstants.ROLE_STANDARD })
public Xapp getXapp(@PathVariable("xAppName") String xAppName) {
logger.debug("getXapp {}", xAppName);
return xappApi.getXappByName(xAppName);
@@ -174,6 +185,7 @@ public class AppManagerController {
@ApiOperation(value = "Deploy a xapp.", response = Xapp.class)
@PostMapping(XAPPS_METHOD)
+ @Secured({ DashboardConstants.ROLE_ADMIN })
public Xapp deployXapp(@RequestBody XAppInfo xAppInfo) {
logger.debug("deployXapp {}", xAppInfo);
return xappApi.deployXapp(xAppInfo);
@@ -181,6 +193,7 @@ public class AppManagerController {
@ApiOperation(value = "Undeploy an existing xapp.")
@DeleteMapping(XAPPS_METHOD + "/{" + PP_XAPP_NAME + "}")
+ @Secured({ DashboardConstants.ROLE_ADMIN })
public void undeployXapp(@PathVariable("xAppName") String xAppName, HttpServletResponse response) {
logger.debug("undeployXapp {}", xAppName);
xappApi.undeployXapp(xAppName);
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/CustomResponseEntityExceptionHandler.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/CustomResponseEntityExceptionHandler.java
index 062d04fe..b1ac2e8f 100644
--- a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/CustomResponseEntityExceptionHandler.java
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/CustomResponseEntityExceptionHandler.java
@@ -24,8 +24,10 @@ import java.lang.invoke.MethodHandles;
import org.oransc.ric.portal.dashboard.model.ErrorTransport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.client.HttpStatusCodeException;
@@ -55,25 +57,45 @@ public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptio
// Superclass has "logger" that is exposed here, so use a different name
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
- /**
+ /*
* Generates the response when a REST controller method takes an
- * HttpStatusCodeException. Confusingly, the container first redirects to /error
- * which invokes the
- * {@link org.oransc.ric.portal.dashboard.controller.SimpleErrorController}
- * method, and that response arrives here as the response body.
+ * HttpStatusCodeException.
+ *
+ * It appears that the container internally redirects to /error because the web
+ * request that arrives here has URI /error, and {@link
+ * org.oransc.ric.portal.dashboard.controller.SimpleErrorController} runs before
+ * this.
+ *
+ * @param ex The exception
+ *
+ * @param request The original request
*
- * @param ex
- * The exception
- * @param request
- * The orignal request
* @return A response entity with status code 502 plus some details in the body.
*/
@ExceptionHandler(HttpStatusCodeException.class)
public final ResponseEntity handleHttpStatusCodeException(HttpStatusCodeException ex,
WebRequest request) {
- log.warn("Request {} failed, status code {}", request.getDescription(false), ex.getStatusCode());
+ log.warn("handleHttpStatusCodeException: request {}, status code {}", request.getDescription(false),
+ ex.getStatusCode());
return new ResponseEntity<>(new ErrorTransport(ex.getRawStatusCode(), ex.getResponseBodyAsString(), ex),
HttpStatus.BAD_GATEWAY);
}
+ /*
+ * This exception also happens when Spring security denies access to a method
+ * due to missing/wrong roles (granted authorities). Override the method to
+ * answer permission denied, even though that may obscure a genuine developer
+ * error.
+ *
+ * The web request that arrives here has URI /error; how to obtain the URI of
+ * the original request?!?
+ */
+ @Override
+ public final ResponseEntity handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
+ HttpHeaders headers, HttpStatus status, WebRequest request) {
+ log.warn("handleHttpRequestMethodNotSupported: answering 'permission denied' for method {}", ex.getMethod());
+ return new ResponseEntity(new ErrorTransport(HttpStatus.UNAUTHORIZED.value(),
+ "Permission denied for method " + ex.getMethod(), ex), HttpStatus.UNAUTHORIZED);
+ }
+
}
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/E2ManagerController.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/E2ManagerController.java
index e2523982..b200c9a8 100644
--- a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/E2ManagerController.java
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/E2ManagerController.java
@@ -39,6 +39,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
+import org.springframework.security.access.annotation.Secured;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@@ -91,12 +92,14 @@ public class E2ManagerController {
@ApiOperation(value = "Gets the E2 manager client library MANIFEST.MF property Implementation-Version.", response = SuccessTransport.class)
@GetMapping(VERSION_METHOD)
+ // No role required
public SuccessTransport getClientVersion() {
return new SuccessTransport(200, DashboardApplication.getImplementationVersion(HealthCheckApi.class));
}
@ApiOperation(value = "Gets the health from the E2 manager, expressed as the response code.")
@GetMapping(HEALTH_METHOD)
+ // No role required
public void healthGet(HttpServletResponse response) {
logger.debug("healthGet");
e2HealthCheckApi.healthGet();
@@ -106,6 +109,7 @@ public class E2ManagerController {
// This calls other methods to simplify the task of the front-end.
@ApiOperation(value = "Gets all RAN identities and statuses from the E2 manager.", response = RanDetailsTransport.class, responseContainer = "List")
@GetMapping(RAN_METHOD)
+ @Secured({ DashboardConstants.ROLE_ADMIN, DashboardConstants.ROLE_STANDARD })
public List getRanDetails() {
logger.debug("getRanDetails");
List nodebIdList = e2NodebApi.getNodebIdList();
@@ -127,6 +131,7 @@ public class E2ManagerController {
@ApiOperation(value = "Get RAN identities list.", response = NodebIdentity.class, responseContainer = "List")
@GetMapping(NODEB_LIST_METHOD)
+ @Secured({ DashboardConstants.ROLE_ADMIN, DashboardConstants.ROLE_STANDARD })
public List getNodebIdList() {
logger.debug("getNodebIdList");
return e2NodebApi.getNodebIdList();
@@ -134,6 +139,7 @@ public class E2ManagerController {
@ApiOperation(value = "Get RAN by name.", response = GetNodebResponse.class)
@GetMapping(NODEB_METHOD + "/{" + PP_RANNAME + "}")
+ @Secured({ DashboardConstants.ROLE_ADMIN, DashboardConstants.ROLE_STANDARD })
public GetNodebResponse getNb(@PathVariable(PP_RANNAME) String ranName) {
logger.debug("getNb {}", ranName);
return e2NodebApi.getNb(ranName);
@@ -141,6 +147,7 @@ public class E2ManagerController {
@ApiOperation(value = "Close all connections to the RANs and delete the data from the nodeb-rnib DB.")
@DeleteMapping(NODEB_METHOD)
+ @Secured({ DashboardConstants.ROLE_ADMIN })
public void nodebDelete(HttpServletResponse response) {
logger.debug("nodebDelete");
e2NodebApi.nodebDelete();
@@ -149,6 +156,7 @@ public class E2ManagerController {
@ApiOperation(value = "Sets up an EN-DC RAN connection via the E2 manager.")
@PostMapping(ENDC_SETUP_METHOD)
+ @Secured({ DashboardConstants.ROLE_ADMIN })
public void endcSetup(@RequestBody SetupRequest setupRequest, HttpServletResponse response) {
logger.debug("endcSetup {}", setupRequest);
e2NodebApi.endcSetup(setupRequest);
@@ -157,6 +165,7 @@ public class E2ManagerController {
@ApiOperation(value = "Sets up an X2 RAN connection via the E2 manager.")
@PostMapping(X2_SETUP_METHOD)
+ @Secured({ DashboardConstants.ROLE_ADMIN })
public void x2Setup(@RequestBody SetupRequest setupRequest, HttpServletResponse response) {
logger.debug("x2Setup {}", setupRequest);
e2NodebApi.x2Setup(setupRequest);
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/EcompUserDetails.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/EcompUserDetails.java
new file mode 100644
index 00000000..80919838
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/EcompUserDetails.java
@@ -0,0 +1,85 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property and Nokia
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.model;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.onap.portalsdk.core.restful.domain.EcompRole;
+import org.onap.portalsdk.core.restful.domain.EcompUser;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+public class EcompUserDetails implements UserDetails {
+
+ private static final long serialVersionUID = 1L;
+ private final EcompUser ecompUser;
+
+ // This is the default Spring role-name prefix.
+ private static final String ROLEP = "ROLE_";
+
+ public EcompUserDetails(EcompUser ecompUser) {
+ this.ecompUser = ecompUser;
+ }
+
+ /*
+ * Gets a list of authorities (roles) for this user. To keep Spring happy, every
+ * item has prefix ROLE_.
+ */
+ public Collection extends GrantedAuthority> getAuthorities() {
+ List roleList = new ArrayList<>();
+ Iterator roleIter = ecompUser.getRoles().iterator();
+ while (roleIter.hasNext()) {
+ EcompRole role = roleIter.next();
+ // Add the prefix if the ONAP portal doesn't supply it.
+ final String roleName = role.getName().startsWith(ROLEP) ? role.getName() : ROLEP + role.getName();
+ roleList.add(new SimpleGrantedAuthority(roleName));
+ }
+ return roleList;
+ }
+
+ public String getPassword() {
+ return null;
+ }
+
+ public String getUsername() {
+ return ecompUser.getLoginId();
+ }
+
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ public boolean isEnabled() {
+ return ecompUser.isActive();
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/ErrorTransport.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/ErrorTransport.java
index 035b9efb..2d3a5c23 100644
--- a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/ErrorTransport.java
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/ErrorTransport.java
@@ -26,7 +26,7 @@ package org.oransc.ric.portal.dashboard.model;
public class ErrorTransport implements IDashboardResponse {
private Integer status;
- private String error;
+ private String message;
private String exception;
/**
@@ -62,7 +62,7 @@ public class ErrorTransport implements IDashboardResponse {
*/
public ErrorTransport(int statusCode, String errMsg, Exception exception) {
this.status = statusCode;
- this.error = errMsg;
+ this.message = errMsg;
if (exception != null) {
final int enough = 512;
String exString = exception.toString();
@@ -79,12 +79,12 @@ public class ErrorTransport implements IDashboardResponse {
this.status = status;
}
- public String getError() {
- return error;
+ public String getMessage() {
+ return message;
}
- public void setError(String error) {
- this.error = error;
+ public void setMessage(String error) {
+ this.message = error;
}
public String getException() {
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/DashboardUserManager.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/DashboardUserManager.java
new file mode 100644
index 00000000..b02d0261
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/DashboardUserManager.java
@@ -0,0 +1,122 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property and Nokia
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.portalapi;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.onap.portalsdk.core.onboarding.exception.PortalAPIException;
+import org.onap.portalsdk.core.restful.domain.EcompUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Provides user-management services.
+ *
+ * This first implementation serializes user details to a file. TODO: migrate to
+ * a database.
+ */
+public class DashboardUserManager {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private final File userFile;
+ private final List users;
+
+ public DashboardUserManager(final String userFilePath) throws IOException {
+ logger.debug("ctor: userfile {}", userFilePath);
+ if (userFilePath == null)
+ throw new IllegalArgumentException("Missing or empty user file property");
+ userFile = new File(userFilePath);
+ logger.debug("ctor: managing users in file {}", userFile.getAbsolutePath());
+ if (userFile.exists()) {
+ final ObjectMapper mapper = new ObjectMapper();
+ users = mapper.readValue(userFile, new TypeReference>() {
+ });
+ } else {
+ users = new ArrayList<>();
+ }
+ }
+
+ /**
+ * Gets the user with the specified login Id
+ *
+ * @param loginId
+ * Desired login Id
+ * @return User object; null if Id is not known
+ */
+ public EcompUser getUser(String loginId) {
+ for (EcompUser u : this.users) {
+ if (u.getLoginId().equals(loginId)) {
+ logger.debug("getUser: match on {}", loginId);
+ return u;
+ }
+ }
+ logger.debug("getUser: no match on {}", loginId);
+ return null;
+ }
+
+ private void saveUsers() throws JsonGenerationException, JsonMappingException, IOException {
+ final ObjectMapper mapper = new ObjectMapper();
+ mapper.writeValue(userFile, users);
+ }
+
+ /*
+ * Allow at most one thread to create a user at one time.
+ */
+ public synchronized void createUser(EcompUser user) throws PortalAPIException {
+ logger.debug("createUser: loginId is " + user.getLoginId());
+ if (users.contains(user))
+ throw new PortalAPIException("User exists: " + user.getLoginId());
+ users.add(user);
+ try {
+ saveUsers();
+ } catch (Exception ex) {
+ throw new PortalAPIException("Save failed", ex);
+ }
+ }
+
+ /*
+ * Allow at most one thread to modify a user at one time. We still have
+ * last-edit-wins of course.
+ */
+ public synchronized void updateUser(String loginId, EcompUser user) throws PortalAPIException {
+ logger.debug("editUser: loginId is " + loginId);
+ int index = users.indexOf(user);
+ if (index < 0)
+ throw new PortalAPIException("User does not exist: " + user.getLoginId());
+ users.remove(index);
+ users.add(user);
+ try {
+ saveUsers();
+ } catch (Exception ex) {
+ throw new PortalAPIException("Save failed", ex);
+ }
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/IPortalSdkDecryptor.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/IPortalSdkDecryptor.java
new file mode 100644
index 00000000..9862e163
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/IPortalSdkDecryptor.java
@@ -0,0 +1,41 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property and Nokia
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.portalapi;
+
+import org.onap.portalsdk.core.onboarding.exception.CipherUtilException;
+
+/**
+ * Supports an upgrade path among methods in CipherUtil because the PortalSDK is
+ * changing encryption methods.
+ */
+public interface IPortalSdkDecryptor {
+
+ /**
+ * Decrypts the specified value using a known key.
+ *
+ * @param cipherText
+ * Encrypted value
+ * @return Clear text on success, null otherwise.
+ * @throws CipherUtilException
+ * if any decryption step fails
+ */
+ String decrypt(String cipherText) throws CipherUtilException;
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthManager.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthManager.java
new file mode 100644
index 00000000..e4714473
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthManager.java
@@ -0,0 +1,118 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property and Nokia
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.portalapi;
+
+import java.lang.invoke.MethodHandles;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+import org.onap.portalsdk.core.onboarding.exception.CipherUtilException;
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides services to authenticate requests from/to ONAP Portal.
+ */
+public class PortalAuthManager {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ final Map credentialsMap;
+ private final IPortalSdkDecryptor portalSdkDecryptor;
+ private final String userIdCookieName;
+
+ public PortalAuthManager(final String appName, final String username, final String password,
+ final String decryptorClassName, final String userCookie)
+ throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+ credentialsMap = new HashMap<>();
+ // The map keys are hardcoded in EPSDK-FW, no constants are defined :(
+ credentialsMap.put("appName", appName);
+ credentialsMap.put("username", username);
+ credentialsMap.put("password", password);
+ this.userIdCookieName = userCookie;
+ // Instantiate here so configuration errors are detected at app-start time
+ logger.debug("ctor: using decryptor class {}", decryptorClassName);
+ Class> decryptorClass = Class.forName(decryptorClassName);
+ portalSdkDecryptor = (IPortalSdkDecryptor) decryptorClass.newInstance();
+ }
+
+ /**
+ * @return A map of key-value pairs with application name, user name and
+ * password.
+ */
+ public Map getAppCredentials() {
+ return credentialsMap;
+ }
+
+ /**
+ * Searches the request for a cookie with the specified name.
+ *
+ * @param request
+ * HttpServletRequest
+ * @param cookieName
+ * Cookie name
+ * @return Cookie, or null if not found.
+ */
+ private Cookie getCookie(HttpServletRequest request, String cookieName) {
+ Cookie[] cookies = request.getCookies();
+ if (cookies != null)
+ for (Cookie cookie : cookies)
+ if (cookie.getName().equals(cookieName))
+ return cookie;
+ return null;
+ }
+
+ /**
+ * Validates whether the ECOMP Portal sign-on process has completed. Checks for
+ * the ECOMP cookie first, then the user cookie.
+ *
+ * @param request
+ * HttpServletRequest
+ * @return User ID if the ECOMP cookie is present and the sign-on process
+ * established a user ID; else null.
+ */
+ public String valdiateEcompSso(HttpServletRequest request) {
+ // Check ECOMP Portal cookie
+ Cookie ep = getCookie(request, PortalApiConstants.EP_SERVICE);
+ if (ep == null) {
+ logger.debug("valdiateEcompSso: cookie not found: {}", PortalApiConstants.EP_SERVICE);
+ return null;
+ }
+ logger.trace("validateEcompSso: found cookie {}", PortalApiConstants.EP_SERVICE);
+ Cookie user = getCookie(request, userIdCookieName);
+ if (user == null) {
+ logger.debug("valdiateEcompSso: cookie not found: {}", userIdCookieName);
+ return null;
+ }
+ logger.trace("validateEcompSso: user cookie {}", userIdCookieName);
+ String userid = null;
+ try {
+ userid = portalSdkDecryptor.decrypt(user.getValue());
+ } catch (CipherUtilException e) {
+ throw new IllegalArgumentException("valdiateEcompSso failed", e);
+ }
+ return userid;
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthenticationFilter.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthenticationFilter.java
new file mode 100644
index 00000000..2ec5938d
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthenticationFilter.java
@@ -0,0 +1,177 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property and Nokia
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.portalapi;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.lang.invoke.MethodHandles;
+import java.net.URLEncoder;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.onap.portalsdk.core.onboarding.util.PortalApiProperties;
+import org.onap.portalsdk.core.restful.domain.EcompUser;
+import org.oransc.ric.portal.dashboard.DashboardConstants;
+import org.oransc.ric.portal.dashboard.model.EcompUserDetails;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
+
+/**
+ * This filter checks every request for the cookie set by the ONAP Portal single
+ * sign on process. The possible paths and actions:
+ *
+ * User starts at an app page via a bookmark. No Portal cookie is set.
+ * Redirect there to get one; then continue as below.
+ * User starts at Portal and goes to app. Alternately, the user's session
+ * times out and the user hits refresh. The Portal cookie is set, but there is
+ * no valid session. Create one and publish info.
+ * User has valid Portal cookie and session. Reset the max idle in that
+ * session.
+ *
+ *
+ * Notes:
+ *
+ * While redirecting, the cookie "redirectUrl" should also be set so that
+ * Portal knows where to forward the request to once the Portal Session is
+ * created and EPService cookie is set.
+ *
+ *
+ * TODO: What about sessions? Will this be stateless?
+ *
+ * This filter uses no annotations to avoid Spring's automatic registration,
+ * which add this filter in the chain in the wrong order.
+ */
+public class PortalAuthenticationFilter implements Filter {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ public static final String REDIRECT_URL_KEY = "redirectUrl";
+
+ private final PortalAuthManager authManager;
+
+ private final DashboardUserManager userManager;
+
+ public PortalAuthenticationFilter(PortalAuthManager authManager, DashboardUserManager userManager) {
+ this.authManager = authManager;
+ this.userManager = userManager;
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ // complain loudly if this key property is missing
+ String url = PortalApiProperties.getProperty(PortalApiConstants.ECOMP_REDIRECT_URL);
+ logger.debug("init: Portal redirect URL {}", url);
+ if (url == null)
+ logger.error(
+ "init: Failed to find property in portal.properties: " + PortalApiConstants.ECOMP_REDIRECT_URL);
+ }
+
+ @Override
+ public void destroy() {
+ // No resources to release
+ }
+
+ /**
+ * Checks for valid cookies and allows request to be served if found; redirects
+ * to Portal otherwise. Requests for pages ignored in the web security config do
+ * not hit this filter.
+ */
+ @Override
+ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
+ throws IOException, ServletException {
+ logger.debug("doFilter {}", req);
+ HttpServletRequest request = (HttpServletRequest) req;
+ HttpServletResponse response = (HttpServletResponse) res;
+ // Need to authenticate the request
+ final String userId = authManager.valdiateEcompSso(request);
+ final EcompUser ecompUser = (userId == null ? null : userManager.getUser(userId));
+ if (userId == null || ecompUser == null) {
+ String redirectURL = buildLoginPageUrl(request);
+ logger.trace("doFilter: unauthorized, redirecting to {}", redirectURL);
+ response.sendRedirect(redirectURL);
+ } else {
+ EcompUserDetails userDetails = new EcompUserDetails(ecompUser);
+ // Using portal session as credentials is a hack
+ PreAuthenticatedAuthenticationToken authToken = new PreAuthenticatedAuthenticationToken(userDetails,
+ getPortalSessionId(request), userDetails.getAuthorities());
+ SecurityContextHolder.getContext().setAuthentication(authToken);
+ // Pass request back down the filter chain
+ chain.doFilter(request, response);
+ }
+ }
+
+ private String buildLoginPageUrl(HttpServletRequest request) {
+ logger.trace("buildLoginPageUrl");
+ // Why so much work to recover the original request?
+ final StringBuffer sb = request.getRequestURL();
+ sb.append(request.getQueryString() == null ? "" : "?" + request.getQueryString());
+ final String requestedUrl = sb.toString();
+ String encodedUrl = null;
+ try {
+ encodedUrl = URLEncoder.encode(requestedUrl, "UTF-8");
+ } catch (UnsupportedEncodingException ex) {
+ logger.error("buildLoginPageUrl: Failed to encode {}", requestedUrl);
+ }
+ return DashboardConstants.LOGIN_PAGE + "?" + REDIRECT_URL_KEY + "=" + encodedUrl;
+ }
+
+ /**
+ * Searches the request for a cookie with the specified name.
+ *
+ * @param request
+ * HttpServletRequest
+ * @param cookieName
+ * Cookie name
+ * @return Cookie, or null if not found.
+ */
+ private Cookie getCookie(HttpServletRequest request, String cookieName) {
+ Cookie[] cookies = request.getCookies();
+ if (cookies != null)
+ for (Cookie cookie : cookies)
+ if (cookie.getName().equals(cookieName))
+ return cookie;
+ return null;
+ }
+
+ /**
+ * Gets the ECOMP Portal service cookie value.
+ *
+ * @param request
+ * @return Cookie value, or null if not found.
+ */
+ private String getPortalSessionId(HttpServletRequest request) {
+ Cookie ep = getCookie(request, PortalApiConstants.EP_SERVICE);
+ if (ep == null)
+ return null;
+ return ep.getValue();
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalRestCentralServiceImpl.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalRestCentralServiceImpl.java
new file mode 100644
index 00000000..f5d37596
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalRestCentralServiceImpl.java
@@ -0,0 +1,88 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property and Nokia
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.portalapi;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.onap.portalsdk.core.onboarding.crossapi.IPortalRestCentralService;
+import org.onap.portalsdk.core.onboarding.exception.PortalAPIException;
+import org.onap.portalsdk.core.restful.domain.EcompUser;
+import org.oransc.ric.portal.dashboard.config.SpringContextCache;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * Implements the contract used by the Portal to transmit user details to this
+ * on-boarded application. The requests are intercepted first by a servlet in
+ * the EPSDK-FW library, which proxies the calls to these methods.
+ *
+ * An instance of this class is created upon first request to the API. But this
+ * class is found and instantiated via Class.forName(), so cannot use Spring
+ * annotations.
+ */
+public class PortalRestCentralServiceImpl implements IPortalRestCentralService {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private final PortalAuthManager authManager;
+ private final DashboardUserManager userManager;
+
+ public PortalRestCentralServiceImpl() throws IOException, PortalAPIException {
+ final ApplicationContext context = SpringContextCache.getApplicationContext();
+ authManager = (PortalAuthManager) context.getBean(PortalAuthManager.class);
+ userManager = (DashboardUserManager) context.getBean(DashboardUserManager.class);
+ }
+
+ /*
+ * Answers the Portal API credentials.
+ */
+ @Override
+ public Map getAppCredentials() throws PortalAPIException {
+ logger.debug("getAppCredentials");
+ return authManager.getAppCredentials();
+ }
+
+ /*
+ * Extracts the user ID from a cookie in the header
+ */
+ @Override
+ public String getUserId(HttpServletRequest request) throws PortalAPIException {
+ logger.debug("getuserId");
+ return authManager.valdiateEcompSso(request);
+ }
+
+ @Override
+ public void pushUser(EcompUser user) throws PortalAPIException {
+ logger.debug("pushUser: {}", user);
+ userManager.createUser(user);
+ }
+
+ @Override
+ public void editUser(String loginId, EcompUser user) throws PortalAPIException {
+ logger.debug("editUser: {}", user);
+ userManager.updateUser(loginId, user);
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorAes.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorAes.java
new file mode 100644
index 00000000..3019f527
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorAes.java
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property and Nokia
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.portalapi;
+
+import org.onap.portalsdk.core.onboarding.exception.CipherUtilException;
+import org.onap.portalsdk.core.onboarding.util.CipherUtil;
+
+public class PortalSdkDecryptorAes implements IPortalSdkDecryptor {
+
+ @SuppressWarnings("deprecation")
+ public String decrypt(String cipherText) throws CipherUtilException {
+ return CipherUtil.decrypt(cipherText);
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorPkc.java b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorPkc.java
new file mode 100644
index 00000000..04d44cd1
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorPkc.java
@@ -0,0 +1,31 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property and Nokia
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.portalapi;
+
+import org.onap.portalsdk.core.onboarding.exception.CipherUtilException;
+import org.onap.portalsdk.core.onboarding.util.CipherUtil;
+
+public class PortalSdkDecryptorPkc implements IPortalSdkDecryptor {
+
+ public String decrypt(String cipherText) throws CipherUtilException {
+ return CipherUtil.decryptPKC(cipherText);
+ }
+
+}
diff --git a/webapp-backend/src/main/resources/ESAPI.properties b/webapp-backend/src/main/resources/ESAPI.properties
new file mode 100644
index 00000000..f27b1acf
--- /dev/null
+++ b/webapp-backend/src/main/resources/ESAPI.properties
@@ -0,0 +1,385 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property and Nokia
+# %%
+# Licensed 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.
+# ========================LICENSE_END===================================
+
+#===========================================================================
+# ESAPI Configuration
+#
+# If true, then print all the ESAPI properties set here when they are loaded.
+# If false, they are not printed. Useful to reduce output when running JUnit tests.
+# If you need to troubleshoot a properties related problem, turning this on may help.
+# This is 'false' in the src/test/resources/.esapi version. It is 'true' by
+# default for reasons of backward compatibility with earlier ESAPI versions.
+ESAPI.printProperties=false
+
+# ESAPI is designed to be easily extensible. You can use the reference implementation
+# or implement your own providers to take advantage of your enterprise's security
+# infrastructure. The functions in ESAPI are referenced using the ESAPI locator, like:
+#
+# String ciphertext =
+# ESAPI.encryptor().encrypt("Secret message"); // Deprecated in 2.0
+# CipherText cipherText =
+# ESAPI.encryptor().encrypt(new PlainText("Secret message")); // Preferred
+#
+# Below you can specify the classname for the provider that you wish to use in your
+# application. The only requirement is that it implement the appropriate ESAPI interface.
+# This allows you to switch security implementations in the future without rewriting the
+# entire application.
+#
+# ExperimentalAccessController requires ESAPI-AccessControlPolicy.xml in .esapi directory
+ESAPI.AccessControl=org.owasp.esapi.reference.DefaultAccessController
+# FileBasedAuthenticator requires users.txt file in .esapi directory
+ESAPI.Authenticator=org.owasp.esapi.reference.FileBasedAuthenticator
+ESAPI.Encoder=org.owasp.esapi.reference.DefaultEncoder
+ESAPI.Encryptor=org.owasp.esapi.reference.crypto.JavaEncryptor
+
+ESAPI.Executor=org.owasp.esapi.reference.DefaultExecutor
+ESAPI.HTTPUtilities=org.owasp.esapi.reference.DefaultHTTPUtilities
+ESAPI.IntrusionDetector=org.owasp.esapi.reference.DefaultIntrusionDetector
+#ESAPI.Logger=org.owasp.esapi.reference.JavaLogFactory
+ESAPI.Randomizer=org.owasp.esapi.reference.DefaultRandomizer
+ESAPI.Validator=org.owasp.esapi.reference.DefaultValidator
+
+#===========================================================================
+# ESAPI Authenticator
+#
+Authenticator.AllowedLoginAttempts=3
+#Authenticator.MaxOldPasswordHashes=13
+Authenticator.UsernameParameterName=username
+#Authenticator.PasswordParameterName=password
+# RememberTokenDuration (in days)
+Authenticator.RememberTokenDuration=14
+# Session Timeouts (in minutes)
+Authenticator.IdleTimeoutDuration=20
+Authenticator.AbsoluteTimeoutDuration=120
+
+#===========================================================================
+# ESAPI Encoder
+#
+# ESAPI canonicalizes input before validation to prevent bypassing filters with encoded attacks.
+# Failure to canonicalize input is a very common mistake when implementing validation schemes.
+# Canonicalization is automatic when using the ESAPI Validator, but you can also use the
+# following code to canonicalize data.
+#
+# ESAPI.Encoder().canonicalize( "%22hello world"" );
+#
+# Multiple encoding is when a single encoding format is applied multiple times. Allowing
+# multiple encoding is strongly discouraged.
+Encoder.AllowMultipleEncoding=false
+
+# Mixed encoding is when multiple different encoding formats are applied, or when
+# multiple formats are nested. Allowing multiple encoding is strongly discouraged.
+Encoder.AllowMixedEncoding=false
+
+# The default list of codecs to apply when canonicalizing untrusted data. The list should include the codecs
+# for all downstream interpreters or decoders. For example, if the data is likely to end up in a URL, HTML, or
+# inside JavaScript, then the list of codecs below is appropriate. The order of the list is not terribly important.
+Encoder.DefaultCodecList=HTMLEntityCodec,PercentCodec,JavaScriptCodec
+
+
+#===========================================================================
+# ESAPI Encryption
+#
+# The ESAPI Encryptor provides basic cryptographic functions with a simplified API.
+# To get started, generate a new key using java -classpath esapi.jar org.owasp.esapi.reference.crypto.JavaEncryptor
+# There is not currently any support for key rotation, so be careful when changing your key and salt as it
+# will invalidate all signed, encrypted, and hashed data.
+#
+# WARNING: Not all combinations of algorithms and key lengths are supported.
+# If you choose to use a key length greater than 128, you MUST download the
+# unlimited strength policy files and install in the lib directory of your JRE/JDK.
+# See http://java.sun.com/javase/downloads/index.jsp for more information.
+#
+# Backward compatibility with ESAPI Java 1.4 is supported by the two deprecated API
+# methods, Encryptor.encrypt(String) and Encryptor.decrypt(String). However, whenever
+# possible, these methods should be avoided as they use ECB cipher mode, which in almost
+# all circumstances a poor choice because of it's weakness. CBC cipher mode is the default
+# for the new Encryptor encrypt / decrypt methods for ESAPI Java 2.0. In general, you
+# should only use this compatibility setting if you have persistent data encrypted with
+# version 1.4 and even then, you should ONLY set this compatibility mode UNTIL
+# you have decrypted all of your old encrypted data and then re-encrypted it with
+# ESAPI 2.0 using CBC mode. If you have some reason to mix the deprecated 1.4 mode
+# with the new 2.0 methods, make sure that you use the same cipher algorithm for both
+# (256-bit AES was the default for 1.4; 128-bit is the default for 2.0; see below for
+# more details.) Otherwise, you will have to use the new 2.0 encrypt / decrypt methods
+# where you can specify a SecretKey. (Note that if you are using the 256-bit AES,
+# that requires downloading the special jurisdiction policy files mentioned above.)
+#
+# ***** IMPORTANT: Do NOT forget to replace these with your own values! *****
+# To calculate these values, you can run:
+# java -classpath esapi.jar org.owasp.esapi.reference.crypto.JavaEncryptor
+#
+Encryptor.MasterKey=tzfztf56ftv
+Encryptor.MasterSalt=123456ztrewq
+
+# Provides the default JCE provider that ESAPI will "prefer" for its symmetric
+# encryption and hashing. (That is it will look to this provider first, but it
+# will defer to other providers if the requested algorithm is not implemented
+# by this provider.) If left unset, ESAPI will just use your Java VM's current
+# preferred JCE provider, which is generally set in the file
+# "$JAVA_HOME/jre/lib/security/java.security".
+#
+# The main intent of this is to allow ESAPI symmetric encryption to be
+# used with a FIPS 140-2 compliant crypto-module. For details, see the section
+# "Using ESAPI Symmetric Encryption with FIPS 140-2 Cryptographic Modules" in
+# the ESAPI 2.0 Symmetric Encryption User Guide, at:
+# http://owasp-esapi-java.googlecode.com/svn/trunk/documentation/esapi4java-core-2.0-symmetric-crypto-user-guide.html
+# However, this property also allows you to easily use an alternate JCE provider
+# such as "Bouncy Castle" without having to make changes to "java.security".
+# See Javadoc for SecurityProviderLoader for further details. If you wish to use
+# a provider that is not known to SecurityProviderLoader, you may specify the
+# fully-qualified class name of the JCE provider class that implements
+# java.security.Provider. If the name contains a '.', this is interpreted as
+# a fully-qualified class name that implements java.security.Provider.
+#
+# NOTE: Setting this property has the side-effect of changing it in your application
+# as well, so if you are using JCE in your application directly rather than
+# through ESAPI (you wouldn't do that, would you? ;-), it will change the
+# preferred JCE provider there as well.
+#
+# Default: Keeps the JCE provider set to whatever JVM sets it to.
+Encryptor.PreferredJCEProvider=
+
+# AES is the most widely used and strongest encryption algorithm. This
+# should agree with your Encryptor.CipherTransformation property.
+# By default, ESAPI Java 1.4 uses "PBEWithMD5AndDES" and which is
+# very weak. It is essentially a password-based encryption key, hashed
+# with MD5 around 1K times and then encrypted with the weak DES algorithm
+# (56-bits) using ECB mode and an unspecified padding (it is
+# JCE provider specific, but most likely "NoPadding"). However, 2.0 uses
+# "AES/CBC/PKCSPadding". If you want to change these, change them here.
+# Warning: This property does not control the default reference implementation for
+# ESAPI 2.0 using JavaEncryptor. Also, this property will be dropped
+# in the future.
+# @deprecated
+Encryptor.EncryptionAlgorithm=AES
+# For ESAPI Java 2.0 - New encrypt / decrypt methods use this.
+Encryptor.CipherTransformation=AES/CBC/PKCS5Padding
+
+# Applies to ESAPI 2.0 and later only!
+# Comma-separated list of cipher modes that provide *BOTH*
+# confidentiality *AND* message authenticity. (NIST refers to such cipher
+# modes as "combined modes" so that's what we shall call them.) If any of these
+# cipher modes are used then no MAC is calculated and stored
+# in the CipherText upon encryption. Likewise, if one of these
+# cipher modes is used with decryption, no attempt will be made
+# to validate the MAC contained in the CipherText object regardless
+# of whether it contains one or not. Since the expectation is that
+# these cipher modes support support message authenticity already,
+# injecting a MAC in the CipherText object would be at best redundant.
+#
+# Note that as of JDK 1.5, the SunJCE provider does not support *any*
+# of these cipher modes. Of these listed, only GCM and CCM are currently
+# NIST approved. YMMV for other JCE providers. E.g., Bouncy Castle supports
+# GCM and CCM with "NoPadding" mode, but not with "PKCS5Padding" or other
+# padding modes.
+Encryptor.cipher_modes.combined_modes=GCM,CCM,IAPM,EAX,OCB,CWC
+
+# Applies to ESAPI 2.0 and later only!
+# Additional cipher modes allowed for ESAPI 2.0 encryption. These
+# cipher modes are in _addition_ to those specified by the property
+# 'Encryptor.cipher_modes.combined_modes'.
+# Note: We will add support for streaming modes like CFB & OFB once
+# we add support for 'specified' to the property 'Encryptor.ChooseIVMethod'
+# (probably in ESAPI 2.1).
+# DISCUSS: Better name?
+Encryptor.cipher_modes.additional_allowed=CBC
+
+# 128-bit is almost always sufficient and appears to be more resistant to
+# related key attacks than is 256-bit AES. Use '_' to use default key size
+# for cipher algorithms (where it makes sense because the algorithm supports
+# a variable key size). Key length must agree to what's provided as the
+# cipher transformation, otherwise this will be ignored after logging a
+# warning.
+#
+# NOTE: This is what applies BOTH ESAPI 1.4 and 2.0. See warning above about mixing!
+Encryptor.EncryptionKeyLength=128
+
+# Because 2.0 uses CBC mode by default, it requires an initialization vector (IV).
+# (All cipher modes except ECB require an IV.) There are two choices: we can either
+# use a fixed IV known to both parties or allow ESAPI to choose a random IV. While
+# the IV does not need to be hidden from adversaries, it is important that the
+# adversary not be allowed to choose it. Also, random IVs are generally much more
+# secure than fixed IVs. (In fact, it is essential that feed-back cipher modes
+# such as CFB and OFB use a different IV for each encryption with a given key so
+# in such cases, random IVs are much preferred. By default, ESAPI 2.0 uses random
+# IVs. If you wish to use 'fixed' IVs, set 'Encryptor.ChooseIVMethod=fixed' and
+# uncomment the Encryptor.fixedIV.
+#
+# Valid values: random|fixed|specified 'specified' not yet implemented; planned for 2.1
+Encryptor.ChooseIVMethod=random
+# If you choose to use a fixed IV, then you must place a fixed IV here that
+# is known to all others who are sharing your secret key. The format should
+# be a hex string that is the same length as the cipher block size for the
+# cipher algorithm that you are using. The following is an *example* for AES
+# from an AES test vector for AES-128/CBC as described in:
+# NIST Special Publication 800-38A (2001 Edition)
+# "Recommendation for Block Cipher Modes of Operation".
+# (Note that the block size for AES is 16 bytes == 128 bits.)
+#
+Encryptor.fixedIV=0x000102030405060708090a0b0c0d0e0f
+
+# Whether or not CipherText should use a message authentication code (MAC) with it.
+# This prevents an adversary from altering the IV as well as allowing a more
+# fool-proof way of determining the decryption failed because of an incorrect
+# key being supplied. This refers to the "separate" MAC calculated and stored
+# in CipherText, not part of any MAC that is calculated as a result of a
+# "combined mode" cipher mode.
+#
+# If you are using ESAPI with a FIPS 140-2 cryptographic module, you *must* also
+# set this property to false.
+Encryptor.CipherText.useMAC=true
+
+# Whether or not the PlainText object may be overwritten and then marked
+# eligible for garbage collection. If not set, this is still treated as 'true'.
+Encryptor.PlainText.overwrite=true
+
+# Do not use DES except in a legacy situations. 56-bit is way too small key size.
+#Encryptor.EncryptionKeyLength=56
+#Encryptor.EncryptionAlgorithm=DES
+
+# TripleDES is considered strong enough for most purposes.
+# Note: There is also a 112-bit version of DESede. Using the 168-bit version
+# requires downloading the special jurisdiction policy from Sun.
+#Encryptor.EncryptionKeyLength=168
+#Encryptor.EncryptionAlgorithm=DESede
+
+Encryptor.HashAlgorithm=SHA-512
+Encryptor.HashIterations=1024
+Encryptor.DigitalSignatureAlgorithm=SHA1withDSA
+Encryptor.DigitalSignatureKeyLength=1024
+Encryptor.RandomAlgorithm=SHA1PRNG
+Encryptor.CharacterEncoding=UTF-8
+
+# This is the Pseudo Random Function (PRF) that ESAPI's Key Derivation Function
+# (KDF) normally uses. Note this is *only* the PRF used for ESAPI's KDF and
+# *not* what is used for ESAPI's MAC. (Currently, HmacSHA1 is always used for
+# the MAC, mostly to keep the overall size at a minimum.)
+#
+# Currently supported choices for JDK 1.5 and 1.6 are:
+# HmacSHA1 (160 bits), HmacSHA256 (256 bits), HmacSHA384 (384 bits), and
+# HmacSHA512 (512 bits).
+# Note that HmacMD5 is *not* supported for the PRF used by the KDF even though
+# the JDKs support it. See the ESAPI 2.0 Symmetric Encryption User Guide
+# further details.
+Encryptor.KDF.PRF=HmacSHA256
+#===========================================================================
+# ESAPI Logging
+# Set the application name if these logs are combined with other applications
+Logger.ApplicationName=portal_ric_dashboard
+# If you use an HTML log viewer that does not properly HTML escape log data, you can set LogEncodingRequired to true
+Logger.LogEncodingRequired=false
+# Determines whether ESAPI should log the application name. This might be clutter in some single-server/single-app environments.
+Logger.LogApplicationName=true
+# Determines whether ESAPI should log the server IP and port. This might be clutter in some single-server environments.
+Logger.LogServerIP=true
+# LogFileName, the name of the logging file. Provide a full directory path (e.g., C:\\ESAPI\\ESAPI_logging_file) if you
+# want to place it in a specific directory.
+Logger.LogFileName=portal_ric_dashboard_esapi_log
+# MaxLogFileSize, the max size (in bytes) of a single log file before it cuts over to a new one (default is 10,000,000)
+Logger.MaxLogFileSize=10000000
+
+
+#===========================================================================
+# ESAPI Intrusion Detection
+#
+# Each event has a base to which .count, .interval, and .action are added
+# The IntrusionException will fire if we receive "count" events within "interval" seconds
+# The IntrusionDetector is configurable to take the following actions: log, logout, and disable
+# (multiple actions separated by commas are allowed e.g. event.test.actions=log,disable
+#
+# Custom Events
+# Names must start with "event." as the base
+# Use IntrusionDetector.addEvent( "test" ) in your code to trigger "event.test" here
+# You can also disable intrusion detection completely by changing
+# the following parameter to true
+#
+IntrusionDetector.Disable=false
+#
+IntrusionDetector.event.test.count=2
+IntrusionDetector.event.test.interval=10
+IntrusionDetector.event.test.actions=disable,log
+
+# Exception Events
+# All EnterpriseSecurityExceptions are registered automatically
+# Call IntrusionDetector.getInstance().addException(e) for Exceptions that do not extend EnterpriseSecurityException
+# Use the fully qualified classname of the exception as the base
+
+# any intrusion is an attack
+IntrusionDetector.org.owasp.esapi.errors.IntrusionException.count=1
+IntrusionDetector.org.owasp.esapi.errors.IntrusionException.interval=1
+IntrusionDetector.org.owasp.esapi.errors.IntrusionException.actions=log,disable,logout
+
+# for test purposes
+# CHECKME: Shouldn't there be something in the property name itself that designates
+# that these are for testing???
+IntrusionDetector.org.owasp.esapi.errors.IntegrityException.count=10
+IntrusionDetector.org.owasp.esapi.errors.IntegrityException.interval=5
+IntrusionDetector.org.owasp.esapi.errors.IntegrityException.actions=log,disable,logout
+
+# rapid validation errors indicate scans or attacks in progress
+# org.owasp.esapi.errors.ValidationException.count=10
+# org.owasp.esapi.errors.ValidationException.interval=10
+# org.owasp.esapi.errors.ValidationException.actions=log,logout
+
+# sessions jumping between hosts indicates session hijacking
+IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.count=2
+IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.interval=10
+IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.actions=log,logout
+
+
+#===========================================================================
+# ESAPI Validation
+#
+# The ESAPI Validator works on regular expressions with defined names. You can define names
+# either here, or you may define application specific patterns in a separate file defined below.
+# This allows enterprises to specify both organizational standards as well as application specific
+# validation rules.
+#
+Validator.ConfigurationFile=validation.properties
+Validator.ConfigurationFile.MultiValued=false
+
+# Validators used by ESAPI
+Validator.AccountName=^[a-zA-Z0-9]{3,20}$
+Validator.SystemCommand=^[a-zA-Z\\-\\/]{1,64}$
+Validator.RoleName=^[a-z]{1,20}$
+
+#the word TEST below should be changed to your application
+#name - only relative URL's are supported
+Validator.Redirect=^\\/test.*$
+
+# Global HTTP Validation Rules
+# Values with Base64 encoded data (e.g. encrypted state) will need at least [a-zA-Z0-9\/+=]
+Validator.HTTPScheme=^(http|https)$
+Validator.HTTPServerName=^[a-zA-Z0-9_.\\-]*$
+Validator.HTTPParameterName=^[a-zA-Z0-9_]{1,32}$
+Validator.HTTPParameterValue=^[a-zA-Z0-9.\\-\\/+=@_ ]*$
+Validator.HTTPCookieName=^[a-zA-Z0-9\\-_]{1,32}$
+Validator.HTTPCookieValue=^[a-zA-Z0-9\\-\\/+=_ ]*$
+Validator.HTTPHeaderName=^[a-zA-Z0-9\\-_]{1,32}$
+Validator.HTTPHeaderValue=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$
+Validator.HTTPContextPath=^\\/?[a-zA-Z0-9.\\-\\/_]*$
+Validator.HTTPServletPath=^[a-zA-Z0-9.\\-\\/_]*$
+Validator.HTTPPath=^[a-zA-Z0-9.\\-_]*$
+Validator.HTTPQueryString=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ %]*$
+Validator.HTTPURI=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$
+Validator.HTTPURL=^.*$
+Validator.HTTPJSESSIONID=^[A-Z0-9]{10,30}$
+
+# Validation of file related input
+Validator.FileName=^[a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$
+Validator.DirectoryName=^[a-zA-Z0-9:/\\\\!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$
diff --git a/webapp-backend/src/main/resources/application.properties b/webapp-backend/src/main/resources/application.properties
index 382cd191..1b7bd1c6 100644
--- a/webapp-backend/src/main/resources/application.properties
+++ b/webapp-backend/src/main/resources/application.properties
@@ -1,4 +1,3 @@
-###
# ========================LICENSE_START=================================
# O-RAN-SC
# %%
@@ -16,26 +15,42 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# ========================LICENSE_END===================================
-###
-# Defines property keys for the RIC Dashboard, and some defaults
+# Defines RIC Dashboard property keys and default values.
+# Create a copy in the launch directory to override values; or
+# copy to "application-abc.properties" as mentioned in the README.
-# Confusingly, this key has no "spring" prefix
-# The port number is chosen RANDOMLY when running a test
+# A spring property but without a "spring" prefix;
+# the port number is chosen RANDOMLY when running tests
server.port = 8080
+# path to file that stores user details;
+# use a persistent volume in a K8S deployment
+userfile = users.json
+
+# class that decrypts ciphertext from Portal
+portalapi.decryptor = org.oransc.ric.portal.dashboard.portalapi.PortalSdkDecryptorAes
+# name of request cookie with user ID
+portalapi.usercookie = UserId
+
+# portal credentials must be supplied at deployment time
+portalapi.appname = RIC Dashboard
+portalapi.username =
+portalapi.password =
+
+# endpoint URLs must be supplied at deployment time
# A1 Mediator
a1med.url.prefix = http://jar-app-props-default-A1-URL-prefix
a1med.url.suffix =
-
# ANR xApp
anrxapp.url.prefix = http://jar-app-props-default-ANR-URL-prefix
anrxapp.url.suffix =
-
# App Manager
appmgr.url.prefix = http://jar-app-props-default-Xapp-Mgr-URL
appmgr.url.suffix = /ric/v1
-
# E2 Manager
e2mgr.url.prefix = http://jar-app-props-default-E2-URL
e2mgr.url.suffix = /v1
+
+# Mimic slow endpoints by defining sleep period, in milliseconds
+mock.config.delay = 0
diff --git a/webapp-backend/src/main/resources/logback.xml b/webapp-backend/src/main/resources/logback.xml
index 26c9ffb2..b17cbe2c 100644
--- a/webapp-backend/src/main/resources/logback.xml
+++ b/webapp-backend/src/main/resources/logback.xml
@@ -19,16 +19,15 @@
========================LICENSE_END===================================
-->
-
-
+
-
+
@@ -44,11 +43,11 @@
${logDirectory}/${componentName}.%i.log.zip
1
- 5
+ 9
- 1MB
+ 10MB
@@ -65,7 +64,10 @@
>
-
+
+
+
+
diff --git a/webapp-backend/src/main/resources/validation.properties b/webapp-backend/src/main/resources/validation.properties
new file mode 100644
index 00000000..0785d066
--- /dev/null
+++ b/webapp-backend/src/main/resources/validation.properties
@@ -0,0 +1,19 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property and Nokia
+# %%
+# Licensed 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.
+# ========================LICENSE_END===================================
+
+# empty file to suppress OWASP complaints emitted to stdout
diff --git a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/config/A1MediatorMockConfiguration.java b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/A1MediatorMockConfiguration.java
similarity index 85%
rename from webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/config/A1MediatorMockConfiguration.java
rename to webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/A1MediatorMockConfiguration.java
index a4fb63fd..aef5bc4c 100644
--- a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/config/A1MediatorMockConfiguration.java
+++ b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/A1MediatorMockConfiguration.java
@@ -17,7 +17,7 @@
* limitations under the License.
* ========================LICENSE_END===================================
*/
-package org.oransc.ric.portal.dashboard.test.config;
+package org.oransc.ric.portal.dashboard.config;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
@@ -30,6 +30,7 @@ import org.oransc.ric.a1med.client.api.A1MediatorApi;
import org.oransc.ric.a1med.client.invoker.ApiClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@@ -43,8 +44,10 @@ import org.springframework.http.HttpStatus;
public class A1MediatorMockConfiguration {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
// Simulate remote method delay for UI testing
- private final int delayMs = 500;
+ @Value("${mock.config.delay:0}")
+ private int delayMs;
public A1MediatorMockConfiguration() {
logger.info("Configuring mock A1 Mediator");
@@ -63,13 +66,17 @@ public class A1MediatorMockConfiguration {
A1MediatorApi mockApi = mock(A1MediatorApi.class);
when(mockApi.getApiClient()).thenReturn(apiClient);
doAnswer(inv -> {
- logger.debug("a1ControllerGetHandler sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("a1ControllerGetHandler sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return null;
}).when(mockApi).a1ControllerGetHandler(any(String.class));
doAnswer(inv -> {
- logger.debug("a1ControllerPutHandler sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("a1ControllerPutHandler sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return null;
}).when(mockApi).a1ControllerPutHandler(any(String.class), any(Object.class));
return mockApi;
diff --git a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/config/AnrXappMockConfiguration.java b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/AnrXappMockConfiguration.java
similarity index 85%
rename from webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/config/AnrXappMockConfiguration.java
rename to webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/AnrXappMockConfiguration.java
index d291945b..bc189693 100644
--- a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/config/AnrXappMockConfiguration.java
+++ b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/AnrXappMockConfiguration.java
@@ -17,7 +17,7 @@
* limitations under the License.
* ========================LICENSE_END===================================
*/
-package org.oransc.ric.portal.dashboard.test.config;
+package org.oransc.ric.portal.dashboard.config;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -37,6 +37,7 @@ import org.oransc.ric.anrxapp.client.model.NeighborCellRelationMod;
import org.oransc.ric.anrxapp.client.model.NeighborCellRelationTable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@@ -51,17 +52,20 @@ public class AnrXappMockConfiguration {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ // Simulate remote method delay for UI testing
+ @Value("${mock.config.delay:0}")
+ private int delayMs;
+
private static final String GNODEB1 = "001EF5:0045FE50";
private static final String GNODEB2 = "001EF6:0045FE51";
private static final String GNODEB3 = "001EF7:0045FE52";
+
// Sonar wants separate declarations
private final NeighborCellRelationTable ncrt;
private final NeighborCellRelationTable ncrtNodeB1;
private final NeighborCellRelationTable ncrtNodeB2;
private final NeighborCellRelationTable ncrtNodeB3;
private final GgNodeBTable gNodebTable;
- // Simulate remote method delay for UI testing
- private final int delayMs = 500;
public AnrXappMockConfiguration() {
logger.info("Configuring mock ANR xApp client");
@@ -118,44 +122,60 @@ public class AnrXappMockConfiguration {
NcrtApi mockApi = mock(NcrtApi.class);
when(mockApi.getApiClient()).thenReturn(apiClient);
doAnswer(inv -> {
- logger.debug("getgNodeB sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("getgNodeB sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return gNodebTable;
}).when(mockApi).getgNodeB();
// Swagger sends nulls; front end sends empty strings
doAnswer(inv -> {
- logger.debug("getNcrt (1) sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("getNcrt (1) sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return ncrt;
}).when(mockApi).getNcrt((String) isNull(), (String) isNull(), (String) isNull());
doAnswer(inv -> {
- logger.debug("getNcrt (2) sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("getNcrt (2) sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return ncrt;
}).when(mockApi).getNcrt(eq(""), any(String.class), any(String.class));
doAnswer(inv -> {
- logger.debug("getNcrt (3) sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("getNcrt (3) sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return ncrtNodeB1;
}).when(mockApi).getNcrt(eq(GNODEB1), any(String.class), any(String.class));
doAnswer(inv -> {
- logger.debug("getNcrt (4) sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("getNcrt (4) sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return ncrtNodeB2;
}).when(mockApi).getNcrt(eq(GNODEB2), any(String.class), any(String.class));
doAnswer(inv -> {
- logger.debug("getNcrt (5) sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("getNcrt (5) sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return ncrtNodeB3;
}).when(mockApi).getNcrt(eq(GNODEB3), any(String.class), any(String.class));
doAnswer(inv -> {
- logger.debug("deleteNcrt sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("deleteNcrt sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return null;
}).when(mockApi).deleteNcrt(any(String.class), any(String.class));
doAnswer(inv -> {
- logger.debug("modifyNcrt sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("modifyNcrt sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return null;
}).when(mockApi).modifyNcrt(any(String.class), any(String.class), any(NeighborCellRelationMod.class));
return mockApi;
diff --git a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/config/AppManagerMockConfiguration.java b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/AppManagerMockConfiguration.java
similarity index 79%
rename from webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/config/AppManagerMockConfiguration.java
rename to webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/AppManagerMockConfiguration.java
index 2df2b3d6..f4cd4955 100644
--- a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/config/AppManagerMockConfiguration.java
+++ b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/AppManagerMockConfiguration.java
@@ -17,7 +17,7 @@
* limitations under the License.
* ========================LICENSE_END===================================
*/
-package org.oransc.ric.portal.dashboard.test.config;
+package org.oransc.ric.portal.dashboard.config;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
@@ -42,6 +42,7 @@ import org.oransc.ric.plt.appmgr.client.model.Xapp.StatusEnum;
import org.oransc.ric.plt.appmgr.client.model.XappInstance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@@ -57,12 +58,14 @@ public class AppManagerMockConfiguration {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ // Simulate remote method delay for UI testing
+ @Value("${mock.config.delay:0}")
+ private int delayMs;
+
private final AllDeployableXapps availXapps;
private final AllDeployedXapps deployedXapps;
private final AllXappConfig allXappConfigs;
private final SubscriptionResponse subRes;
- // Simulate remote method delay for UI testing
- private final int delayMs = 500;
public AppManagerMockConfiguration() {
logger.info("Configuring mock xApp Manager");
@@ -105,58 +108,80 @@ public class AppManagerMockConfiguration {
XappApi mockApi = mock(XappApi.class);
when(mockApi.getApiClient()).thenReturn(mockClient);
doAnswer(inv -> {
- logger.debug("getAllXappConfig sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("getAllXappConfig sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return allXappConfigs;
}).when(mockApi).getAllXappConfig();
doAnswer(inv -> {
- logger.debug("createXappConfig sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("createXappConfig sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return allXappConfigs.get(0);
}).when(mockApi).createXappConfig(any(XAppConfig.class));
doAnswer(inv -> {
- logger.debug("modifyXappConfig sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("modifyXappConfig sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return allXappConfigs.get(0);
}).when(mockApi).modifyXappConfig(any(XAppConfig.class));
doAnswer(inv -> {
- logger.debug("deleteXappConfig sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("deleteXappConfig sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return null;
}).when(mockApi).deleteXappConfig(any(ConfigMetadata.class));
doAnswer(inv -> {
- logger.debug("deployXapp of {} sleeping {}", inv.getArgument(0), delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("deployXapp of {} sleeping {}", inv.getArgument(0), delayMs);
+ Thread.sleep(delayMs);
+ }
return deployedXapps.get(0);
}).when(mockApi).deployXapp(any(XAppInfo.class));
doAnswer(inv -> {
- logger.debug("listAllXapps sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("listAllXapps sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return availXapps;
}).when(mockApi).listAllXapps();
doAnswer(inv -> {
- logger.debug("getAllXapps sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("getAllXapps sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return deployedXapps;
}).when(mockApi).getAllXapps();
doAnswer(inv -> {
- logger.debug("getXappByName of {} sleeping {}", inv.getArgument(0), delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("getXappByName of {} sleeping {}", inv.getArgument(0), delayMs);
+ Thread.sleep(delayMs);
+ }
return deployedXapps.get(0);
}).when(mockApi).getXappByName(any(String.class));
doAnswer(inv -> {
- logger.debug("undeployXapp of {} sleeping {}", inv.getArgument(0), delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("undeployXapp of {} sleeping {}", inv.getArgument(0), delayMs);
+ Thread.sleep(delayMs);
+ }
return null;
}).when(mockApi).undeployXapp(any(String.class));
doAnswer(inv -> {
- logger.debug("addSubscription sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("addSubscription sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return subRes;
}).when(mockApi).addSubscription(any(SubscriptionRequest.class));
doAnswer(inv -> {
- logger.debug("deleteSubscription sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("deleteSubscription sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return null;
}).when(mockApi).deleteSubscription(any(String.class));
return mockApi;
diff --git a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/config/E2ManagerMockConfiguration.java b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/E2ManagerMockConfiguration.java
similarity index 84%
rename from webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/config/E2ManagerMockConfiguration.java
rename to webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/E2ManagerMockConfiguration.java
index 9454bfb6..d3385267 100644
--- a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/config/E2ManagerMockConfiguration.java
+++ b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/E2ManagerMockConfiguration.java
@@ -17,7 +17,7 @@
* limitations under the License.
* ========================LICENSE_END===================================
*/
-package org.oransc.ric.portal.dashboard.test.config;
+package org.oransc.ric.portal.dashboard.config;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
@@ -37,6 +37,7 @@ import org.oransc.ric.e2mgr.client.model.NodebIdentityGlobalNbId;
import org.oransc.ric.e2mgr.client.model.SetupRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@@ -51,10 +52,12 @@ public class E2ManagerMockConfiguration {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ // Simulate remote method delay for UI testing
+ @Value("${mock.config.delay:0}")
+ private int delayMs;
+
private final List nodebIdList;
private final GetNodebResponse nodebResponse;
- // Simulate remote method delay for UI testing
- private final int delayMs = 500;
public E2ManagerMockConfiguration() {
logger.info("Configuring mock E2 Manager");
@@ -89,28 +92,38 @@ public class E2ManagerMockConfiguration {
NodebApi mockApi = mock(NodebApi.class);
when(mockApi.getApiClient()).thenReturn(apiClient);
doAnswer(inv -> {
- logger.debug("nodebDelete sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("nodebDelete sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return null;
}).when(mockApi).nodebDelete();
doAnswer(inv -> {
- logger.debug("getNb sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("getNb sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return nodebResponse;
}).when(mockApi).getNb(any(String.class));
doAnswer(inv -> {
- logger.debug("getNodebIdList sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("getNodebIdList sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return nodebIdList;
}).when(mockApi).getNodebIdList();
doAnswer(inv -> {
- logger.debug("endcSetup sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("endcSetup sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return null;
}).when(mockApi).endcSetup(any(SetupRequest.class));
doAnswer(inv -> {
- logger.debug("x2Setup sleeping {}", delayMs);
- Thread.sleep(delayMs);
+ if (delayMs > 0) {
+ logger.debug("x2Setup sleeping {}", delayMs);
+ Thread.sleep(delayMs);
+ }
return null;
}).when(mockApi).x2Setup(any(SetupRequest.class));
return mockApi;
diff --git a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/WebSecurityMockConfiguration.java b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/WebSecurityMockConfiguration.java
new file mode 100644
index 00000000..c17baefd
--- /dev/null
+++ b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/WebSecurityMockConfiguration.java
@@ -0,0 +1,171 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property and Nokia
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.config;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.onap.portalsdk.core.onboarding.crossapi.PortalRestAPIProxy;
+import org.onap.portalsdk.core.onboarding.exception.PortalAPIException;
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.onap.portalsdk.core.restful.domain.EcompRole;
+import org.onap.portalsdk.core.restful.domain.EcompUser;
+import org.oransc.ric.portal.dashboard.DashboardConstants;
+import org.oransc.ric.portal.dashboard.LoginServlet;
+import org.oransc.ric.portal.dashboard.portalapi.DashboardUserManager;
+import org.oransc.ric.portal.dashboard.portalapi.PortalAuthManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.web.servlet.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(securedEnabled = true)
+@Profile("test")
+public class WebSecurityMockConfiguration extends WebSecurityConfigurerAdapter {
+
+ public static final String TEST_CRED_ADMIN = "admin";
+ public static final String TEST_CRED_STANDARD = "standard";
+
+ // Unfortunately EPSDK-FW does not define these as constants
+ public static final String PORTAL_USERNAME_HEADER_KEY = "username";
+ public static final String PORTAL_PASSWORD_HEADER_KEY = "password";
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ public WebSecurityMockConfiguration(@Value("${userfile}") final String userFilePath) {
+ logger.debug("ctor: user file path {}", userFilePath);
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
+ auth.inMemoryAuthentication() //
+ .passwordEncoder(encoder) //
+ // The admin user has the admin AND standard roles
+ .withUser(TEST_CRED_ADMIN) //
+ .password(encoder.encode(TEST_CRED_ADMIN))
+ .roles(DashboardConstants.ROLE_NAME_ADMIN, DashboardConstants.ROLE_NAME_STANDARD)//
+ .and()//
+ // The standard user has only the standard role
+ .withUser(TEST_CRED_STANDARD) //
+ .password(encoder.encode(TEST_CRED_STANDARD)) //
+ .roles(DashboardConstants.ROLE_NAME_STANDARD);
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http.authorizeRequests().anyRequest().authenticated()//
+ .and().httpBasic() //
+ .and().csrf().disable();
+ }
+
+ @Override
+ public void configure(WebSecurity web) throws Exception {
+ // This disables Spring security, but not the app's filter.
+ web.ignoring().antMatchers(WebSecurityConfiguration.OPEN_PATHS);
+ }
+
+ @Bean
+ public ServletRegistrationBean loginServlet() {
+ LoginServlet servlet = new LoginServlet();
+ final ServletRegistrationBean servletBean = new ServletRegistrationBean<>(servlet,
+ DashboardConstants.LOGIN_PAGE);
+ servletBean.setName("LoginServlet");
+ return servletBean;
+ }
+
+ @Bean
+ public ServletRegistrationBean portalApiProxyServlet() {
+ PortalRestAPIProxy servlet = new PortalRestAPIProxy();
+ final ServletRegistrationBean servletBean = new ServletRegistrationBean<>(servlet,
+ PortalApiConstants.API_PREFIX + "/*");
+ servletBean.setName("PortalRestApiProxyServlet");
+ return servletBean;
+ }
+
+ @Bean
+ public PortalAuthManager portalAuthManager() throws Exception {
+ PortalAuthManager mockManager = mock(PortalAuthManager.class);
+ final Map credentialsMap = new HashMap<>();
+ credentialsMap.put("appName", "appName");
+ credentialsMap.put(PORTAL_USERNAME_HEADER_KEY, PORTAL_USERNAME_HEADER_KEY);
+ credentialsMap.put(PORTAL_PASSWORD_HEADER_KEY, PORTAL_PASSWORD_HEADER_KEY);
+ doAnswer(inv -> {
+ logger.debug("getAppCredentials");
+ return credentialsMap;
+ }).when(mockManager).getAppCredentials();
+ doAnswer(inv -> {
+ logger.debug("getUserId");
+ return "userId";
+ }).when(mockManager).valdiateEcompSso(any(HttpServletRequest.class));
+ doAnswer(inv -> {
+ logger.debug("getAppCredentials");
+ return credentialsMap;
+ }).when(mockManager).getAppCredentials();
+ return mockManager;
+ }
+
+ // This implementation is so light it can be used during tests.
+ @Bean
+ public DashboardUserManager dashboardUserManager() throws IOException, PortalAPIException {
+ File f = new File("/tmp/users.json");
+ if (f.exists())
+ f.delete();
+ DashboardUserManager um = new DashboardUserManager(f.getAbsolutePath());
+ // Mock user for convenience in testing
+ EcompUser demo = new EcompUser();
+ demo.setLoginId("demo");
+ demo.setFirstName("Demo");
+ demo.setLastName("User");
+ demo.setActive(true);
+ EcompRole role = new EcompRole();
+ role.setName("view");
+ Set roles = new HashSet<>();
+ roles.add(role);
+ demo.setRoles(roles);
+ um.createUser(demo);
+ return um;
+ }
+
+}
diff --git a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/AbstractControllerTest.java b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AbstractControllerTest.java
similarity index 87%
rename from webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/AbstractControllerTest.java
rename to webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AbstractControllerTest.java
index c4d9a4d6..0fa2785e 100644
--- a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/AbstractControllerTest.java
+++ b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AbstractControllerTest.java
@@ -17,7 +17,7 @@
* limitations under the License.
* ========================LICENSE_END===================================
*/
-package org.oransc.ric.portal.dashboard.test.controller;
+package org.oransc.ric.portal.dashboard.controller;
import java.lang.invoke.MethodHandles;
import java.net.URI;
@@ -26,6 +26,7 @@ import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.oransc.ric.portal.dashboard.config.WebSecurityMockConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -98,4 +99,14 @@ public class AbstractControllerTest {
logger.info("Context loads on mock profile");
}
+ public TestRestTemplate testRestTemplateAdminRole() {
+ return restTemplate.withBasicAuth(WebSecurityMockConfiguration.TEST_CRED_ADMIN,
+ WebSecurityMockConfiguration.TEST_CRED_ADMIN);
+ }
+
+ public TestRestTemplate testRestTemplateStandardRole() {
+ return restTemplate.withBasicAuth(WebSecurityMockConfiguration.TEST_CRED_STANDARD,
+ WebSecurityMockConfiguration.TEST_CRED_STANDARD);
+ }
+
}
diff --git a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/AcXappControllerTest.java b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AcXappControllerTest.java
similarity index 88%
rename from webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/AcXappControllerTest.java
rename to webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AcXappControllerTest.java
index f0ba8b58..8f8292f1 100644
--- a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/AcXappControllerTest.java
+++ b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AcXappControllerTest.java
@@ -17,15 +17,14 @@
* limitations under the License.
* ========================LICENSE_END===================================
*/
-package org.oransc.ric.portal.dashboard.test.controller;
+package org.oransc.ric.portal.dashboard.controller;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.net.URI;
+
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
-
-import org.oransc.ric.portal.dashboard.controller.AcXappController;
import org.oransc.ric.portal.dashboard.model.SuccessTransport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -53,7 +52,8 @@ public class AcXappControllerTest extends AbstractControllerTest {
// Always returns 501; surprised that no exception is thrown.
URI uri = buildUri(null, AcXappController.CONTROLLER_PATH, AcXappController.ADMCTRL_METHOD);
logger.info("Invoking {}", uri);
- ResponseEntity response = restTemplate.exchange(uri, HttpMethod.GET, null, String.class);
+ ResponseEntity response = testRestTemplateStandardRole().exchange(uri, HttpMethod.GET, null,
+ String.class);
Assertions.assertTrue(response.getStatusCode().is5xxServerError());
}
@@ -64,7 +64,8 @@ public class AcXappControllerTest extends AbstractControllerTest {
URI uri = buildUri(null, AcXappController.CONTROLLER_PATH, AcXappController.ADMCTRL_METHOD);
HttpEntity entity = new HttpEntity<>(body);
logger.info("Invoking {}", uri);
- ResponseEntity voidResponse = restTemplate.exchange(uri, HttpMethod.PUT, entity, Void.class);
+ ResponseEntity voidResponse = testRestTemplateAdminRole().exchange(uri, HttpMethod.PUT, entity,
+ Void.class);
Assertions.assertTrue(voidResponse.getStatusCode().is2xxSuccessful());
}
diff --git a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/AdminControllerTest.java b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AdminControllerTest.java
similarity index 80%
rename from webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/AdminControllerTest.java
rename to webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AdminControllerTest.java
index be66270f..018350d6 100644
--- a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/AdminControllerTest.java
+++ b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AdminControllerTest.java
@@ -17,7 +17,7 @@
* limitations under the License.
* ========================LICENSE_END===================================
*/
-package org.oransc.ric.portal.dashboard.test.controller;
+package org.oransc.ric.portal.dashboard.controller;
import java.lang.invoke.MethodHandles;
import java.net.URI;
@@ -25,7 +25,6 @@ import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
-import org.oransc.ric.portal.dashboard.controller.AdminController;
import org.oransc.ric.portal.dashboard.model.DashboardUser;
import org.oransc.ric.portal.dashboard.model.SuccessTransport;
import org.slf4j.Logger;
@@ -55,13 +54,22 @@ public class AdminControllerTest extends AbstractControllerTest {
}
@Test
- public void usersTest() {
+ public void getUsersTest() {
URI uri = buildUri(null, AdminController.CONTROLLER_PATH, AdminController.USER_METHOD);
logger.info("Invoking {}", uri);
- ResponseEntity> response = restTemplate.exchange(uri, HttpMethod.GET, null,
+ ResponseEntity> response = testRestTemplateAdminRole().exchange(uri, HttpMethod.GET, null,
new ParameterizedTypeReference>() {
});
Assertions.assertFalse(response.getBody().isEmpty());
}
+ @Test
+ public void getUsersTestRoleAuthFail() {
+ URI uri = buildUri(null, AdminController.CONTROLLER_PATH, AdminController.USER_METHOD);
+ logger.info("Invoking {}", uri);
+ ResponseEntity response = testRestTemplateStandardRole().exchange(uri, HttpMethod.GET, null,
+ String.class);
+ Assertions.assertTrue(response.getStatusCode().is4xxClientError());
+ }
+
}
diff --git a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/AnrXappControllerTest.java b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AnrXappControllerTest.java
similarity index 88%
rename from webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/AnrXappControllerTest.java
rename to webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AnrXappControllerTest.java
index 7ce5976e..3155563b 100644
--- a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/AnrXappControllerTest.java
+++ b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AnrXappControllerTest.java
@@ -17,7 +17,7 @@
* limitations under the License.
* ========================LICENSE_END===================================
*/
-package org.oransc.ric.portal.dashboard.test.controller;
+package org.oransc.ric.portal.dashboard.controller;
import java.lang.invoke.MethodHandles;
import java.net.URI;
@@ -27,7 +27,6 @@ import org.junit.jupiter.api.Test;
import org.oransc.ric.anrxapp.client.model.GgNodeBTable;
import org.oransc.ric.anrxapp.client.model.NeighborCellRelationMod;
import org.oransc.ric.anrxapp.client.model.NeighborCellRelationTable;
-import org.oransc.ric.portal.dashboard.controller.AnrXappController;
import org.oransc.ric.portal.dashboard.model.SuccessTransport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -67,7 +66,7 @@ public class AnrXappControllerTest extends AbstractControllerTest {
public void gnodebsTest() {
URI uri = buildUri(null, AnrXappController.CONTROLLER_PATH, AnrXappController.GNODEBS_METHOD);
logger.info("Invoking {}", uri);
- GgNodeBTable list = restTemplate.getForObject(uri, GgNodeBTable.class);
+ GgNodeBTable list = testRestTemplateStandardRole().getForObject(uri, GgNodeBTable.class);
Assertions.assertFalse(list.getGNodeBIds().isEmpty());
}
@@ -75,7 +74,8 @@ public class AnrXappControllerTest extends AbstractControllerTest {
public void ncrtGetTest() {
URI uri = buildUri(null, AnrXappController.CONTROLLER_PATH, AnrXappController.NCRT_METHOD);
logger.info("Invoking {}", uri);
- NeighborCellRelationTable table = restTemplate.getForObject(uri, NeighborCellRelationTable.class);
+ NeighborCellRelationTable table = testRestTemplateStandardRole().getForObject(uri,
+ NeighborCellRelationTable.class);
Assertions.assertFalse(table.getNcrtRelations().isEmpty());
}
@@ -85,7 +85,8 @@ public class AnrXappControllerTest extends AbstractControllerTest {
AnrXappController.PP_SERVING, "serving", AnrXappController.PP_NEIGHBOR, "neighbor");
logger.info("Invoking {}", uri);
HttpEntity entity = new HttpEntity<>(new NeighborCellRelationMod());
- ResponseEntity voidResponse = restTemplate.exchange(uri, HttpMethod.PUT, entity, Void.class);
+ ResponseEntity voidResponse = testRestTemplateAdminRole().exchange(uri, HttpMethod.PUT, entity,
+ Void.class);
Assertions.assertTrue(voidResponse.getStatusCode().is2xxSuccessful());
}
@@ -94,7 +95,8 @@ public class AnrXappControllerTest extends AbstractControllerTest {
URI uri = buildUri(null, AnrXappController.CONTROLLER_PATH, AnrXappController.NCRT_METHOD,
AnrXappController.PP_SERVING, "serving", AnrXappController.PP_NEIGHBOR, "neighbor");
logger.info("Invoking {}", uri);
- ResponseEntity voidResponse = restTemplate.exchange(uri, HttpMethod.DELETE, null, Void.class);
+ ResponseEntity voidResponse = testRestTemplateAdminRole().exchange(uri, HttpMethod.DELETE, null,
+ Void.class);
Assertions.assertTrue(voidResponse.getStatusCode().is2xxSuccessful());
}
diff --git a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/AppManagerControllerTest.java b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AppManagerControllerTest.java
similarity index 84%
rename from webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/AppManagerControllerTest.java
rename to webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AppManagerControllerTest.java
index 936d698f..a216c764 100644
--- a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/AppManagerControllerTest.java
+++ b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AppManagerControllerTest.java
@@ -17,7 +17,7 @@
* limitations under the License.
* ========================LICENSE_END===================================
*/
-package org.oransc.ric.portal.dashboard.test.controller;
+package org.oransc.ric.portal.dashboard.controller;
import java.lang.invoke.MethodHandles;
import java.net.URI;
@@ -30,7 +30,6 @@ import org.oransc.ric.plt.appmgr.client.model.ConfigMetadata;
import org.oransc.ric.plt.appmgr.client.model.XAppConfig;
import org.oransc.ric.plt.appmgr.client.model.XAppInfo;
import org.oransc.ric.plt.appmgr.client.model.Xapp;
-import org.oransc.ric.portal.dashboard.controller.AppManagerController;
import org.oransc.ric.portal.dashboard.model.DashboardDeployableXapps;
import org.oransc.ric.portal.dashboard.model.SuccessTransport;
import org.slf4j.Logger;
@@ -71,7 +70,8 @@ public class AppManagerControllerTest extends AbstractControllerTest {
public void appListTest() {
URI uri = buildUri(null, AppManagerController.CONTROLLER_PATH, AppManagerController.XAPPS_LIST_METHOD);
logger.info("Invoking {}", uri);
- DashboardDeployableXapps apps = restTemplate.getForObject(uri, DashboardDeployableXapps.class);
+ DashboardDeployableXapps apps = testRestTemplateStandardRole().getForObject(uri,
+ DashboardDeployableXapps.class);
Assertions.assertFalse(apps.isEmpty());
}
@@ -79,7 +79,7 @@ public class AppManagerControllerTest extends AbstractControllerTest {
public void appStatusesTest() {
URI uri = buildUri(null, AppManagerController.CONTROLLER_PATH, AppManagerController.XAPPS_METHOD);
logger.info("Invoking {}", uri);
- AllDeployedXapps apps = restTemplate.getForObject(uri, AllDeployedXapps.class);
+ AllDeployedXapps apps = testRestTemplateStandardRole().getForObject(uri, AllDeployedXapps.class);
Assertions.assertFalse(apps.isEmpty());
}
@@ -87,7 +87,7 @@ public class AppManagerControllerTest extends AbstractControllerTest {
public void appStatusTest() {
URI uri = buildUri(null, AppManagerController.CONTROLLER_PATH, AppManagerController.XAPPS_METHOD, "app");
logger.info("Invoking {}", uri);
- Xapp app = restTemplate.getForObject(uri, Xapp.class);
+ Xapp app = testRestTemplateStandardRole().getForObject(uri, Xapp.class);
Assertions.assertFalse(app.getName().isEmpty());
}
@@ -96,7 +96,7 @@ public class AppManagerControllerTest extends AbstractControllerTest {
URI uri = buildUri(null, AppManagerController.CONTROLLER_PATH, AppManagerController.XAPPS_METHOD);
logger.info("Invoking {}", uri);
XAppInfo info = new XAppInfo();
- Xapp app = restTemplate.postForObject(uri, info, Xapp.class);
+ Xapp app = testRestTemplateAdminRole().postForObject(uri, info, Xapp.class);
Assertions.assertFalse(app.getName().isEmpty());
}
@@ -104,7 +104,8 @@ public class AppManagerControllerTest extends AbstractControllerTest {
public void undeployAppTest() {
URI uri = buildUri(null, AppManagerController.CONTROLLER_PATH, AppManagerController.XAPPS_METHOD, "app");
logger.info("Invoking {}", uri);
- ResponseEntity voidResponse = restTemplate.exchange(uri, HttpMethod.DELETE, null, Void.class);
+ ResponseEntity voidResponse = testRestTemplateAdminRole().exchange(uri, HttpMethod.DELETE, null,
+ Void.class);
Assertions.assertTrue(voidResponse.getStatusCode().is2xxSuccessful());
}
@@ -112,7 +113,7 @@ public class AppManagerControllerTest extends AbstractControllerTest {
public void getConfigTest() {
URI uri = buildUri(null, AppManagerController.CONTROLLER_PATH, AppManagerController.CONFIG_METHOD);
logger.info("Invoking {}", uri);
- AllXappConfig config = restTemplate.getForObject(uri, AllXappConfig.class);
+ AllXappConfig config = testRestTemplateStandardRole().getForObject(uri, AllXappConfig.class);
Assertions.assertFalse(config.isEmpty());
}
@@ -121,7 +122,7 @@ public class AppManagerControllerTest extends AbstractControllerTest {
URI uri = buildUri(null, AppManagerController.CONTROLLER_PATH, AppManagerController.CONFIG_METHOD);
logger.info("Invoking {}", uri);
XAppConfig newConfig = new XAppConfig();
- XAppConfig response = restTemplate.postForObject(uri, newConfig, XAppConfig.class);
+ XAppConfig response = testRestTemplateAdminRole().postForObject(uri, newConfig, XAppConfig.class);
Assertions.assertNotNull(response.getConfig());
}
@@ -131,7 +132,8 @@ public class AppManagerControllerTest extends AbstractControllerTest {
logger.info("Invoking {}", uri);
ConfigMetadata delConfig = new ConfigMetadata();
HttpEntity entity = new HttpEntity<>(delConfig);
- ResponseEntity voidResponse = restTemplate.exchange(uri, HttpMethod.DELETE, entity, Void.class);
+ ResponseEntity voidResponse = testRestTemplateAdminRole().exchange(uri, HttpMethod.DELETE, entity,
+ Void.class);
Assertions.assertTrue(voidResponse.getStatusCode().is2xxSuccessful());
}
diff --git a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/DefaultContextTest.java b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/DefaultContextTest.java
similarity index 96%
rename from webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/DefaultContextTest.java
rename to webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/DefaultContextTest.java
index b82c3346..328be6d6 100644
--- a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/DefaultContextTest.java
+++ b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/DefaultContextTest.java
@@ -17,7 +17,7 @@
* limitations under the License.
* ========================LICENSE_END===================================
*/
-package org.oransc.ric.portal.dashboard.test.controller;
+package org.oransc.ric.portal.dashboard.controller;
import java.lang.invoke.MethodHandles;
diff --git a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/E2ManagerControllerTest.java b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/E2ManagerControllerTest.java
similarity index 81%
rename from webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/E2ManagerControllerTest.java
rename to webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/E2ManagerControllerTest.java
index 10baeb6a..d65f2b47 100644
--- a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/test/controller/E2ManagerControllerTest.java
+++ b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/E2ManagerControllerTest.java
@@ -17,7 +17,7 @@
* limitations under the License.
* ========================LICENSE_END===================================
*/
-package org.oransc.ric.portal.dashboard.test.controller;
+package org.oransc.ric.portal.dashboard.controller;
import java.lang.invoke.MethodHandles;
import java.net.URI;
@@ -28,7 +28,6 @@ import org.junit.jupiter.api.Test;
import org.oransc.ric.e2mgr.client.model.GetNodebResponse;
import org.oransc.ric.e2mgr.client.model.NodebIdentity;
import org.oransc.ric.e2mgr.client.model.SetupRequest;
-import org.oransc.ric.portal.dashboard.controller.E2ManagerController;
import org.oransc.ric.portal.dashboard.model.RanDetailsTransport;
import org.oransc.ric.portal.dashboard.model.SuccessTransport;
import org.slf4j.Logger;
@@ -62,8 +61,8 @@ public class E2ManagerControllerTest extends AbstractControllerTest {
public void ranDetailsTest() {
URI uri = buildUri(null, E2ManagerController.CONTROLLER_PATH, E2ManagerController.RAN_METHOD);
logger.info("Invoking {}", uri);
- ResponseEntity> response = restTemplate.exchange(uri, HttpMethod.GET, null,
- new ParameterizedTypeReference>() {
+ ResponseEntity> response = testRestTemplateStandardRole().exchange(uri,
+ HttpMethod.GET, null, new ParameterizedTypeReference>() {
});
Assertions.assertFalse(response.getBody().isEmpty());
}
@@ -72,8 +71,8 @@ public class E2ManagerControllerTest extends AbstractControllerTest {
public void nodebListTest() {
URI uri = buildUri(null, E2ManagerController.CONTROLLER_PATH, E2ManagerController.NODEB_LIST_METHOD);
logger.info("Invoking {}", uri);
- ResponseEntity> response = restTemplate.exchange(uri, HttpMethod.GET, null,
- new ParameterizedTypeReference>() {
+ ResponseEntity> response = testRestTemplateStandardRole().exchange(uri, HttpMethod.GET,
+ null, new ParameterizedTypeReference>() {
});
Assertions.assertFalse(response.getBody().isEmpty());
}
@@ -82,7 +81,7 @@ public class E2ManagerControllerTest extends AbstractControllerTest {
public void nodebStatusTest() {
URI uri = buildUri(null, E2ManagerController.CONTROLLER_PATH, E2ManagerController.NODEB_METHOD, "nodeb");
logger.info("Invoking {}", uri);
- GetNodebResponse response = restTemplate.getForObject(uri, GetNodebResponse.class);
+ GetNodebResponse response = testRestTemplateStandardRole().getForObject(uri, GetNodebResponse.class);
Assertions.assertNotNull(response.getRanName());
}
@@ -90,7 +89,8 @@ public class E2ManagerControllerTest extends AbstractControllerTest {
public void bigRedButtonTest() {
URI uri = buildUri(null, E2ManagerController.CONTROLLER_PATH, E2ManagerController.NODEB_METHOD);
logger.info("Invoking {}", uri);
- ResponseEntity voidResponse = restTemplate.exchange(uri, HttpMethod.DELETE, null, Void.class);
+ ResponseEntity voidResponse = testRestTemplateAdminRole().exchange(uri, HttpMethod.DELETE, null,
+ Void.class);
Assertions.assertTrue(voidResponse.getStatusCode().is2xxSuccessful());
}
@@ -100,7 +100,8 @@ public class E2ManagerControllerTest extends AbstractControllerTest {
logger.info("Invoking {}", uri);
SetupRequest setup = new SetupRequest();
HttpEntity entity = new HttpEntity<>(setup);
- ResponseEntity voidResponse = restTemplate.exchange(uri, HttpMethod.POST, entity, Void.class);
+ ResponseEntity voidResponse = testRestTemplateAdminRole().exchange(uri, HttpMethod.POST, entity,
+ Void.class);
Assertions.assertTrue(voidResponse.getStatusCode().is2xxSuccessful());
}
@@ -110,7 +111,8 @@ public class E2ManagerControllerTest extends AbstractControllerTest {
logger.info("Invoking {}", uri);
SetupRequest setup = new SetupRequest();
HttpEntity entity = new HttpEntity<>(setup);
- ResponseEntity voidResponse = restTemplate.exchange(uri, HttpMethod.POST, entity, Void.class);
+ ResponseEntity voidResponse = testRestTemplateAdminRole().exchange(uri, HttpMethod.POST, entity,
+ Void.class);
Assertions.assertTrue(voidResponse.getStatusCode().is2xxSuccessful());
}
diff --git a/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/PortalRestCentralServiceTest.java b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/PortalRestCentralServiceTest.java
new file mode 100644
index 00000000..509dda7c
--- /dev/null
+++ b/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/PortalRestCentralServiceTest.java
@@ -0,0 +1,103 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property and Nokia
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.controller;
+
+import java.lang.invoke.MethodHandles;
+import java.net.URI;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.onap.portalsdk.core.restful.domain.EcompUser;
+import org.oransc.ric.portal.dashboard.DashboardConstants;
+import org.oransc.ric.portal.dashboard.config.WebSecurityMockConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+
+public class PortalRestCentralServiceTest extends AbstractControllerTest {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ // paths are hardcoded here exactly like the EPSDK-FW library :(
+
+ @Test
+ public void getAnalyticsTest() {
+ // paths are hardcoded here exactly like the EPSDK-FW library :(
+ URI uri = buildUri(null, PortalApiConstants.API_PREFIX, "/analytics");
+ logger.info("Invoking {}", uri);
+ ResponseEntity response = restTemplate.exchange(uri, HttpMethod.GET, null, String.class);
+ // No Portal is available so this always fails
+ Assertions.assertTrue(response.getStatusCode().is4xxClientError());
+ }
+
+ @Test
+ public void getLoginPageTest() {
+ URI uri = buildUri(null, DashboardConstants.LOGIN_PAGE);
+ logger.info("Invoking {}", uri);
+ ResponseEntity response = restTemplate.exchange(uri, HttpMethod.GET, null, String.class);
+ Assertions.assertTrue(response.getStatusCode().is2xxSuccessful());
+ Assertions.assertTrue(response.getBody().contains("Please log in"));
+ }
+
+ private HttpEntity getEntityWithHeaders(Object body) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.set(WebSecurityMockConfiguration.PORTAL_USERNAME_HEADER_KEY,
+ WebSecurityMockConfiguration.PORTAL_USERNAME_HEADER_KEY);
+ headers.set(WebSecurityMockConfiguration.PORTAL_PASSWORD_HEADER_KEY,
+ WebSecurityMockConfiguration.PORTAL_PASSWORD_HEADER_KEY);
+ HttpEntity entity = new HttpEntity<>(body, headers);
+ return entity;
+ }
+
+ @Test
+ public void createUserTest() {
+ final String loginId = "login1";
+ URI create = buildUri(null, PortalApiConstants.API_PREFIX, "user");
+ logger.info("Invoking {}", create);
+ EcompUser user = new EcompUser();
+ user.setLoginId(loginId);
+ HttpEntity requestEntity = getEntityWithHeaders(user);
+ ResponseEntity response = restTemplate.exchange(create, HttpMethod.POST, requestEntity, String.class);
+ Assertions.assertTrue(response.getStatusCode().is2xxSuccessful());
+ }
+
+ @Test
+ public void updateUserTest() {
+ final String loginId = "login2";
+ URI create = buildUri(null, PortalApiConstants.API_PREFIX, "user");
+ logger.info("Invoking {}", create);
+ EcompUser user = new EcompUser();
+ user.setLoginId(loginId);
+ HttpEntity requestEntity = getEntityWithHeaders(user);
+ // Create
+ ResponseEntity response = restTemplate.exchange(create, HttpMethod.POST, requestEntity, String.class);
+ Assertions.assertTrue(response.getStatusCode().is2xxSuccessful());
+ URI update = buildUri(null, PortalApiConstants.API_PREFIX, "user", loginId);
+ user.setEmail("user@company.org");
+ requestEntity = getEntityWithHeaders(user);
+ response = restTemplate.exchange(update, HttpMethod.POST, requestEntity, String.class);
+ Assertions.assertTrue(response.getStatusCode().is2xxSuccessful());
+ }
+
+}
diff --git a/webapp-backend/src/test/resources/key.properties b/webapp-backend/src/test/resources/key.properties
new file mode 100644
index 00000000..ff9d220e
--- /dev/null
+++ b/webapp-backend/src/test/resources/key.properties
@@ -0,0 +1,22 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property and Nokia
+# %%
+# Licensed 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.
+# ========================LICENSE_END===================================
+
+# Test properties for the EPSDK-FW library.
+# This file must be present on the Java classpath.
+
+cipher.enc.key = bogus
diff --git a/webapp-backend/src/test/resources/portal.properties b/webapp-backend/src/test/resources/portal.properties
new file mode 100644
index 00000000..94e7391b
--- /dev/null
+++ b/webapp-backend/src/test/resources/portal.properties
@@ -0,0 +1,26 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property and Nokia
+# %%
+# Licensed 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.
+# ========================LICENSE_END===================================
+
+# Test properties for the EPSDK-FW library.
+# This file must be present on the Java classpath.
+
+portal.api.impl.class = org.oransc.ric.portal.dashboard.portalapi.PortalRestCentralServiceImpl
+role_access_centralized = remote
+ecomp_redirect_url = https://www.wikipedia.org
+ecomp_rest_url = http://localhost/portal
+ueb_app_key = abcdef1234567890
diff --git a/webapp-frontend/src/app/anr-xapp/anr-xapp.datasource.ts b/webapp-frontend/src/app/anr-xapp/anr-xapp.datasource.ts
index cbf353f5..5d923c32 100644
--- a/webapp-frontend/src/app/anr-xapp/anr-xapp.datasource.ts
+++ b/webapp-frontend/src/app/anr-xapp/anr-xapp.datasource.ts
@@ -50,9 +50,9 @@ export class ANRXappDataSource extends DataSource {
this.loadingSubject.next(true);
this.anrXappService.getNcrtInfo(ggnodeb, servingCellNrcgi, neighborCellNrpci)
.pipe(
- catchError( (err: HttpErrorResponse) => {
- console.log('ANRXappDataSource failed: ' + err.message);
- this.notificationService.error('Failed to get data.');
+ catchError( (her: HttpErrorResponse) => {
+ console.log('ANRXappDataSource failed: ' + her.message);
+ this.notificationService.error('Failed to get data: ' + her.message);
return of([]);
}),
finalize(() => this.loadingSubject.next(false))
diff --git a/webapp-frontend/src/app/app-control/app-control.component.ts b/webapp-frontend/src/app/app-control/app-control.component.ts
index 341754b0..35c910d8 100644
--- a/webapp-frontend/src/app/app-control/app-control.component.ts
+++ b/webapp-frontend/src/app/app-control/app-control.component.ts
@@ -69,7 +69,7 @@ export class AppControlComponent implements OnInit {
}
onUndeployApp(app: XappControlRow): void {
- this.confirmDialogService.openConfirmDialog('Are you sure you want to undeploy xApp ' + app.xapp + '?')
+ this.confirmDialogService.openConfirmDialog('Are you sure you want to undeploy App ' + app.xapp + '?')
.afterClosed().subscribe( (res: boolean) => {
if (res) {
this.appMgrSvc.undeployXapp(app.xapp).subscribe(
@@ -77,14 +77,19 @@ export class AppControlComponent implements OnInit {
this.dataSource.loadTable();
switch (httpResponse.status) {
case 200:
- this.notificationService.success('xApp undeployed successfully!');
+ this.notificationService.success('App undeployed successfully!');
break;
default:
- this.notificationService.warn('xApp undeploy failed.');
+ this.notificationService.warn('App undeploy failed.');
}
},
- ( (error: HttpErrorResponse) => {
- this.notificationService.warn(error.message);
+ ( (her: HttpErrorResponse) => {
+ // the error field should have an ErrorTransport object
+ let msg = her.message;
+ if (her.error && her.error.message) {
+ msg = her.error.message;
+ }
+ this.notificationService.warn('App undeploy failed: ' + msg);
})
);
}
diff --git a/webapp-frontend/src/app/app-control/app-control.datasource.ts b/webapp-frontend/src/app/app-control/app-control.datasource.ts
index fa98dfad..6a19286c 100644
--- a/webapp-frontend/src/app/app-control/app-control.datasource.ts
+++ b/webapp-frontend/src/app/app-control/app-control.datasource.ts
@@ -59,9 +59,9 @@ export class AppControlDataSource extends DataSource {
this.loadingSubject.next(true);
this.appMgrSvc.getDeployed()
.pipe(
- catchError( (err: HttpErrorResponse) => {
- console.log('AppControlDataSource failed: ' + err.message);
- this.notificationService.error('Failed to get applications.');
+ catchError( (her: HttpErrorResponse) => {
+ console.log('AppControlDataSource failed: ' + her.message);
+ this.notificationService.error('Failed to get applications: ' + her.message);
return of([]);
}),
finalize(() => this.loadingSubject.next(false))
diff --git a/webapp-frontend/src/app/catalog/catalog.component.ts b/webapp-frontend/src/app/catalog/catalog.component.ts
index 4eab4083..21860ac7 100644
--- a/webapp-frontend/src/app/catalog/catalog.component.ts
+++ b/webapp-frontend/src/app/catalog/catalog.component.ts
@@ -60,11 +60,16 @@ export class CatalogComponent implements OnInit {
if (res) {
this.appMgrService.deployXapp(app.name).subscribe(
(response: HttpResponse) => {
- this.notificationService.success('Deploy succeeded!');
+ this.notificationService.success('App deploy succeeded!');
},
- (error: HttpErrorResponse) => {
- this.notificationService.warn('Deploy failed: ' + error.message);
- }
+ ( (her: HttpErrorResponse) => {
+ // the error field should have an ErrorTransport object
+ let msg = her.message;
+ if (her.error && her.error.message) {
+ msg = her.error.message;
+ }
+ this.notificationService.warn('App deploy failed: ' + msg);
+ })
);
}
}
diff --git a/webapp-frontend/src/app/catalog/catalog.datasource.ts b/webapp-frontend/src/app/catalog/catalog.datasource.ts
index 5c9ac947..fb54b84f 100644
--- a/webapp-frontend/src/app/catalog/catalog.datasource.ts
+++ b/webapp-frontend/src/app/catalog/catalog.datasource.ts
@@ -50,9 +50,9 @@ export class CatalogDataSource extends DataSource {
this.loadingSubject.next(true);
this.appMgrSvc.getDeployable()
.pipe(
- catchError( (err: HttpErrorResponse) => {
- console.log('CatalogDataSource failed: ' + err.message);
- this.notificationService.error('Failed to get applications.');
+ catchError( (her: HttpErrorResponse) => {
+ console.log('CatalogDataSource failed: ' + her.message);
+ this.notificationService.error('Failed to get applications: ' + her.message);
return of([]);
}),
finalize(() => this.loadingSubject.next(false))
diff --git a/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html b/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html
index 2b586ffa..2ab5f7c3 100644
--- a/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html
+++ b/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html
@@ -20,7 +20,7 @@
-
+
home Home
diff --git a/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.ts b/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.ts
index 25442f77..7c63f132 100644
--- a/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.ts
+++ b/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.ts
@@ -86,9 +86,14 @@ export class RanControlConnectDialogComponent implements OnInit {
this.notifService.success('Connect succeeded!');
this.dialogRef.close(true);
},
- ( (error: HttpErrorResponse) => {
+ ( (her: HttpErrorResponse) => {
this.processing = false;
- this.errorService.displayError('RAN Connection Failed: ' + error.message);
+ // the error field carries the server's response
+ let msg = her.message;
+ if (her.error && her.error.message) {
+ msg = her.error.message;
+ }
+ this.errorService.displayError('Connect failed: ' + msg);
// keep the dialog open
})
);
diff --git a/webapp-frontend/src/app/ran-control/ran-control.component.ts b/webapp-frontend/src/app/ran-control/ran-control.component.ts
index 26665ca6..f5ef36ca 100644
--- a/webapp-frontend/src/app/ran-control/ran-control.component.ts
+++ b/webapp-frontend/src/app/ran-control/ran-control.component.ts
@@ -66,12 +66,17 @@ export class RanControlComponent implements OnInit {
this.e2MgrSvc.nodebDelete().subscribe(
( response: HttpResponse) => {
if (response.status === 200) {
- this.notificationService.success('Disconnect all RAN Connections Succeeded!');
+ this.notificationService.success('Disconnect succeeded!');
this.dataSource.loadTable();
}
},
- ( (error: HttpErrorResponse) => {
- this.errorDialogService.displayError(aboutError + error.message);
+ ( (her: HttpErrorResponse) => {
+ // the error field should have an ErrorTransport object
+ let msg = her.message;
+ if (her.error && her.error.message) {
+ msg = her.error.message;
+ }
+ this.errorDialogService.displayError('Disconnect failed: ' + msg);
})
);
}
diff --git a/webapp-frontend/src/app/ran-control/ran-control.datasource.ts b/webapp-frontend/src/app/ran-control/ran-control.datasource.ts
index d919f4ea..50626b55 100644
--- a/webapp-frontend/src/app/ran-control/ran-control.datasource.ts
+++ b/webapp-frontend/src/app/ran-control/ran-control.datasource.ts
@@ -47,9 +47,9 @@ export class RANControlDataSource extends DataSource {
this.loadingSubject.next(true);
this.e2MgrSvcservice.getRan()
.pipe(
- catchError( (err: HttpErrorResponse) => {
- console.log('RANControlDataSource failed: ' + err.message);
- this.notificationService.error('Failed to get RAN details.');
+ catchError( (her: HttpErrorResponse) => {
+ console.log('RANControlDataSource failed: ' + her.message);
+ this.notificationService.error('Failed to get RAN details: ' + her.message);
return of([]);
}),
finalize( () => this.loadingSubject.next(false) )
diff --git a/webapp-frontend/src/app/services/stats/stats.service.ts b/webapp-frontend/src/app/services/stats/stats.service.ts
index 255dbca7..e4e667a5 100644
--- a/webapp-frontend/src/app/services/stats/stats.service.ts
+++ b/webapp-frontend/src/app/services/stats/stats.service.ts
@@ -134,8 +134,8 @@ export class StatsService {
this.delayMax = res[5].value;
this.loadMax = res[6].value;
},
- (err: HttpErrorResponse) => {
- console.log (err.message);
+ (her: HttpErrorResponse) => {
+ console.log ('loadConfig failed: ' + her.message);
});
}
}
diff --git a/webapp-frontend/src/app/user/user.datasource.ts b/webapp-frontend/src/app/user/user.datasource.ts
index dcc03690..f53a2d3e 100644
--- a/webapp-frontend/src/app/user/user.datasource.ts
+++ b/webapp-frontend/src/app/user/user.datasource.ts
@@ -50,9 +50,9 @@ export class UserDataSource extends DataSource {
this.loadingSubject.next(true);
this.dashboardSvc.getUsers()
.pipe(
- catchError( (err: HttpErrorResponse) => {
- console.log('UserDataSource failed: ' + err.message);
- this.notificationService.error('Failed to get users.');
+ catchError( (her: HttpErrorResponse) => {
+ console.log('UserDataSource failed: ' + her.message);
+ this.notificationService.error('Failed to get users: ' + her.message);
return of([]);
}),
finalize(() => this.loadingSubject.next(false))
--
2.16.6