From 93844197b39e8ba4d7623f8c5bbc395b667b7310 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Wed, 23 Aug 2023 15:35:54 +0200
Subject: [PATCH 01/42] Save&restore service: added authentication for non-GET
requests
---
.../saveandrestore/model/UserData.java | 56 ++++++
services/save-and-restore/pom.xml | 18 ++
.../web/config/SessionFilter.java | 102 ++++++++++
.../web/config/WebSecurityConfig.java | 185 ++++++++++++++++++
.../controllers/AuthenticationController.java | 71 +++++++
.../src/main/resources/application.properties | 43 ++++
6 files changed, 475 insertions(+)
create mode 100644 app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/UserData.java
create mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/SessionFilter.java
create mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
create mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java
diff --git a/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/UserData.java b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/UserData.java
new file mode 100644
index 0000000000..97c5cfd91c
--- /dev/null
+++ b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/UserData.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package org.phoebus.applications.saveandrestore.model;
+
+import java.util.List;
+
+/**
+ * Simple pojo used to convey username and list of roles to a client upon
+ * login or explicit request.
+ */
+public class UserData {
+
+ private String userName;
+ private List roles;
+
+ public UserData(){
+
+ }
+
+ public UserData(String userName, List roles){
+ this.userName = userName;
+ this.roles = roles;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public List getRoles() {
+ return roles;
+ }
+
+ public void setRoles(List roles) {
+ this.roles = roles;
+ }
+}
diff --git a/services/save-and-restore/pom.xml b/services/save-and-restore/pom.xml
index 39b45f3f9b..ac969996ea 100644
--- a/services/save-and-restore/pom.xml
+++ b/services/save-and-restore/pom.xml
@@ -74,6 +74,24 @@
spring-boot-starter-web
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+ org.springframework.ldap
+ spring-ldap-core
+
+
+ org.springframework.security
+ spring-security-ldap
+
+
+ org.springframework.security
+ spring-security-test
+
+
jakarta.jsonjakarta.json-api
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/SessionFilter.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/SessionFilter.java
new file mode 100644
index 0000000000..2db87594c2
--- /dev/null
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/SessionFilter.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package org.phoebus.service.saveandrestore.web.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.filter.GenericFilterBean;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Filter that will take care of authenticating requests that come with
+ * a basic authentication header.
+ *
+ * This class should not be instantiated as a bean in the application configuration. If it is, the
+ * doFilter() method will be called for each endpoint URI, effectively defeating the purpose of the
+ * configuration of ignored URI patterns set up in the Spring Security context, see
+ * {@link WebSecurityConfig#configure(WebSecurity)}.
+ */
+public class SessionFilter extends GenericFilterBean {
+
+ private AuthenticationManager authenticationManager;
+ private ObjectMapper objectMapper;
+
+ public SessionFilter(AuthenticationManager authenticationManager) {
+ this.authenticationManager = authenticationManager;
+ objectMapper = new ObjectMapper();
+ }
+
+ /**
+ * @param request A {@link ServletRequest}
+ * @param response A {@link ServletResponse}
+ * @param filterChain The {@link FilterChain} to which this implementation contributes.
+ * @throws IOException May be thrown by upstream filters.
+ * @throws ServletException May be thrown by upstream filters.
+ */
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
+ HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+
+ String basicAuthenticationHeader = httpServletRequest.getHeader("Authorization");
+ String[] usernameAndPassword = getUsernameAndPassword(basicAuthenticationHeader);
+ if (usernameAndPassword == null) {
+ SecurityContextHolder.getContext().setAuthentication(null);
+ } else {
+ Authentication authentication = new UsernamePasswordAuthenticationToken(usernameAndPassword[0],
+ usernameAndPassword[1]);
+ try {
+ authentication = authenticationManager.authenticate(authentication);
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ } catch (AuthenticationException e) {
+ Logger.getLogger(SessionFilter.class.getName())
+ .log(Level.FINE, String.format("User %s not authenticated through authorization header",
+ usernameAndPassword[0]));
+ }
+ }
+
+ filterChain.doFilter(request, response);
+ }
+
+ protected String[] getUsernameAndPassword(String authorization) {
+ if (authorization != null && authorization.toLowerCase().startsWith("basic")) {
+ // Authorization: Basic base64credentials
+ String base64Credentials = authorization.substring("Basic".length()).trim();
+ byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
+ String credentials = new String(credDecoded, StandardCharsets.UTF_8);
+ // credentials = username:password
+ return credentials.split(":", 2);
+ }
+ return null;
+ }
+}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
new file mode 100644
index 0000000000..097f5b91f4
--- /dev/null
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
@@ -0,0 +1,185 @@
+package org.phoebus.service.saveandrestore.web.config;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+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;
+import org.springframework.context.annotation.Scope;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer;
+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.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
+import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
+import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+@Configuration
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+
+ /**
+ * External Active Directory configuration properties
+ */
+ @Value("${ad.enabled:false}")
+ boolean ad_enabled;
+ @Value("${ad.url:ldap://localhost:389/}")
+ String ad_url;
+ @Value("${ad.domain}")
+ String ad_domain;
+ /**
+ * External LDAP configuration properties
+ */
+ @Value("${ldap.enabled:false}")
+ boolean ldap_enabled;
+ @Value("${ldap.urls:ldaps://localhost:389/}")
+ String ldap_url;
+ @Value("${ldap.base.dn}")
+ String ldap_base_dn;
+ @Value("${ldap.user.dn.pattern}")
+ String ldap_user_dn_pattern;
+ @Value("${ldap.groups.search.base}")
+ String ldap_groups_search_base;
+ @Value("${ldap.groups.search.pattern}")
+ String ldap_groups_search_pattern;
+ @Value("${ldap.manager.dn}")
+ String ldap_manager_dn;
+ @Value("${ldap.manager.password}")
+ String ldap_manager_password;
+ @Value("${ldap.user.search.base}")
+ String ldap_user_search_base;
+ @Value("${ldap.user.search.filter}")
+ String ldap_user_search_filter;
+
+ /**
+ * Embedded LDAP configuration properties
+ */
+ @Value("${embedded_ldap.enabled:false}")
+ boolean embedded_ldap_enabled;
+ @Value("${embedded_ldap.urls:ldaps://localhost:389/}")
+ String embedded_ldap_url;
+ @Value("${embedded_ldap.base.dn}")
+ String embedded_ldap_base_dn;
+ @Value("${embedded_ldap.user.dn.pattern}")
+ String embedded_ldap_user_dn_pattern;
+ @Value("${embedded_ldap.groups.search.base}")
+ String embedded_ldap_groups_search_base;
+ @Value("${embedded_ldap.groups.search.pattern}")
+ String embedded_ldap_groups_search_pattern;
+
+ /**
+ * Demo authorization based on in memory user credentials
+ */
+ @Value("${demo_auth.enabled:false}")
+ boolean demo_auth_enabled;
+
+ @Value("${securityDisabled:false}")
+ boolean securityDisabled;
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http.csrf().disable();
+ if(!securityDisabled){
+ http.authorizeRequests().anyRequest().authenticated();
+ http.addFilterBefore(new SessionFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
+ }
+ }
+
+ @Override
+ public void configure(WebSecurity web) {
+ // The below lists exceptions for authentication.
+ web.ignoring().antMatchers(HttpMethod.GET, "/**");
+ web.ignoring().antMatchers(HttpMethod.POST, "/**/login*");
+ }
+
+
+ @Override
+ public void configure(AuthenticationManagerBuilder auth) throws Exception {
+ if (ad_enabled) {
+ ActiveDirectoryLdapAuthenticationProvider adProvider = new ActiveDirectoryLdapAuthenticationProvider(ad_domain, ad_url);
+ adProvider.setConvertSubErrorCodesToExceptions(true);
+ adProvider.setUseAuthenticationRequestCredentials(true);
+ auth.authenticationProvider(adProvider);
+ }
+ if (ldap_enabled) {
+ DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(ldap_url);
+ if (ldap_manager_dn != null && !ldap_manager_dn.isEmpty() && ldap_manager_password != null && !ldap_manager_password.isEmpty()) {
+ contextSource.setUserDn(ldap_manager_dn);
+ contextSource.setPassword(ldap_manager_password);
+ }
+ contextSource.afterPropertiesSet();
+
+ DefaultLdapAuthoritiesPopulator myAuthPopulator = new DefaultLdapAuthoritiesPopulator(contextSource, ldap_groups_search_base);
+ myAuthPopulator.setGroupSearchFilter(ldap_groups_search_pattern);
+ myAuthPopulator.setSearchSubtree(true);
+ myAuthPopulator.setIgnorePartialResultException(true);
+
+ LdapAuthenticationProviderConfigurer configurer = auth.ldapAuthentication()
+ .ldapAuthoritiesPopulator(myAuthPopulator);
+ if (ldap_user_dn_pattern != null && !ldap_user_dn_pattern.isEmpty()) {
+ configurer.userDnPatterns(ldap_user_dn_pattern);
+ }
+ if (ldap_user_search_filter != null && !ldap_user_search_filter.isEmpty()) {
+ configurer.userSearchFilter(ldap_user_search_filter);
+ }
+ if (ldap_user_search_base != null && !ldap_user_search_base.isEmpty()) {
+ configurer.userSearchBase(ldap_user_search_base);
+ }
+ configurer.contextSource(contextSource);
+ }
+
+ if (embedded_ldap_enabled) {
+ DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(embedded_ldap_url);
+ contextSource.afterPropertiesSet();
+
+ DefaultLdapAuthoritiesPopulator myAuthPopulator = new DefaultLdapAuthoritiesPopulator(contextSource, embedded_ldap_groups_search_base);
+ myAuthPopulator.setGroupSearchFilter(embedded_ldap_groups_search_pattern);
+ myAuthPopulator.setSearchSubtree(true);
+ myAuthPopulator.setIgnorePartialResultException(true);
+
+
+ auth.ldapAuthentication()
+ .userDnPatterns(embedded_ldap_user_dn_pattern)
+ .ldapAuthoritiesPopulator(myAuthPopulator)
+ .groupSearchBase("ou=Group")
+ .contextSource(contextSource);
+
+ }
+
+ if (demo_auth_enabled) {
+ auth.inMemoryAuthentication()
+ .withUser("admin").password(encoder().encode("adminPass")).roles("ADMIN").and()
+ .withUser("user").password(encoder().encode("userPass")).roles("USER");
+ }
+ }
+
+ @Bean
+ public PasswordEncoder encoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager() {
+ try {
+ return super.authenticationManager();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ @Bean
+ @Scope("singleton")
+ public ObjectMapper objectMapper() {
+ ObjectMapper objectMapper = new ObjectMapper();
+ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ return objectMapper;
+ }
+}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java
new file mode 100644
index 0000000000..f7c60da540
--- /dev/null
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package org.phoebus.service.saveandrestore.web.controllers;
+
+import org.phoebus.applications.saveandrestore.model.UserData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@SuppressWarnings("unused")
+@RestController
+public class AuthenticationController extends BaseController {
+
+
+ @Autowired
+ private AuthenticationManager authenticationManager;
+
+ /**
+ * Authenticates user.
+ *
+ * @param userName The user principal name
+ * @param password User's password
+ * @return A {@link ResponseEntity} carrying a {@link UserData} object if the login was successful,
+ * otherwise the body will be null.
+ */
+ @PostMapping(value = "login")
+ public ResponseEntity login(@RequestParam(value = "username") String userName,
+ @RequestParam(value = "password") String password) {
+ Authentication authentication = new UsernamePasswordAuthenticationToken(userName, password);
+ try {
+ authentication = authenticationManager.authenticate(authentication);
+ } catch (AuthenticationException e) {
+ return new ResponseEntity<>(
+ null,
+ HttpStatus.UNAUTHORIZED);
+ }
+ List roles = authentication.getAuthorities().stream()
+ .map(GrantedAuthority::getAuthority).collect(Collectors.toList());
+ return new ResponseEntity<>(
+ new UserData(userName, roles),
+ HttpStatus.OK);
+ }
+}
diff --git a/services/save-and-restore/src/main/resources/application.properties b/services/save-and-restore/src/main/resources/application.properties
index 8b483393a7..ed98ee7f43 100644
--- a/services/save-and-restore/src/main/resources/application.properties
+++ b/services/save-and-restore/src/main/resources/application.properties
@@ -27,3 +27,46 @@ logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=INFO
# serve a resource like for instance SearchHelp.html
spring.mvc.static-path-pattern=/**
+############## AD - External ##############
+ad.enabled = false
+ad.url = ldap://127.0.0.1
+ad.domain = test.com
+
+############## LDAP - External ##############
+ldap.enabled = false
+ldap.urls = ldaps://controlns02.nsls2.bnl.gov/dc=nsls2,dc=bnl,dc=gov
+ldap.base.dn = dc=nsls2,dc=bnl,dc=gov
+ldap.user.search.base=
+# User search pattern, e.g. uid={0},ou=People. No default value as LDAP environment may not
+# support user search by pattern.
+ldap.user.dn.pattern=
+# User search filter, e.g. (&(objectClass=person)(SAMAccountName={0})). No default value as LDAP environment
+# may not support user search by filter.
+ldap.user.search.filter=
+ldap.groups.search.base = ou=Group
+ldap.groups.search.pattern = (memberUid= {1})
+# dn of manager account, may be required for group search
+ldap.manager.dn=
+# password of account
+ldap.manager.password=
+
+############## LDAP - Embedded ##############
+embedded_ldap.enabled = false
+embedded_ldap.urls = ldap://localhost:8389/dc=olog,dc=local
+embedded_ldap.base.dn = dc=olog,dc=local
+embedded_ldap.user.dn.pattern = uid={0},ou=People
+embedded_ldap.groups.search.base = ou=Group
+embedded_ldap.groups.search.pattern = (memberUid= {1})
+spring.ldap.embedded.ldif=classpath:olog.ldif
+spring.ldap.embedded.base-dn=dc=olog,dc=local
+spring.ldap.embedded.port=8389
+spring.ldap.embedded.validation.enabled=false
+
+
+############## Demo Auth ##############
+demo_auth.enabled = true
+
+############## Security for POST/PUT endpoints ##############
+# Set to true to skip authentication
+securityDisabled=false
+
From 300083a22d33c1540ce444bf2de673b0bcee59f9 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Wed, 23 Aug 2023 16:21:41 +0200
Subject: [PATCH 02/42] Save&restore client side authentication setup
---
.../SaveAndRestoreAuthenticationProvider.java | 54 +++++++++++++++++++
.../ui/SaveAndRestoreService.java | 24 +++++----
...uthorization.ServiceAuthenticationProvider | 19 +++++++
app/save-and-restore/service/pom.xml | 6 +++
.../saveandrestore/SaveAndRestoreClient.java | 20 +++----
.../impl/SaveAndRestoreJerseyClient.java | 39 ++++++++++----
.../saveandrestore/service/Messages.java | 1 +
.../service/messages.properties | 1 +
8 files changed, 134 insertions(+), 30 deletions(-)
create mode 100644 app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java
create mode 100644 app/save-and-restore/app/src/main/resources/META-INF/services/org.phoebus.security.authorization.ServiceAuthenticationProvider
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java
new file mode 100644
index 0000000000..089bf9d45d
--- /dev/null
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package org.phoebus.applications.saveandrestore.authentication;
+
+import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService;
+import org.phoebus.security.authorization.ServiceAuthenticationProvider;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Authentication provider for the save-and-restore service.
+ */
+public class SaveAndRestoreAuthenticationProvider implements ServiceAuthenticationProvider {
+
+ @Override
+ public void authenticate(String username, String password){
+ SaveAndRestoreService saveAndRestoreService = SaveAndRestoreService.getInstance();
+ try {
+ saveAndRestoreService.authenticate(username, password);
+ } catch (Exception e) {
+ Logger.getLogger(SaveAndRestoreAuthenticationProvider.class.getName())
+ .log(Level.WARNING, "Failed to authenticate user " + username + " against save&restore service", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void logout(String token) {
+ // Not implemented for save&restore
+ }
+
+ @Override
+ public String getServiceName(){
+ return "save-and-restore";
+ }
+}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java
index 9182676aa3..c32360c7f0 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java
@@ -22,16 +22,7 @@
import org.phoebus.applications.saveandrestore.common.VDisconnectedData;
import org.phoebus.applications.saveandrestore.common.VNoData;
import org.phoebus.applications.saveandrestore.impl.SaveAndRestoreJerseyClient;
-import org.phoebus.applications.saveandrestore.model.CompositeSnapshot;
-import org.phoebus.applications.saveandrestore.model.Configuration;
-import org.phoebus.applications.saveandrestore.model.ConfigurationData;
-import org.phoebus.applications.saveandrestore.model.Node;
-import org.phoebus.applications.saveandrestore.model.NodeType;
-import org.phoebus.applications.saveandrestore.model.Snapshot;
-import org.phoebus.applications.saveandrestore.model.SnapshotData;
-import org.phoebus.applications.saveandrestore.model.SnapshotItem;
-import org.phoebus.applications.saveandrestore.model.Tag;
-import org.phoebus.applications.saveandrestore.model.TagData;
+import org.phoebus.applications.saveandrestore.model.*;
import org.phoebus.applications.saveandrestore.model.search.Filter;
import org.phoebus.applications.saveandrestore.model.search.SearchResult;
@@ -373,4 +364,17 @@ private void notifyFilterAddedOrUpdated(Filter filter) {
private void notifyFilterDeleted(Filter filter) {
filterChangeListeners.forEach(l -> l.filterRemoved(filter));
}
+
+ /**
+ * Authenticate user, needed for all non-GET endpoints if service requires it
+ * @param userName User's account name
+ * @param password User's password
+ * @return A {@link UserData} object
+ * @throws Exception if authentication fails
+ */
+ public UserData authenticate(String userName, String password) throws Exception{
+ Future future =
+ executor.submit(() -> saveAndRestoreClient.authenticate(userName, password));
+ return future.get();
+ }
}
diff --git a/app/save-and-restore/app/src/main/resources/META-INF/services/org.phoebus.security.authorization.ServiceAuthenticationProvider b/app/save-and-restore/app/src/main/resources/META-INF/services/org.phoebus.security.authorization.ServiceAuthenticationProvider
new file mode 100644
index 0000000000..bce0fb5234
--- /dev/null
+++ b/app/save-and-restore/app/src/main/resources/META-INF/services/org.phoebus.security.authorization.ServiceAuthenticationProvider
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2020 European Spallation Source ERIC.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+
+org.phoebus.applications.saveandrestore.authentication.SaveAndRestoreAuthenticationProvider
\ No newline at end of file
diff --git a/app/save-and-restore/service/pom.xml b/app/save-and-restore/service/pom.xml
index 3fe15bd6e8..be5f840686 100644
--- a/app/save-and-restore/service/pom.xml
+++ b/app/save-and-restore/service/pom.xml
@@ -29,6 +29,12 @@
core-pv4.7.3-SNAPSHOT
+
+
+ org.phoebus
+ core-security
+ 4.7.3-SNAPSHOT
+ org.epics
diff --git a/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreClient.java b/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreClient.java
index a0af67e87a..47d62f4bdb 100644
--- a/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreClient.java
+++ b/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreClient.java
@@ -18,16 +18,7 @@
package org.phoebus.applications.saveandrestore;
-import org.phoebus.applications.saveandrestore.model.CompositeSnapshot;
-import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData;
-import org.phoebus.applications.saveandrestore.model.Configuration;
-import org.phoebus.applications.saveandrestore.model.ConfigurationData;
-import org.phoebus.applications.saveandrestore.model.Node;
-import org.phoebus.applications.saveandrestore.model.Snapshot;
-import org.phoebus.applications.saveandrestore.model.SnapshotData;
-import org.phoebus.applications.saveandrestore.model.SnapshotItem;
-import org.phoebus.applications.saveandrestore.model.Tag;
-import org.phoebus.applications.saveandrestore.model.TagData;
+import org.phoebus.applications.saveandrestore.model.*;
import org.phoebus.applications.saveandrestore.model.search.Filter;
import org.phoebus.applications.saveandrestore.model.search.SearchResult;
@@ -222,4 +213,13 @@ public interface SaveAndRestoreClient {
* passed in the tagData parameter.
*/
List deleteTag(TagData tagData);
+
+ /**
+ * For the purpose of login and authentication in the service.
+ * @param userName User's account name
+ * @param password User's password
+ * @return A {@link UserData} object if login is successful, otherwise implementation should throw
+ * an exception.
+ */
+ UserData authenticate(String userName, String password);
}
diff --git a/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/impl/SaveAndRestoreJerseyClient.java b/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/impl/SaveAndRestoreJerseyClient.java
index 3bd563c1ad..244cb45496 100644
--- a/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/impl/SaveAndRestoreJerseyClient.java
+++ b/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/impl/SaveAndRestoreJerseyClient.java
@@ -32,16 +32,7 @@
import com.sun.jersey.api.client.config.DefaultClientConfig;
import org.phoebus.applications.saveandrestore.SaveAndRestoreClient;
import org.phoebus.applications.saveandrestore.SaveAndRestoreClientException;
-import org.phoebus.applications.saveandrestore.model.CompositeSnapshot;
-import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData;
-import org.phoebus.applications.saveandrestore.model.Configuration;
-import org.phoebus.applications.saveandrestore.model.ConfigurationData;
-import org.phoebus.applications.saveandrestore.model.Node;
-import org.phoebus.applications.saveandrestore.model.Snapshot;
-import org.phoebus.applications.saveandrestore.model.SnapshotData;
-import org.phoebus.applications.saveandrestore.model.SnapshotItem;
-import org.phoebus.applications.saveandrestore.model.Tag;
-import org.phoebus.applications.saveandrestore.model.TagData;
+import org.phoebus.applications.saveandrestore.model.*;
import org.phoebus.applications.saveandrestore.model.search.Filter;
import org.phoebus.applications.saveandrestore.model.search.SearchResult;
import org.phoebus.applications.saveandrestore.service.Messages;
@@ -610,4 +601,32 @@ public List deleteTag(TagData tagData){
return response.getEntity(new GenericType>() {
});
}
+
+ @Override
+ public UserData authenticate(String userName, String password){
+ WebResource webResource =
+ client.resource(jmasarServiceUrl + "/login")
+ .queryParam("username", userName)
+ .queryParam("password", password);
+ ClientResponse response;
+ try {
+ response = webResource.accept(CONTENT_TYPE_JSON)
+ .post(ClientResponse.class);
+ } catch (UniformInterfaceException e) {
+ throw new RuntimeException(e);
+ } catch (ClientHandlerException e) {
+ throw new RuntimeException(e);
+ }
+ if (response.getStatus() != 200) {
+ String message = Messages.authenticationFailed;
+ try {
+ message = new String(response.getEntityInputStream().readAllBytes());
+ } catch (IOException e) {
+ // Ignore
+ }
+ throw new SaveAndRestoreClientException(message);
+ }
+ return response.getEntity(new GenericType() {
+ });
+ }
}
diff --git a/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/service/Messages.java b/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/service/Messages.java
index 6c93843f82..b2cbd4a897 100644
--- a/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/service/Messages.java
+++ b/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/service/Messages.java
@@ -21,6 +21,7 @@
public class Messages {
+ public static String authenticationFailed;
public static String compositeSnapshotConsistencyCheckFailed;
public static String copyOrMoveNotAllowedBody;
public static String createNodeFailed;
diff --git a/app/save-and-restore/service/src/main/resources/org/phoebus/applications/saveandrestore/service/messages.properties b/app/save-and-restore/service/src/main/resources/org/phoebus/applications/saveandrestore/service/messages.properties
index 4eef2ff376..08d595919e 100644
--- a/app/save-and-restore/service/src/main/resources/org/phoebus/applications/saveandrestore/service/messages.properties
+++ b/app/save-and-restore/service/src/main/resources/org/phoebus/applications/saveandrestore/service/messages.properties
@@ -1,3 +1,4 @@
+uthenticationFailed=Authentication Failed
compositeSnapshotConsistencyCheckFailed=Failed to check consistency for composite snapshot
copyOrMoveNotAllowedBody=Selection cannot be moved/copied to the specified target node.
createCompositeSnapshotFailed=Failed to create composite snapshot
From 584b9fac4d2f57f2aadd1ec3e55fc583401f225a Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Thu, 24 Aug 2023 14:29:46 +0200
Subject: [PATCH 03/42] Save&restore authentication, client side
---
.../impl/SaveAndRestoreJerseyClient.java | 91 ++++++++++++-------
.../web/config/WebSecurityConfig.java | 5 +-
2 files changed, 62 insertions(+), 34 deletions(-)
diff --git a/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/impl/SaveAndRestoreJerseyClient.java b/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/impl/SaveAndRestoreJerseyClient.java
index 244cb45496..df23bc96dd 100644
--- a/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/impl/SaveAndRestoreJerseyClient.java
+++ b/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/impl/SaveAndRestoreJerseyClient.java
@@ -30,6 +30,7 @@
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
+import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
import org.phoebus.applications.saveandrestore.SaveAndRestoreClient;
import org.phoebus.applications.saveandrestore.SaveAndRestoreClientException;
import org.phoebus.applications.saveandrestore.model.*;
@@ -37,6 +38,8 @@
import org.phoebus.applications.saveandrestore.model.search.SearchResult;
import org.phoebus.applications.saveandrestore.service.Messages;
import org.phoebus.framework.preferences.PreferencesReader;
+import org.phoebus.security.store.SecureStore;
+import org.phoebus.security.tokens.ScopedAuthenticationToken;
import javax.ws.rs.core.MultivaluedMap;
import java.io.IOException;
@@ -46,7 +49,7 @@
public class SaveAndRestoreJerseyClient implements SaveAndRestoreClient {
- private final Client client;
+ private Client client;
private static final String CONTENT_TYPE_JSON = "application/json; charset=UTF-8";
private final Logger logger = Logger.getLogger(SaveAndRestoreJerseyClient.class.getName());
@@ -55,39 +58,65 @@ public class SaveAndRestoreJerseyClient implements SaveAndRestoreClient {
private static final int DEFAULT_READ_TIMEOUT = 5000; // ms
private static final int DEFAULT_CONNECT_TIMEOUT = 3000; // ms
+ private int httpClientReadTimeout = DEFAULT_READ_TIMEOUT;
+ private int httpClientConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
+
+ ObjectMapper mapper = new ObjectMapper();
+
+ private HTTPBasicAuthFilter httpBasicAuthFilter;
+
public SaveAndRestoreJerseyClient() {
+ mapper.registerModule(new JavaTimeModule());
+ mapper.setSerializationInclusion(Include.NON_NULL);
+
PreferencesReader preferencesReader = new PreferencesReader(SaveAndRestoreClient.class, "/client_preferences.properties");
this.jmasarServiceUrl = preferencesReader.get("jmasar.service.url");
- int httpClientReadTimeout = DEFAULT_READ_TIMEOUT;
String readTimeoutString = preferencesReader.get("httpClient.readTimeout");
try {
httpClientReadTimeout = Integer.parseInt(readTimeoutString);
- logger.log(Level.INFO, "JMasar client using read timeout " + httpClientReadTimeout + " ms");
+ logger.log(Level.INFO, "Save&restore client using read timeout " + httpClientReadTimeout + " ms");
} catch (NumberFormatException e) {
logger.log(Level.INFO, "Property httpClient.readTimeout \"" + readTimeoutString + "\" is not a number, using default value " + DEFAULT_READ_TIMEOUT + " ms");
}
- int httpClientConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
String connectTimeoutString = preferencesReader.get("httpClient.connectTimeout");
try {
httpClientConnectTimeout = Integer.parseInt(connectTimeoutString);
- logger.log(Level.INFO, "JMasar client using connect timeout " + httpClientConnectTimeout + " ms");
+ logger.log(Level.INFO, "Save&restore client using connect timeout " + httpClientConnectTimeout + " ms");
} catch (NumberFormatException e) {
logger.log(Level.INFO, "Property httpClient.connectTimeout \"" + connectTimeoutString + "\" is not a number, using default value " + DEFAULT_CONNECT_TIMEOUT + " ms");
}
+ }
-
+ private Client getClient(){
DefaultClientConfig defaultClientConfig = new DefaultClientConfig();
defaultClientConfig.getProperties().put(ClientConfig.PROPERTY_READ_TIMEOUT, httpClientReadTimeout);
defaultClientConfig.getProperties().put(ClientConfig.PROPERTY_CONNECT_TIMEOUT, httpClientConnectTimeout);
- ObjectMapper mapper = new ObjectMapper();
- mapper.registerModule(new JavaTimeModule());
- mapper.setSerializationInclusion(Include.NON_NULL);
+
JacksonJsonProvider jacksonJsonProvider = new JacksonJsonProvider(mapper);
defaultClientConfig.getSingletons().add(jacksonJsonProvider);
+
client = Client.create(defaultClientConfig);
+
+ try {
+ SecureStore store = new SecureStore();
+ ScopedAuthenticationToken scopedAuthenticationToken = store.getScopedAuthenticationToken("save-and-restore");
+ if(scopedAuthenticationToken != null){
+ String username = scopedAuthenticationToken.getUsername();
+ String password = scopedAuthenticationToken.getPassword();
+ httpBasicAuthFilter = new HTTPBasicAuthFilter(username, password);
+ client.addFilter(httpBasicAuthFilter);
+ }
+ else if(httpBasicAuthFilter != null){
+ client.removeFilter(httpBasicAuthFilter);
+ }
+ } catch (Exception e) {
+ logger.log(Level.WARNING, "Unable to retrieve credentials from secure store", e);
+ }
+
+ return client;
}
@Override
@@ -107,7 +136,7 @@ public Node getNode(String uniqueNodeId) {
@Override
public List getCompositeSnapshotReferencedNodes(String uniqueNodeId){
- WebResource webResource = client.resource(jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/nodes");
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/nodes");
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).get(ClientResponse.class);
if (response.getStatus() != 200) {
@@ -126,7 +155,7 @@ public List getCompositeSnapshotReferencedNodes(String uniqueNodeId){
@Override
public List getCompositeSnapshotItems(String uniqueNodeId){
- WebResource webResource = client.resource(jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/items");
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/items");
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).get(ClientResponse.class);
if (response.getStatus() != 200) {
@@ -158,7 +187,7 @@ public List getChildNodes(String uniqueNodeId) throws SaveAndRestoreClient
@Override
public Node createNewNode(String parentNodeId, Node node) {
node.setUserName(getCurrentUsersName());
- WebResource webResource = client.resource(jmasarServiceUrl + "/node").queryParam("parentNodeId", parentNodeId);
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/node").queryParam("parentNodeId", parentNodeId);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(node, CONTENT_TYPE_JSON)
.put(ClientResponse.class);
@@ -186,7 +215,7 @@ public Node updateNode(Node nodeToUpdate, boolean customTimeForMigration) {
if (nodeToUpdate.getUserName() == null || nodeToUpdate.getUserName().isEmpty()) {
nodeToUpdate.setUserName(getCurrentUsersName());
}
- WebResource webResource = client.resource(jmasarServiceUrl + "/node")
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/node")
.queryParam("customTimeForMigration", customTimeForMigration ? "true" : "false");
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
@@ -213,7 +242,7 @@ private T getCall(String relativeUrl, Class clazz) {
}
private ClientResponse getCall(String relativeUrl) {
- WebResource webResource = client.resource(jmasarServiceUrl + relativeUrl);
+ WebResource webResource = getClient().resource(jmasarServiceUrl + relativeUrl);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).get(ClientResponse.class);
if (response.getStatus() != 200) {
@@ -231,7 +260,7 @@ private ClientResponse getCall(String relativeUrl) {
@Override
public void deleteNode(String uniqueNodeId) {
- WebResource webResource = client.resource(jmasarServiceUrl + "/node/" + uniqueNodeId);
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/node/" + uniqueNodeId);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).delete(ClientResponse.class);
if (response.getStatus() != 200) {
String message = response.getEntity(String.class);
@@ -265,7 +294,7 @@ public List getAllSnapshots() {
@Override
public Node moveNodes(List sourceNodeIds, String targetNodeId) {
WebResource webResource =
- client.resource(jmasarServiceUrl + "/move")
+ getClient().resource(jmasarServiceUrl + "/move")
.queryParam("to", targetNodeId)
.queryParam("username", getCurrentUsersName());
@@ -288,7 +317,7 @@ public Node moveNodes(List sourceNodeIds, String targetNodeId) {
@Override
public Node copyNodes(List sourceNodeIds, String targetNodeId) {
WebResource webResource =
- client.resource(jmasarServiceUrl + "/copy")
+ getClient().resource(jmasarServiceUrl + "/copy")
.queryParam("to", targetNodeId)
.queryParam("username", getCurrentUsersName());
@@ -311,7 +340,7 @@ public Node copyNodes(List sourceNodeIds, String targetNodeId) {
@Override
public String getFullPath(String uniqueNodeId) {
WebResource webResource =
- client.resource(jmasarServiceUrl + "/path/" + uniqueNodeId);
+ getClient().resource(jmasarServiceUrl + "/path/" + uniqueNodeId);
ClientResponse response = webResource.get(ClientResponse.class);
if (response.getStatus() != 200) {
@@ -335,7 +364,7 @@ public ConfigurationData getConfigurationData(String nodeId) {
public Configuration createConfiguration(String parentNodeId, Configuration configuration) {
configuration.getConfigurationNode().setUserName(getCurrentUsersName());
WebResource webResource =
- client.resource(jmasarServiceUrl + "/config")
+ getClient().resource(jmasarServiceUrl + "/config")
.queryParam("parentNodeId", parentNodeId);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(configuration, CONTENT_TYPE_JSON)
@@ -354,7 +383,7 @@ public Configuration createConfiguration(String parentNodeId, Configuration conf
@Override
public Configuration updateConfiguration(Configuration configuration) {
- WebResource webResource = client.resource(jmasarServiceUrl + "/config");
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/config");
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(configuration, CONTENT_TYPE_JSON)
@@ -381,7 +410,7 @@ public SnapshotData getSnapshotData(String nodeId) {
public Snapshot saveSnapshot(String parentNodeId, Snapshot snapshot) {
snapshot.getSnapshotNode().setUserName(getCurrentUsersName());
WebResource webResource =
- client.resource(jmasarServiceUrl + "/snapshot")
+ getClient().resource(jmasarServiceUrl + "/snapshot")
.queryParam("parentNodeId", parentNodeId);
ClientResponse response;
try {
@@ -409,7 +438,7 @@ public Snapshot saveSnapshot(String parentNodeId, Snapshot snapshot) {
public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeSnapshot compositeSnapshot){
compositeSnapshot.getCompositeSnapshotNode().setUserName(getCurrentUsersName());
WebResource webResource =
- client.resource(jmasarServiceUrl + "/composite-snapshot")
+ getClient().resource(jmasarServiceUrl + "/composite-snapshot")
.queryParam("parentNodeId", parentNodeId);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(compositeSnapshot, CONTENT_TYPE_JSON)
@@ -429,7 +458,7 @@ public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeS
@Override
public List checkCompositeSnapshotConsistency(List snapshotNodeIds){
WebResource webResource =
- client.resource(jmasarServiceUrl + "/composite-snapshot-consistency-check");
+ getClient().resource(jmasarServiceUrl + "/composite-snapshot-consistency-check");
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(snapshotNodeIds, CONTENT_TYPE_JSON)
.post(ClientResponse.class);
@@ -448,7 +477,7 @@ public List checkCompositeSnapshotConsistency(List snapshotNodeI
@Override
public CompositeSnapshot updateCompositeSnapshot(CompositeSnapshot compositeSnapshot){
- WebResource webResource = client.resource(jmasarServiceUrl + "/composite-snapshot");
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/composite-snapshot");
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(compositeSnapshot, CONTENT_TYPE_JSON)
@@ -467,7 +496,7 @@ public CompositeSnapshot updateCompositeSnapshot(CompositeSnapshot compositeSnap
@Override
public SearchResult search(MultivaluedMap searchParams){
- WebResource webResource = client.resource(jmasarServiceUrl + "/search")
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/search")
.queryParams(searchParams);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.get(ClientResponse.class);
@@ -486,7 +515,7 @@ public SearchResult search(MultivaluedMap searchParams){
@Override
public Filter saveFilter(Filter filter){
filter.setUser(getCurrentUsersName());
- WebResource webResource = client.resource(jmasarServiceUrl + "/filter");
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/filter");
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(filter, CONTENT_TYPE_JSON)
@@ -505,7 +534,7 @@ public Filter saveFilter(Filter filter){
@Override
public List getAllFilters(){
- WebResource webResource = client.resource(jmasarServiceUrl + "/filters");
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/filters");
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.get(ClientResponse.class);
if (response.getStatus() != 200) {
@@ -524,7 +553,7 @@ public List getAllFilters(){
public void deleteFilter(String name){
// Filter name may contain space chars, need to URL encode these.
String filterName = name.replace(" ", "%20");
- WebResource webResource = client.resource(jmasarServiceUrl + "/filter/" + filterName);
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/filter/" + filterName);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.delete(ClientResponse.class);
if (response.getStatus() != 200) {
@@ -546,7 +575,7 @@ public void deleteFilter(String name){
*/
public List addTag(TagData tagData){
WebResource webResource =
- client.resource(jmasarServiceUrl + "/tags");
+ getClient().resource(jmasarServiceUrl + "/tags");
ClientResponse response;
try {
response = webResource.accept(CONTENT_TYPE_JSON)
@@ -578,7 +607,7 @@ public List addTag(TagData tagData){
*/
public List deleteTag(TagData tagData){
WebResource webResource =
- client.resource(jmasarServiceUrl + "/tags");
+ getClient().resource(jmasarServiceUrl + "/tags");
ClientResponse response;
try {
response = webResource.accept(CONTENT_TYPE_JSON)
@@ -605,7 +634,7 @@ public List deleteTag(TagData tagData){
@Override
public UserData authenticate(String userName, String password){
WebResource webResource =
- client.resource(jmasarServiceUrl + "/login")
+ getClient().resource(jmasarServiceUrl + "/login")
.queryParam("username", userName)
.queryParam("password", password);
ClientResponse response;
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
index 097f5b91f4..be347bd813 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
@@ -5,7 +5,6 @@
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;
import org.springframework.context.annotation.Scope;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
@@ -13,7 +12,6 @@
import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer;
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.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@@ -86,8 +84,9 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
- if(!securityDisabled){
+ if (!securityDisabled) {
http.authorizeRequests().anyRequest().authenticated();
+ AuthenticationManager am = authenticationManager();
http.addFilterBefore(new SessionFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
}
}
From 86202362b667f18386532eab8cdbaa3b0ba91c72 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Wed, 30 Aug 2023 16:05:52 +0200
Subject: [PATCH 04/42] Save&restore authentication mandatory for all except
GET. Demo scheme is default.
---
.../web/config/WebSecurityConfig.java | 141 +++++++++---------
.../src/main/resources/application.properties | 20 +--
.../src/main/resources/sar.ldif | 43 ++++++
3 files changed, 123 insertions(+), 81 deletions(-)
create mode 100644 services/save-and-restore/src/main/resources/sar.ldif
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
index be347bd813..07a6ceea94 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
@@ -20,14 +20,15 @@
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* External Active Directory configuration properties
*/
- @Value("${ad.enabled:false}")
- boolean ad_enabled;
@Value("${ad.url:ldap://localhost:389/}")
String ad_url;
@Value("${ad.domain}")
@@ -35,8 +36,6 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* External LDAP configuration properties
*/
- @Value("${ldap.enabled:false}")
- boolean ldap_enabled;
@Value("${ldap.urls:ldaps://localhost:389/}")
String ldap_url;
@Value("${ldap.base.dn}")
@@ -59,8 +58,6 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* Embedded LDAP configuration properties
*/
- @Value("${embedded_ldap.enabled:false}")
- boolean embedded_ldap_enabled;
@Value("${embedded_ldap.urls:ldaps://localhost:389/}")
String embedded_ldap_url;
@Value("${embedded_ldap.base.dn}")
@@ -73,22 +70,17 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
String embedded_ldap_groups_search_pattern;
/**
- * Demo authorization based on in memory user credentials
+ * Authentication implementation.
*/
- @Value("${demo_auth.enabled:false}")
- boolean demo_auth_enabled;
-
- @Value("${securityDisabled:false}")
- boolean securityDisabled;
+ @Value("${auth.impl:demo}")
+ String authenitcationImplementation;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
- if (!securityDisabled) {
- http.authorizeRequests().anyRequest().authenticated();
- AuthenticationManager am = authenticationManager();
- http.addFilterBefore(new SessionFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
- }
+ http.authorizeRequests().anyRequest().authenticated();
+ AuthenticationManager am = authenticationManager();
+ http.addFilterBefore(new SessionFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
}
@Override
@@ -101,61 +93,66 @@ public void configure(WebSecurity web) {
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
- if (ad_enabled) {
- ActiveDirectoryLdapAuthenticationProvider adProvider = new ActiveDirectoryLdapAuthenticationProvider(ad_domain, ad_url);
- adProvider.setConvertSubErrorCodesToExceptions(true);
- adProvider.setUseAuthenticationRequestCredentials(true);
- auth.authenticationProvider(adProvider);
- }
- if (ldap_enabled) {
- DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(ldap_url);
- if (ldap_manager_dn != null && !ldap_manager_dn.isEmpty() && ldap_manager_password != null && !ldap_manager_password.isEmpty()) {
- contextSource.setUserDn(ldap_manager_dn);
- contextSource.setPassword(ldap_manager_password);
- }
- contextSource.afterPropertiesSet();
-
- DefaultLdapAuthoritiesPopulator myAuthPopulator = new DefaultLdapAuthoritiesPopulator(contextSource, ldap_groups_search_base);
- myAuthPopulator.setGroupSearchFilter(ldap_groups_search_pattern);
- myAuthPopulator.setSearchSubtree(true);
- myAuthPopulator.setIgnorePartialResultException(true);
-
- LdapAuthenticationProviderConfigurer configurer = auth.ldapAuthentication()
- .ldapAuthoritiesPopulator(myAuthPopulator);
- if (ldap_user_dn_pattern != null && !ldap_user_dn_pattern.isEmpty()) {
- configurer.userDnPatterns(ldap_user_dn_pattern);
- }
- if (ldap_user_search_filter != null && !ldap_user_search_filter.isEmpty()) {
- configurer.userSearchFilter(ldap_user_search_filter);
- }
- if (ldap_user_search_base != null && !ldap_user_search_base.isEmpty()) {
- configurer.userSearchBase(ldap_user_search_base);
- }
- configurer.contextSource(contextSource);
- }
-
- if (embedded_ldap_enabled) {
- DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(embedded_ldap_url);
- contextSource.afterPropertiesSet();
-
- DefaultLdapAuthoritiesPopulator myAuthPopulator = new DefaultLdapAuthoritiesPopulator(contextSource, embedded_ldap_groups_search_base);
- myAuthPopulator.setGroupSearchFilter(embedded_ldap_groups_search_pattern);
- myAuthPopulator.setSearchSubtree(true);
- myAuthPopulator.setIgnorePartialResultException(true);
-
-
- auth.ldapAuthentication()
- .userDnPatterns(embedded_ldap_user_dn_pattern)
- .ldapAuthoritiesPopulator(myAuthPopulator)
- .groupSearchBase("ou=Group")
- .contextSource(contextSource);
-
- }
-
- if (demo_auth_enabled) {
- auth.inMemoryAuthentication()
- .withUser("admin").password(encoder().encode("adminPass")).roles("ADMIN").and()
- .withUser("user").password(encoder().encode("userPass")).roles("USER");
+ DefaultSpringSecurityContextSource contextSource;
+ DefaultLdapAuthoritiesPopulator myAuthPopulator;
+ switch(authenitcationImplementation){
+ case "ad":
+ ActiveDirectoryLdapAuthenticationProvider adProvider = new ActiveDirectoryLdapAuthenticationProvider(ad_domain, ad_url);
+ adProvider.setConvertSubErrorCodesToExceptions(true);
+ adProvider.setUseAuthenticationRequestCredentials(true);
+ auth.authenticationProvider(adProvider);
+ break;
+ case "ldap":
+ contextSource = new DefaultSpringSecurityContextSource(ldap_url);
+ if (ldap_manager_dn != null && !ldap_manager_dn.isEmpty() && ldap_manager_password != null && !ldap_manager_password.isEmpty()) {
+ contextSource.setUserDn(ldap_manager_dn);
+ contextSource.setPassword(ldap_manager_password);
+ }
+ contextSource.afterPropertiesSet();
+
+ myAuthPopulator = new DefaultLdapAuthoritiesPopulator(contextSource, ldap_groups_search_base);
+ myAuthPopulator.setGroupSearchFilter(ldap_groups_search_pattern);
+ myAuthPopulator.setSearchSubtree(true);
+ myAuthPopulator.setIgnorePartialResultException(true);
+
+ LdapAuthenticationProviderConfigurer configurer = auth.ldapAuthentication()
+ .ldapAuthoritiesPopulator(myAuthPopulator);
+ if (ldap_user_dn_pattern != null && !ldap_user_dn_pattern.isEmpty()) {
+ configurer.userDnPatterns(ldap_user_dn_pattern);
+ }
+ if (ldap_user_search_filter != null && !ldap_user_search_filter.isEmpty()) {
+ configurer.userSearchFilter(ldap_user_search_filter);
+ }
+ if (ldap_user_search_base != null && !ldap_user_search_base.isEmpty()) {
+ configurer.userSearchBase(ldap_user_search_base);
+ }
+ configurer.contextSource(contextSource);
+ break;
+ case "ldap_embedded":
+ contextSource = new DefaultSpringSecurityContextSource(embedded_ldap_url);
+ contextSource.afterPropertiesSet();
+
+ myAuthPopulator
+ = new DefaultLdapAuthoritiesPopulator(contextSource, embedded_ldap_groups_search_base);
+ myAuthPopulator.setGroupSearchFilter(embedded_ldap_groups_search_pattern);
+ myAuthPopulator.setSearchSubtree(true);
+ myAuthPopulator.setIgnorePartialResultException(true);
+
+ auth.ldapAuthentication()
+ .userDnPatterns(embedded_ldap_user_dn_pattern)
+ .ldapAuthoritiesPopulator(myAuthPopulator)
+ .groupSearchBase("ou=Group")
+ .contextSource(contextSource);
+ break;
+ case "demo":
+ auth.inMemoryAuthentication()
+ .withUser("admin").password(encoder().encode("adminPass")).roles("ADMIN").and()
+ .withUser("user").password(encoder().encode("userPass")).roles("USER");
+ break;
+ default:
+ Logger.getLogger(WebSecurityConfig.class.getName())
+ .log(Level.SEVERE, "Authentication Implementation \"" + authenitcationImplementation + "\" not supported");
+ throw new IllegalArgumentException("Authentication Implementation \"" + authenitcationImplementation + "\" not supported");
}
}
diff --git a/services/save-and-restore/src/main/resources/application.properties b/services/save-and-restore/src/main/resources/application.properties
index ed98ee7f43..4956003780 100644
--- a/services/save-and-restore/src/main/resources/application.properties
+++ b/services/save-and-restore/src/main/resources/application.properties
@@ -52,21 +52,23 @@ ldap.manager.password=
############## LDAP - Embedded ##############
embedded_ldap.enabled = false
-embedded_ldap.urls = ldap://localhost:8389/dc=olog,dc=local
-embedded_ldap.base.dn = dc=olog,dc=local
+embedded_ldap.urls = ldap://localhost:8389/dc=sar,dc=local
+embedded_ldap.base.dn = dc=sar,dc=local
embedded_ldap.user.dn.pattern = uid={0},ou=People
embedded_ldap.groups.search.base = ou=Group
embedded_ldap.groups.search.pattern = (memberUid= {1})
-spring.ldap.embedded.ldif=classpath:olog.ldif
-spring.ldap.embedded.base-dn=dc=olog,dc=local
+spring.ldap.embedded.ldif=classpath:sar.ldif
+spring.ldap.embedded.base-dn=dc=sar,dc=local
spring.ldap.embedded.port=8389
spring.ldap.embedded.validation.enabled=false
-############## Demo Auth ##############
-demo_auth.enabled = true
+############## Authentication Implementation ##############
+# Supported options:
+# ad - Microsoft AD
+# ldap - Probably Open LDAP
+# ldap_embedded - Embedded LDAP. Config on sar.ldif
+# demo - Hard coded, see WebSecurityConfig class
+auth.impl = demo
-############## Security for POST/PUT endpoints ##############
-# Set to true to skip authentication
-securityDisabled=false
diff --git a/services/save-and-restore/src/main/resources/sar.ldif b/services/save-and-restore/src/main/resources/sar.ldif
new file mode 100644
index 0000000000..52dcef9ec0
--- /dev/null
+++ b/services/save-and-restore/src/main/resources/sar.ldif
@@ -0,0 +1,43 @@
+dn: dc=sar,dc=local
+objectClass: dcObject
+objectClass: organization
+dc: sar
+o: sar
+
+dn: cn=sar,dc=sar,dc=local
+objectClass: simpleSecurityObject
+objectClass: organizationalRole
+userPassword:: e1NIQX1jUkR0cE5DZUJpcWw1S09Rc0tWeXJBMHNBaUE9
+cn: sar
+
+dn: ou=Group,dc=sar,dc=local
+objectClass: organizationalunit
+ou: Group
+description: groups branch
+
+dn: ou=People,dc=sar,dc=local
+objectClass: organizationalunit
+ou: People
+description: people branch
+
+dn: uid=saveandrestore,ou=People,dc=sar,dc=local
+uid: saveandrestore
+objectClass: account
+objectClass: posixAccount
+description: User with saveandrestore role
+cn: saveandrestore
+userPassword: 1234
+uidNumber: 23003
+gidNumber: 23003
+homeDirectory: /dev/null
+
+dn: uid=admin,ou=People,dc=sar,dc=local
+uid: admin
+objectClass: account
+objectClass: posixAccount
+description: User with admin role
+cn: admin
+userPassword: 1234
+uidNumber: 23005
+gidNumber: 23005
+homeDirectory: /dev/null
From 91bdf077d46bcd18119e5813a4151d7ac79626cd Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Thu, 31 Aug 2023 10:24:51 +0200
Subject: [PATCH 05/42] Save&restore service: user id from Principal
---
.../CompositeSnapshotController.java | 10 ++++-
.../controllers/ConfigurationController.java | 10 ++++-
.../web/controllers/FilterController.java | 20 ++++-----
.../web/controllers/NodeController.java | 41 +++++++++----------
.../web/controllers/SnapshotController.java | 5 ++-
.../web/controllers/StructureController.java | 22 +++++-----
.../web/controllers/TagController.java | 36 ++++++++--------
7 files changed, 77 insertions(+), 67 deletions(-)
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
index 66358d7f6a..0d7c7bf60d 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
@@ -33,6 +33,7 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
+import java.security.Principal;
import java.util.List;
@SuppressWarnings("unused")
@@ -44,12 +45,16 @@ public class CompositeSnapshotController extends BaseController {
@PutMapping(value = "/composite-snapshot", produces = JSON)
public CompositeSnapshot createCompositeSnapshot(@RequestParam(value = "parentNodeId") String parentNodeId,
- @RequestBody CompositeSnapshot compositeSnapshot) {
+ @RequestBody CompositeSnapshot compositeSnapshot,
+ Principal principal) {
+ compositeSnapshot.getCompositeSnapshotNode().setUserName(principal.getName());
return nodeDAO.createCompositeSnapshot(parentNodeId, compositeSnapshot);
}
@PostMapping(value = "/composite-snapshot", produces = JSON)
- public CompositeSnapshot updateCompositeSnapshot(@RequestBody CompositeSnapshot compositeSnapshot) {
+ public CompositeSnapshot updateCompositeSnapshot(@RequestBody CompositeSnapshot compositeSnapshot,
+ Principal principal) {
+ compositeSnapshot.getCompositeSnapshotNode().setUserName(principal.getName());
return nodeDAO.updateCompositeSnapshot(compositeSnapshot);
}
@@ -80,6 +85,7 @@ public List getCompositeSnapshotItems(@PathVariable String uniqueI
* @return A list of PV names that occur more than once across the list of {@link Node}s corresponding
* to the input. Empty if no duplicates are found.
*/
+ // TODO: Should be GET, not POST
@PostMapping(value = "/composite-snapshot-consistency-check", produces = JSON)
public List checkSnapshotsConsistency(@RequestBody List snapshotNodeIds) {
return nodeDAO.checkForPVNameDuplicates(snapshotNodeIds);
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java
index f4d4d96e16..9df5f7d8ca 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java
@@ -30,6 +30,8 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
+import java.security.Principal;
+
@RestController
@RequestMapping("/config")
public class ConfigurationController extends BaseController {
@@ -41,7 +43,9 @@ public class ConfigurationController extends BaseController {
@SuppressWarnings("unused")
@PutMapping(produces = JSON)
public Configuration createConfiguration(@RequestParam(value = "parentNodeId") String parentNodeId,
- @RequestBody Configuration configuration) {
+ @RequestBody Configuration configuration,
+ Principal principal) {
+ configuration.getConfigurationNode().setUserName(principal.getName());
return nodeDAO.createConfiguration(parentNodeId, configuration);
}
@@ -53,7 +57,9 @@ public ConfigurationData getConfigurationData(@PathVariable String uniqueId) {
@SuppressWarnings("unused")
@PostMapping(produces = JSON)
- public Configuration updateConfiguration(@RequestBody Configuration configuration) {
+ public Configuration updateConfiguration(@RequestBody Configuration configuration,
+ Principal principal) {
+ configuration.getConfigurationNode().setUserName(principal.getName());
return nodeDAO.updateConfiguration(configuration);
}
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java
index 19a26af94e..6e58e541d4 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java
@@ -22,15 +22,10 @@
import org.phoebus.applications.saveandrestore.model.search.Filter;
import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
+import java.security.Principal;
import java.util.List;
-import java.util.logging.Logger;
@RestController
public class FilterController extends BaseController {
@@ -39,25 +34,28 @@ public class FilterController extends BaseController {
@Autowired
private NodeDAO nodeDAO;
- private Logger logger = Logger.getLogger(FilterController.class.getName());
-
/**
* Saves a new or updated {@link Filter}.
*
- * @param filter The {@link Filter} to save.
+ * @param filter The {@link Filter} to save.
+ * @param principal The {@link java.security.Principal} of the authenticated user
* @return The saved {@link Filter}.
*/
@SuppressWarnings("unused")
@PutMapping(value = "/filter", produces = JSON)
- public Filter saveFilter(@RequestBody final Filter filter) {
+ public Filter saveFilter(@RequestBody final Filter filter,
+ Principal principal) {
+ filter.setUser(principal.getName());
return nodeDAO.saveFilter(filter);
}
+ @SuppressWarnings("unused")
@GetMapping(value = "/filters", produces = JSON)
public List getAllFilters() {
return nodeDAO.getAllFilters();
}
+ @SuppressWarnings("unused")
@DeleteMapping(value = "/filter/{name}")
public void deleteFilter(@PathVariable final String name) {
nodeDAO.deleteFilter(name);
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
index 31e90a7fd4..561071997e 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
@@ -23,16 +23,9 @@
import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
+import java.security.Principal;
import java.util.List;
import java.util.logging.Logger;
@@ -47,7 +40,7 @@ public class NodeController extends BaseController {
@Autowired
private NodeDAO nodeDAO;
- private Logger logger = Logger.getLogger(NodeController.class.getName());
+ private final Logger logger = Logger.getLogger(NodeController.class.getName());
/**
* Create a new folder in the tree structure.
@@ -60,22 +53,26 @@ public class NodeController extends BaseController {
*
*
* @param parentsUniqueId The unique id of the parent node for the new node.
- * @param node A {@link Node} object. The {@link Node#getName()} and {@link Node#getUserName()} ()} fields must be
+ * @param node A {@link Node} object. The {@link Node#getName()} field must be
* non-null and non-empty.
+ * @param principal Authenticated user's {@link java.security.Principal}
* @return The new folder in the tree.
*/
@SuppressWarnings("unused")
@PutMapping(value = "/node", produces = JSON)
- public Node createNode(@RequestParam(name = "parentNodeId") String parentsUniqueId, @RequestBody final Node node) {
+ public Node createNode(@RequestParam(name = "parentNodeId") String parentsUniqueId,
+ @RequestBody final Node node,
+ Principal principal) {cd
if (node.getUserName() == null || node.getUserName().isEmpty()) {
throw new IllegalArgumentException("User name must be non-null and of non-zero length");
}
if (node.getName() == null || node.getName().isEmpty()) {
throw new IllegalArgumentException("Node name must be non-null and of non-zero length");
}
- if(!areTagsValid(node)){
+ if (!areTagsValid(node)) {
throw new IllegalArgumentException("Node may not contain golden tag");
}
+ node.setUserName(principal.getName());
return nodeDAO.createNode(parentsUniqueId, node);
}
@@ -150,15 +147,16 @@ public void deleteNode(@PathVariable final String uniqueNodeId) {
*
*
* @param customTimeForMigration Self-explanatory
- * @param nodeToUpdate {@link Node} object containing updated data. Only name, description and properties may be changed. The user name
- * should be set by the client in an automated fashion and will be updated by the persistence layer.
+ * @param nodeToUpdate {@link Node} object containing updated data. Only name, description and properties may be changed.
+ * @param principal Authenticated user's {@link java.security.Principal}
* @return A {@link Node} object representing the updated node.
*/
@SuppressWarnings("unused")
@PostMapping(value = "/node", produces = JSON)
public Node updateNode(@RequestParam(value = "customTimeForMigration", required = false, defaultValue = "false") String customTimeForMigration,
- @RequestBody Node nodeToUpdate) {
- if(!areTagsValid(nodeToUpdate)){
+ @RequestBody Node nodeToUpdate,
+ Principal principal) {
+ if (!areTagsValid(nodeToUpdate)) {
throw new IllegalArgumentException("Node may not contain golden tag");
}
return nodeDAO.updateNode(nodeToUpdate, Boolean.valueOf(customTimeForMigration));
@@ -166,16 +164,17 @@ public Node updateNode(@RequestParam(value = "customTimeForMigration", required
/**
* Checks if a {@link Node} has a tag named "golden". If so, it must be of type {@link NodeType#SNAPSHOT}.
+ *
* @param node A {@link Node} with potentially null or empty list of tags.
* @return true if the {@link Node} in question has a valid list of tags, otherwise false.
*/
- private boolean areTagsValid(Node node){
- if(node.getTags() == null || node.getTags().isEmpty()){
+ private boolean areTagsValid(Node node) {
+ if (node.getTags() == null || node.getTags().isEmpty()) {
return true;
}
- if(!node.getNodeType().equals(NodeType.SNAPSHOT) &&
- node.getTags().stream().filter(t -> t.getName().equalsIgnoreCase(Tag.GOLDEN)).findFirst().isPresent()){
+ if (!node.getNodeType().equals(NodeType.SNAPSHOT) &&
+ node.getTags().stream().anyMatch(t -> t.getName().equalsIgnoreCase(Tag.GOLDEN))) {
return false;
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java
index 629dd48061..8b5c8de8bf 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java
@@ -33,6 +33,7 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
+import java.security.Principal;
import java.util.List;
@SuppressWarnings("unused")
@@ -54,7 +55,9 @@ public List getAllSnapshots() {
@PutMapping(value = "/snapshot", produces = JSON)
public Snapshot saveSnapshot(@RequestParam(value = "parentNodeId") String parentNodeId,
- @RequestBody Snapshot snapshot) {
+ @RequestBody Snapshot snapshot,
+ Principal principal) {
+ snapshot.getSnapshotNode().setUserName(principal.getName());
return nodeDAO.saveSnapshot(parentNodeId, snapshot);
}
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/StructureController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/StructureController.java
index 72c4f94359..1dc53e5c7a 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/StructureController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/StructureController.java
@@ -29,6 +29,7 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
+import java.security.Principal;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;
@@ -52,22 +53,21 @@ public class StructureController extends BaseController {
*
* @param to The unique id of the new parent, which must be a folder. If empty or if
* target node does not exist, {@link HttpStatus#BAD_REQUEST} is returned.
- * @param userName Identity of the user performing the action on the client.
- * If empty, {@link HttpStatus#BAD_REQUEST} is returned.
* @param nodes List of source nodes to move. If empty, or if any of the listed source nodes does not exist,
* {@link HttpStatus#BAD_REQUEST} is returned.
+ * @param principal The {@link Principal} of the authenticated user.
* @return The (updated) target node.
*/
@SuppressWarnings("unused")
@PostMapping(value = "/move", produces = JSON)
public Node moveNodes(@RequestParam(value = "to") String to,
- @RequestParam(value = "username") String userName,
- @RequestBody List nodes) {
- if (to.isEmpty() || userName.isEmpty() || nodes.isEmpty()) {
+ @RequestBody List nodes,
+ Principal principal) {
+ if (to.isEmpty() || nodes.isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Username, target node and list of source nodes must all be non-empty.");
}
Logger.getLogger(StructureController.class.getName()).info(Thread.currentThread().getName() + " " + (new Date()) + " move");
- return nodeDAO.moveNodes(nodes, to, userName);
+ return nodeDAO.moveNodes(nodes, to, principal.getName());
}
/**
@@ -76,22 +76,22 @@ public Node moveNodes(@RequestParam(value = "to") String to,
*
* @param to The unique id of the target parent node, which must be a folder. If empty or if
* target node does not exist, {@link HttpStatus#BAD_REQUEST} is returned.
- * @param userName Identity of the user performing the action on the client.
- * If empty, {@link HttpStatus#BAD_REQUEST} is returned.
* @param nodes List of source nodes to copy. If empty, or if any of the listed source nodes does not exist,
* {@link HttpStatus#BAD_REQUEST} is returned.
+ * @param principal The {@link Principal} of the authenticated user.
* @return The (updated) target node.
*/
@SuppressWarnings("unused")
@PostMapping(value = "/copy", produces = JSON)
public Node copyNodes(@RequestParam(value = "to") String to,
@RequestParam(value = "username") String userName,
- @RequestBody List nodes) {
- if (to.isEmpty() || userName.isEmpty() || nodes.isEmpty()) {
+ @RequestBody List nodes,
+ Principal principal) {
+ if (to.isEmpty() || nodes.isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Username, target node and list of source nodes must all be non-empty.");
}
Logger.getLogger(StructureController.class.getName()).info(Thread.currentThread().getName() + " " + (new Date()) + " move");
- return nodeDAO.copyNodes(nodes, to, userName);
+ return nodeDAO.copyNodes(nodes, to, principal.getName());
}
/**
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java
index 67a925c030..efd7e4f958 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java
@@ -1,23 +1,23 @@
/**
* Copyright (C) 2020 Facility for Rare Isotope Beams
- *
+ *
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
- *
+ *
* Contact Information: Facility for Rare Isotope Beam,
- * Michigan State University,
- * East Lansing, MI 48824-1321
- * http://frib.msu.edu
+ * Michigan State University,
+ * East Lansing, MI 48824-1321
+ * http://frib.msu.edu
*/
package org.phoebus.service.saveandrestore.web.controllers;
@@ -26,13 +26,9 @@
import org.phoebus.applications.saveandrestore.model.TagData;
import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
+import java.security.Principal;
import java.util.List;
/**
@@ -60,13 +56,15 @@ public List getTags() {
* @return The list of updated {@link Node}s
*/
@PostMapping("/tags")
- public List addTag(@RequestBody TagData tagData){
- if(tagData.getTag() == null ||
+ public List addTag(@RequestBody TagData tagData,
+ Principal principal) {
+ if (tagData.getTag() == null ||
tagData.getTag().getName() == null ||
tagData.getTag().getName().isEmpty() ||
- tagData.getUniqueNodeIds() == null){
+ tagData.getUniqueNodeIds() == null) {
throw new IllegalArgumentException("Cannot add tag, data invalid");
}
+ tagData.getTag().setUserName(principal.getName());
return nodeDAO.addTag(tagData);
}
@@ -77,11 +75,11 @@ public List addTag(@RequestBody TagData tagData){
* @return The list of updated {@link Node}s
*/
@DeleteMapping("/tags")
- public List deleteTag(@RequestBody TagData tagData){
- if(tagData.getTag() == null ||
+ public List deleteTag(@RequestBody TagData tagData) {
+ if (tagData.getTag() == null ||
tagData.getTag().getName() == null ||
tagData.getTag().getName().isEmpty() ||
- tagData.getUniqueNodeIds() == null){
+ tagData.getUniqueNodeIds() == null) {
throw new IllegalArgumentException("Cannot add tag, data invalid");
}
return nodeDAO.deleteTag(tagData);
From a606b3b60427bfd76b4a0d328913b3ecd9f63a01 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Thu, 31 Aug 2023 17:24:23 +0200
Subject: [PATCH 06/42] Client side of save&restore authentication
---
.../ui/ContextMenuSnapshot.java | 20 +-
.../ui/SaveAndRestoreController.java | 6 +-
.../ConfigurationController.java | 21 +--
.../snapshot/CompositeSnapshotController.java | 12 +-
.../ui/snapshot/tag/TagUtil.java | 4 +-
.../saveandrestore/SaveAndRestoreClient.java | 8 +
.../impl/SaveAndRestoreJerseyClient.java | 175 ++++++++----------
.../service/messages.properties | 2 +-
.../CompositeSnapshotController.java | 1 -
.../web/controllers/NodeController.java | 2 +-
.../web/controllers/StructureController.java | 31 ++--
.../CompositeSnapshotControllerTest.java | 2 +-
12 files changed, 117 insertions(+), 167 deletions(-)
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java
index b27475aa65..e3d5b1fccc 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java
@@ -18,20 +18,13 @@
package org.phoebus.applications.saveandrestore.ui;
-import javafx.application.Platform;
-import javafx.beans.property.SimpleObjectProperty;
-import javafx.beans.property.SimpleStringProperty;
import javafx.collections.ObservableList;
-import javafx.event.EventHandler;
-import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
-import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
-import javafx.stage.WindowEvent;
import org.phoebus.applications.saveandrestore.Messages;
import org.phoebus.applications.saveandrestore.model.Node;
import org.phoebus.applications.saveandrestore.model.NodeType;
@@ -43,9 +36,9 @@ public class ContextMenuSnapshot extends ContextMenuBase {
protected Image compareSnapshotIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/save-and-restore/compare.png");
protected Image csvExportIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/csv_export.png");
- private MenuItem compareSnapshotsMenuItem;
+ private final MenuItem compareSnapshotsMenuItem;
- private Menu tagWithComment;
+ private final Menu tagWithComment;
public ContextMenuSnapshot(SaveAndRestoreController saveAndRestoreController,
TreeView treeView) {
@@ -105,8 +98,7 @@ protected void runChecks() {
treeView.getSelectionModel().getSelectedItems();
if (multipleSelection.get() && checkNotTaggable(selected)) {
tagWithComment.disableProperty().set(true);
- }
- else{
+ } else {
tagWithComment.disableProperty().set(false);
}
}
@@ -119,11 +111,12 @@ protected void runChecks() {
*
The snapshot selected from the tree view must have same parent as the one shown in the active {@link org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab}
*
The snapshot selected from the tree view must not be the same as as the one shown in the active {@link org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab}
*
+ *
* @return true if selection can be added to snapshot view for comparison.
*/
private boolean compareSnapshotsPossible() {
Node[] configAndSnapshotNode = saveAndRestoreController.getConfigAndSnapshotForActiveSnapshotTab();
- if(configAndSnapshotNode == null){
+ if (configAndSnapshotNode == null) {
return false;
}
TreeItem selectedItem = treeView.getSelectionModel().getSelectedItem();
@@ -135,11 +128,12 @@ private boolean compareSnapshotsPossible() {
/**
* Checks if selection is not allowed, i.e. not all selected nodes are snapshot nodes.
+ *
* @param selectedItems List of selected nodes
* @return true if any of the selected nodes is of type {@link NodeType#FOLDER} or
* {@link NodeType#CONFIGURATION}.
*/
- private boolean checkNotTaggable(ObservableList> selectedItems){
+ private boolean checkNotTaggable(ObservableList> selectedItems) {
return selectedItems.stream().filter(i -> i.getValue().getNodeType().equals(NodeType.FOLDER) ||
i.getValue().getNodeType().equals(NodeType.CONFIGURATION)).findFirst().isPresent();
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
index 4c938b7d00..3b8803e32a 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
@@ -571,9 +571,11 @@ protected Void call() throws Exception {
@Override
public void failed() {
- expandTreeNode(parentTreeItem.getParent());
+ if(parentTreeItem.getParent() != null) { // Parent is null for root folder
+ expandTreeNode(parentTreeItem.getParent());
+ }
ExceptionDetailsErrorDialog.openError(Messages.errorGeneric,
- Messages.errorCreateFolderFailed, null);
+ Messages.errorCreateFolderFailed, new Exception(getException()));
}
};
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java
index 7bc0b23481..da604ecd28 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java
@@ -28,31 +28,14 @@
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
-import javafx.scene.control.Alert;
-import javafx.scene.control.Button;
-import javafx.scene.control.ButtonType;
-import javafx.scene.control.CheckBox;
-import javafx.scene.control.ContextMenu;
-import javafx.scene.control.Label;
-import javafx.scene.control.MenuItem;
-import javafx.scene.control.SelectionMode;
-import javafx.scene.control.SeparatorMenuItem;
-import javafx.scene.control.TableCell;
-import javafx.scene.control.TableColumn;
-import javafx.scene.control.TableView;
-import javafx.scene.control.TextArea;
-import javafx.scene.control.TextField;
+import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.util.Callback;
import org.phoebus.applications.saveandrestore.Messages;
import org.phoebus.applications.saveandrestore.SaveAndRestoreApplication;
-import org.phoebus.applications.saveandrestore.model.ConfigPv;
-import org.phoebus.applications.saveandrestore.model.Configuration;
-import org.phoebus.applications.saveandrestore.model.ConfigurationData;
-import org.phoebus.applications.saveandrestore.model.Node;
-import org.phoebus.applications.saveandrestore.model.NodeType;
+import org.phoebus.applications.saveandrestore.model.*;
import org.phoebus.applications.saveandrestore.ui.NodeChangedListener;
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreController;
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService;
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotController.java
index b4db6e120c..4bb9d25c46 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotController.java
@@ -318,9 +318,8 @@ public void updateItem(Node item, boolean empty) {
@FXML
public void save() {
- doSave(compositeSnapshot -> {
- loadCompositeSnapshot(compositeSnapshot.getCompositeSnapshotNode(), Collections.emptyList());
- });
+ doSave(compositeSnapshot ->
+ loadCompositeSnapshot(compositeSnapshot.getCompositeSnapshotNode(), Collections.emptyList()));
}
private void doSave(Consumer completion) {
@@ -466,15 +465,16 @@ public void addToCompositeSnapshot(List sourceNodes) {
e);
}
disabledUi.set(false);
- if (duplicates.isEmpty()) {
+ if (duplicates != null && duplicates.isEmpty()) {
snapshotEntries.addAll(sourceNodes);
} else {
int maxItems = 10;
StringBuilder stringBuilder = new StringBuilder();
- for (int i = 0; i < Math.min(duplicates.size(), maxItems); i++) {
+ int duplicatesSize = duplicates != null ? duplicates.size() : 0;
+ for (int i = 0; i < Math.min(duplicatesSize, maxItems); i++) {
stringBuilder.append(duplicates.get(i)).append(System.lineSeparator());
}
- if (duplicates.size() > maxItems) {
+ if (duplicatesSize > maxItems) {
stringBuilder.append(".").append(System.lineSeparator())
.append(".").append(System.lineSeparator())
.append(".").append(System.lineSeparator());
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/tag/TagUtil.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/tag/TagUtil.java
index ec5601b566..0fa41f0ca9 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/tag/TagUtil.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/tag/TagUtil.java
@@ -96,7 +96,7 @@ public static void tagWithComment(Menu parentMenu,
Consumer> callback) {
AtomicInteger nonSnapshotCount = new AtomicInteger(0);
selectedNodes.forEach(n -> {
- if (!n.getNodeType().equals(NodeType.SNAPSHOT)) {
+ if (!n.getNodeType().equals(NodeType.SNAPSHOT) && !n.getNodeType().equals(NodeType.COMPOSITE_SNAPSHOT)) {
nonSnapshotCount.incrementAndGet();
}
});
@@ -195,7 +195,7 @@ public static void configureGoldenItem(List {
if (node.hasTag(Tag.GOLDEN)) {
goldenTagCount.incrementAndGet();
- } else if (!node.getNodeType().equals(NodeType.SNAPSHOT) && !node.getNodeType().equals(NodeType.COMPOSITE_SNAPSHOT)) {
+ } else if (!node.getNodeType().equals(NodeType.SNAPSHOT)) {
nonSnapshotCount.incrementAndGet();
}
});
diff --git a/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreClient.java b/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreClient.java
index 47d62f4bdb..6657268f28 100644
--- a/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreClient.java
+++ b/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreClient.java
@@ -137,6 +137,14 @@ public interface SaveAndRestoreClient {
ConfigurationData getConfigurationData(String nodeId);
+ /**
+ * Creates a new {@link Node} of type {@link NodeType#CONFIGURATION} in the remote
+ * service.
+ * @param parentNodeId Non-null and non-empty unique id of an existing parent {@link Node},
+ * which must be of type {@link NodeType#FOLDER}.
+ * @param configuration {@link ConfigurationData} object
+ * @return A representation of the persisted {@link Configuration}.
+ */
Configuration createConfiguration(String parentNodeId, Configuration configuration);
Configuration updateConfiguration(Configuration configuration);
diff --git a/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/impl/SaveAndRestoreJerseyClient.java b/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/impl/SaveAndRestoreJerseyClient.java
index df23bc96dd..b1ed5a50e4 100644
--- a/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/impl/SaveAndRestoreJerseyClient.java
+++ b/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/impl/SaveAndRestoreJerseyClient.java
@@ -22,12 +22,9 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
-import com.sun.jersey.api.client.Client;
-import com.sun.jersey.api.client.ClientHandlerException;
-import com.sun.jersey.api.client.ClientResponse;
-import com.sun.jersey.api.client.GenericType;
-import com.sun.jersey.api.client.UniformInterfaceException;
-import com.sun.jersey.api.client.WebResource;
+
+
+import com.sun.jersey.api.client.*;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
@@ -49,7 +46,6 @@
public class SaveAndRestoreJerseyClient implements SaveAndRestoreClient {
- private Client client;
private static final String CONTENT_TYPE_JSON = "application/json; charset=UTF-8";
private final Logger logger = Logger.getLogger(SaveAndRestoreJerseyClient.class.getName());
@@ -90,7 +86,7 @@ public SaveAndRestoreJerseyClient() {
}
}
- private Client getClient(){
+ private Client getClient() {
DefaultClientConfig defaultClientConfig = new DefaultClientConfig();
defaultClientConfig.getProperties().put(ClientConfig.PROPERTY_READ_TIMEOUT, httpClientReadTimeout);
defaultClientConfig.getProperties().put(ClientConfig.PROPERTY_CONNECT_TIMEOUT, httpClientConnectTimeout);
@@ -98,18 +94,17 @@ private Client getClient(){
JacksonJsonProvider jacksonJsonProvider = new JacksonJsonProvider(mapper);
defaultClientConfig.getSingletons().add(jacksonJsonProvider);
- client = Client.create(defaultClientConfig);
+ Client client = Client.create(defaultClientConfig);
try {
SecureStore store = new SecureStore();
ScopedAuthenticationToken scopedAuthenticationToken = store.getScopedAuthenticationToken("save-and-restore");
- if(scopedAuthenticationToken != null){
+ if (scopedAuthenticationToken != null) {
String username = scopedAuthenticationToken.getUsername();
String password = scopedAuthenticationToken.getPassword();
httpBasicAuthFilter = new HTTPBasicAuthFilter(username, password);
client.addFilter(httpBasicAuthFilter);
- }
- else if(httpBasicAuthFilter != null){
+ } else if (httpBasicAuthFilter != null) {
client.removeFilter(httpBasicAuthFilter);
}
} catch (Exception e) {
@@ -135,11 +130,11 @@ public Node getNode(String uniqueNodeId) {
}
@Override
- public List getCompositeSnapshotReferencedNodes(String uniqueNodeId){
+ public List getCompositeSnapshotReferencedNodes(String uniqueNodeId) {
WebResource webResource = getClient().resource(jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/nodes");
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).get(ClientResponse.class);
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message;
try {
message = new String(response.getEntityInputStream().readAllBytes());
@@ -149,16 +144,16 @@ public List getCompositeSnapshotReferencedNodes(String uniqueNodeId){
throw new SaveAndRestoreClientException("Failed : HTTP error code : " + response.getStatus() + ", error message: " + message);
}
- return response.getEntity(new GenericType>() {
+ return response.getEntity(new GenericType<>() {
});
}
@Override
- public List getCompositeSnapshotItems(String uniqueNodeId){
+ public List getCompositeSnapshotItems(String uniqueNodeId) {
WebResource webResource = getClient().resource(jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/items");
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).get(ClientResponse.class);
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message;
try {
message = new String(response.getEntityInputStream().readAllBytes());
@@ -168,7 +163,7 @@ public List getCompositeSnapshotItems(String uniqueNodeId){
throw new SaveAndRestoreClientException("Failed : HTTP error code : " + response.getStatus() + ", error message: " + message);
}
- return response.getEntity(new GenericType>() {
+ return response.getEntity(new GenericType<>() {
});
}
@@ -180,29 +175,26 @@ public Node getParentNode(String unqiueNodeId) {
@Override
public List getChildNodes(String uniqueNodeId) throws SaveAndRestoreClientException {
ClientResponse response = getCall("/node/" + uniqueNodeId + "/children");
- return response.getEntity(new GenericType>() {
+ return response.getEntity(new GenericType<>() {
});
}
@Override
public Node createNewNode(String parentNodeId, Node node) {
- node.setUserName(getCurrentUsersName());
WebResource webResource = getClient().resource(jmasarServiceUrl + "/node").queryParam("parentNodeId", parentNodeId);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(node, CONTENT_TYPE_JSON)
.put(ClientResponse.class);
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = Messages.createNodeFailed;
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
- // Ignore
+ logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new SaveAndRestoreClientException(message);
}
-
return response.getEntity(Node.class);
-
}
@Override
@@ -212,9 +204,6 @@ public Node updateNode(Node nodeToUpdate) {
@Override
public Node updateNode(Node nodeToUpdate, boolean customTimeForMigration) {
- if (nodeToUpdate.getUserName() == null || nodeToUpdate.getUserName().isEmpty()) {
- nodeToUpdate.setUserName(getCurrentUsersName());
- }
WebResource webResource = getClient().resource(jmasarServiceUrl + "/node")
.queryParam("customTimeForMigration", customTimeForMigration ? "true" : "false");
@@ -222,12 +211,12 @@ public Node updateNode(Node nodeToUpdate, boolean customTimeForMigration) {
.entity(nodeToUpdate, CONTENT_TYPE_JSON)
.post(ClientResponse.class);
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = Messages.updateNodeFailed;
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
- // Ignore
+ logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new SaveAndRestoreClientException(message);
}
@@ -236,7 +225,6 @@ public Node updateNode(Node nodeToUpdate, boolean customTimeForMigration) {
}
private T getCall(String relativeUrl, Class clazz) {
-
ClientResponse response = getCall(relativeUrl);
return response.getEntity(clazz);
}
@@ -245,7 +233,7 @@ private ClientResponse getCall(String relativeUrl) {
WebResource webResource = getClient().resource(jmasarServiceUrl + relativeUrl);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).get(ClientResponse.class);
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message;
try {
message = new String(response.getEntityInputStream().readAllBytes());
@@ -262,7 +250,7 @@ private ClientResponse getCall(String relativeUrl) {
public void deleteNode(String uniqueNodeId) {
WebResource webResource = getClient().resource(jmasarServiceUrl + "/node/" + uniqueNodeId);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).delete(ClientResponse.class);
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = response.getEntity(String.class);
throw new SaveAndRestoreClientException("Failed : HTTP error code : " + response.getStatus() + ", error message: " + message);
}
@@ -273,21 +261,17 @@ public void deleteNodes(List nodeIds) {
nodeIds.forEach(this::deleteNode);
}
- private String getCurrentUsersName() {
- return System.getProperty("user.name");
- }
-
@Override
public List getAllTags() {
ClientResponse response = getCall("/tags");
- return response.getEntity(new GenericType>() {
+ return response.getEntity(new GenericType<>() {
});
}
@Override
public List getAllSnapshots() {
ClientResponse response = getCall("/snapshots");
- return response.getEntity(new GenericType>() {
+ return response.getEntity(new GenericType<>() {
});
}
@@ -295,19 +279,18 @@ public List getAllSnapshots() {
public Node moveNodes(List sourceNodeIds, String targetNodeId) {
WebResource webResource =
getClient().resource(jmasarServiceUrl + "/move")
- .queryParam("to", targetNodeId)
- .queryParam("username", getCurrentUsersName());
+ .queryParam("to", targetNodeId);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(sourceNodeIds, CONTENT_TYPE_JSON)
.post(ClientResponse.class);
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = Messages.copyOrMoveNotAllowedBody;
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
- // Ignore
+ logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new SaveAndRestoreClientException(message);
}
@@ -318,19 +301,18 @@ public Node moveNodes(List sourceNodeIds, String targetNodeId) {
public Node copyNodes(List sourceNodeIds, String targetNodeId) {
WebResource webResource =
getClient().resource(jmasarServiceUrl + "/copy")
- .queryParam("to", targetNodeId)
- .queryParam("username", getCurrentUsersName());
+ .queryParam("to", targetNodeId);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(sourceNodeIds, CONTENT_TYPE_JSON)
.post(ClientResponse.class);
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = Messages.copyOrMoveNotAllowedBody;
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
- // Ignore
+ logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new SaveAndRestoreClientException(message);
}
@@ -343,7 +325,7 @@ public String getFullPath(String uniqueNodeId) {
getClient().resource(jmasarServiceUrl + "/path/" + uniqueNodeId);
ClientResponse response = webResource.get(ClientResponse.class);
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
return null;
}
return response.getEntity(String.class);
@@ -362,19 +344,18 @@ public ConfigurationData getConfigurationData(String nodeId) {
@Override
public Configuration createConfiguration(String parentNodeId, Configuration configuration) {
- configuration.getConfigurationNode().setUserName(getCurrentUsersName());
WebResource webResource =
getClient().resource(jmasarServiceUrl + "/config")
.queryParam("parentNodeId", parentNodeId);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(configuration, CONTENT_TYPE_JSON)
.put(ClientResponse.class);
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = Messages.createConfigurationFailed;
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
- // Ignore
+ logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new SaveAndRestoreClientException(message);
}
@@ -388,12 +369,12 @@ public Configuration updateConfiguration(Configuration configuration) {
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(configuration, CONTENT_TYPE_JSON)
.post(ClientResponse.class);
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = Messages.updateConfigurationFailed;
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
- // Ignore
+ logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new RuntimeException(message);
}
@@ -408,7 +389,6 @@ public SnapshotData getSnapshotData(String nodeId) {
@Override
public Snapshot saveSnapshot(String parentNodeId, Snapshot snapshot) {
- snapshot.getSnapshotNode().setUserName(getCurrentUsersName());
WebResource webResource =
getClient().resource(jmasarServiceUrl + "/snapshot")
.queryParam("parentNodeId", parentNodeId);
@@ -419,15 +399,13 @@ public Snapshot saveSnapshot(String parentNodeId, Snapshot snapshot) {
.put(ClientResponse.class);
} catch (UniformInterfaceException e) {
throw new RuntimeException(e);
- } catch (ClientHandlerException e) {
- throw new RuntimeException(e);
}
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = Messages.searchFailed;
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
- // Ignore
+ logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new SaveAndRestoreClientException(message);
}
@@ -435,20 +413,19 @@ public Snapshot saveSnapshot(String parentNodeId, Snapshot snapshot) {
}
@Override
- public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeSnapshot compositeSnapshot){
- compositeSnapshot.getCompositeSnapshotNode().setUserName(getCurrentUsersName());
+ public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeSnapshot compositeSnapshot) {
WebResource webResource =
getClient().resource(jmasarServiceUrl + "/composite-snapshot")
.queryParam("parentNodeId", parentNodeId);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(compositeSnapshot, CONTENT_TYPE_JSON)
.put(ClientResponse.class);
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = Messages.createConfigurationFailed;
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
- // Ignore
+ logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new SaveAndRestoreClientException(message);
}
@@ -456,38 +433,38 @@ public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeS
}
@Override
- public List checkCompositeSnapshotConsistency(List snapshotNodeIds){
+ public List checkCompositeSnapshotConsistency(List snapshotNodeIds) {
WebResource webResource =
getClient().resource(jmasarServiceUrl + "/composite-snapshot-consistency-check");
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(snapshotNodeIds, CONTENT_TYPE_JSON)
.post(ClientResponse.class);
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = Messages.compositeSnapshotConsistencyCheckFailed;
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
- // Ignore
+ logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new SaveAndRestoreClientException(message);
}
- return response.getEntity(new GenericType>() {
+ return response.getEntity(new GenericType<>() {
});
}
@Override
- public CompositeSnapshot updateCompositeSnapshot(CompositeSnapshot compositeSnapshot){
+ public CompositeSnapshot updateCompositeSnapshot(CompositeSnapshot compositeSnapshot) {
WebResource webResource = getClient().resource(jmasarServiceUrl + "/composite-snapshot");
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(compositeSnapshot, CONTENT_TYPE_JSON)
.post(ClientResponse.class);
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = Messages.updateConfigurationFailed;
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
- // Ignore
+ logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new RuntimeException(message);
}
@@ -495,17 +472,17 @@ public CompositeSnapshot updateCompositeSnapshot(CompositeSnapshot compositeSnap
}
@Override
- public SearchResult search(MultivaluedMap searchParams){
+ public SearchResult search(MultivaluedMap searchParams) {
WebResource webResource = getClient().resource(jmasarServiceUrl + "/search")
.queryParams(searchParams);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.get(ClientResponse.class);
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = Messages.searchFailed;
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
- // Ignore
+ logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new RuntimeException(message);
}
@@ -513,19 +490,18 @@ public SearchResult search(MultivaluedMap searchParams){
}
@Override
- public Filter saveFilter(Filter filter){
- filter.setUser(getCurrentUsersName());
+ public Filter saveFilter(Filter filter) {
WebResource webResource = getClient().resource(jmasarServiceUrl + "/filter");
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(filter, CONTENT_TYPE_JSON)
.put(ClientResponse.class);
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = Messages.saveFilterFailed;
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
- // Ignore
+ logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new RuntimeException(message);
}
@@ -533,35 +509,36 @@ public Filter saveFilter(Filter filter){
}
@Override
- public List getAllFilters(){
+ public List getAllFilters() {
WebResource webResource = getClient().resource(jmasarServiceUrl + "/filters");
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.get(ClientResponse.class);
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = Messages.searchFailed;
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
- // Ignore
+ logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new RuntimeException(message);
}
- return response.getEntity(new GenericType>(){});
+ return response.getEntity(new GenericType<>() {
+ });
}
@Override
- public void deleteFilter(String name){
+ public void deleteFilter(String name) {
// Filter name may contain space chars, need to URL encode these.
String filterName = name.replace(" ", "%20");
- WebResource webResource = getClient().resource(jmasarServiceUrl + "/filter/" + filterName);
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/filter/" + filterName);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.delete(ClientResponse.class);
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = Messages.deleteFilterFailed;
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
- // Ignore
+ logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new RuntimeException(message);
}
@@ -569,11 +546,12 @@ public void deleteFilter(String name){
/**
* Adds a tag to a list of unique node ids, see {@link TagData}
+ *
* @param tagData see {@link TagData}
* @return A list of updated {@link Node}s. This may contain fewer elements than the list of unique node ids
* passed in the tagData parameter.
*/
- public List addTag(TagData tagData){
+ public List addTag(TagData tagData) {
WebResource webResource =
getClient().resource(jmasarServiceUrl + "/tags");
ClientResponse response;
@@ -583,29 +561,28 @@ public List addTag(TagData tagData){
.post(ClientResponse.class);
} catch (UniformInterfaceException e) {
throw new RuntimeException(e);
- } catch (ClientHandlerException e) {
- throw new RuntimeException(e);
}
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = Messages.tagAddFailed;
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
- // Ignore
+ logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new SaveAndRestoreClientException(message);
}
- return response.getEntity(new GenericType>() {
+ return response.getEntity(new GenericType<>() {
});
}
/**
* Deletes a tag from a list of unique node ids, see {@link TagData}
+ *
* @param tagData see {@link TagData}
* @return A list of updated {@link Node}s. This may contain fewer elements than the list of unique node ids
* passed in the tagData parameter.
*/
- public List deleteTag(TagData tagData){
+ public List deleteTag(TagData tagData) {
WebResource webResource =
getClient().resource(jmasarServiceUrl + "/tags");
ClientResponse response;
@@ -615,24 +592,22 @@ public List deleteTag(TagData tagData){
.delete(ClientResponse.class);
} catch (UniformInterfaceException e) {
throw new RuntimeException(e);
- } catch (ClientHandlerException e) {
- throw new RuntimeException(e);
}
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = Messages.tagAddFailed;
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
- // Ignore
+ logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new SaveAndRestoreClientException(message);
}
- return response.getEntity(new GenericType>() {
+ return response.getEntity(new GenericType<>() {
});
}
@Override
- public UserData authenticate(String userName, String password){
+ public UserData authenticate(String userName, String password) {
WebResource webResource =
getClient().resource(jmasarServiceUrl + "/login")
.queryParam("username", userName)
@@ -643,19 +618,17 @@ public UserData authenticate(String userName, String password){
.post(ClientResponse.class);
} catch (UniformInterfaceException e) {
throw new RuntimeException(e);
- } catch (ClientHandlerException e) {
- throw new RuntimeException(e);
}
- if (response.getStatus() != 200) {
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = Messages.authenticationFailed;
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
- // Ignore
+ logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new SaveAndRestoreClientException(message);
}
- return response.getEntity(new GenericType() {
+ return response.getEntity(new GenericType<>() {
});
}
}
diff --git a/app/save-and-restore/service/src/main/resources/org/phoebus/applications/saveandrestore/service/messages.properties b/app/save-and-restore/service/src/main/resources/org/phoebus/applications/saveandrestore/service/messages.properties
index 08d595919e..dca0da6d2a 100644
--- a/app/save-and-restore/service/src/main/resources/org/phoebus/applications/saveandrestore/service/messages.properties
+++ b/app/save-and-restore/service/src/main/resources/org/phoebus/applications/saveandrestore/service/messages.properties
@@ -1,4 +1,4 @@
-uthenticationFailed=Authentication Failed
+authenticationFailed=Authentication Failed
compositeSnapshotConsistencyCheckFailed=Failed to check consistency for composite snapshot
copyOrMoveNotAllowedBody=Selection cannot be moved/copied to the specified target node.
createCompositeSnapshotFailed=Failed to create composite snapshot
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
index 0d7c7bf60d..32461f63d1 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
@@ -85,7 +85,6 @@ public List getCompositeSnapshotItems(@PathVariable String uniqueI
* @return A list of PV names that occur more than once across the list of {@link Node}s corresponding
* to the input. Empty if no duplicates are found.
*/
- // TODO: Should be GET, not POST
@PostMapping(value = "/composite-snapshot-consistency-check", produces = JSON)
public List checkSnapshotsConsistency(@RequestBody List snapshotNodeIds) {
return nodeDAO.checkForPVNameDuplicates(snapshotNodeIds);
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
index 561071997e..6666cd2c6c 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
@@ -62,7 +62,7 @@ public class NodeController extends BaseController {
@PutMapping(value = "/node", produces = JSON)
public Node createNode(@RequestParam(name = "parentNodeId") String parentsUniqueId,
@RequestBody final Node node,
- Principal principal) {cd
+ Principal principal) {
if (node.getUserName() == null || node.getUserName().isEmpty()) {
throw new IllegalArgumentException("User name must be non-null and of non-zero length");
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/StructureController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/StructureController.java
index 1dc53e5c7a..077c2fbe0c 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/StructureController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/StructureController.java
@@ -21,12 +21,7 @@
import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.security.Principal;
@@ -45,16 +40,13 @@ public class StructureController extends BaseController {
@Autowired
private NodeDAO nodeDAO;
- private Logger logger = Logger.getLogger(StructureController.class.getName());
-
-
/**
* Moves a list of source nodes to a new target (parent) node.
*
- * @param to The unique id of the new parent, which must be a folder. If empty or if
- * target node does not exist, {@link HttpStatus#BAD_REQUEST} is returned.
- * @param nodes List of source nodes to move. If empty, or if any of the listed source nodes does not exist,
- * {@link HttpStatus#BAD_REQUEST} is returned.
+ * @param to The unique id of the new parent, which must be a folder. If empty or if
+ * target node does not exist, {@link HttpStatus#BAD_REQUEST} is returned.
+ * @param nodes List of source nodes to move. If empty, or if any of the listed source nodes does not exist,
+ * {@link HttpStatus#BAD_REQUEST} is returned.
* @param principal The {@link Principal} of the authenticated user.
* @return The (updated) target node.
*/
@@ -64,7 +56,7 @@ public Node moveNodes(@RequestParam(value = "to") String to,
@RequestBody List nodes,
Principal principal) {
if (to.isEmpty() || nodes.isEmpty()) {
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Username, target node and list of source nodes must all be non-empty.");
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Target node and list of source nodes must all be non-empty.");
}
Logger.getLogger(StructureController.class.getName()).info(Thread.currentThread().getName() + " " + (new Date()) + " move");
return nodeDAO.moveNodes(nodes, to, principal.getName());
@@ -74,21 +66,20 @@ public Node moveNodes(@RequestParam(value = "to") String to,
* Copies a list of source nodes to a target (parent) node. Since the source nodes may contain sub-trees at
* any depth, the copy operation needs to do a deep copy, which may take some time to complete.
*
- * @param to The unique id of the target parent node, which must be a folder. If empty or if
- * target node does not exist, {@link HttpStatus#BAD_REQUEST} is returned.
- * @param nodes List of source nodes to copy. If empty, or if any of the listed source nodes does not exist,
- * {@link HttpStatus#BAD_REQUEST} is returned.
+ * @param to The unique id of the target parent node, which must be a folder. If empty or if
+ * target node does not exist, {@link HttpStatus#BAD_REQUEST} is returned.
+ * @param nodes List of source nodes to copy. If empty, or if any of the listed source nodes does not exist,
+ * {@link HttpStatus#BAD_REQUEST} is returned.
* @param principal The {@link Principal} of the authenticated user.
* @return The (updated) target node.
*/
@SuppressWarnings("unused")
@PostMapping(value = "/copy", produces = JSON)
public Node copyNodes(@RequestParam(value = "to") String to,
- @RequestParam(value = "username") String userName,
@RequestBody List nodes,
Principal principal) {
if (to.isEmpty() || nodes.isEmpty()) {
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Username, target node and list of source nodes must all be non-empty.");
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Target node and list of source nodes must all be non-empty.");
}
Logger.getLogger(StructureController.class.getName()).info(Thread.currentThread().getName() + " " + (new Date()) + " move");
return nodeDAO.copyNodes(nodes, to, principal.getName());
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java
index 12dbb26113..4dd0979d39 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java
@@ -178,7 +178,7 @@ public void testGetCompositeSnapshotConsistency() throws Exception {
when(nodeDAO.checkForPVNameDuplicates(Mockito.any(List.class))).thenReturn(List.of("ref"));
- MockHttpServletRequestBuilder request = post("/composite-snapshot-consistency-check").contentType(JSON)
+ MockHttpServletRequestBuilder request = get("/composite-snapshot-consistency-check").contentType(JSON)
.content(objectMapper.writeValueAsString(List.of("id")));
MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))
From 12270de3196560679bbcc01646c86e17b61556f7 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Mon, 4 Sep 2023 15:54:37 +0200
Subject: [PATCH 07/42] Save&restore context menu items disabled based on user
authentication status
---
.../saveandrestore/ui/BrowserTreeCell.java | 2 +-
.../saveandrestore/ui/ContextMenuBase.java | 101 +++++++-------
.../ui/ContextMenuCompositeSnapshot.java | 28 ++--
.../ui/ContextMenuConfiguration.java | 34 +++--
.../saveandrestore/ui/ContextMenuFolder.java | 39 ++++--
.../ui/ContextMenuSnapshot.java | 87 ++++--------
.../ui/SaveAndRestoreController.java | 128 +++++++++++++++---
.../ui/snapshot/tag/TagUtil.java | 12 +-
8 files changed, 263 insertions(+), 168 deletions(-)
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java
index 799b695d82..8809d50357 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java
@@ -86,7 +86,7 @@ public BrowserTreeCell(ContextMenu folderContextMenu, ContextMenu configurationC
});
setOnDragDetected(event -> {
- if (!saveAndRestoreController.checkMultipleSelection()) {
+ if (!saveAndRestoreController.selectedNodesOfSameType()) {
return;
}
final ClipboardContent content = new ClipboardContent();
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java
index 045d8009f8..ead8fd679b 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java
@@ -18,18 +18,12 @@
package org.phoebus.applications.saveandrestore.ui;
-import javafx.application.Platform;
+import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
-import javafx.collections.ListChangeListener;
-import javafx.collections.ObservableList;
-import javafx.event.EventHandler;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
-import javafx.scene.control.TreeItem;
-import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
-import javafx.scene.input.ContextMenuEvent;
import org.phoebus.applications.saveandrestore.Messages;
import org.phoebus.applications.saveandrestore.model.Node;
import org.phoebus.ui.javafx.ImageCache;
@@ -41,71 +35,72 @@ public abstract class ContextMenuBase extends ContextMenu {
protected MenuItem deleteNodesMenuItem;
protected MenuItem copyUniqueIdToClipboardMenuItem;
- protected TreeView treeView;
-
- protected SimpleBooleanProperty multipleSelection = new SimpleBooleanProperty();
+ protected SimpleBooleanProperty multipleNodesSelectedProperty = new SimpleBooleanProperty();
protected SaveAndRestoreController saveAndRestoreController;
- public ContextMenuBase(SaveAndRestoreController saveAndRestoreController, TreeView treeView) {
- this.treeView = treeView;
+ /**
+ * Property showing if user has signed in or not. Context menus should in onShowing
+ * check the sign-in status and set the property accordingly to determine which
+ * menu items to disable (e.g. create or delete data).
+ */
+ protected SimpleBooleanProperty userIsAuthenticatedProperty =
+ new SimpleBooleanProperty();
+
+ /**
+ * Property showing if selected {@link Node}s have the same parent {@link Node}. Context menus should
+ * in onShowing check the selection and determine which menu items to disable.
+ */
+ protected SimpleBooleanProperty hasSameParentProperty =
+ new SimpleBooleanProperty();
+
+ /**
+ * Property updated based on check of multiple {@link Node} selection,
+ * e.g. selection of different type of {@link Node}s. Context menus should
+ * in onShowing check the selection and determine which menu items to disable.
+ */
+ protected SimpleBooleanProperty nodesOfSameTypeProperty =
+ new SimpleBooleanProperty();
+
+ protected SimpleBooleanProperty mayPasteProperty =
+ new SimpleBooleanProperty();
+
+ protected SimpleBooleanProperty mayCopyProperty =
+ new SimpleBooleanProperty();
+
+ public ContextMenuBase(SaveAndRestoreController saveAndRestoreController) {
this.saveAndRestoreController = saveAndRestoreController;
deleteNodesMenuItem = new MenuItem(Messages.contextMenuDelete, new ImageView(ImageRepository.DELETE));
deleteNodesMenuItem.setOnAction(ae -> saveAndRestoreController.deleteNodes());
+ deleteNodesMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
+ userIsAuthenticatedProperty.not().get() ||
+ hasSameParentProperty.not().get(),
+ userIsAuthenticatedProperty, hasSameParentProperty));
copyUniqueIdToClipboardMenuItem = new MenuItem(Messages.copyUniqueIdToClipboard, ImageCache.getImageView(ImageCache.class, "/icons/copy.png"));
copyUniqueIdToClipboardMenuItem.setOnAction(ae -> saveAndRestoreController.copyUniqueNodeIdToClipboard());
- copyUniqueIdToClipboardMenuItem.disableProperty().bind(multipleSelection);
-
- treeView.getSelectionModel().getSelectedItems()
- .addListener((ListChangeListener>) c ->
- multipleSelection.set(treeView.getSelectionModel().getSelectedItems().size() > 1));
+ copyUniqueIdToClipboardMenuItem.disableProperty().bind(multipleNodesSelectedProperty);
setOnShowing(event -> {
- if(!saveAndRestoreController.checkMultipleSelection()){
- Platform.runLater(() -> hide());
- }
- else{
- runChecks();
- }
+ runChecks();
});
}
/**
- * Applies logic to determine which context menu items to disable as some actions (e.g. rename) do not
- * make sense if multiple items are selected. Special case is if nodes in different parent nodes
- * are selected, in this case none of the menu items make sense, to the context menu is suppressed.
+ * Applies logic to determine if the user is authenticated and if multiple {@link Node}s have been selected.
+ * Subclasses use this to disable menu items if needed, e.g. disable delete if user has not signed in.
*/
protected void runChecks() {
- ObservableList> selected =
- treeView.getSelectionModel().getSelectedItems();
- if (multipleSelection.get() && !hasSameParent(selected)) {
- deleteNodesMenuItem.disableProperty().set(true);
+ userIsAuthenticatedProperty.set(saveAndRestoreController.isUserAuthenticated());
+ boolean multipleNodesSelected = saveAndRestoreController.multipleNodesSelected();
+ multipleNodesSelectedProperty.set(multipleNodesSelected);
+ if (multipleNodesSelected) { // No need to check this if only one node was selected
+ hasSameParentProperty.set(saveAndRestoreController.hasSameParent());
+ nodesOfSameTypeProperty.set(saveAndRestoreController.selectedNodesOfSameType());
} else {
- deleteNodesMenuItem.disableProperty().set(false);
- }
- }
-
- /**
- * Used to determine if nodes selected in the tree view have the same parent node. Most menu items
- * do not make sense unless the selected nodes have same the parent node.
- *
- * @param selectedItems The selected tree nodes.
- * @return true if all selected nodes have the same parent node, false otherwise.
- */
- protected boolean hasSameParent(ObservableList> selectedItems) {
- if (selectedItems.size() == 1) {
- return true;
- }
- Node parentNodeOfFirst = selectedItems.get(0).getParent().getValue();
- for (int i = 1; i < selectedItems.size(); i++) {
- TreeItem treeItem = selectedItems.get(i);
- if (!treeItem.getParent().getValue().getUniqueId().equals(parentNodeOfFirst.getUniqueId())) {
- System.out.println(parentNodeOfFirst.getUniqueId() + " " + treeItem.getParent().getValue().getUniqueId());
- return false;
- }
+ hasSameParentProperty.set(true);
+ nodesOfSameTypeProperty.set(true);
}
- return true;
}
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuCompositeSnapshot.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuCompositeSnapshot.java
index 0403c1b58c..238dd32aed 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuCompositeSnapshot.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuCompositeSnapshot.java
@@ -18,29 +18,31 @@
package org.phoebus.applications.saveandrestore.ui;
+import javafx.beans.binding.Bindings;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
-import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import org.phoebus.applications.saveandrestore.Messages;
-import org.phoebus.applications.saveandrestore.model.Node;
import org.phoebus.applications.saveandrestore.ui.snapshot.tag.TagWidget;
import org.phoebus.ui.javafx.ImageCache;
public class ContextMenuCompositeSnapshot extends ContextMenuBase {
- public ContextMenuCompositeSnapshot(SaveAndRestoreController saveAndRestoreController, TreeView treeView) {
- super(saveAndRestoreController, treeView);
+ public ContextMenuCompositeSnapshot(SaveAndRestoreController saveAndRestoreController) {
+ super(saveAndRestoreController);
Image snapshotTagsWithCommentIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/save-and-restore/snapshot-tags.png");
MenuItem editCompositeSnapshotMenuItem = new MenuItem(Messages.Edit, new ImageView(ImageRepository.EDIT_CONFIGURATION));
- editCompositeSnapshotMenuItem.disableProperty().bind(multipleSelection);
+ editCompositeSnapshotMenuItem.disableProperty().bind(multipleNodesSelectedProperty);
editCompositeSnapshotMenuItem.setOnAction(ae ->
saveAndRestoreController.editCompositeSnapshot());
+ editCompositeSnapshotMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
+ userIsAuthenticatedProperty.not().get() || multipleNodesSelectedProperty.get(),
+ userIsAuthenticatedProperty, multipleNodesSelectedProperty));
ImageView snapshotTagsWithCommentIconImage = new ImageView(snapshotTagsWithCommentIcon);
snapshotTagsWithCommentIconImage.setFitHeight(22);
@@ -48,6 +50,9 @@ public ContextMenuCompositeSnapshot(SaveAndRestoreController saveAndRestoreContr
Menu tagWithComment = new Menu(Messages.contextMenuTagsWithComment, snapshotTagsWithCommentIconImage);
tagWithComment.setOnShowing(event -> saveAndRestoreController.tagWithComment(tagWithComment));
+ tagWithComment.disableProperty().bind(Bindings.createBooleanBinding(() ->
+ multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(),
+ multipleNodesSelectedProperty, userIsAuthenticatedProperty));
CustomMenuItem addTagWithCommentMenuItem = TagWidget.AddTagWithCommentMenuItem();
addTagWithCommentMenuItem.setOnAction(action -> saveAndRestoreController.addTagToSnapshots());
@@ -57,10 +62,9 @@ public ContextMenuCompositeSnapshot(SaveAndRestoreController saveAndRestoreContr
Image copyIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/copy.png");
MenuItem copyMenuItem = new MenuItem(Messages.copy, new ImageView(copyIcon));
copyMenuItem.setOnAction(action -> saveAndRestoreController.copySelectionToClipboard());
-
- setOnShowing(event -> {
- copyMenuItem.setDisable(!saveAndRestoreController.mayCopy());
- });
+ copyMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
+ userIsAuthenticatedProperty.not().get() || mayCopyProperty.not().get(),
+ userIsAuthenticatedProperty, mayCopyProperty));
getItems().addAll(editCompositeSnapshotMenuItem,
copyMenuItem,
@@ -68,4 +72,10 @@ public ContextMenuCompositeSnapshot(SaveAndRestoreController saveAndRestoreContr
copyUniqueIdToClipboardMenuItem,
tagWithComment);
}
+
+ @Override
+ public void runChecks() {
+ super.runChecks();
+ mayCopyProperty.set(saveAndRestoreController.mayCopy());
+ }
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuConfiguration.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuConfiguration.java
index 81c7454468..8a35ce0144 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuConfiguration.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuConfiguration.java
@@ -18,6 +18,7 @@
package org.phoebus.applications.saveandrestore.ui;
+import javafx.beans.binding.Bindings;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
@@ -28,18 +29,21 @@
public class ContextMenuConfiguration extends ContextMenuBase{
- public ContextMenuConfiguration(SaveAndRestoreController saveAndRestoreController,
- TreeView treeView) {
- super(saveAndRestoreController, treeView);
+ public ContextMenuConfiguration(SaveAndRestoreController saveAndRestoreController) {
+ super(saveAndRestoreController);
Image csvExportIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/csv_export.png");
MenuItem openConfigurationMenuItem = new MenuItem(Messages.contextMenuCreateSnapshot, new ImageView(ImageRepository.CONFIGURATION));
- openConfigurationMenuItem.disableProperty().bind(multipleSelection);
+ openConfigurationMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
+ multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(),
+ multipleNodesSelectedProperty, userIsAuthenticatedProperty));
openConfigurationMenuItem.setOnAction(ae -> saveAndRestoreController.openConfigurationForSnapshot());
MenuItem editConfigurationMenuItem = new MenuItem(Messages.Edit, new ImageView(ImageRepository.EDIT_CONFIGURATION));
- editConfigurationMenuItem.disableProperty().bind(multipleSelection);
+ editConfigurationMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
+ multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(),
+ multipleNodesSelectedProperty, userIsAuthenticatedProperty));
editConfigurationMenuItem.setOnAction(ae -> saveAndRestoreController.nodeDoubleClicked());
ImageView exportConfigurationIconImageView = new ImageView(csvExportIcon);
@@ -47,7 +51,7 @@ public ContextMenuConfiguration(SaveAndRestoreController saveAndRestoreControlle
exportConfigurationIconImageView.setFitHeight(18);
MenuItem exportConfigurationMenuItem = new MenuItem(Messages.exportConfigurationLabel, exportConfigurationIconImageView);
- exportConfigurationMenuItem.disableProperty().bind(multipleSelection);
+ exportConfigurationMenuItem.disableProperty().bind(multipleNodesSelectedProperty);
exportConfigurationMenuItem.setOnAction(ae -> saveAndRestoreController.exportConfiguration());
ImageView importSnapshotIconImageView = new ImageView(csvImportIcon);
@@ -55,21 +59,20 @@ public ContextMenuConfiguration(SaveAndRestoreController saveAndRestoreControlle
importSnapshotIconImageView.setFitHeight(18);
MenuItem importSnapshotMenuItem = new MenuItem(Messages.importSnapshotLabel, importSnapshotIconImageView);
- importSnapshotMenuItem.disableProperty().bind(multipleSelection);
+ importSnapshotMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
+ multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(),
+ multipleNodesSelectedProperty, userIsAuthenticatedProperty));
importSnapshotMenuItem.setOnAction(ae -> saveAndRestoreController.importSnapshot());
Image copyIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/copy.png");
MenuItem copyMenuItem = new MenuItem(Messages.copy, new ImageView(copyIcon));
copyMenuItem.setOnAction(action -> saveAndRestoreController.copySelectionToClipboard());
+ copyMenuItem.disableProperty().bind(mayCopyProperty.not());
Image pasteIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/paste.png");
MenuItem pasteMenuItem = new MenuItem(Messages.paste, new ImageView(pasteIcon));
pasteMenuItem.setOnAction(ae -> saveAndRestoreController.pasteFromClipboard());
-
- setOnShowing(event -> {
- pasteMenuItem.setDisable(!saveAndRestoreController.mayPaste());
- copyMenuItem.setDisable(!saveAndRestoreController.mayCopy());
- });
+ pasteMenuItem.disableProperty().bind(mayPasteProperty.not());
getItems().addAll(openConfigurationMenuItem,
editConfigurationMenuItem,
@@ -80,4 +83,11 @@ public ContextMenuConfiguration(SaveAndRestoreController saveAndRestoreControlle
exportConfigurationMenuItem,
importSnapshotMenuItem);
}
+
+ @Override
+ protected void runChecks(){
+ super.runChecks();
+ mayPasteProperty.set(saveAndRestoreController.mayPaste());
+ mayCopyProperty.set(saveAndRestoreController.mayCopy());
+ }
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuFolder.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuFolder.java
index 3e15ac3afc..8a717b90b5 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuFolder.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuFolder.java
@@ -18,35 +18,40 @@
package org.phoebus.applications.saveandrestore.ui;
+import javafx.application.Platform;
import javafx.scene.control.MenuItem;
-import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import org.phoebus.applications.saveandrestore.Messages;
import org.phoebus.applications.saveandrestore.model.Node;
import org.phoebus.ui.javafx.ImageCache;
+/**
+ * Context menu for {@link Node}s of type {@link org.phoebus.applications.saveandrestore.model.NodeType#FOLDER}.
+ * All item actions require user to be authenticated, and if that is not the case,
+ * the context menu is hidden rather than showing a list of disabled context menu items.
+ */
public class ContextMenuFolder extends ContextMenuBase {
- public ContextMenuFolder(SaveAndRestoreController saveAndRestoreController, TreeView treeView) {
- super(saveAndRestoreController, treeView);
+ public ContextMenuFolder(SaveAndRestoreController saveAndRestoreController) {
+ super(saveAndRestoreController);
Image renameIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/rename_col.png");
MenuItem renameNodeMenuItem = new MenuItem(Messages.contextMenuRename, new ImageView(renameIcon));
renameNodeMenuItem.setOnAction(ae -> saveAndRestoreController.renameNode());
- renameNodeMenuItem.disableProperty().bind(multipleSelection);
+ renameNodeMenuItem.disableProperty().bind(multipleNodesSelectedProperty);
MenuItem newFolderMenuItem = new MenuItem(Messages.contextMenuNewFolder, new ImageView(ImageRepository.FOLDER));
- newFolderMenuItem.disableProperty().bind(multipleSelection);
+ newFolderMenuItem.disableProperty().bind(multipleNodesSelectedProperty);
newFolderMenuItem.setOnAction(ae -> saveAndRestoreController.createNewFolder());
MenuItem newConfigurationMenuItem = new MenuItem(Messages.contextMenuNewConfiguration, new ImageView(ImageRepository.CONFIGURATION));
- newConfigurationMenuItem.disableProperty().bind(multipleSelection);
+ newConfigurationMenuItem.disableProperty().bind(multipleNodesSelectedProperty);
newConfigurationMenuItem.setOnAction(ae -> saveAndRestoreController.createNewConfiguration());
MenuItem newCompositeSnapshotMenuItem = new MenuItem(Messages.contextMenuNewCompositeSnapshot, new ImageView(ImageRepository.COMPOSITE_SNAPSHOT));
- newCompositeSnapshotMenuItem.disableProperty().bind(multipleSelection);
+ newCompositeSnapshotMenuItem.disableProperty().bind(multipleNodesSelectedProperty);
newCompositeSnapshotMenuItem.setOnAction(ae -> saveAndRestoreController.createNewCompositeSnapshot());
ImageView importConfigurationIconImageView = new ImageView(csvImportIcon);
@@ -54,16 +59,13 @@ public ContextMenuFolder(SaveAndRestoreController saveAndRestoreController, Tree
importConfigurationIconImageView.setFitHeight(18);
MenuItem importConfigurationMenuItem = new MenuItem(Messages.importConfigurationLabel, importConfigurationIconImageView);
- importConfigurationMenuItem.disableProperty().bind(multipleSelection);
+ importConfigurationMenuItem.disableProperty().bind(multipleNodesSelectedProperty);
importConfigurationMenuItem.setOnAction(ae -> saveAndRestoreController.importConfiguration());
Image pasteIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/paste.png");
MenuItem pasteMenuItem = new MenuItem(Messages.paste, new ImageView(pasteIcon));
pasteMenuItem.setOnAction(ae -> saveAndRestoreController.pasteFromClipboard());
-
- setOnShowing(event -> {
- pasteMenuItem.setDisable(!saveAndRestoreController.mayPaste());
- });
+ pasteMenuItem.disableProperty().bind(mayPasteProperty.not());
getItems().addAll(newFolderMenuItem,
renameNodeMenuItem,
@@ -73,5 +75,18 @@ public ContextMenuFolder(SaveAndRestoreController saveAndRestoreController, Tree
newCompositeSnapshotMenuItem,
copyUniqueIdToClipboardMenuItem,
importConfigurationMenuItem);
+
+ }
+
+ @Override
+ protected void runChecks() {
+ super.runChecks();
+ // If user is not authenticated then all menu items are non-functional,
+ // disable menu completely instead of showing list of disabled menu items.
+ if (userIsAuthenticatedProperty.not().get()) {
+ Platform.runLater(this::hide);
+ } else {
+ mayPasteProperty.set(saveAndRestoreController.mayPaste());
+ }
}
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java
index e3d5b1fccc..72e7c206bf 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java
@@ -18,16 +18,13 @@
package org.phoebus.applications.saveandrestore.ui;
-import javafx.collections.ObservableList;
+import javafx.beans.binding.Bindings;
+import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
-import javafx.scene.control.TreeItem;
-import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import org.phoebus.applications.saveandrestore.Messages;
-import org.phoebus.applications.saveandrestore.model.Node;
-import org.phoebus.applications.saveandrestore.model.NodeType;
import org.phoebus.applications.saveandrestore.ui.snapshot.tag.TagWidget;
import org.phoebus.ui.javafx.ImageCache;
@@ -38,22 +35,36 @@ public class ContextMenuSnapshot extends ContextMenuBase {
private final MenuItem compareSnapshotsMenuItem;
+ private MenuItem tagGoldenMenuItem;
+
private final Menu tagWithComment;
- public ContextMenuSnapshot(SaveAndRestoreController saveAndRestoreController,
- TreeView treeView) {
- super(saveAndRestoreController, treeView);
+ private SimpleBooleanProperty mayTagProperty = new SimpleBooleanProperty();
+
+ private SimpleBooleanProperty mayCompareSnapshotsProperty = new SimpleBooleanProperty();
+
+ private SimpleBooleanProperty mayTagOrUntagGoldenProperty = new SimpleBooleanProperty();
+
+ public ContextMenuSnapshot(SaveAndRestoreController saveAndRestoreController) {
+ super(saveAndRestoreController);
compareSnapshotsMenuItem = new MenuItem(Messages.contextMenuCompareSnapshots, new ImageView(compareSnapshotIcon));
compareSnapshotsMenuItem.setOnAction(ae -> saveAndRestoreController.compareSnapshot());
+ compareSnapshotsMenuItem.disableProperty().bind(mayCompareSnapshotsProperty.not());
ImageView snapshotTagsWithCommentIconImage = new ImageView(ImageRepository.SNAPSHOT_ADD_TAG_WITH_COMMENT);
tagWithComment = new Menu(Messages.contextMenuTagsWithComment, snapshotTagsWithCommentIconImage);
tagWithComment.setOnShowing(event -> saveAndRestoreController.tagWithComment(tagWithComment));
+ tagWithComment.disableProperty().bind(Bindings.createBooleanBinding(() ->
+ multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(),
+ multipleNodesSelectedProperty, userIsAuthenticatedProperty));
MenuItem addTagWithCommentMenuItem = TagWidget.AddTagWithCommentMenuItem();
addTagWithCommentMenuItem.setOnAction(action -> saveAndRestoreController.addTagToSnapshots());
+ addTagWithCommentMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
+ multipleNodesSelectedProperty.get() || mayTagProperty.not().get(),
+ multipleNodesSelectedProperty, mayTagProperty));
tagWithComment.getItems().addAll(addTagWithCommentMenuItem);
@@ -65,21 +76,18 @@ public ContextMenuSnapshot(SaveAndRestoreController saveAndRestoreController,
exportSnapshotIconImageView.setFitHeight(18);
MenuItem exportSnapshotMenuItem = new MenuItem(Messages.exportSnapshotLabel, exportSnapshotIconImageView);
- exportSnapshotMenuItem.disableProperty().bind(multipleSelection);
+ exportSnapshotMenuItem.disableProperty().bind(multipleNodesSelectedProperty);
exportSnapshotMenuItem.setOnAction(ae -> saveAndRestoreController.exportSnapshot());
- MenuItem tagGoldenMenuItem = new MenuItem(Messages.contextMenuTagAsGolden, new ImageView(ImageRepository.SNAPSHOT));
+ tagGoldenMenuItem = new MenuItem(Messages.contextMenuTagAsGolden, new ImageView(ImageRepository.SNAPSHOT));
+ tagGoldenMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
+ multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get() || mayTagOrUntagGoldenProperty.not().get(),
+ multipleNodesSelectedProperty, userIsAuthenticatedProperty, mayTagOrUntagGoldenProperty));
Image copyIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/copy.png");
MenuItem copyMenuItem = new MenuItem(Messages.copy, new ImageView(copyIcon));
copyMenuItem.setOnAction(action -> saveAndRestoreController.copySelectionToClipboard());
-
- setOnShowing(event -> {
- saveAndRestoreController.configureGoldenItem(tagGoldenMenuItem);
- copyMenuItem.setDisable(!saveAndRestoreController.mayCopy());
- compareSnapshotsMenuItem.disableProperty().set(!compareSnapshotsPossible());
- runChecks();
- });
+ copyMenuItem.disableProperty().bind(mayCopyProperty.not());
getItems().addAll(deleteNodesMenuItem,
compareSnapshotsMenuItem,
@@ -94,47 +102,8 @@ public ContextMenuSnapshot(SaveAndRestoreController saveAndRestoreController,
@Override
protected void runChecks() {
super.runChecks();
- ObservableList> selected =
- treeView.getSelectionModel().getSelectedItems();
- if (multipleSelection.get() && checkNotTaggable(selected)) {
- tagWithComment.disableProperty().set(true);
- } else {
- tagWithComment.disableProperty().set(false);
- }
- }
-
- /**
- * Determines if comparing snapshots is possible, which is the case if all of the following holds true:
- *
- *
The active tab must be a {@link org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab}
- *
The active {@link org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab} must not show an unsaved snapshot.
- *
The snapshot selected from the tree view must have same parent as the one shown in the active {@link org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab}
- *
The snapshot selected from the tree view must not be the same as as the one shown in the active {@link org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab}
- *
- *
- * @return true if selection can be added to snapshot view for comparison.
- */
- private boolean compareSnapshotsPossible() {
- Node[] configAndSnapshotNode = saveAndRestoreController.getConfigAndSnapshotForActiveSnapshotTab();
- if (configAndSnapshotNode == null) {
- return false;
- }
- TreeItem selectedItem = treeView.getSelectionModel().getSelectedItem();
- TreeItem parentItem = selectedItem.getParent();
- return configAndSnapshotNode[1].getUniqueId() != null &&
- parentItem.getValue().getUniqueId().equals(configAndSnapshotNode[0].getUniqueId()) &&
- !selectedItem.getValue().getUniqueId().equals(configAndSnapshotNode[1].getUniqueId());
- }
-
- /**
- * Checks if selection is not allowed, i.e. not all selected nodes are snapshot nodes.
- *
- * @param selectedItems List of selected nodes
- * @return true if any of the selected nodes is of type {@link NodeType#FOLDER} or
- * {@link NodeType#CONFIGURATION}.
- */
- private boolean checkNotTaggable(ObservableList> selectedItems) {
- return selectedItems.stream().filter(i -> i.getValue().getNodeType().equals(NodeType.FOLDER) ||
- i.getValue().getNodeType().equals(NodeType.CONFIGURATION)).findFirst().isPresent();
+ mayTagProperty.set(saveAndRestoreController.checkTaggable());
+ mayCompareSnapshotsProperty.set(saveAndRestoreController.compareSnapshotsPossible());
+ mayTagOrUntagGoldenProperty.set(saveAndRestoreController.configureGoldenItem(tagGoldenMenuItem));
}
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
index 3b8803e32a..25953ee256 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
@@ -80,6 +80,8 @@
import org.phoebus.applications.saveandrestore.ui.snapshot.tag.TagUtil;
import org.phoebus.framework.jobs.JobManager;
import org.phoebus.framework.preferences.PhoebusPreferenceService;
+import org.phoebus.security.store.SecureStore;
+import org.phoebus.security.tokens.ScopedAuthenticationToken;
import org.phoebus.ui.dialog.DialogHelper;
import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
import org.phoebus.ui.javafx.ImageCache;
@@ -167,6 +169,8 @@ public class SaveAndRestoreController implements Initializable, NodeChangedListe
private final ObservableList filtersList = FXCollections.observableArrayList();
+ private SecureStore secureStore;
+
/**
* @param uri If non-null, this is used to load a configuration or snapshot into the view.
*/
@@ -174,6 +178,8 @@ public SaveAndRestoreController(URI uri) {
this.uri = uri;
}
+
+
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
@@ -194,17 +200,22 @@ public void initialize(URL url, ResourceBundle resourceBundle) {
filtersComboBox.disableProperty().bind(filterEnabledProperty.not());
filterEnabledProperty.addListener((observable, oldValue, newValue) -> filterEnabledChanged(newValue));
- folderContextMenu = new ContextMenuFolder(this, treeView);
- configurationContextMenu = new ContextMenuConfiguration(this, treeView);
+ folderContextMenu = new ContextMenuFolder(this);
+ configurationContextMenu = new ContextMenuConfiguration(this);
rootFolderContextMenu = new ContextMenu();
MenuItem newRootFolderMenuItem = new MenuItem(Messages.contextMenuNewFolder, new ImageView(ImageRepository.FOLDER));
newRootFolderMenuItem.setOnAction(ae -> createNewFolder());
rootFolderContextMenu.getItems().add(newRootFolderMenuItem);
+ rootFolderContextMenu.setOnShowing(event -> {
+ if(!isUserAuthenticated()){
+ Platform.runLater(() -> rootFolderContextMenu.hide());
+ }
+ });
- snapshotContextMenu = new ContextMenuSnapshot(this, treeView);
+ snapshotContextMenu = new ContextMenuSnapshot(this);
- compositeSnapshotContextMenu = new ContextMenuCompositeSnapshot(this, treeView);
+ compositeSnapshotContextMenu = new ContextMenuCompositeSnapshot(this);
treeView.setEditable(true);
@@ -281,6 +292,12 @@ public Filter fromString(String s) {
// considered in paste actions.
Clipboard.getSystemClipboard().clear();
+ try {
+ secureStore = new SecureStore();
+ } catch (Exception e) {
+ LOG.log(Level.WARNING, "SecureStore could not be created. Authentication won't work.", e);
+ }
+
loadTreeData();
}
@@ -303,7 +320,7 @@ protected TreeItem call() {
HashMap>> childNodesMap = new HashMap<>();
savedTreeViewStructure.forEach(s -> {
List childNodes = saveAndRestoreService.getChildNodes(Node.builder().uniqueId(s).build());
- if (childNodes != null) { // This may be the case if the tree structure was modified outside of the UI
+ if (childNodes != null) { // This may be the case if the tree structure was modified externally
List> childItems = childNodes.stream().map(n -> createTreeItem(n)).sorted(treeNodeComparator).collect(Collectors.toList());
childNodesMap.put(s, childItems);
}
@@ -1064,32 +1081,28 @@ public void tagWithComment(final Menu tagWithCommentMenu) {
*
*
* @param menuItem The {@link MenuItem} subject to configuration.
+ * @return false if the menu item should be disabled.
*/
- public void configureGoldenItem(MenuItem menuItem) {
+ public boolean configureGoldenItem(MenuItem menuItem) {
List selectedNodes =
browserSelectionModel.getSelectedItems().stream().map(TreeItem::getValue).collect(Collectors.toList());
- TagUtil.configureGoldenItem(selectedNodes, menuItem);
+ return TagUtil.configureGoldenItem(selectedNodes, menuItem);
}
/**
- * Performs check of multiple selection to determine if it fulfills the criteria:
- *
- *
All selected nodes must be of same type.
- *
All selected nodes must have same parent node.
- *
+ * Performs check of selection to determine if all selected nodes are of same type.
*
* @return true if criteria are met, otherwise false
*/
- protected boolean checkMultipleSelection() {
+ public boolean selectedNodesOfSameType() {
ObservableList> selectedItems = browserSelectionModel.getSelectedItems();
if (selectedItems.size() < 2) {
return true;
}
- TreeItem parent = selectedItems.get(0).getParent();
NodeType nodeType = selectedItems.get(0).getValue().getNodeType();
for(int i = 1; i < selectedItems.size(); i++){
- if(!selectedItems.get(i).getParent().equals(parent) || !selectedItems.get(i).getValue().getNodeType().equals(nodeType)){
+ if(!selectedItems.get(i).getValue().getNodeType().equals(nodeType)){
return false;
}
}
@@ -1337,6 +1350,9 @@ public void copySelectionToClipboard(){
* @return true if selection may be copied to clipboard, otherwise false.
*/
public boolean mayCopy(){
+ if(!isUserAuthenticated()){
+ return false;
+ }
List selectedNodes = browserSelectionModel.getSelectedItems().stream().map(TreeItem::getValue).collect(Collectors.toList());
if(selectedNodes.size() == 1){
return true;
@@ -1352,7 +1368,7 @@ public boolean mayCopy(){
/**
* Checks if the clipboard content may be pasted onto a target node:
*
- *
Clipboard c ontent must be of {@link SaveAndRestoreApplication#NODE_SELECTION_FORMAT}.
+ *
Clipboard content must be of {@link SaveAndRestoreApplication#NODE_SELECTION_FORMAT}.
*
Selected node for paste (target) must be single node.
*
Configurations and composite snapshots may be pasted only onto folder.
*
Snapshot may be pasted only onto configuration.
@@ -1360,6 +1376,9 @@ public boolean mayCopy(){
* @return true if selection may be pasted, otherwise false.
*/
public boolean mayPaste(){
+ if(!isUserAuthenticated()){
+ return false;
+ }
Object clipBoardContent = Clipboard.getSystemClipboard().getContent(SaveAndRestoreApplication.NODE_SELECTION_FORMAT);
if(clipBoardContent == null || browserSelectionModel.getSelectedItems().size() != 1){
return false;
@@ -1406,4 +1425,81 @@ public void pasteFromClipboard(){
});
});
}
+
+ public boolean isUserAuthenticated(){
+ if(secureStore == null){
+ return false;
+ }
+ try {
+ ScopedAuthenticationToken token =
+ secureStore.getScopedAuthenticationToken("save-and-restore");
+ return token != null && token.getUsername() != null && token.getPassword() != null;
+ } catch (Exception e) {
+ LOG.log(Level.WARNING, "Unable to retrieve authentication token for save-and-restore scope", e);
+ }
+ return false;
+ }
+
+ /**
+ * Used to determine if nodes selected in the tree view have the same parent node. Most menu items
+ * do not make sense unless the selected nodes have same the parent node.
+ *
+ * @return true if all selected nodes have the same parent node, false otherwise.
+ */
+ public boolean hasSameParent() {
+ ObservableList> selectedItems = browserSelectionModel.getSelectedItems();
+ if (selectedItems.size() == 1) {
+ return true;
+ }
+ Node parentNodeOfFirst = selectedItems.get(0).getParent().getValue();
+ for (int i = 1; i < selectedItems.size(); i++) {
+ TreeItem treeItem = selectedItems.get(i);
+ if (!treeItem.getParent().getValue().getUniqueId().equals(parentNodeOfFirst.getUniqueId())) {
+ System.out.println(parentNodeOfFirst.getUniqueId() + " " + treeItem.getParent().getValue().getUniqueId());
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @return true selection contains multiple {@link Node}s, otherwise false.
+ */
+ public boolean multipleNodesSelected(){
+ return browserSelectionModel.getSelectedItems().size() > 1;
+ }
+
+ /**
+ * Checks if selection is not allowed, i.e. not all selected nodes are snapshot nodes.
+ *
+ * @return false if any of the selected nodes is of type {@link NodeType#FOLDER} or
+ * {@link NodeType#CONFIGURATION}. Since these {@link NodeType}s cannot be tagged.
+ */
+ public boolean checkTaggable() {
+ return browserSelectionModel.getSelectedItems().stream().filter(i -> i.getValue().getNodeType().equals(NodeType.FOLDER) ||
+ i.getValue().getNodeType().equals(NodeType.CONFIGURATION)).findFirst().isEmpty();
+ }
+
+ /**
+ * Determines if comparing snapshots is possible, which is the case if all of the following holds true:
+ *
+ *
The active tab must be a {@link org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab}
+ *
The active {@link org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab} must not show an unsaved snapshot.
+ *
The snapshot selected from the tree view must have same parent as the one shown in the active {@link org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab}
+ *
The snapshot selected from the tree view must not be the same as as the one shown in the active {@link org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab}
+ *
+ *
+ * @return true if selection can be added to snapshot view for comparison.
+ */
+ public boolean compareSnapshotsPossible() {
+ Node[] configAndSnapshotNode = getConfigAndSnapshotForActiveSnapshotTab();
+ if (configAndSnapshotNode == null) {
+ return false;
+ }
+ TreeItem selectedItem = treeView.getSelectionModel().getSelectedItem();
+ TreeItem parentItem = selectedItem.getParent();
+ return configAndSnapshotNode[1].getUniqueId() != null &&
+ parentItem.getValue().getUniqueId().equals(configAndSnapshotNode[0].getUniqueId()) &&
+ !selectedItem.getValue().getUniqueId().equals(configAndSnapshotNode[1].getUniqueId());
+ }
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/tag/TagUtil.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/tag/TagUtil.java
index 0fa41f0ca9..b1cb36a210 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/tag/TagUtil.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/tag/TagUtil.java
@@ -188,8 +188,9 @@ public static List addTag(Li
*
* @param selectedNodes List of {@link org.phoebus.applications.saveandrestore.model.Node}s selected by user.
* @param menuItem The {@link javafx.scene.control.MenuItem} subject to configuration.
+ * @return false if the menu item should be disabled.
*/
- public static void configureGoldenItem(List selectedNodes, MenuItem menuItem) {
+ public static boolean configureGoldenItem(List selectedNodes, MenuItem menuItem) {
AtomicInteger goldenTagCount = new AtomicInteger(0);
AtomicInteger nonSnapshotCount = new AtomicInteger(0);
selectedNodes.forEach(node -> {
@@ -200,11 +201,9 @@ public static void configureGoldenItem(List 0) {
- menuItem.disableProperty().set(true);
- return;
+ return false;
}
if (goldenTagCount.get() == selectedNodes.size()) {
- menuItem.disableProperty().set(false);
menuItem.setText(Messages.contextMenuRemoveGoldenTag);
menuItem.setGraphic(new ImageView(ImageRepository.SNAPSHOT));
menuItem.setOnAction(event -> {
@@ -217,8 +216,8 @@ public static void configureGoldenItem(List {
@@ -231,8 +230,9 @@ public static void configureGoldenItem(List
Date: Tue, 5 Sep 2023 13:56:55 +0200
Subject: [PATCH 08/42] Bug fix snapshot comparison logic
---
.../saveandrestore/ui/ContextMenuBase.java | 7 +++---
.../ui/ContextMenuCompositeSnapshot.java | 10 ++++++++
.../ui/ContextMenuConfiguration.java | 19 +++++++++++----
.../saveandrestore/ui/ContextMenuFolder.java | 4 ++++
.../ui/ContextMenuSnapshot.java | 23 ++++++++++++++-----
.../BaseSnapshotTableViewController.java | 7 +++++-
.../ui/snapshot/SnapshotController.java | 1 +
.../snapshot/SnapshotTableViewController.java | 11 ++++-----
8 files changed, 61 insertions(+), 21 deletions(-)
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java
index ead8fd679b..d08011a680 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java
@@ -28,6 +28,9 @@
import org.phoebus.applications.saveandrestore.model.Node;
import org.phoebus.ui.javafx.ImageCache;
+/**
+ * Abstract base class for context menus.
+ */
public abstract class ContextMenuBase extends ContextMenu {
protected Image csvImportIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/csv_import.png");
@@ -81,9 +84,7 @@ public ContextMenuBase(SaveAndRestoreController saveAndRestoreController) {
copyUniqueIdToClipboardMenuItem.setOnAction(ae -> saveAndRestoreController.copyUniqueNodeIdToClipboard());
copyUniqueIdToClipboardMenuItem.disableProperty().bind(multipleNodesSelectedProperty);
- setOnShowing(event -> {
- runChecks();
- });
+ setOnShowing(event -> runChecks());
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuCompositeSnapshot.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuCompositeSnapshot.java
index 238dd32aed..eadec07c4e 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuCompositeSnapshot.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuCompositeSnapshot.java
@@ -29,6 +29,10 @@
import org.phoebus.applications.saveandrestore.ui.snapshot.tag.TagWidget;
import org.phoebus.ui.javafx.ImageCache;
+/**
+ * Context menu for {@link org.phoebus.applications.saveandrestore.model.Node}s of type
+ * {@link org.phoebus.applications.saveandrestore.model.NodeType#COMPOSITE_SNAPSHOT}.
+ */
public class ContextMenuCompositeSnapshot extends ContextMenuBase {
public ContextMenuCompositeSnapshot(SaveAndRestoreController saveAndRestoreController) {
@@ -73,6 +77,12 @@ public ContextMenuCompositeSnapshot(SaveAndRestoreController saveAndRestoreContr
tagWithComment);
}
+ /**
+ * Execute common checks (see {@link ContextMenuBase#runChecks()}) and:
+ *
+ *
If copy operation is possible on selected {@link org.phoebus.applications.saveandrestore.model.Node}s
+ *
+ */
@Override
public void runChecks() {
super.runChecks();
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuConfiguration.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuConfiguration.java
index 8a35ce0144..a72ab7ebd1 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuConfiguration.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuConfiguration.java
@@ -20,14 +20,16 @@
import javafx.beans.binding.Bindings;
import javafx.scene.control.MenuItem;
-import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import org.phoebus.applications.saveandrestore.Messages;
-import org.phoebus.applications.saveandrestore.model.Node;
import org.phoebus.ui.javafx.ImageCache;
-public class ContextMenuConfiguration extends ContextMenuBase{
+/**
+ * Context menu for {@link org.phoebus.applications.saveandrestore.model.Node}s of type
+ * {@link org.phoebus.applications.saveandrestore.model.NodeType#CONFIGURATION}.
+ */
+public class ContextMenuConfiguration extends ContextMenuBase {
public ContextMenuConfiguration(SaveAndRestoreController saveAndRestoreController) {
super(saveAndRestoreController);
@@ -36,7 +38,7 @@ public ContextMenuConfiguration(SaveAndRestoreController saveAndRestoreControlle
MenuItem openConfigurationMenuItem = new MenuItem(Messages.contextMenuCreateSnapshot, new ImageView(ImageRepository.CONFIGURATION));
openConfigurationMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
- multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(),
+ multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(),
multipleNodesSelectedProperty, userIsAuthenticatedProperty));
openConfigurationMenuItem.setOnAction(ae -> saveAndRestoreController.openConfigurationForSnapshot());
@@ -84,8 +86,15 @@ public ContextMenuConfiguration(SaveAndRestoreController saveAndRestoreControlle
importSnapshotMenuItem);
}
+ /**
+ * Execute common checks (see {@link ContextMenuBase#runChecks()}) and:
+ *
+ *
If copy operation is possible on selected {@link org.phoebus.applications.saveandrestore.model.Node}s
+ *
If paste operation is possible on selected {@link org.phoebus.applications.saveandrestore.model.Node}s
+ *
+ */
@Override
- protected void runChecks(){
+ protected void runChecks() {
super.runChecks();
mayPasteProperty.set(saveAndRestoreController.mayPaste());
mayCopyProperty.set(saveAndRestoreController.mayCopy());
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuFolder.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuFolder.java
index 8a717b90b5..c495d4c070 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuFolder.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuFolder.java
@@ -78,6 +78,10 @@ public ContextMenuFolder(SaveAndRestoreController saveAndRestoreController) {
}
+ /**
+ * Execute common checks (see {@link ContextMenuBase#runChecks()}) and hides the menu
+ * if user is not authenticated.
+ */
@Override
protected void runChecks() {
super.runChecks();
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java
index 72e7c206bf..4a4086b687 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java
@@ -28,27 +28,30 @@
import org.phoebus.applications.saveandrestore.ui.snapshot.tag.TagWidget;
import org.phoebus.ui.javafx.ImageCache;
+/**
+ * Context menu for {@link org.phoebus.applications.saveandrestore.model.Node}s of type
+ * {@link org.phoebus.applications.saveandrestore.model.NodeType#SNAPSHOT}.
+ */
public class ContextMenuSnapshot extends ContextMenuBase {
protected Image compareSnapshotIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/save-and-restore/compare.png");
protected Image csvExportIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/csv_export.png");
- private final MenuItem compareSnapshotsMenuItem;
- private MenuItem tagGoldenMenuItem;
+ private final MenuItem tagGoldenMenuItem;
private final Menu tagWithComment;
- private SimpleBooleanProperty mayTagProperty = new SimpleBooleanProperty();
+ private final SimpleBooleanProperty mayTagProperty = new SimpleBooleanProperty();
- private SimpleBooleanProperty mayCompareSnapshotsProperty = new SimpleBooleanProperty();
+ private final SimpleBooleanProperty mayCompareSnapshotsProperty = new SimpleBooleanProperty();
- private SimpleBooleanProperty mayTagOrUntagGoldenProperty = new SimpleBooleanProperty();
+ private final SimpleBooleanProperty mayTagOrUntagGoldenProperty = new SimpleBooleanProperty();
public ContextMenuSnapshot(SaveAndRestoreController saveAndRestoreController) {
super(saveAndRestoreController);
- compareSnapshotsMenuItem = new MenuItem(Messages.contextMenuCompareSnapshots, new ImageView(compareSnapshotIcon));
+ MenuItem compareSnapshotsMenuItem = new MenuItem(Messages.contextMenuCompareSnapshots, new ImageView(compareSnapshotIcon));
compareSnapshotsMenuItem.setOnAction(ae -> saveAndRestoreController.compareSnapshot());
compareSnapshotsMenuItem.disableProperty().bind(mayCompareSnapshotsProperty.not());
@@ -99,6 +102,14 @@ public ContextMenuSnapshot(SaveAndRestoreController saveAndRestoreController) {
}
+ /**
+ * Execute common checks (see {@link ContextMenuBase#runChecks()}) and:
+ *
+ *
If tagging is possible on selected {@link org.phoebus.applications.saveandrestore.model.Node}s
+ *
If comparing snapshots is possible
+ *
If setting/unsetting golden tag is possible
+ *
+ */
@Override
protected void runChecks() {
super.runChecks();
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/BaseSnapshotTableViewController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/BaseSnapshotTableViewController.java
index 2433705706..3bac8f68f2 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/BaseSnapshotTableViewController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/BaseSnapshotTableViewController.java
@@ -273,7 +273,12 @@ protected String getPVKey(String pvName, boolean isReadonly) {
}
protected void showSnapshotInTable(Snapshot snapshot){
- snapshots.add(0, snapshot);
+ if(snapshots.isEmpty()){
+ snapshots.add(snapshot);
+ }
+ else{
+ snapshots.set(0, snapshot);
+ }
AtomicInteger counter = new AtomicInteger(0);
snapshot.getSnapshotData().getSnapshotItems().forEach(entry -> {
TableEntry tableEntry = new TableEntry();
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java
index b1239e1e4a..65cd5c7778 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java
@@ -134,6 +134,7 @@ public void newSnapshot(Node configurationNode) {
@SuppressWarnings("unused")
public void takeSnapshot() {
disabledUi.set(true);
+ snapshotTab.setText(Messages.unnamedSnapshot);
snapshotTableViewController.takeSnapshot(snapshot -> {
disabledUi.set(false);
snapshotProperty.set(snapshot);
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTableViewController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTableViewController.java
index 2ac4e03f17..14f2e4cff1 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTableViewController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTableViewController.java
@@ -37,7 +37,6 @@
import org.phoebus.framework.jobs.JobManager;
import org.phoebus.util.time.TimestampFormats;
-import java.security.AccessController;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.*;
@@ -120,7 +119,6 @@ public void initialize() {
}));
-
hideEqualItems.addListener((ob, o, n) -> {
});
@@ -162,6 +160,8 @@ public void updateItem(final Boolean item, final boolean empty) {
}
public void takeSnapshot(Consumer consumer) {
+ // Clear snapshots array
+ snapshots.clear();
List entries = new ArrayList<>();
readAll(list ->
Platform.runLater(() -> {
@@ -429,7 +429,7 @@ public void addSnapshot(Snapshot snapshot) {
VType updatedValue = e.getRowValue().readOnlyProperty().get() ? e.getOldValue() : e.getNewValue();
ObjectProperty value = e.getRowValue().valueProperty();
value.setValue(new VTypePair(value.get().base, updatedValue, value.get().threshold));
- snapshotController.updateLoadedSnapshot( e.getRowValue(), updatedValue);
+ snapshotController.updateLoadedSnapshot(e.getRowValue(), updatedValue);
for (int i = 1; i < snapshots.size(); i++) {
ObjectProperty compareValue = e.getRowValue().compareValueProperty(i);
compareValue.setValue(new VTypePair(updatedValue, compareValue.get().value, compareValue.get().threshold));
@@ -442,14 +442,13 @@ public void addSnapshot(Snapshot snapshot) {
baseSnapshotDeltaColumn.getStyleClass().add("snapshot-table-left-aligned");
baseSnapshotColumn.getColumns().addAll(baseSnapshotValueColumn, baseSnapshotDeltaColumn, new DividerTableColumn());
- }
- else{
+ } else {
compareColumn.getColumns().clear();
}
compareColumn.getColumns().add(0, baseSnapshotColumn);
- for(int s = 1; s < snapshots.size(); s++) {
+ for (int s = 1; s < snapshots.size(); s++) {
Node snapshotNode = snapshots.get(s).getSnapshotNode();
String snapshotName = snapshotNode.getName();
From 4060b939617dd7bd4577a9028911a39dfb482483 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Mon, 11 Sep 2023 13:36:21 +0200
Subject: [PATCH 09/42] Updates after merge with master
---
.../SaveAndRestoreAuthenticationProvider.java | 5 +++--
.../saveandrestore/ui/SaveAndRestoreController.java | 7 ++-----
.../saveandrestore/impl/SaveAndRestoreJerseyClient.java | 3 ++-
3 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java
index 089bf9d45d..98c35263a7 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java
@@ -21,6 +21,7 @@
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService;
import org.phoebus.security.authorization.ServiceAuthenticationProvider;
+import org.phoebus.security.tokens.AuthenticationScope;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -48,7 +49,7 @@ public void logout(String token) {
}
@Override
- public String getServiceName(){
- return "save-and-restore";
+ public AuthenticationScope getAuthenticationScope(){
+ return AuthenticationScope.SAVE_AND_RESTORE;
}
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
index 16fbaf8049..8991bf5160 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
@@ -81,6 +81,7 @@
import org.phoebus.framework.jobs.JobManager;
import org.phoebus.framework.preferences.PhoebusPreferenceService;
import org.phoebus.security.store.SecureStore;
+import org.phoebus.security.tokens.AuthenticationScope;
import org.phoebus.security.tokens.ScopedAuthenticationToken;
import org.phoebus.ui.dialog.DialogHelper;
import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
@@ -1397,10 +1398,6 @@ else if(nodeTypeOfFirst.equals(NodeType.SNAPSHOT) && !nodeTypeOfTarget.equals(No
return true;
}
- public List> getSelectedItems(){
- return treeView.getSelectionModel().getSelectedItems();
- }
-
public void pasteFromClipboard(){
disabledUi.set(true);
Object selectedNodes = Clipboard.getSystemClipboard().getContent(SaveAndRestoreApplication.NODE_SELECTION_FORMAT);
@@ -1432,7 +1429,7 @@ public boolean isUserAuthenticated(){
}
try {
ScopedAuthenticationToken token =
- secureStore.getScopedAuthenticationToken("save-and-restore");
+ secureStore.getScopedAuthenticationToken(AuthenticationScope.SAVE_AND_RESTORE);
return token != null && token.getUsername() != null && token.getPassword() != null;
} catch (Exception e) {
LOG.log(Level.WARNING, "Unable to retrieve authentication token for save-and-restore scope", e);
diff --git a/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/impl/SaveAndRestoreJerseyClient.java b/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/impl/SaveAndRestoreJerseyClient.java
index b1ed5a50e4..f82e1a69d7 100644
--- a/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/impl/SaveAndRestoreJerseyClient.java
+++ b/app/save-and-restore/service/src/main/java/org/phoebus/applications/saveandrestore/impl/SaveAndRestoreJerseyClient.java
@@ -36,6 +36,7 @@
import org.phoebus.applications.saveandrestore.service.Messages;
import org.phoebus.framework.preferences.PreferencesReader;
import org.phoebus.security.store.SecureStore;
+import org.phoebus.security.tokens.AuthenticationScope;
import org.phoebus.security.tokens.ScopedAuthenticationToken;
import javax.ws.rs.core.MultivaluedMap;
@@ -98,7 +99,7 @@ private Client getClient() {
try {
SecureStore store = new SecureStore();
- ScopedAuthenticationToken scopedAuthenticationToken = store.getScopedAuthenticationToken("save-and-restore");
+ ScopedAuthenticationToken scopedAuthenticationToken = store.getScopedAuthenticationToken(AuthenticationScope.SAVE_AND_RESTORE);
if (scopedAuthenticationToken != null) {
String username = scopedAuthenticationToken.getUsername();
String password = scopedAuthenticationToken.getPassword();
From 9235222d83b4dfd090a3cfc4f2d3526eafaf5471 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Mon, 11 Sep 2023 13:51:49 +0200
Subject: [PATCH 10/42] Added missing javadoc
---
.../phoebus/security/store/SecureStoreChangeHandler.java | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/core/security/src/main/java/org/phoebus/security/store/SecureStoreChangeHandler.java b/core/security/src/main/java/org/phoebus/security/store/SecureStoreChangeHandler.java
index f641b30138..2ca402768c 100644
--- a/core/security/src/main/java/org/phoebus/security/store/SecureStoreChangeHandler.java
+++ b/core/security/src/main/java/org/phoebus/security/store/SecureStoreChangeHandler.java
@@ -23,7 +23,16 @@
import java.util.List;
+/**
+ * Interface used to listen to changes in authentication status. Implementations can register over SPI
+ * to get notified when user logs in or logs out from an {@link org.phoebus.security.tokens.AuthenticationScope}.
+ */
public interface SecureStoreChangeHandler {
+ /**
+ * Callback method implemented by listeners.
+ * @param validTokens A list of valid {@link ScopedAuthenticationToken}s, i.e. a list of tokens associated
+ * with scopes where is authenticated.
+ */
void secureStoreChanged(List validTokens);
}
From 800e60a62bccfd5c0335f5cead966de93241db7c Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Tue, 12 Sep 2023 09:49:28 +0200
Subject: [PATCH 11/42] Save&restore handler for secure store changes
---
.../SaveAndRestoreApplication.java | 12 ++++-
.../SaveAndRestoreInstance.java | 6 +++
.../SecureStoreChangeHandlerImpl.java | 48 +++++++++++++++++++
.../ui/SaveAndRestoreController.java | 24 +++++-----
...us.security.store.SecureStoreChangeHandler | 20 ++++++++
5 files changed, 97 insertions(+), 13 deletions(-)
create mode 100644 app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SecureStoreChangeHandlerImpl.java
create mode 100644 app/save-and-restore/app/src/main/resources/META-INF/services/org.phoebus.security.store.SecureStoreChangeHandler
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreApplication.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreApplication.java
index f87feaf619..eaa346dd26 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreApplication.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreApplication.java
@@ -37,6 +37,8 @@ public class SaveAndRestoreApplication implements AppResourceDescriptor {
public static final String NAME = "saveandrestore";
public static final String DISPLAY_NAME = "Save And Restore";
+ private AppInstance instance;
+
/**
* Custom MIME type definition for the purpose of drag-n-drop in the
*/
@@ -71,12 +73,18 @@ public AppInstance create(URI uri) {
if(uri != null){
((SaveAndRestoreInstance)tab.getApplication()).openResource(uri);
}
- return tab.getApplication();
+ instance = tab.getApplication();
+ return instance;
}
}
}
}
- return new SaveAndRestoreInstance(this, uri);
+ instance = new SaveAndRestoreInstance(this, uri);
+ return instance;
+ }
+
+ public AppInstance getInstance(){
+ return instance;
}
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreInstance.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreInstance.java
index 4f7a3611f6..c21bc75189 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreInstance.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreInstance.java
@@ -24,10 +24,12 @@
import org.phoebus.framework.persistence.Memento;
import org.phoebus.framework.spi.AppDescriptor;
import org.phoebus.framework.spi.AppInstance;
+import org.phoebus.security.tokens.ScopedAuthenticationToken;
import org.phoebus.ui.docking.DockItem;
import org.phoebus.ui.docking.DockPane;
import java.net.URI;
+import java.util.List;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -82,4 +84,8 @@ public void save(Memento memento) {
public void openResource(URI uri) {
controller.openResource(uri);
}
+
+ public void secureStoreChanged(List validTokens){
+ controller.secureStoreChanged(validTokens);
+ }
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SecureStoreChangeHandlerImpl.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SecureStoreChangeHandlerImpl.java
new file mode 100644
index 0000000000..f2177aed32
--- /dev/null
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SecureStoreChangeHandlerImpl.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package org.phoebus.applications.saveandrestore.authentication;
+
+import org.phoebus.applications.saveandrestore.SaveAndRestoreApplication;
+import org.phoebus.applications.saveandrestore.SaveAndRestoreInstance;
+import org.phoebus.framework.spi.AppDescriptor;
+import org.phoebus.framework.workbench.ApplicationService;
+import org.phoebus.security.store.SecureStoreChangeHandler;
+import org.phoebus.security.tokens.ScopedAuthenticationToken;
+
+import java.util.List;
+
+public class SecureStoreChangeHandlerImpl implements SecureStoreChangeHandler {
+
+ /**
+ * Callback method implementation.
+ *
+ * @param validTokens A list of valid {@link ScopedAuthenticationToken}s, i.e. a list of tokens associated
+ * with scopes where is authenticated.
+ */
+ @Override
+ public void secureStoreChanged(List validTokens) {
+ AppDescriptor appDescriptor = ApplicationService.findApplication(SaveAndRestoreApplication.NAME);
+ if (appDescriptor != null && appDescriptor instanceof SaveAndRestoreApplication) {
+ SaveAndRestoreApplication saveAndRestoreApplication = (SaveAndRestoreApplication) appDescriptor;
+ SaveAndRestoreInstance saveAndRestoreInstance = (SaveAndRestoreInstance) saveAndRestoreApplication.getInstance();
+ saveAndRestoreInstance.secureStoreChanged(validTokens);
+ }
+ }
+}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
index 8991bf5160..ecd3040760 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
@@ -172,6 +172,8 @@ public class SaveAndRestoreController implements Initializable, NodeChangedListe
private SecureStore secureStore;
+ private SimpleBooleanProperty userIsAuthenticated = new SimpleBooleanProperty();
+
/**
* @param uri If non-null, this is used to load a configuration or snapshot into the view.
*/
@@ -1424,17 +1426,7 @@ public void pasteFromClipboard(){
}
public boolean isUserAuthenticated(){
- if(secureStore == null){
- return false;
- }
- try {
- ScopedAuthenticationToken token =
- secureStore.getScopedAuthenticationToken(AuthenticationScope.SAVE_AND_RESTORE);
- return token != null && token.getUsername() != null && token.getPassword() != null;
- } catch (Exception e) {
- LOG.log(Level.WARNING, "Unable to retrieve authentication token for save-and-restore scope", e);
- }
- return false;
+ return userIsAuthenticated.get();
}
/**
@@ -1499,4 +1491,14 @@ public boolean compareSnapshotsPossible() {
parentItem.getValue().getUniqueId().equals(configAndSnapshotNode[0].getUniqueId()) &&
!selectedItem.getValue().getUniqueId().equals(configAndSnapshotNode[1].getUniqueId());
}
+
+ /**
+ * Sets {@link #userIsAuthenticated} value based on presence of
+ * {@link AuthenticationScope#SAVE_AND_RESTORE} in the list of valid tokens.
+ * @param validTokens List of valid {@link ScopedAuthenticationToken}s in the {@link SecureStore}.
+ */
+ public void secureStoreChanged(List validTokens){
+ userIsAuthenticated.set(validTokens.stream()
+ .filter(t -> t.getAuthenticationScope().equals(AuthenticationScope.SAVE_AND_RESTORE)).findFirst().isPresent());
+ }
}
diff --git a/app/save-and-restore/app/src/main/resources/META-INF/services/org.phoebus.security.store.SecureStoreChangeHandler b/app/save-and-restore/app/src/main/resources/META-INF/services/org.phoebus.security.store.SecureStoreChangeHandler
new file mode 100644
index 0000000000..df20f820c2
--- /dev/null
+++ b/app/save-and-restore/app/src/main/resources/META-INF/services/org.phoebus.security.store.SecureStoreChangeHandler
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2020 European Spallation Source ERIC.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+#
+
+org.phoebus.applications.saveandrestore.authentication.SecureStoreChangeHandlerImpl
\ No newline at end of file
From 20714f45102e19d58977e35f9f0b95c238394221 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Tue, 12 Sep 2023 10:04:03 +0200
Subject: [PATCH 12/42] Remove Edit context menu item for save&restore
configurations
---
.../saveandrestore/ui/ContextMenuConfiguration.java | 7 -------
.../applications/saveandrestore/ui/ImageRepository.java | 3 ---
2 files changed, 10 deletions(-)
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuConfiguration.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuConfiguration.java
index a72ab7ebd1..8347e13785 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuConfiguration.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuConfiguration.java
@@ -42,12 +42,6 @@ public ContextMenuConfiguration(SaveAndRestoreController saveAndRestoreControlle
multipleNodesSelectedProperty, userIsAuthenticatedProperty));
openConfigurationMenuItem.setOnAction(ae -> saveAndRestoreController.openConfigurationForSnapshot());
- MenuItem editConfigurationMenuItem = new MenuItem(Messages.Edit, new ImageView(ImageRepository.EDIT_CONFIGURATION));
- editConfigurationMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
- multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(),
- multipleNodesSelectedProperty, userIsAuthenticatedProperty));
- editConfigurationMenuItem.setOnAction(ae -> saveAndRestoreController.nodeDoubleClicked());
-
ImageView exportConfigurationIconImageView = new ImageView(csvExportIcon);
exportConfigurationIconImageView.setFitWidth(18);
exportConfigurationIconImageView.setFitHeight(18);
@@ -77,7 +71,6 @@ public ContextMenuConfiguration(SaveAndRestoreController saveAndRestoreControlle
pasteMenuItem.disableProperty().bind(mayPasteProperty.not());
getItems().addAll(openConfigurationMenuItem,
- editConfigurationMenuItem,
copyMenuItem,
pasteMenuItem,
deleteNodesMenuItem,
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ImageRepository.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ImageRepository.java
index d2b3192889..ddaaeb2b25 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ImageRepository.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ImageRepository.java
@@ -46,8 +46,5 @@ public class ImageRepository {
public static final Image SNAPSHOT_ADD_TAG_WITH_COMMENT =
ImageCache.getImage(ImageRepository.class, "/icons/save-and-restore/snapshot-add_tag.png");
- public static final Image SNAPSHOT_REMOVE_TAG_WITH_COMMENT =
- ImageCache.getImage(ImageRepository.class, "/icons/save-and-restore/snapshot-remove_tag.png");
-
public static final Image DELETE = ImageCache.getImage(SaveAndRestoreController.class, "/icons/delete.png");
}
From bb01cf8f9f39fd25eb1aabeb722cdf30edb54ed0 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Tue, 12 Sep 2023 14:12:17 +0200
Subject: [PATCH 13/42] Save&restore: configuration UI elements enabled only if
user is authenticated
---
.../applications/saveandrestore/Messages.java | 1 +
.../SaveAndRestoreInstance.java | 12 ++--
.../ui/SaveAndRestoreController.java | 23 +++----
.../BaseConfigurationSelectionController.java | 35 -----------
.../ConfigurationController.java | 63 ++++++++++++++++---
.../ui/configuration/ConfigurationTab.java | 6 ++
.../saveandrestore/messages.properties | 2 +
.../ui/configuration/ConfigurationEditor.fxml | 34 ++++++++--
8 files changed, 110 insertions(+), 66 deletions(-)
delete mode 100644 app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/BaseConfigurationSelectionController.java
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java
index 9a3411ba7e..51ad6f62be 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java
@@ -24,6 +24,7 @@ public class Messages {
public static String alertContinue;
public static String alertAddingPVsToConfiguration;
public static String authenticationFailed;
+ public static String authenticatedUserNone;
public static String baseSetpoint;
public static String buttonSearch;
public static String cannotCompareHeader;
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreInstance.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreInstance.java
index c21bc75189..ea7c5a5abc 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreInstance.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreInstance.java
@@ -37,7 +37,7 @@
public class SaveAndRestoreInstance implements AppInstance {
private final AppDescriptor appDescriptor;
- private SaveAndRestoreController controller;
+ private final SaveAndRestoreController saveAndRestoreController;
public SaveAndRestoreInstance(AppDescriptor appDescriptor, URI uri) {
this.appDescriptor = appDescriptor;
@@ -64,9 +64,9 @@ public SaveAndRestoreInstance(AppDescriptor appDescriptor, URI uri) {
Logger.getLogger(SaveAndRestoreApplication.class.getName()).log(Level.SEVERE, "Failed loading fxml", e);
}
- controller = loader.getController();
+ saveAndRestoreController = loader.getController();
- tab.setOnCloseRequest(event -> controller.handleTabClosed());
+ tab.setOnCloseRequest(event -> saveAndRestoreController.handleTabClosed());
DockPane.getActiveDockPane().addTab(tab);
}
@@ -78,14 +78,14 @@ public AppDescriptor getAppDescriptor() {
@Override
public void save(Memento memento) {
- controller.saveLocalState();
+ saveAndRestoreController.saveLocalState();
}
public void openResource(URI uri) {
- controller.openResource(uri);
+ saveAndRestoreController.openResource(uri);
}
public void secureStoreChanged(List validTokens){
- controller.secureStoreChanged(validTokens);
+ saveAndRestoreController.secureStoreChanged(validTokens);
}
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
index ecd3040760..0cd3cc2b38 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
@@ -603,10 +603,6 @@ public void failed() {
}
}
- public void nodeDoubleClicked() {
- nodeDoubleClicked(treeView.getSelectionModel().getSelectedItem().getValue());
- }
-
/**
* Handles double click on the specified tree node. Actual action depends on the {@link Node} type.
*
@@ -628,7 +624,9 @@ public void nodeDoubleClicked(Node node) {
((SnapshotTab) tab).loadSnapshot(node);
break;
case COMPOSITE_SNAPSHOT:
- openCompositeSnapshotForRestore();
+ TreeItem treeItem = browserSelectionModel.getSelectedItems().get(0);
+ tab = new SnapshotTab(treeItem.getValue(), saveAndRestoreService);
+ ((SnapshotTab) tab).loadSnapshot(treeItem.getValue());
return;
case FOLDER:
default:
@@ -1297,15 +1295,6 @@ public Node[] getConfigAndSnapshotForActiveSnapshotTab() {
return null;
}
- protected void openCompositeSnapshotForRestore() {
- TreeItem treeItem = browserSelectionModel.getSelectedItems().get(0);
- SnapshotTab tab = new SnapshotTab(treeItem.getValue(), saveAndRestoreService);
- tab.loadSnapshot(treeItem.getValue());
-
- tabPane.getTabs().add(tab);
- tabPane.getSelectionModel().select(tab);
- }
-
@Override
public void filterAddedOrUpdated(Filter filter) {
if (!filtersList.contains(filter)) {
@@ -1500,5 +1489,11 @@ public boolean compareSnapshotsPossible() {
public void secureStoreChanged(List validTokens){
userIsAuthenticated.set(validTokens.stream()
.filter(t -> t.getAuthenticationScope().equals(AuthenticationScope.SAVE_AND_RESTORE)).findFirst().isPresent());
+ tabPane.getTabs().forEach(t -> {
+ if(t instanceof ConfigurationTab){
+ ConfigurationTab configurationTab = (ConfigurationTab)t;
+ configurationTab.secureStoreChanged(validTokens);
+ }
+ });
}
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/BaseConfigurationSelectionController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/BaseConfigurationSelectionController.java
deleted file mode 100644
index 1289028814..0000000000
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/BaseConfigurationSelectionController.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2020 European Spallation Source ERIC.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- */
-package org.phoebus.applications.saveandrestore.ui.configuration;
-
-import org.phoebus.applications.saveandrestore.model.Node;
-
-/**
- * Abstract base class for {@link Node} selection dialog controller
- *
- * @author Genie Jhang
- */
-
-public abstract class BaseConfigurationSelectionController implements ISelectedNodeProvider {
- protected boolean isDisabledConfigurationSelection;
-
- protected void disableConfigurationSelection() {
- isDisabledConfigurationSelection = true;
- }
-}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java
index da604ecd28..2605b0cd36 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java
@@ -32,15 +32,19 @@
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.Pane;
import javafx.util.Callback;
import org.phoebus.applications.saveandrestore.Messages;
import org.phoebus.applications.saveandrestore.SaveAndRestoreApplication;
import org.phoebus.applications.saveandrestore.model.*;
import org.phoebus.applications.saveandrestore.ui.NodeChangedListener;
-import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreController;
+//import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreController;
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService;
import org.phoebus.core.types.ProcessVariable;
import org.phoebus.framework.selection.SelectionService;
+import org.phoebus.security.store.SecureStore;
+import org.phoebus.security.tokens.AuthenticationScope;
+import org.phoebus.security.tokens.ScopedAuthenticationToken;
import org.phoebus.ui.application.ContextMenuHelper;
import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
import org.phoebus.ui.javafx.ImageCache;
@@ -102,6 +106,11 @@ public class ConfigurationController implements NodeChangedListener {
private Label configurationLastModifiedDateField;
@FXML
private Label createdByField;
+ @FXML
+ private Label authenticatedUserId;
+
+ @FXML
+ private Pane addPVsPane;
private SaveAndRestoreService saveAndRestoreService;
@@ -128,26 +137,32 @@ public ConfigurationController(ConfigurationTab configurationTab) {
this.configurationTab = configurationTab;
}
+ private final SimpleBooleanProperty userIsAuthenticated = new SimpleBooleanProperty();
+
+ private final SimpleStringProperty authenticatedUserProperty = new SimpleStringProperty(Messages.authenticatedUserNone);
+
@FXML
public void initialize() {
saveAndRestoreService = SaveAndRestoreService.getInstance();
+
pvTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
pvTable.getSelectionModel().selectedItemProperty().addListener((obs, ov, nv) -> selectionEmpty.set(nv == null));
ContextMenu pvNameContextMenu = new ContextMenu();
MenuItem deleteMenuItem = new MenuItem(Messages.menuItemDeleteSelectedPVs,
- new ImageView(ImageCache.getImage(SaveAndRestoreController.class, "/icons/delete.png")));
+ new ImageView(ImageCache.getImage(ConfigurationController.class, "/icons/delete.png")));
deleteMenuItem.setOnAction(ae -> {
configurationEntries.removeAll(pvTable.getSelectionModel().getSelectedItems());
configurationTab.annotateDirty(true);
pvTable.refresh();
});
- deleteMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> pvTable.getSelectionModel().getSelectedItems().isEmpty(),
- pvTable.getSelectionModel().getSelectedItems()));
+ deleteMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> pvTable.getSelectionModel().getSelectedItems().isEmpty()
+ || userIsAuthenticated.not().get(),
+ pvTable.getSelectionModel().getSelectedItems(), userIsAuthenticated));
pvNameColumn.setEditable(true);
pvNameColumn.setCellValueFactory(new PropertyValueFactory<>("pvName"));
@@ -196,7 +211,9 @@ public void updateItem(String item, boolean empty) {
pvNameField.textProperty().bindBidirectional(pvNameProperty);
readbackPvNameField.textProperty().bindBidirectional(readbackPvNameProperty);
configurationNameField.textProperty().bindBidirectional(configurationNameProperty);
+ configurationNameField.disableProperty().bind(userIsAuthenticated.not());
descriptionTextArea.textProperty().bindBidirectional(configurationDescriptionProperty);
+ descriptionTextArea.disableProperty().bind(userIsAuthenticated.not());
configurationEntries.addListener((ListChangeListener) change -> {
while (change.next()) {
@@ -208,13 +225,13 @@ public void updateItem(String item, boolean empty) {
});
configurationNameProperty.addListener((observableValue, oldValue, newValue) -> dirty.set(!newValue.equals(configurationNode.getName())));
-
configurationDescriptionProperty.addListener((observable, oldValue, newValue) -> dirty.set(!newValue.equals(configurationNode.get().getDescription())));
saveButton.disableProperty().bind(Bindings.createBooleanBinding(() -> dirty.not().get() ||
configurationDescriptionProperty.isEmpty().get() ||
- configurationNameProperty.isEmpty().get(),
- dirty, configurationDescriptionProperty, configurationNameProperty));
+ configurationNameProperty.isEmpty().get() ||
+ userIsAuthenticated.not().get(),
+ dirty, configurationDescriptionProperty, configurationNameProperty, userIsAuthenticated));
addPvButton.disableProperty().bind(pvNameField.textProperty().isEmpty());
@@ -236,6 +253,24 @@ public void updateItem(String item, boolean empty) {
}
});
+ authenticatedUserId.textProperty().bind(authenticatedUserProperty);
+ addPVsPane.disableProperty().bind(userIsAuthenticated.not());
+
+ // Initialize userIsAuthenticated property
+
+ try {
+ SecureStore secureStore = new SecureStore();
+ ScopedAuthenticationToken token =
+ secureStore.getScopedAuthenticationToken(AuthenticationScope.SAVE_AND_RESTORE);
+ if(token != null){
+ userIsAuthenticated.set(true);
+ authenticatedUserProperty.set(token.getUsername());
+ }
+ } catch (Exception e) {
+ logger.log(Level.WARNING, "Unable to retrieve authentication token for " +
+ AuthenticationScope.SAVE_AND_RESTORE.getName() + " scope", e);
+ }
+
SaveAndRestoreService.getInstance().addNodeChangeListener(this);
}
@@ -384,4 +419,18 @@ public void nodeChanged(Node node) {
.build());
}
}
+
+ public void secureStoreChanged(List validTokens){
+ Optional token =
+ validTokens.stream()
+ .filter(t -> t.getAuthenticationScope().equals(AuthenticationScope.SAVE_AND_RESTORE)).findFirst();
+ if(token.isPresent()){
+ userIsAuthenticated.set(true);
+ authenticatedUserProperty.set(token.get().getUsername());
+ }
+ else{
+ userIsAuthenticated.set(false);
+ authenticatedUserProperty.set(Messages.authenticatedUserNone);
+ }
+ }
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationTab.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationTab.java
index 96579ad17a..a1fe14b791 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationTab.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationTab.java
@@ -27,7 +27,9 @@
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService;
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreTab;
import org.phoebus.framework.nls.NLS;
+import org.phoebus.security.tokens.ScopedAuthenticationToken;
+import java.util.List;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -117,4 +119,8 @@ public void annotateDirty(boolean dirty) {
updateTabTitle(tabTitle.substring(2));
}
}
+
+ public void secureStoreChanged(List validTokens){
+ configurationController.secureStoreChanged(validTokens);
+ }
}
diff --git a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties
index d31b3acb26..01dd9d5de7 100644
--- a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties
+++ b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties
@@ -2,6 +2,8 @@ add=Add
alertContinue=Do you wish to continue?
alertAddingPVsToConfiguration=Adding PV to configuration
authenticationFailed=Authentication Failed
+authenticatedUserLabel=Authenticated User
+authenticatedUserNone=
baseSetpoint=Base Setpoint
browseForLocation=Browse to select location
buttonSearch=Open snapshot/tag search window
diff --git a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationEditor.fxml b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationEditor.fxml
index 48708928e6..7ac6b8eea3 100644
--- a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationEditor.fxml
+++ b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationEditor.fxml
@@ -5,9 +5,10 @@
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 1e7bf0beac0c5da3c98e2131fc795f098234903d Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Wed, 13 Sep 2023 10:56:33 +0200
Subject: [PATCH 16/42] Save&restore: composite snapshot UI elements enabled
only if user is authenticated
---
.../ui/SaveAndRestoreController.java | 4 ++
.../snapshot/CompositeSnapshotController.java | 46 +++++++++++++++++--
.../ui/snapshot/CompositeSnapshotTab.java | 5 ++
3 files changed, 51 insertions(+), 4 deletions(-)
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
index af430b9284..dd25a383c7 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
@@ -1498,6 +1498,10 @@ else if(t instanceof SnapshotTab){
SnapshotTab snapshotTab = (SnapshotTab)t;
snapshotTab.secureStoreChanged(validTokens);
}
+ else if(t instanceof CompositeSnapshotTab){
+ CompositeSnapshotTab compositeSnapshotTab = (CompositeSnapshotTab) t;
+ compositeSnapshotTab.secureStoreChanged(validTokens);
+ }
});
}
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotController.java
index 4bb9d25c46..ea74f96058 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotController.java
@@ -62,6 +62,9 @@
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreController;
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService;
import org.phoebus.framework.jobs.JobManager;
+import org.phoebus.security.store.SecureStore;
+import org.phoebus.security.tokens.AuthenticationScope;
+import org.phoebus.security.tokens.ScopedAuthenticationToken;
import org.phoebus.ui.dialog.DialogHelper;
import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
import org.phoebus.ui.javafx.ImageCache;
@@ -75,6 +78,8 @@
import java.util.Optional;
import java.util.Stack;
import java.util.function.Consumer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import java.util.stream.Collectors;
public class CompositeSnapshotController {
@@ -145,6 +150,8 @@ public class CompositeSnapshotController {
private ChangeListener nodeNameChangeListener;
private ChangeListener descriptionChangeListener;
+ private final SimpleBooleanProperty userIsAuthenticated = new SimpleBooleanProperty();
+
public CompositeSnapshotController(CompositeSnapshotTab compositeSnapshotTab, SaveAndRestoreController saveAndRestoreController) {
this.compositeSnapshotTab = compositeSnapshotTab;
this.saveAndRestoreController = saveAndRestoreController;
@@ -167,8 +174,10 @@ public void initialize() {
snapshotTable.refresh();
});
- deleteMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> snapshotTable.getSelectionModel().getSelectedItems().isEmpty(),
- snapshotTable.getSelectionModel().getSelectedItems()));
+ deleteMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
+ snapshotTable.getSelectionModel().getSelectedItems().isEmpty() ||
+ userIsAuthenticated.not().get(),
+ snapshotTable.getSelectionModel().getSelectedItems(), userIsAuthenticated));
snapshotDateColumn.setCellFactory(new Callback<>() {
@Override
@@ -274,7 +283,9 @@ public void updateItem(Node item, boolean empty) {
});
compositeSnapshotNameField.textProperty().bindBidirectional(compositeSnapshotNameProperty);
+ compositeSnapshotNameField.disableProperty().bind(userIsAuthenticated.not());
descriptionTextArea.textProperty().bindBidirectional(compositeSnapshotDescriptionProperty);
+ descriptionTextArea.disableProperty().bind(userIsAuthenticated.not());
compositeSnapshotLastModifiedDateField.textProperty().bindBidirectional(lastUpdatedProperty);
compositeSnapshotCreatedDateField.textProperty().bindBidirectional(createdDateProperty);
createdByField.textProperty().bindBidirectional(createdByProperty);
@@ -286,8 +297,9 @@ public void updateItem(Node item, boolean empty) {
saveButton.disableProperty().bind(Bindings.createBooleanBinding(() -> dirty.not().get() ||
compositeSnapshotDescriptionProperty.isEmpty().get() ||
- compositeSnapshotNameProperty.isEmpty().get(),
- dirty, compositeSnapshotDescriptionProperty, compositeSnapshotNameProperty));
+ compositeSnapshotNameProperty.isEmpty().get() ||
+ userIsAuthenticated.not().get(),
+ dirty, compositeSnapshotDescriptionProperty, compositeSnapshotNameProperty, userIsAuthenticated.not()));
snapshotTable.setOnDragOver(event -> {
if (event.getDragboard().hasContent(SaveAndRestoreApplication.NODE_SELECTION_FORMAT)) {
@@ -297,6 +309,10 @@ public void updateItem(Node item, boolean empty) {
});
snapshotTable.setOnDragDropped(event -> {
+ if(userIsAuthenticated.not().get()){
+ event.consume();
+ return;
+ }
List sourceNodes = (List) event.getDragboard().getContent(SaveAndRestoreApplication.NODE_SELECTION_FORMAT);
if (!mayDrop(sourceNodes)) {
return;
@@ -314,6 +330,18 @@ public void updateItem(Node item, boolean empty) {
}
});
+ try {
+ SecureStore secureStore = new SecureStore();
+ ScopedAuthenticationToken token =
+ secureStore.getScopedAuthenticationToken(AuthenticationScope.SAVE_AND_RESTORE);
+ if (token != null) {
+ userIsAuthenticated.set(true);
+ }
+ } catch (Exception e) {
+ Logger.getLogger(CompositeSnapshotController.class.getName()).log(Level.WARNING, "Unable to retrieve authentication token for " +
+ AuthenticationScope.SAVE_AND_RESTORE.getName() + " scope", e);
+ }
+
}
@FXML
@@ -518,4 +546,14 @@ private void removeListeners() {
compositeSnapshotDescriptionProperty.removeListener(descriptionChangeListener);
}
+ public void secureStoreChanged(List validTokens){
+ Optional token =
+ validTokens.stream()
+ .filter(t -> t.getAuthenticationScope().equals(AuthenticationScope.SAVE_AND_RESTORE)).findFirst();
+ if (token.isPresent()) {
+ userIsAuthenticated.set(true);
+ } else {
+ userIsAuthenticated.set(false);
+ }
+ }
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotTab.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotTab.java
index 27cfebfc19..ff20fbfb6c 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotTab.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotTab.java
@@ -32,6 +32,7 @@
import org.phoebus.applications.saveandrestore.ui.ImageRepository;
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreController;
import org.phoebus.framework.nls.NLS;
+import org.phoebus.security.tokens.ScopedAuthenticationToken;
import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
import java.io.IOException;
@@ -146,4 +147,8 @@ public void editCompositeSnapshot(Node compositeSnapshotNode, List snapsho
public void addToCompositeSnapshot(List snapshotNodes) {
compositeSnapshotController.addToCompositeSnapshot(snapshotNodes);
}
+
+ public void secureStoreChanged(List validTokens){
+ compositeSnapshotController.secureStoreChanged(validTokens);
+ }
}
From 39cf91743bbc74e687099af3bd2a1d9853d32e26 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Wed, 13 Sep 2023 11:03:32 +0200
Subject: [PATCH 17/42] Save&restore: disable drag-n-drop if user is not
authenticated
---
.../applications/saveandrestore/ui/BrowserTreeCell.java | 2 +-
.../saveandrestore/ui/SaveAndRestoreController.java | 4 ++++
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java
index 8809d50357..48fa70151f 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java
@@ -86,7 +86,7 @@ public BrowserTreeCell(ContextMenu folderContextMenu, ContextMenu configurationC
});
setOnDragDetected(event -> {
- if (!saveAndRestoreController.selectedNodesOfSameType()) {
+ if (!saveAndRestoreController.selectedNodesOfSameType() || !saveAndRestoreController.getUserIsAuthenticated()) {
return;
}
final ClipboardContent content = new ClipboardContent();
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
index dd25a383c7..c1636f9712 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
@@ -1504,4 +1504,8 @@ else if(t instanceof CompositeSnapshotTab){
}
});
}
+
+ public boolean getUserIsAuthenticated(){
+ return userIsAuthenticated.get();
+ }
}
From c9f193281d117287b693cea6f76b2199bdb788d4 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Wed, 13 Sep 2023 15:15:37 +0200
Subject: [PATCH 18/42] Save&restore: filter&search view UI elements disabled
if user not authenticated. Also some duplicate code removed
---
.../saveandrestore/ui/BrowserTreeCell.java | 2 +-
.../saveandrestore/ui/ContextMenuBase.java | 2 +-
.../ui/SaveAndRestoreBaseController.java | 68 +++++++++
.../ui/SaveAndRestoreController.java | 130 +++++-------------
.../saveandrestore/ui/SaveAndRestoreTab.java | 7 +
.../ConfigurationController.java | 52 ++-----
.../ui/configuration/ConfigurationTab.java | 15 +-
.../ui/search/SearchAndFilterTab.java | 18 +--
.../search/SearchAndFilterViewController.java | 18 +--
.../snapshot/CompositeSnapshotController.java | 84 +++--------
.../ui/snapshot/CompositeSnapshotTab.java | 50 +++----
.../ui/snapshot/SnapshotController.java | 5 +-
.../SnapshotControlsViewController.java | 56 ++------
.../ui/snapshot/SnapshotTab.java | 35 ++---
.../main/resources/icons/sar-search_18x18.png | Bin 0 -> 7140 bytes
15 files changed, 199 insertions(+), 343 deletions(-)
create mode 100644 app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreBaseController.java
create mode 100644 app/save-and-restore/app/src/main/resources/icons/sar-search_18x18.png
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java
index 48fa70151f..06961b7629 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java
@@ -86,7 +86,7 @@ public BrowserTreeCell(ContextMenu folderContextMenu, ContextMenu configurationC
});
setOnDragDetected(event -> {
- if (!saveAndRestoreController.selectedNodesOfSameType() || !saveAndRestoreController.getUserIsAuthenticated()) {
+ if (saveAndRestoreController.getUserIdentity().isNull().get() || !saveAndRestoreController.selectedNodesOfSameType()) {
return;
}
final ClipboardContent content = new ClipboardContent();
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java
index d08011a680..974ef4d1e3 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java
@@ -93,7 +93,7 @@ public ContextMenuBase(SaveAndRestoreController saveAndRestoreController) {
* Subclasses use this to disable menu items if needed, e.g. disable delete if user has not signed in.
*/
protected void runChecks() {
- userIsAuthenticatedProperty.set(saveAndRestoreController.isUserAuthenticated());
+ userIsAuthenticatedProperty.set(saveAndRestoreController.getUserIdentity().isNotNull().get());
boolean multipleNodesSelected = saveAndRestoreController.multipleNodesSelected();
multipleNodesSelectedProperty.set(multipleNodesSelected);
if (multipleNodesSelected) { // No need to check this if only one node was selected
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreBaseController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreBaseController.java
new file mode 100644
index 0000000000..6e5fbf8e2f
--- /dev/null
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreBaseController.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package org.phoebus.applications.saveandrestore.ui;
+
+import javafx.beans.property.SimpleStringProperty;
+import org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotControlsViewController;
+import org.phoebus.security.store.SecureStore;
+import org.phoebus.security.tokens.AuthenticationScope;
+import org.phoebus.security.tokens.ScopedAuthenticationToken;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public abstract class SaveAndRestoreBaseController {
+
+ protected final SimpleStringProperty userIdentity = new SimpleStringProperty();
+
+ public SaveAndRestoreBaseController(){
+ try {
+ SecureStore secureStore = new SecureStore();
+ ScopedAuthenticationToken token =
+ secureStore.getScopedAuthenticationToken(AuthenticationScope.SAVE_AND_RESTORE);
+ if (token != null) {
+ userIdentity.set(token.getUsername());
+ }
+ else{
+ userIdentity.set(null);
+ }
+ } catch (Exception e) {
+ Logger.getLogger(SnapshotControlsViewController.class.getName()).log(Level.WARNING, "Unable to retrieve authentication token for " +
+ AuthenticationScope.SAVE_AND_RESTORE.getName() + " scope", e);
+ }
+ }
+
+ public void secureStoreChanged(List validTokens) {
+ Optional token =
+ validTokens.stream()
+ .filter(t -> t.getAuthenticationScope().equals(AuthenticationScope.SAVE_AND_RESTORE)).findFirst();
+ if (token.isPresent()) {
+ userIdentity.set(token.get().getUsername());
+ } else {
+ userIdentity.set(null);
+ }
+ }
+
+ public SimpleStringProperty getUserIdentity(){
+ return userIdentity;
+ }
+}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
index c1636f9712..7eaee67ced 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
@@ -30,33 +30,10 @@
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
-import javafx.scene.control.Alert;
+import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
-import javafx.scene.control.Button;
-import javafx.scene.control.ButtonType;
-import javafx.scene.control.CheckBox;
-import javafx.scene.control.ComboBox;
-import javafx.scene.control.ContextMenu;
-import javafx.scene.control.ListCell;
-import javafx.scene.control.ListView;
-import javafx.scene.control.Menu;
-import javafx.scene.control.MenuItem;
-import javafx.scene.control.MultipleSelectionModel;
-import javafx.scene.control.ProgressIndicator;
-import javafx.scene.control.SelectionMode;
-import javafx.scene.control.SplitPane;
-import javafx.scene.control.Tab;
-import javafx.scene.control.TabPane;
-import javafx.scene.control.TextInputDialog;
-import javafx.scene.control.Tooltip;
-import javafx.scene.control.TreeItem;
-import javafx.scene.control.TreeView;
import javafx.scene.image.ImageView;
-import javafx.scene.input.Clipboard;
-import javafx.scene.input.ClipboardContent;
-import javafx.scene.input.KeyCode;
-import javafx.scene.input.KeyEvent;
-import javafx.scene.input.TransferMode;
+import javafx.scene.input.*;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.util.Callback;
@@ -94,15 +71,7 @@
import java.net.URI;
import java.net.URL;
import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.ResourceBundle;
-import java.util.Stack;
+import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@@ -110,7 +79,8 @@
/**
* Main controller for the save and restore UI.
*/
-public class SaveAndRestoreController implements Initializable, NodeChangedListener, NodeAddedListener, FilterChangeListener {
+public class SaveAndRestoreController extends SaveAndRestoreBaseController
+ implements Initializable, NodeChangedListener, NodeAddedListener, FilterChangeListener {
@FXML
protected TreeView treeView;
@@ -170,10 +140,6 @@ public class SaveAndRestoreController implements Initializable, NodeChangedListe
private final ObservableList filtersList = FXCollections.observableArrayList();
- private SecureStore secureStore;
-
- private SimpleBooleanProperty userIsAuthenticated = new SimpleBooleanProperty();
-
/**
* @param uri If non-null, this is used to load a configuration or snapshot into the view.
*/
@@ -182,7 +148,6 @@ public SaveAndRestoreController(URI uri) {
}
-
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
@@ -211,7 +176,7 @@ public void initialize(URL url, ResourceBundle resourceBundle) {
newRootFolderMenuItem.setOnAction(ae -> createNewFolder());
rootFolderContextMenu.getItems().add(newRootFolderMenuItem);
rootFolderContextMenu.setOnShowing(event -> {
- if(!isUserAuthenticated()){
+ if (userIdentity.isNull().get()) {
Platform.runLater(() -> rootFolderContextMenu.hide());
}
});
@@ -295,18 +260,12 @@ public Filter fromString(String s) {
// considered in paste actions.
Clipboard.getSystemClipboard().clear();
- try {
- secureStore = new SecureStore();
- } catch (Exception e) {
- LOG.log(Level.WARNING, "SecureStore could not be created. Authentication won't work.", e);
- }
-
loadTreeData();
}
/**
* Loads the data for the tree root as provided (persisted) by the current
- * {@link org.phoebus.applications.saveandrestore.SaveAndRestoreClient}.
+ * {@link org.phoebus.applications.saveandrestore.client.SaveAndRestoreClient}.
*/
public void loadTreeData() {
@@ -591,7 +550,7 @@ protected Void call() throws Exception {
@Override
public void failed() {
- if(parentTreeItem.getParent() != null) { // Parent is null for root folder
+ if (parentTreeItem.getParent() != null) { // Parent is null for root folder
expandTreeNode(parentTreeItem.getParent());
}
ExceptionDetailsErrorDialog.openError(Messages.errorGeneric,
@@ -1102,8 +1061,8 @@ public boolean selectedNodesOfSameType() {
return true;
}
NodeType nodeType = selectedItems.get(0).getValue().getNodeType();
- for(int i = 1; i < selectedItems.size(); i++){
- if(!selectedItems.get(i).getValue().getNodeType().equals(nodeType)){
+ for (int i = 1; i < selectedItems.size(); i++) {
+ if (!selectedItems.get(i).getValue().getNodeType().equals(nodeType)) {
return false;
}
}
@@ -1324,7 +1283,7 @@ public void filterRemoved(Filter filter) {
}
}
- public void copySelectionToClipboard(){
+ public void copySelectionToClipboard() {
List> selectedItems = browserSelectionModel.getSelectedItems();
List selectedNodes = selectedItems.stream().map(TreeItem::getValue).collect(Collectors.toList());
ClipboardContent clipboardContent = new ClipboardContent();
@@ -1339,18 +1298,19 @@ public void copySelectionToClipboard(){
*
All selected nodes must be of same type.
*
All selected nodes must have same parent.
*
+ *
* @return true if selection may be copied to clipboard, otherwise false.
*/
- public boolean mayCopy(){
- if(!isUserAuthenticated()){
+ public boolean mayCopy() {
+ if (userIdentity.isNull().get()) {
return false;
}
List selectedNodes = browserSelectionModel.getSelectedItems().stream().map(TreeItem::getValue).collect(Collectors.toList());
- if(selectedNodes.size() == 1){
+ if (selectedNodes.size() == 1) {
return true;
}
NodeType nodeTypeOfFirst = selectedNodes.get(0).getNodeType();
- if(selectedNodes.stream().filter(n -> !n.getNodeType().equals(nodeTypeOfFirst)).findFirst().isPresent()){
+ if (selectedNodes.stream().filter(n -> !n.getNodeType().equals(nodeTypeOfFirst)).findFirst().isPresent()) {
return false;
}
TreeItem parentOfFirst = browserSelectionModel.getSelectedItems().get(0).getParent();
@@ -1365,38 +1325,38 @@ public boolean mayCopy(){
*
Configurations and composite snapshots may be pasted only onto folder.
*
Snapshot may be pasted only onto configuration.
*
- * @return true if selection may be pasted, otherwise false.
+ *
+ * @return true if selection may be pasted, otherwise false.
*/
- public boolean mayPaste(){
- if(!isUserAuthenticated()){
+ public boolean mayPaste() {
+ if (userIdentity.isNull().get()) {
return false;
}
Object clipBoardContent = Clipboard.getSystemClipboard().getContent(SaveAndRestoreApplication.NODE_SELECTION_FORMAT);
- if(clipBoardContent == null || browserSelectionModel.getSelectedItems().size() != 1){
+ if (clipBoardContent == null || browserSelectionModel.getSelectedItems().size() != 1) {
return false;
}
// Check is made if target node is of supported type for the clipboard content.
- List selectedNodes = (List)clipBoardContent;
+ List selectedNodes = (List) clipBoardContent;
NodeType nodeTypeOfFirst = selectedNodes.get(0).getNodeType();
NodeType nodeTypeOfTarget = browserSelectionModel.getSelectedItem().getValue().getNodeType();
- if((nodeTypeOfFirst.equals(NodeType.COMPOSITE_SNAPSHOT) ||
- nodeTypeOfFirst.equals(NodeType.CONFIGURATION)) && !nodeTypeOfTarget.equals(NodeType.FOLDER)){
+ if ((nodeTypeOfFirst.equals(NodeType.COMPOSITE_SNAPSHOT) ||
+ nodeTypeOfFirst.equals(NodeType.CONFIGURATION)) && !nodeTypeOfTarget.equals(NodeType.FOLDER)) {
return false;
- }
- else if(nodeTypeOfFirst.equals(NodeType.SNAPSHOT) && !nodeTypeOfTarget.equals(NodeType.CONFIGURATION)){
+ } else if (nodeTypeOfFirst.equals(NodeType.SNAPSHOT) && !nodeTypeOfTarget.equals(NodeType.CONFIGURATION)) {
return false;
}
return true;
}
- public void pasteFromClipboard(){
+ public void pasteFromClipboard() {
disabledUi.set(true);
Object selectedNodes = Clipboard.getSystemClipboard().getContent(SaveAndRestoreApplication.NODE_SELECTION_FORMAT);
- if(selectedNodes == null || browserSelectionModel.getSelectedItems().size() != 1){
+ if (selectedNodes == null || browserSelectionModel.getSelectedItems().size() != 1) {
return;
}
List selectedNodeIds =
- ((List)selectedNodes).stream().map(Node::getUniqueId).collect(Collectors.toList());
+ ((List) selectedNodes).stream().map(Node::getUniqueId).collect(Collectors.toList());
JobManager.schedule("copy nodes", monitor -> {
try {
saveAndRestoreService.copyNodes(selectedNodeIds, browserSelectionModel.getSelectedItem().getValue().getUniqueId());
@@ -1414,10 +1374,6 @@ public void pasteFromClipboard(){
});
}
- public boolean isUserAuthenticated(){
- return userIsAuthenticated.get();
- }
-
/**
* Used to determine if nodes selected in the tree view have the same parent node. Most menu items
* do not make sense unless the selected nodes have same the parent node.
@@ -1443,7 +1399,7 @@ public boolean hasSameParent() {
/**
* @return true selection contains multiple {@link Node}s, otherwise false.
*/
- public boolean multipleNodesSelected(){
+ public boolean multipleNodesSelected() {
return browserSelectionModel.getSelectedItems().size() > 1;
}
@@ -1481,31 +1437,11 @@ public boolean compareSnapshotsPossible() {
!selectedItem.getValue().getUniqueId().equals(configAndSnapshotNode[1].getUniqueId());
}
- /**
- * Sets {@link #userIsAuthenticated} value based on presence of
- * {@link AuthenticationScope#SAVE_AND_RESTORE} in the list of valid tokens.
- * @param validTokens List of valid {@link ScopedAuthenticationToken}s in the {@link SecureStore}.
- */
- public void secureStoreChanged(List validTokens){
- userIsAuthenticated.set(validTokens.stream()
- .filter(t -> t.getAuthenticationScope().equals(AuthenticationScope.SAVE_AND_RESTORE)).findFirst().isPresent());
+ @Override
+ public void secureStoreChanged(List validTokens) {
+ super.secureStoreChanged(validTokens);
tabPane.getTabs().forEach(t -> {
- if(t instanceof ConfigurationTab){
- ConfigurationTab configurationTab = (ConfigurationTab)t;
- configurationTab.secureStoreChanged(validTokens);
- }
- else if(t instanceof SnapshotTab){
- SnapshotTab snapshotTab = (SnapshotTab)t;
- snapshotTab.secureStoreChanged(validTokens);
- }
- else if(t instanceof CompositeSnapshotTab){
- CompositeSnapshotTab compositeSnapshotTab = (CompositeSnapshotTab) t;
- compositeSnapshotTab.secureStoreChanged(validTokens);
- }
+ ((SaveAndRestoreTab) t).secureStoreChanged(validTokens);
});
}
-
- public boolean getUserIsAuthenticated(){
- return userIsAuthenticated.get();
- }
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreTab.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreTab.java
index bf07029005..3e88ebb1a3 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreTab.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreTab.java
@@ -25,6 +25,7 @@
import javafx.scene.control.Tab;
import javafx.scene.image.ImageView;
import org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab;
+import org.phoebus.security.tokens.ScopedAuthenticationToken;
import org.phoebus.ui.javafx.ImageCache;
import java.util.List;
@@ -34,6 +35,8 @@
*/
public abstract class SaveAndRestoreTab extends Tab implements NodeChangedListener{
+ protected SaveAndRestoreBaseController controller;
+
public SaveAndRestoreTab(){
ContextMenu contextMenu = new ContextMenu();
@@ -57,4 +60,8 @@ public SaveAndRestoreTab(){
contextMenu.getItems().addAll(closeAll, closeOthers);
setContextMenu(contextMenu);
}
+
+ public void secureStoreChanged(List validTokens){
+ controller.secureStoreChanged(validTokens);
+ }
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java
index 367441e716..1e8f82109c 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java
@@ -38,13 +38,10 @@
import org.phoebus.applications.saveandrestore.SaveAndRestoreApplication;
import org.phoebus.applications.saveandrestore.model.*;
import org.phoebus.applications.saveandrestore.ui.NodeChangedListener;
-//import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreController;
+import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreBaseController;
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService;
import org.phoebus.core.types.ProcessVariable;
import org.phoebus.framework.selection.SelectionService;
-import org.phoebus.security.store.SecureStore;
-import org.phoebus.security.tokens.AuthenticationScope;
-import org.phoebus.security.tokens.ScopedAuthenticationToken;
import org.phoebus.ui.application.ContextMenuHelper;
import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
import org.phoebus.ui.javafx.ImageCache;
@@ -60,7 +57,7 @@
import java.util.logging.Logger;
import java.util.stream.Collectors;
-public class ConfigurationController implements NodeChangedListener {
+public class ConfigurationController extends SaveAndRestoreBaseController implements NodeChangedListener {
@FXML
private BorderPane root;
@@ -137,8 +134,6 @@ public ConfigurationController(ConfigurationTab configurationTab) {
this.configurationTab = configurationTab;
}
- private final SimpleBooleanProperty userIsAuthenticated = new SimpleBooleanProperty();
-
private final SimpleStringProperty authenticatedUserProperty = new SimpleStringProperty(Messages.authenticatedUserNone);
@FXML
@@ -160,8 +155,8 @@ public void initialize() {
});
deleteMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> pvTable.getSelectionModel().getSelectedItems().isEmpty()
- || userIsAuthenticated.not().get(),
- pvTable.getSelectionModel().getSelectedItems(), userIsAuthenticated));
+ || userIdentity.isNull().get(),
+ pvTable.getSelectionModel().getSelectedItems(), userIdentity));
pvNameColumn.setEditable(true);
pvNameColumn.setCellValueFactory(new PropertyValueFactory<>("pvName"));
@@ -210,9 +205,9 @@ public void updateItem(String item, boolean empty) {
pvNameField.textProperty().bindBidirectional(pvNameProperty);
readbackPvNameField.textProperty().bindBidirectional(readbackPvNameProperty);
configurationNameField.textProperty().bindBidirectional(configurationNameProperty);
- configurationNameField.disableProperty().bind(userIsAuthenticated.not());
+ configurationNameField.disableProperty().bind(userIdentity.isNull());
descriptionTextArea.textProperty().bindBidirectional(configurationDescriptionProperty);
- descriptionTextArea.disableProperty().bind(userIsAuthenticated.not());
+ descriptionTextArea.disableProperty().bind(userIdentity.isNull());
configurationEntries.addListener((ListChangeListener) change -> {
while (change.next()) {
@@ -229,8 +224,8 @@ public void updateItem(String item, boolean empty) {
saveButton.disableProperty().bind(Bindings.createBooleanBinding(() -> dirty.not().get() ||
configurationDescriptionProperty.isEmpty().get() ||
configurationNameProperty.isEmpty().get() ||
- userIsAuthenticated.not().get(),
- dirty, configurationDescriptionProperty, configurationNameProperty, userIsAuthenticated));
+ userIdentity.isNull().get(),
+ dirty, configurationDescriptionProperty, configurationNameProperty, userIdentity));
addPvButton.disableProperty().bind(pvNameField.textProperty().isEmpty());
@@ -253,22 +248,7 @@ public void updateItem(String item, boolean empty) {
});
authenticatedUserId.textProperty().bind(authenticatedUserProperty);
- addPVsPane.disableProperty().bind(userIsAuthenticated.not());
-
- // Initialize userIsAuthenticated property
-
- try {
- SecureStore secureStore = new SecureStore();
- ScopedAuthenticationToken token =
- secureStore.getScopedAuthenticationToken(AuthenticationScope.SAVE_AND_RESTORE);
- if(token != null){
- userIsAuthenticated.set(true);
- authenticatedUserProperty.set(token.getUsername());
- }
- } catch (Exception e) {
- logger.log(Level.WARNING, "Unable to retrieve authentication token for " +
- AuthenticationScope.SAVE_AND_RESTORE.getName() + " scope", e);
- }
+ addPVsPane.disableProperty().bind(userIdentity.isNull());
SaveAndRestoreService.getInstance().addNodeChangeListener(this);
}
@@ -418,18 +398,4 @@ public void nodeChanged(Node node) {
.build());
}
}
-
- public void secureStoreChanged(List validTokens){
- Optional token =
- validTokens.stream()
- .filter(t -> t.getAuthenticationScope().equals(AuthenticationScope.SAVE_AND_RESTORE)).findFirst();
- if(token.isPresent()){
- userIsAuthenticated.set(true);
- authenticatedUserProperty.set(token.get().getUsername());
- }
- else{
- userIsAuthenticated.set(false);
- authenticatedUserProperty.set(Messages.authenticatedUserNone);
- }
- }
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationTab.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationTab.java
index 2217146996..a905df15fc 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationTab.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationTab.java
@@ -36,8 +36,6 @@
public class ConfigurationTab extends SaveAndRestoreTab {
- private ConfigurationController configurationController;
-
public ConfigurationTab() {
configure();
}
@@ -61,7 +59,7 @@ private void configure() {
return null;
});
setContent(loader.load());
- configurationController = loader.getController();
+ controller = loader.getController();
setGraphic(new ImageView(ImageRepository.CONFIGURATION));
} catch (Exception e) {
Logger.getLogger(ConfigurationTab.class.getName())
@@ -70,7 +68,7 @@ private void configure() {
}
setOnCloseRequest(event -> {
- if (!configurationController.handleConfigurationTabClosed()) {
+ if (!((ConfigurationController)controller).handleConfigurationTabClosed()) {
event.consume();
} else {
SaveAndRestoreService.getInstance().removeNodeChangeListener(this);
@@ -88,11 +86,12 @@ private void configure() {
public void editConfiguration(Node configurationNode) {
setId(configurationNode.getUniqueId());
textProperty().set(configurationNode.getName());
- configurationController.loadConfiguration(configurationNode);
+ ((ConfigurationController)controller).loadConfiguration(configurationNode);
}
public void configureForNewConfiguration(Node parentNode) {
- configurationController.newConfiguration(parentNode);
+ textProperty().set(Messages.contextMenuNewConfiguration);
+ ((ConfigurationController)controller).newConfiguration(parentNode);
}
@Override
@@ -119,8 +118,4 @@ public void annotateDirty(boolean dirty) {
updateTabTitle(tabTitle.substring(2));
}
}
-
- public void secureStoreChanged(List validTokens){
- configurationController.secureStoreChanged(validTokens);
- }
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterTab.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterTab.java
index 473b760395..411695a76c 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterTab.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterTab.java
@@ -20,17 +20,14 @@
package org.phoebus.applications.saveandrestore.ui.search;
import javafx.fxml.FXMLLoader;
-import javafx.geometry.Insets;
import javafx.scene.Node;
-import javafx.scene.control.Label;
-import javafx.scene.control.Tab;
import javafx.scene.image.ImageView;
-import javafx.scene.layout.HBox;
import org.phoebus.applications.saveandrestore.Messages;
import org.phoebus.applications.saveandrestore.SaveAndRestoreApplication;
import org.phoebus.applications.saveandrestore.ui.NodeChangedListener;
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreController;
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService;
+import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreTab;
import org.phoebus.framework.nls.NLS;
import org.phoebus.ui.javafx.ImageCache;
@@ -39,7 +36,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
-public class SearchAndFilterTab extends Tab implements NodeChangedListener {
+public class SearchAndFilterTab extends SaveAndRestoreTab implements NodeChangedListener {
public static final String SEARCH_AND_FILTER_TAB_ID = "SearchAndFilterTab";
private SearchAndFilterViewController searchAndFilterViewController;
@@ -78,15 +75,10 @@ public SearchAndFilterTab(SaveAndRestoreController saveAndRestoreController) {
searchAndFilterViewController = loader.getController();
- HBox container = new HBox();
- ImageView imageView = new ImageView(ImageCache.getImage(ImageCache.class, "/icons/sar-search.png"));
- imageView.setFitWidth(18);
- imageView.setFitHeight(18);
- Label label = new Label(Messages.search);
- HBox.setMargin(label, new Insets(1, 0, 0, 5));
- container.getChildren().addAll(imageView, label);
+ ImageView imageView = new ImageView(ImageCache.getImage(ImageCache.class, "/icons/sar-search_18x18.png"));
- setGraphic(container);
+ textProperty().set(Messages.search);
+ setGraphic(imageView);
setOnCloseRequest(event -> SaveAndRestoreService.getInstance().removeNodeChangeListener(this));
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterViewController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterViewController.java
index 6460fd0982..bec5534a6c 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterViewController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterViewController.java
@@ -66,11 +66,7 @@
import org.phoebus.applications.saveandrestore.model.search.SearchQueryUtil;
import org.phoebus.applications.saveandrestore.model.search.SearchQueryUtil.Keys;
import org.phoebus.applications.saveandrestore.model.search.SearchResult;
-import org.phoebus.applications.saveandrestore.ui.FilterChangeListener;
-import org.phoebus.applications.saveandrestore.ui.HelpViewer;
-import org.phoebus.applications.saveandrestore.ui.ImageRepository;
-import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreController;
-import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService;
+import org.phoebus.applications.saveandrestore.ui.*;
import org.phoebus.applications.saveandrestore.ui.snapshot.tag.TagUtil;
import org.phoebus.applications.saveandrestore.ui.snapshot.tag.TagWidget;
import org.phoebus.framework.jobs.JobManager;
@@ -98,7 +94,7 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;
-public class SearchAndFilterViewController implements Initializable, FilterChangeListener {
+public class SearchAndFilterViewController extends SaveAndRestoreBaseController implements Initializable, FilterChangeListener {
private final SaveAndRestoreController saveAndRestoreController;
@@ -379,8 +375,12 @@ public void initialize(URL url, ResourceBundle resourceBundle) {
queryLabel.textProperty().bind(query);
filterNameTextField.textProperty().bindBidirectional(filterNameProperty);
+ filterNameTextField.disableProperty().bind(saveAndRestoreController.getUserIdentity().isNull());
saveFilterButton.disableProperty().bind(Bindings.createBooleanBinding(() ->
- filterNameProperty.get() == null || filterNameProperty.get().isEmpty(), filterNameProperty));
+ filterNameProperty.get() == null ||
+ filterNameProperty.get().isEmpty() ||
+ saveAndRestoreController.getUserIdentity().isNull().get(),
+ filterNameProperty, saveAndRestoreController.getUserIdentity()));
resultTableView.setRowFactory(tableView -> new TableRow<>() {
@Override
@@ -722,6 +722,8 @@ private class DeleteTableCell extends TableCell {
@Override
protected void updateItem(final Filter filter, final boolean empty) {
super.updateItem(filter, empty);
+ // If user clicks on the delete column cell, consume the mouse event to prevent the filter from being loaded.
+ setOnMouseClicked(event -> event.consume());
if (empty) {
setGraphic(null);
} else {
@@ -731,13 +733,13 @@ protected void updateItem(final Filter filter, final boolean empty) {
button.setOnAction(event -> {
try {
saveAndRestoreService.deleteFilter(filter);
- //loadFilters();
clearFilter(filter);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Failed to delete filter", e);
ExceptionDetailsErrorDialog.openError(Messages.errorGeneric, Messages.faildDeleteFilter, e);
}
});
+ button.disableProperty().bind(saveAndRestoreController.getUserIdentity().isNull());
setGraphic(button);
}
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotController.java
index ea74f96058..52419e7b7e 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotController.java
@@ -29,21 +29,8 @@
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
-import javafx.scene.control.Alert;
+import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
-import javafx.scene.control.Button;
-import javafx.scene.control.ButtonType;
-import javafx.scene.control.ContextMenu;
-import javafx.scene.control.Label;
-import javafx.scene.control.MenuItem;
-import javafx.scene.control.SelectionMode;
-import javafx.scene.control.TableCell;
-import javafx.scene.control.TableColumn;
-import javafx.scene.control.TableRow;
-import javafx.scene.control.TableView;
-import javafx.scene.control.TextArea;
-import javafx.scene.control.TextField;
-import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
@@ -53,18 +40,12 @@
import org.phoebus.applications.saveandrestore.DirectoryUtilities;
import org.phoebus.applications.saveandrestore.Messages;
import org.phoebus.applications.saveandrestore.SaveAndRestoreApplication;
-import org.phoebus.applications.saveandrestore.model.CompositeSnapshot;
-import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData;
-import org.phoebus.applications.saveandrestore.model.Node;
-import org.phoebus.applications.saveandrestore.model.NodeType;
-import org.phoebus.applications.saveandrestore.model.Tag;
+import org.phoebus.applications.saveandrestore.model.*;
import org.phoebus.applications.saveandrestore.ui.ImageRepository;
+import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreBaseController;
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreController;
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService;
import org.phoebus.framework.jobs.JobManager;
-import org.phoebus.security.store.SecureStore;
-import org.phoebus.security.tokens.AuthenticationScope;
-import org.phoebus.security.tokens.ScopedAuthenticationToken;
import org.phoebus.ui.dialog.DialogHelper;
import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
import org.phoebus.ui.javafx.ImageCache;
@@ -72,17 +53,11 @@
import java.text.MessageFormat;
import java.time.Instant;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-import java.util.Optional;
-import java.util.Stack;
+import java.util.*;
import java.util.function.Consumer;
-import java.util.logging.Level;
-import java.util.logging.Logger;
import java.util.stream.Collectors;
-public class CompositeSnapshotController {
+public class CompositeSnapshotController extends SaveAndRestoreBaseController {
@FXML
private StackPane root;
@@ -150,7 +125,6 @@ public class CompositeSnapshotController {
private ChangeListener nodeNameChangeListener;
private ChangeListener descriptionChangeListener;
- private final SimpleBooleanProperty userIsAuthenticated = new SimpleBooleanProperty();
public CompositeSnapshotController(CompositeSnapshotTab compositeSnapshotTab, SaveAndRestoreController saveAndRestoreController) {
this.compositeSnapshotTab = compositeSnapshotTab;
@@ -175,9 +149,8 @@ public void initialize() {
});
deleteMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
- snapshotTable.getSelectionModel().getSelectedItems().isEmpty() ||
- userIsAuthenticated.not().get(),
- snapshotTable.getSelectionModel().getSelectedItems(), userIsAuthenticated));
+ snapshotTable.getSelectionModel().getSelectedItems().isEmpty() || userIdentity.isNull().get(),
+ snapshotTable.getSelectionModel().getSelectedItems(), userIdentity));
snapshotDateColumn.setCellFactory(new Callback<>() {
@Override
@@ -283,9 +256,9 @@ public void updateItem(Node item, boolean empty) {
});
compositeSnapshotNameField.textProperty().bindBidirectional(compositeSnapshotNameProperty);
- compositeSnapshotNameField.disableProperty().bind(userIsAuthenticated.not());
+ compositeSnapshotNameField.disableProperty().bind(userIdentity.isNull());
descriptionTextArea.textProperty().bindBidirectional(compositeSnapshotDescriptionProperty);
- descriptionTextArea.disableProperty().bind(userIsAuthenticated.not());
+ descriptionTextArea.disableProperty().bind(userIdentity.isNull());
compositeSnapshotLastModifiedDateField.textProperty().bindBidirectional(lastUpdatedProperty);
compositeSnapshotCreatedDateField.textProperty().bindBidirectional(createdDateProperty);
createdByField.textProperty().bindBidirectional(createdByProperty);
@@ -298,8 +271,8 @@ public void updateItem(Node item, boolean empty) {
saveButton.disableProperty().bind(Bindings.createBooleanBinding(() -> dirty.not().get() ||
compositeSnapshotDescriptionProperty.isEmpty().get() ||
compositeSnapshotNameProperty.isEmpty().get() ||
- userIsAuthenticated.not().get(),
- dirty, compositeSnapshotDescriptionProperty, compositeSnapshotNameProperty, userIsAuthenticated.not()));
+ userIdentity.isNull().get(),
+ dirty, compositeSnapshotDescriptionProperty, compositeSnapshotNameProperty, userIdentity));
snapshotTable.setOnDragOver(event -> {
if (event.getDragboard().hasContent(SaveAndRestoreApplication.NODE_SELECTION_FORMAT)) {
@@ -309,7 +282,7 @@ public void updateItem(Node item, boolean empty) {
});
snapshotTable.setOnDragDropped(event -> {
- if(userIsAuthenticated.not().get()){
+ if (userIdentity.isNull().get()) {
event.consume();
return;
}
@@ -329,19 +302,6 @@ public void updateItem(Node item, boolean empty) {
compositeSnapshotTab.annotateDirty(n);
}
});
-
- try {
- SecureStore secureStore = new SecureStore();
- ScopedAuthenticationToken token =
- secureStore.getScopedAuthenticationToken(AuthenticationScope.SAVE_AND_RESTORE);
- if (token != null) {
- userIsAuthenticated.set(true);
- }
- } catch (Exception e) {
- Logger.getLogger(CompositeSnapshotController.class.getName()).log(Level.WARNING, "Unable to retrieve authentication token for " +
- AuthenticationScope.SAVE_AND_RESTORE.getName() + " scope", e);
- }
-
}
@FXML
@@ -438,18 +398,17 @@ public boolean handleCompositeSnapshotTabClosed() {
/**
* Configures the controller to create a new composite snapshot.
*
- * @param parentNode The parent {@link Node} for the new composite, i.e. must be a
- * {@link Node} of type {@link NodeType#FOLDER}.
+ * @param parentNode The parent {@link Node} for the new composite, i.e. must be a
+ * {@link Node} of type {@link NodeType#FOLDER}.
* @param snapshotNodes Potentially empty list of {@link Node}s of type {@link NodeType#SNAPSHOT}
* or {@link NodeType#COMPOSITE_SNAPSHOT}, or both.
*/
public void newCompositeSnapshot(Node parentNode, List snapshotNodes) {
parentFolder = parentNode;
compositeSnapshotNode = Node.builder().nodeType(NodeType.COMPOSITE_SNAPSHOT).build();
- if(snapshotNodes.isEmpty()){
+ if (snapshotNodes.isEmpty()) {
dirty.set(false);
- }
- else{
+ } else {
dirty.set(true);
//snapshotEntries.addAll(snapshotNodes);
addToCompositeSnapshot(snapshotNodes);
@@ -545,15 +504,4 @@ private void removeListeners() {
compositeSnapshotNameProperty.removeListener(nodeNameChangeListener);
compositeSnapshotDescriptionProperty.removeListener(descriptionChangeListener);
}
-
- public void secureStoreChanged(List validTokens){
- Optional token =
- validTokens.stream()
- .filter(t -> t.getAuthenticationScope().equals(AuthenticationScope.SAVE_AND_RESTORE)).findFirst();
- if (token.isPresent()) {
- userIsAuthenticated.set(true);
- } else {
- userIsAuthenticated.set(false);
- }
- }
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotTab.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotTab.java
index ff20fbfb6c..117ab5be3d 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotTab.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotTab.java
@@ -22,17 +22,13 @@
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.fxml.FXMLLoader;
-import javafx.geometry.Insets;
-import javafx.scene.control.Label;
-import javafx.scene.control.Tab;
import javafx.scene.image.ImageView;
-import javafx.scene.layout.HBox;
import org.phoebus.applications.saveandrestore.Messages;
import org.phoebus.applications.saveandrestore.model.Node;
import org.phoebus.applications.saveandrestore.ui.ImageRepository;
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreController;
+import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreTab;
import org.phoebus.framework.nls.NLS;
-import org.phoebus.security.tokens.ScopedAuthenticationToken;
import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
import java.io.IOException;
@@ -45,9 +41,7 @@
* Tab for creating or editing composite snapshots,
* i.e. for node type {@link org.phoebus.applications.saveandrestore.model.NodeType#COMPOSITE_SNAPSHOT}.
*/
-public class CompositeSnapshotTab extends Tab {
-
- private CompositeSnapshotController compositeSnapshotController;
+public class CompositeSnapshotTab extends SaveAndRestoreTab {
private final SimpleStringProperty tabTitleProperty = new SimpleStringProperty(Messages.contextMenuNewCompositeSnapshot);
@@ -86,19 +80,20 @@ private void configure() {
return;
}
- compositeSnapshotController = loader.getController();
+ controller = loader.getController();
setContent(rootNode);
- setGraphic(getTabGraphic());
+ setGraphic(new ImageView(ImageRepository.COMPOSITE_SNAPSHOT));
+ textProperty().bind(tabTitleProperty);
setOnCloseRequest(event -> {
- if (!compositeSnapshotController.handleCompositeSnapshotTabClosed()) {
+ if (!((CompositeSnapshotController) controller).handleCompositeSnapshotTabClosed()) {
event.consume();
}
});
}
- public void setNodeName(String nodeName){
+ public void setNodeName(String nodeName) {
Platform.runLater(() -> tabTitleProperty.set("[" + Messages.Edit + "] " + nodeName));
}
@@ -109,46 +104,35 @@ public void annotateDirty(boolean dirty) {
}
}
- private javafx.scene.Node getTabGraphic() {
- HBox container = new HBox();
- ImageView imageView = new ImageView(ImageRepository.COMPOSITE_SNAPSHOT);
- Label label = new Label("");
- label.textProperty().bindBidirectional(tabTitleProperty);
- HBox.setMargin(label, new Insets(1, 5, 0, 3));
- HBox.setMargin(imageView, new Insets(1, 2, 0, 3));
- container.getChildren().addAll(imageView, label);
-
- return container;
- }
-
public void configureForNewCompositeSnapshot(Node parentNode, List snapshotNodes) {
- compositeSnapshotController.newCompositeSnapshot(parentNode, snapshotNodes);
+ ((CompositeSnapshotController) controller).newCompositeSnapshot(parentNode, snapshotNodes);
}
/**
* Configures UI to edit an existing composite snapshot {@link Node}
*
* @param compositeSnapshotNode non-null configuration {@link Node}
- * @param snapshotNodes A potentially empty (but non-null) list of snapshot nodes that should
- * be added to the list of references snapshots.
+ * @param snapshotNodes A potentially empty (but non-null) list of snapshot nodes that should
+ * be added to the list of references snapshots.
*/
public void editCompositeSnapshot(Node compositeSnapshotNode, List snapshotNodes) {
setId("edit_" + compositeSnapshotNode.getUniqueId());
setNodeName(compositeSnapshotNode.getName());
- compositeSnapshotController.loadCompositeSnapshot(compositeSnapshotNode, snapshotNodes);
+ ((CompositeSnapshotController) controller).loadCompositeSnapshot(compositeSnapshotNode, snapshotNodes);
}
/**
* Adds additional snapshot nodes to an existing composite snapshot.
*
- * @param snapshotNodes Potentially empty (but non-null) list of snapshot nodes to include into
- * the composite snapshot.
+ * @param snapshotNodes Potentially empty (but non-null) list of snapshot nodes to include into
+ * the composite snapshot.
*/
public void addToCompositeSnapshot(List snapshotNodes) {
- compositeSnapshotController.addToCompositeSnapshot(snapshotNodes);
+ ((CompositeSnapshotController) controller).addToCompositeSnapshot(snapshotNodes);
}
- public void secureStoreChanged(List validTokens){
- compositeSnapshotController.secureStoreChanged(validTokens);
+ @Override
+ public void nodeChanged(Node node) {
+
}
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java
index e0f9a469d1..25ad0d710d 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java
@@ -30,6 +30,7 @@
import org.phoebus.applications.saveandrestore.Messages;
import org.phoebus.applications.saveandrestore.model.*;
import org.phoebus.applications.saveandrestore.model.event.SaveAndRestoreEventReceiver;
+import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreBaseController;
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService;
import org.phoebus.applications.saveandrestore.ui.VNoData;
import org.phoebus.framework.jobs.JobManager;
@@ -49,7 +50,7 @@
* Once the snapshot has been saved, this controller calls the {@link SnapshotTab} API to load
* the view associated with restore actions.
*/
-public class SnapshotController {
+public class SnapshotController extends SaveAndRestoreBaseController {
@FXML
@@ -88,6 +89,7 @@ public SnapshotController(SnapshotTab snapshotTab) {
@FXML
public void initialize() {
+
// Locate registered SaveAndRestoreEventReceivers
eventReceivers = ServiceLoader.load(SaveAndRestoreEventReceiver.class);
progressIndicator.visibleProperty().bind(disabledUi);
@@ -389,6 +391,7 @@ private Snapshot getSnapshotFromService(Node snapshotNode) throws Exception {
return snapshot;
}
+ @Override
public void secureStoreChanged(List validTokens){
snapshotControlsViewController.secureStoreChanged(validTokens);
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsViewController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsViewController.java
index 4a7be0ff26..f812dfb416 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsViewController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsViewController.java
@@ -39,6 +39,7 @@
import org.phoebus.applications.saveandrestore.model.Node;
import org.phoebus.applications.saveandrestore.model.NodeType;
import org.phoebus.applications.saveandrestore.model.event.SaveAndRestoreEventReceiver;
+import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreBaseController;
import org.phoebus.security.store.SecureStore;
import org.phoebus.security.tokens.AuthenticationScope;
import org.phoebus.security.tokens.ScopedAuthenticationToken;
@@ -51,7 +52,7 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;
-public class SnapshotControlsViewController {
+public class SnapshotControlsViewController extends SaveAndRestoreBaseController {
private SnapshotController snapshotController;
@@ -135,11 +136,6 @@ public class SnapshotControlsViewController {
private final SimpleObjectProperty snapshotNodeProperty = new SimpleObjectProperty();
- private final SimpleBooleanProperty userIsAuthenticated = new SimpleBooleanProperty();
-
- private final SimpleStringProperty authenticatedUserProperty =
- new SimpleStringProperty(Messages.authenticatedUserNone);
-
public void setSnapshotController(SnapshotController snapshotController) {
this.snapshotController = snapshotController;
}
@@ -148,16 +144,16 @@ public void setSnapshotController(SnapshotController snapshotController) {
public void initialize() {
snapshotName.textProperty().bindBidirectional(snapshotNameProperty);
- snapshotName.disableProperty().bind(userIsAuthenticated.not());
+ snapshotName.disableProperty().bind(userIdentity.isNull());
snapshotComment.textProperty().bindBidirectional(snapshotCommentProperty);
- snapshotComment.disableProperty().bind(userIsAuthenticated.not());
+ snapshotComment.disableProperty().bind(userIdentity.isNull());
createdBy.textProperty().bind(createdByTextProperty);
createdDate.textProperty().bind(createdDateTextProperty);
snapshotLastModifiedLabel.textProperty().bind(lastModifiedDateTextProperty);
takeSnapshotButton.disableProperty().bind(Bindings.createBooleanBinding(() ->
(snapshotNodeProperty.isNotNull().get() && !snapshotNodeProperty.get().getNodeType().equals(NodeType.SNAPSHOT)) ||
- userIsAuthenticated.not().get(), snapshotNodeProperty, userIsAuthenticated));
+ userIdentity.isNull().get(), snapshotNodeProperty, userIdentity));
snapshotNameProperty.addListener(((observableValue, oldValue, newValue) ->
snapshotDataDirty.set(newValue != null && (snapshotNodeProperty.isNull().get() || snapshotNodeProperty.isNotNull().get() && !newValue.equals(snapshotNodeProperty.get().getName())))));
@@ -168,25 +164,24 @@ public void initialize() {
snapshotDataDirty.not().get() ||
snapshotNameProperty.isEmpty().get() ||
snapshotCommentProperty.isEmpty().get() ||
- userIsAuthenticated.not().get(),
- snapshotDataDirty, snapshotNameProperty, snapshotCommentProperty, userIsAuthenticated));
+ userIdentity.isNull().get(),
+ snapshotDataDirty, snapshotNameProperty, snapshotCommentProperty, userIdentity));
saveSnapshotAndCreateLogEntryButton.disableProperty().bind(Bindings.createBooleanBinding(() -> (
snapshotDataDirty.not().get()) ||
snapshotNameProperty.isEmpty().get() ||
snapshotCommentProperty.isEmpty().get() ||
- userIsAuthenticated.not().get(),
- snapshotDataDirty, snapshotNameProperty, snapshotCommentProperty, userIsAuthenticated));
+ userIdentity.isNull().get(),
+ snapshotDataDirty, snapshotNameProperty, snapshotCommentProperty, userIdentity));
// Do not show the create log entry button if no event receivers have been registered
saveSnapshotAndCreateLogEntryButton.visibleProperty().set(ServiceLoader.load(SaveAndRestoreEventReceiver.class).iterator().hasNext());
restoreButton.disableProperty().bind(Bindings.createBooleanBinding(() ->
snapshotRestorableProperty.not().get() ||
- userIsAuthenticated.not().get(), snapshotRestorableProperty, userIsAuthenticated));
+ userIdentity.isNull().get(), snapshotRestorableProperty, userIdentity));
restoreAndLogButton.disableProperty().bind(Bindings.createBooleanBinding(() ->
- snapshotRestorableProperty.not().get() ||
- userIsAuthenticated.not().get(), snapshotRestorableProperty, userIsAuthenticated));
+ snapshotRestorableProperty.not().get() || userIdentity.isNull().get(), snapshotRestorableProperty, userIdentity));
SpinnerValueFactory thresholdSpinnerValueFactory = new SpinnerValueFactory.DoubleSpinnerValueFactory(0.0, 999.0, 0.0, 0.01);
thresholdSpinnerValueFactory.setConverter(new DoubleStringConverter());
@@ -284,22 +279,7 @@ public void initialize() {
}
});
- authenticatedUserId.textProperty().bind(authenticatedUserProperty);
- // Initialize userIsAuthenticated property
-
- try {
- SecureStore secureStore = new SecureStore();
- ScopedAuthenticationToken token =
- secureStore.getScopedAuthenticationToken(AuthenticationScope.SAVE_AND_RESTORE);
- if (token != null) {
- userIsAuthenticated.set(true);
- authenticatedUserProperty.set(token.getUsername());
- }
- } catch (Exception e) {
- Logger.getLogger(SnapshotControlsViewController.class.getName()).log(Level.WARNING, "Unable to retrieve authentication token for " +
- AuthenticationScope.SAVE_AND_RESTORE.getName() + " scope", e);
- }
}
public SimpleStringProperty getSnapshotNameProperty() {
@@ -365,18 +345,4 @@ public void setFilterToolbarDisabled(boolean disabled) {
public void setSnapshotRestorableProperty(boolean restorable) {
snapshotRestorableProperty.set(restorable);
}
-
- public void secureStoreChanged(List validTokens) {
-
- Optional token =
- validTokens.stream()
- .filter(t -> t.getAuthenticationScope().equals(AuthenticationScope.SAVE_AND_RESTORE)).findFirst();
- if (token.isPresent()) {
- userIsAuthenticated.set(true);
- authenticatedUserProperty.set(token.get().getUsername());
- } else {
- userIsAuthenticated.set(false);
- authenticatedUserProperty.set(Messages.authenticatedUserNone);
- }
- }
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTab.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTab.java
index a66841affd..34fdf88271 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTab.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTab.java
@@ -31,11 +31,9 @@
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService;
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreTab;
import org.phoebus.framework.nls.NLS;
-import org.phoebus.security.tokens.ScopedAuthenticationToken;
import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
import java.io.IOException;
-import java.util.List;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -49,8 +47,6 @@ public class SnapshotTab extends SaveAndRestoreTab {
public SaveAndRestoreService saveAndRestoreService;
- private SnapshotController snapshotController;
-
private final SimpleObjectProperty tabGraphicImageProperty = new SimpleObjectProperty<>();
@@ -86,7 +82,7 @@ public SnapshotTab(org.phoebus.applications.saveandrestore.model.Node node, Save
try {
setContent(loader.load());
- snapshotController = loader.getController();
+ controller = loader.getController();
} catch (IOException e) {
Logger.getLogger(SnapshotTab.class.getName())
.log(Level.SEVERE, "Failed to load fxml", e);
@@ -102,7 +98,7 @@ public SnapshotTab(org.phoebus.applications.saveandrestore.model.Node node, Save
setTabImage(node);
setOnCloseRequest(event -> {
- if (snapshotController != null && !snapshotController.handleSnapshotTabClosed()) {
+ if (controller != null && !((SnapshotController) controller).handleSnapshotTabClosed()) {
event.consume();
} else {
SaveAndRestoreService.getInstance().removeNodeChangeListener(this);
@@ -118,15 +114,11 @@ public void updateTabTitle(String name) {
}
private void setTabImage(Node node) {
- if (node.getNodeType().equals(NodeType.COMPOSITE_SNAPSHOT)) {
- tabGraphicImageProperty.set(ImageRepository.COMPOSITE_SNAPSHOT);
+ boolean golden = node.getTags() != null && node.getTags().stream().anyMatch(t -> t.getName().equals(Tag.GOLDEN));
+ if (golden) {
+ tabGraphicImageProperty.set(ImageRepository.GOLDEN_SNAPSHOT);
} else {
- boolean golden = node.getTags() != null && node.getTags().stream().anyMatch(t -> t.getName().equals(Tag.GOLDEN));
- if (golden) {
- tabGraphicImageProperty.set(ImageRepository.GOLDEN_SNAPSHOT);
- } else {
- tabGraphicImageProperty.set(ImageRepository.SNAPSHOT);
- }
+ tabGraphicImageProperty.set(ImageRepository.SNAPSHOT);
}
}
@@ -138,7 +130,7 @@ private void setTabImage(Node node) {
*/
public void newSnapshot(org.phoebus.applications.saveandrestore.model.Node configurationNode) {
setId(null);
- snapshotController.newSnapshot(configurationNode);
+ ((SnapshotController) controller).newSnapshot(configurationNode);
}
/**
@@ -149,32 +141,29 @@ public void newSnapshot(org.phoebus.applications.saveandrestore.model.Node confi
public void loadSnapshot(Node snapshotNode) {
updateTabTitle(snapshotNode.getName());
setId(snapshotNode.getUniqueId());
- snapshotController.loadSnapshot(snapshotNode);
+ ((SnapshotController) controller).loadSnapshot(snapshotNode);
}
public void addSnapshot(org.phoebus.applications.saveandrestore.model.Node node) {
- snapshotController.addSnapshot(node);
+ ((SnapshotController) controller).addSnapshot(node);
}
@Override
public void nodeChanged(Node node) {
if (node.getUniqueId().equals(getId())) {
Platform.runLater(() -> {
- snapshotController.setSnapshotNameProperty(node.getName());
+ ((SnapshotController) controller).setSnapshotNameProperty(node.getName());
setTabImage(node);
});
}
}
public Node getSnapshotNode() {
- return snapshotController.getSnapshot().getSnapshotNode();
+ return ((SnapshotController) controller).getSnapshot().getSnapshotNode();
}
public Node getConfigNode() {
- return snapshotController.getConfigurationNode();
+ return ((SnapshotController) controller).getConfigurationNode();
}
- public void secureStoreChanged(List validTokens){
- snapshotController.secureStoreChanged(validTokens);
- }
}
diff --git a/app/save-and-restore/app/src/main/resources/icons/sar-search_18x18.png b/app/save-and-restore/app/src/main/resources/icons/sar-search_18x18.png
new file mode 100644
index 0000000000000000000000000000000000000000..e6457b96d3811e84947eaee8cda0b31b719f63da
GIT binary patch
literal 7140
zcmV
zaB^>EX>4U6ba`-PAZ2)IW&i+q+O3*ra@@L_hW}#~vjpU@9K@=-gIWGOFW8b5Z26qJ
zBbB>lHX8)K@CJ}(|L=c}`5%6=58lL7YHm4OeqxKwcmAmM`Dypn*?3={|M=Hu?(=c;
z{(6VUR_{Pyrz33
z8s2=UufYh&r{%r9Tl+CUpMw1Mm-!m~_rJab-}l$?@FkY{6(j!m?FTM>IUe3G<3COgOG>&wajnfU*hj_e;dEmb~{P=
zveOQJr)$oWm1A6V%XN3$zVFjbrWpP9g?I0#5BFFN#Wz2L@)Y&!jxS_yg-Wuf--d+7
z;NR!s?seO}-gK2KFUP4YcyeL?=U?;FhyUi+oHG?7XX|^cn3wlY+?6@Kaux}3=asi~
z1N`>;O27S3Ka?sNOgGGx1$H~0Ax3h)YAf742hK~pKP}`;S+4;I5!X&k#sxB9Gu7|1
zv&H-39C559s55gPq90_yrQ{cr^f83&P4U(1+I*+Dd#+DDdmRRwNJI)b6mmmnrIuED4K>zOb1k*jR(ta;fPtyy
zR$6VX_3lmQLY=WX=XZ__Kf;J3jXcVzqm4c(pBZPGd6rpcn|=8e7BI2$Dyy!x`gUf6
z6g%#;^DevYw)>&hPB`(TlTSJIwA0_S_R8wF-+z%c_sW{TnbLXXJ!>2_rMwN{1Sd&3
zBV#@~GG3Gc3fe1Ywz?R-GN+u`CX`E_NhamSET@c-!nmDJ$Gzw7SLXgv-b||hC~y96
znKMe=|3~Hwsr#O{-(+ox=jP4W(}jwu4Rjx$@7j&8U8L6E-$%@oCRofUR@V!Y%ZDhX
zylUJhrA>NH@yrK)%q6)r$~j7Zt$C1U#^2$b<{GcH_e|N0J=8#Xa#&}umT+dS%!gU5
zqGL6tLyPFo#!Aj4JZFb^hqRUWq-XG=!9RT;;O5&SvEutx)?Cf=?Q*8~OPAo<;5#Wk
zM=UoBi_PI|=km(q>@xMm<9#Q5OVfO=Y0kYQv-)1&7}+*xZQSc(o5J{uQp*cmFo;_AS^c`jerT5qGOGl;WstdTnnTiVZWzx~EFe
z+LZgP8*6znZ-4yt9oVr2I@@cUL7aL#O@+MfRI({!gf`EpCn{38AC_qyfrM1=a{6gm
z?~5r6e}hY1-%n~In9=HQvu7XO>tg%t391)z8Hn^+p3*FNtx>0fLW|vtEn1Js996>^
zT$*!xFwzApcbn6iGmRBDp@dxfZE&V*VdkDDF@ZDUsbn^!*>`a>)8=Q8(1p-C%l8(&
z`q=rMTNuIj<$w@){S=Y~08Gxmi&^DaE_lPYS@A5dl@@Nw!=(L&maM)m{Zs@oZQ^r34s%qH!asq88o1+#pFg=n+Yx{t?8&lx&i`};wgh7
z-&J$p+YieEkG(m|V{OCo%jyTD8jh0&xWrG~VBATB_Jl>r_w$sVW8ChA5l`NR?>lvB
z?3m%#jv&|=rL;ul!g=`)Y+X$marfUc&I>C%aie#dO@kgi5KBi
zJExUU0YuU*l)BSJnt9@bIH%ta2P(3qTnNZKj&5+Bl=^uwdlhQ4zatw
zxE4xuC?vhnsQ?wPP|T*L=%(H=GP`%k=xmbdY53L41XMQ7y;`A}+8XyIGRBEXV!02*5}p=Th*OI{8y&ITute;?Wk*0=suM~Y0f=U
zrJmPn=`ahp7bvR0qGt-A>CV27wIW(P&(&j_yMWnf7#gv5rjY9*<83umY0$O7f=Hdy
zi#P|Ue692}U!nNWF21Py=(S|DnAzGOrd^nHe3}z$uuvfhM4K)xI2!4JLU@`l(R^dC
z818o#kT6Ozg$$GYv|UZ`lbw51D$vJV)Q&d)1m%FD?JuUCl=9`QC!}!PaNx?BTmUH~
zw%YaqFB(DaJAuKy4yrGq&cMW7e6Bdi6R);{*ka}ic%Vv?@#EDMoA`UmY^Ow9q&!L9
zgCq<}T_LV!tK*nm8WCHN`5?7S9n>8QFE67aYGy#CjwH
zozB#;+G%q-jmDG?JXINvUZ9@QkV^@|89mptklD$(GENZ<9mFRqL;S8v+irq47lL>x
z=#m2Dml$yiFYR0
z+lLjlmD=vGfvrG?qRzq!5WiQNA>nkYfV1}AYj?Iq4=b2sxUIAH3?
z8hq_Lv5iBJVE+ShJ0MJoT8;7mV^&Zp#z7M`IFl&LQzf-~*IXkAy+x2+jeq*WF(;5VEQT|?`%PEuGLSsZC8
z0XW(uML8zFF39bVZWY2^7K4*u<>U0%~
z4XFwi#yw-s6O{$tD?KQU(2&ZQzIvGN)PmD&2-YAW5FVFw`=2C4v(%&XlUtJvBrFWr
zXaNxV5wQ=+>__U=cqt%FO)cOfA?Mm_q9o8P!=O^Yvz~}E7>i1rZTlu83Qlaq5FXS@
zO>en_(;(^_!H5*wxS5UKS5^$R6*WLh?zfg~=_k!dU2m5kr__XGFS0UXE6NueUN0MG
zYU--O+ngItiPj+`l8*(MsU4Llb4Th3;~eK&E!$0pS2tmglVpItM7lD$Ey+^g!cvRq
zc25+YmjP)3zySUt6~60rz$l|*p0>2NEph#b$O)z)tm!2z9*q@9Z-LD>asXn1)#dy#
zDuh`Jjg)3a`GbUDH7b?DgZU^2ELp0eNYD3W7>AcaWl;KiOyosjIHbwF53yV@X1t!g_>9X
zHX}fXg78;KcQksj%&k|%hCmrfMjII}LhX@)4i6L9?Wh2BAICuj;HFNjNkQ8VP@!xu
z&EGtgQoW1`MSObev>-(7TUint*AdVM~e70
zTO`s`lU;r7c9aTJ;83i#TmqoTRQP7oh_@7}J(?Ng_4IzXts5i;)RZ1=huBlW(C0={!(XcCtD?a}#Dq*ILmXVb%D9oJ)lHJYMY)411+WkUW3nRN7E#;qZgGk_
zq85Y3#(e_E>R=k;j|ohO;G{Ax4ixN!1A4}SO*ysR%29@|3CJ0WcT!rR1Kv6c7pQ7q-bes
z8hB~t*&0Hc0*=HJ^9qhI1ESl*C7jX!F1j#a$
z)n)n}VKyg{fbP#233wPrz|ICdQrGVDvrpT$kaIsU(?`XZZs?S#B4m*(5@xtoI?6}0
zDW47S-u)W{h5PG1&q5s`m8#$TdXyekWDPtmO-Fb4T{Y6%V_()wc8`V
z*R($yu0@4B)@HrwxU4%h%&~hbJD362g4^ZoUy~pF(rgl37jG)Xig6@1js22kfDg%p&WGJuzf}@1WnD{DC
zktziM2VI=CCxKFg%_WwW1=zBxs5DTt4tX
zeA;v(Yv@oM!wpJK
`|WOeW+d_02Ms%fC|>zfCCAYUjwpwQ(q$@UVpO1uVR)hekZq=EvDeGZ^jjARu~g^
zIX#LMVcw<%5RluB(i99LJm~;*>vQWMWGsfZJkm9W+SHyVQD3E!gE_Qyyp<8%+mq95
zqs(E^X#^1j0|u-cci)OaMR1q!fVK+W+|8+HYf5wU5U1w{+Y_gCRT~xHMXv$L3WLx{
zu|qCWorsf}V4o^6X~+*n+qCiT@HzT^2TAj@XF|6>-QOrqInz+@u7~tY!tEAU=<>Ga
zHj;s{w~*0j$W3)RgEdx4=c?Iv#H&&0VFl)^n`Hp{(#9*Y2txPIps3oJu58euhQAG1
z0Br|TyRK^)v}qZ&Ko)u`W$EU>d)?HyyCu)9DDw>1{fyCRUo+6xc_1T60JJ+POKrPB
zuD6Yr;0bNPE5IW?HghXijJAY2?iGRleVb}J-7>3pf0m-qzW1Z7(LZ<;*u1lR)g|Ds
zjYzA3k%p=64#zBQc)-&Uc1$hmZUS1TvF6+vOdIN0s77mzLP0Hni^|5&gh=^p3i96g
zL)NB9q9KpFI~I4F7e^@yI0JXoNR3q=V7$ck&LzS+619GuVoes{DLyrMZmI}|Sd
zb0+#LXwQps)JCpu#1jLwc28lTA!WrtXtS(N0J33h-u%=;GRijmVwt@Lq7Fg(c9Xv7)in_
z-n+WPP_PJsl7}9IA(zd|bb96hMj|^-&|Lv7JyqaQ-W+jJ>0SEFg71SIth=G-sgv4kuANJqr0Q~irIY0HJ=idiFm
zLxm}t#O!tpH^K-+%<10extHY{-lJ;0Us?cx>Kl_yeWmFgl(v(jH?Y>=OAfd@xiwl~
zoab)y5>yTBg}QziT)XlRq`3fQdSns;SbhDOu%8{o8GxFDa$l
z70Gr`54oiM)Y_@REK$grdfIPPk0&f##jFC*C2jc%vo@;z8LY@+>ZTJokdfD=eQQ~(vww5O_vFQ#@Y`?FigjLML!(KZckQP_E1jvh(^?~bi<
z!d@Bg;%OQt-9D}bp$zu{=ut?CU8gbRN>h>}E!v-!BI?gFkkkXX9lBw*^yE_UC=%WC
z@eTjN;SLQJKbKi3a37%E?CA)#olwHjAJAgjS|8N!seHVP`DM8cMZ24eTxigrmR&~5*Ewg!o(n?BY(dwAGf
z3cuw)Z{U1?H~;L!(NPQq_dtd=IzV?4o1PYoesg7{q7e+}$t`)?+kCcYtxci@!KZx*@KPA{{*PWXl>A7mpBya!AFhB3+^R9><{Xu_s
z{F1i5cCoc~?IEZw?0rI{t7jfHfGHU1*|IuDcUx<|%sO6%>h9cWUqz4M*>#^V@%p}>
zQvMDxfRYc7zT^Zkb~Ad|#M`^0T=sbO*ciO3yjc9C3Ny4`b#17>JYX~OuWL8$RI5eK
zz4RUX{o>j@&<0xGSzDQNp!YWbeq@vVqPIf@d%!(T){PHq$N|0{zx98YlS+`)d7DuH
z00D(*LqkwWLqi~Na&Km7Y-Iodc$|HaJxIeq7>3`b)vB~O*g?c0Lv^wsD&nYBC_;r$
zE41oha_Jv5X-HaJ90k{cgFlN^2N!2u9b5%L@CU@z#YxdcO8hPpIPVijSW#Aq&xyxOx*+i**A*r{;+x#A2a?l@4Y_
zQzM=tj;fkY`9j8JmGc&7tz2WRd-4~Ca{9_L*J%wSfkiAqf(QjQlu$+$G1_%fETrf>
z?%^MD{1Ukoa+Scyv49FR$c`WU4}SO7%1=(XNufB<^)|5Tqat9cEGGtSBBtI=7p9kL0=$o>@z%9_X=Ju_1
zj?)JqO}$Fq00)P_Xo0fVJ>K2Z*|&ddTK)S0;&5_pE#i1*00006VoOIv0RI600RN!9
zr;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru=LroB1RkzbPyzq|0!2wgK~y-)
zt(4D8R8bViKlje_J;_l~bTFd<1violb>}8GE=7V#h++`6Xyw1+GEz#JO&F4vw3^LC
zi#ClxS|ozTgrn$aF>1`b-o2-V*$;gNh2PaZn-Axn@An)yC#m0yv9U4s_V!doMMWIw
z11#Per13(8D~%DNE*$u(a_aGM9$-8o4(N`4Tu0O1pvi6-
z=2ru07Yfo^3ot6{nN^#84CRAg-&6|9huGIM$odO_-O0(x
zvy+`nCed2gE2ZuuHW8BSC&0EJn7jEehf_^;Rd*hxQbFjhB@zid&%^V)xQP7RHKkM<
z$UU4}<7!X+R4Fh+o)byMW7R{S_e=78cIGJA$C{m;J$~kZ2VtYCi{7|h*e^Ndz$8Cy
z@9WF4>LehMe?1o;-Ywg%&dzy1Fl}XHZwBAYo3)kqEBb$K&sRT?`YPvEl~-}Cf%sF-
aQ{MsqX7Yc{0*+_^0000
Date: Wed, 13 Sep 2023 18:52:50 +0200
Subject: [PATCH 19/42] Save&restore: move information on authenticated user
---
.../saveandrestore/ui/BrowserTreeCell.java | 84 ++++++++++++-------
.../ui/SaveAndRestoreController.java | 1 +
2 files changed, 57 insertions(+), 28 deletions(-)
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java
index 06961b7629..23b15374c0 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java
@@ -157,7 +157,7 @@ public void updateItem(Node node, boolean empty) {
return;
}
// Use custom layout as this makes it easier to set opacity
- HBox hBox = new HBox();
+ CellGraphic hBox = new CellGraphic(node);
// saveAndRestoreController is null if configuration management is accessed from context menu
if (saveAndRestoreController != null && !saveAndRestoreController.matchesFilter(node)) {
hBox.setOpacity(0.4);
@@ -174,48 +174,76 @@ public void updateItem(Node node, boolean empty) {
setTooltip(new Tooltip(stringBuilder.toString()));
switch (node.getNodeType()) {
case SNAPSHOT:
- if (node.hasTag(Tag.GOLDEN)) {
- hBox.getChildren().add(new ImageView(ImageRepository.GOLDEN_SNAPSHOT));
- } else {
- hBox.getChildren().add(new ImageView(ImageRepository.SNAPSHOT));
- }
- hBox.getChildren().add(new Label(node.getName()));
- if (node.getTags() != null && !node.getTags().isEmpty()) {
- ImageView tagImage = new ImageView(ImageCache.getImage(BrowserTreeCell.class, "/icons/save-and-restore/snapshot-tags.png"));
- tagImage.setFitHeight(13);
- tagImage.setPreserveRatio(true);
- hBox.getChildren().add(tagImage);
- }
setContextMenu(snapshotContextMenu);
break;
case COMPOSITE_SNAPSHOT:
- hBox.getChildren().add(new ImageView(ImageRepository.COMPOSITE_SNAPSHOT));
- hBox.getChildren().add(new Label(node.getName()));
- if (node.getTags() != null && !node.getTags().isEmpty()) {
- ImageView tagImage = new ImageView(ImageCache.getImage(BrowserTreeCell.class, "/icons/save-and-restore/snapshot-tags.png"));
- tagImage.setFitHeight(13);
- tagImage.setPreserveRatio(true);
- hBox.getChildren().add(tagImage);
- }
setContextMenu(compositeSnapshotContextMenu);
break;
case CONFIGURATION:
- hBox.getChildren().add(new ImageView(ImageRepository.CONFIGURATION));
- hBox.getChildren().add(new Label(node.getName()));
setContextMenu(configurationContextMenu);
break;
- case FOLDER:
- String name = node.getName();
+ case FOLDER: ;
if (node.getUniqueId().equals(Node.ROOT_FOLDER_UNIQUE_ID)) {
+ setTooltip(new Tooltip(SaveAndRestoreService.getInstance().getServiceIdentifier()));
setContextMenu(rootFolderContextMenu);
- name += " (" + SaveAndRestoreService.getInstance().getServiceIdentifier() + ")";
} else {
setContextMenu(folderContextMenu);
}
- hBox.getChildren().add(new ImageView(ImageRepository.FOLDER));
- hBox.getChildren().add(new Label(name));
break;
}
setGraphic(hBox);
}
+
+ public class CellGraphic extends HBox{
+ private String text;
+ private Label nameLabel;
+ public CellGraphic(Node node){
+ nameLabel = new Label(node.getName());
+ switch (node.getNodeType()) {
+ case SNAPSHOT:
+ if (node.hasTag(Tag.GOLDEN)) {
+ getChildren().add(new ImageView(ImageRepository.GOLDEN_SNAPSHOT));
+ } else {
+ getChildren().add(new ImageView(ImageRepository.SNAPSHOT));
+ }
+ getChildren().add(new Label(node.getName()));
+ if (node.getTags() != null && !node.getTags().isEmpty()) {
+ ImageView tagImage = new ImageView(ImageCache.getImage(BrowserTreeCell.class, "/icons/save-and-restore/snapshot-tags.png"));
+ tagImage.setFitHeight(13);
+ tagImage.setPreserveRatio(true);
+ getChildren().add(tagImage);
+ }
+ break;
+ case COMPOSITE_SNAPSHOT:
+ getChildren().add(new ImageView(ImageRepository.COMPOSITE_SNAPSHOT));
+ getChildren().add(nameLabel);
+ if (node.getTags() != null && !node.getTags().isEmpty()) {
+ ImageView tagImage = new ImageView(ImageCache.getImage(BrowserTreeCell.class, "/icons/save-and-restore/snapshot-tags.png"));
+ tagImage.setFitHeight(13);
+ tagImage.setPreserveRatio(true);
+ getChildren().add(tagImage);
+ }
+ break;
+ case CONFIGURATION:
+ getChildren().add(new ImageView(ImageRepository.CONFIGURATION));
+ getChildren().add(nameLabel);
+ break;
+ case FOLDER:
+ String name = node.getName();
+ if (node.getUniqueId().equals(Node.ROOT_FOLDER_UNIQUE_ID) && saveAndRestoreController.userIdentity.isNotNull().get()){
+ name += " (" + saveAndRestoreController.userIdentity.get() + ")";
+ }
+ setText(name);
+ getChildren().add(new ImageView(ImageRepository.FOLDER));
+ getChildren().add(nameLabel);
+ break;
+ }
+ }
+
+ public void setText(String text){
+ if(text != null){
+ nameLabel.textProperty().set(text);
+ }
+ }
+ }
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
index 7eaee67ced..416ce5ec50 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
@@ -1440,6 +1440,7 @@ public boolean compareSnapshotsPossible() {
@Override
public void secureStoreChanged(List validTokens) {
super.secureStoreChanged(validTokens);
+ ((BrowserTreeCell.CellGraphic)treeView.getRoot().getGraphic()).setText(userIdentity.get());
tabPane.getTabs().forEach(t -> {
((SaveAndRestoreTab) t).secureStoreChanged(validTokens);
});
From 1e504f2702d50f14c3062e872033a945a9b8cfbf Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Thu, 14 Sep 2023 15:15:36 +0200
Subject: [PATCH 20/42] Save&restore: clean-up in cell renderer and context
menus
---
.../saveandrestore/ui/BrowserTreeCell.java | 149 +++++++-----------
.../saveandrestore/ui/ContextMenuBase.java | 5 +-
.../saveandrestore/ui/ContextMenuFolder.java | 34 ++--
.../ui/SaveAndRestoreController.java | 37 +----
4 files changed, 91 insertions(+), 134 deletions(-)
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java
index 23b15374c0..23eb555205 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java
@@ -18,21 +18,15 @@
package org.phoebus.applications.saveandrestore.ui;
-import javafx.scene.control.ContextMenu;
-import javafx.scene.control.Label;
-import javafx.scene.control.Tooltip;
-import javafx.scene.control.TreeCell;
-import javafx.scene.control.TreeItem;
+import javafx.application.Platform;
+import javafx.scene.control.*;
import javafx.scene.image.ImageView;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
-import javafx.scene.layout.Border;
-import javafx.scene.layout.BorderStroke;
-import javafx.scene.layout.BorderStrokeStyle;
-import javafx.scene.layout.CornerRadii;
-import javafx.scene.layout.HBox;
+import javafx.scene.layout.*;
import javafx.scene.paint.Color;
+import org.phoebus.applications.saveandrestore.Messages;
import org.phoebus.applications.saveandrestore.SaveAndRestoreApplication;
import org.phoebus.applications.saveandrestore.model.Node;
import org.phoebus.applications.saveandrestore.model.NodeType;
@@ -49,29 +43,16 @@
*/
public class BrowserTreeCell extends TreeCell {
- private final ContextMenu folderContextMenu;
- private final ContextMenu configurationContextMenu;
- private final ContextMenu snapshotContextMenu;
- private final ContextMenu rootFolderContextMenu;
- private final ContextMenu compositeSnapshotContextMenu;
private final SaveAndRestoreController saveAndRestoreController;
private static final Border BORDER = new Border(new BorderStroke(Color.GREEN, BorderStrokeStyle.SOLID,
new CornerRadii(5.0), BorderStroke.THIN));
public BrowserTreeCell() {
- this(null, null, null, null, null, null);
+ this(null);
}
- public BrowserTreeCell(ContextMenu folderContextMenu, ContextMenu configurationContextMenu,
- ContextMenu snapshotContextMenu, ContextMenu rootFolderContextMenu,
- ContextMenu compositeSnapshotContextMenu,
- SaveAndRestoreController saveAndRestoreController) {
- this.folderContextMenu = folderContextMenu;
- this.configurationContextMenu = configurationContextMenu;
- this.snapshotContextMenu = snapshotContextMenu;
- this.rootFolderContextMenu = rootFolderContextMenu;
- this.compositeSnapshotContextMenu = compositeSnapshotContextMenu;
+ public BrowserTreeCell(SaveAndRestoreController saveAndRestoreController) {
this.saveAndRestoreController = saveAndRestoreController;
// This is need in order to suppress the context menu when right-clicking in a portion of the
@@ -110,9 +91,9 @@ public BrowserTreeCell(ContextMenu folderContextMenu, ContextMenu configurationC
setOnDragOver(event ->
{
final Node node = getItem();
- if(node != null){
+ if (node != null) {
List sourceNodes = (List) event.getDragboard().getContent(SaveAndRestoreApplication.NODE_SELECTION_FORMAT);
- if(DragNDropUtil.mayDrop(event.getTransferMode(), node, sourceNodes)){
+ if (DragNDropUtil.mayDrop(event.getTransferMode(), node, sourceNodes)) {
event.acceptTransferModes(event.getTransferMode());
setBorder(BORDER);
}
@@ -151,14 +132,15 @@ public BrowserTreeCell(ContextMenu folderContextMenu, ContextMenu configurationC
public void updateItem(Node node, boolean empty) {
super.updateItem(node, empty);
if (empty) {
+ setText(null);
setGraphic(null);
setTooltip(null);
getStyleClass().remove("filter-match");
return;
}
// Use custom layout as this makes it easier to set opacity
- CellGraphic hBox = new CellGraphic(node);
- // saveAndRestoreController is null if configuration management is accessed from context menu
+ HBox hBox = new HBox();
+ // saveAndRestoreController is null if configuration management is from OPI or channel table
if (saveAndRestoreController != null && !saveAndRestoreController.matchesFilter(node)) {
hBox.setOpacity(0.4);
}
@@ -174,76 +156,65 @@ public void updateItem(Node node, boolean empty) {
setTooltip(new Tooltip(stringBuilder.toString()));
switch (node.getNodeType()) {
case SNAPSHOT:
- setContextMenu(snapshotContextMenu);
+ if (node.hasTag(Tag.GOLDEN)) {
+ hBox.getChildren().add(new ImageView(ImageRepository.GOLDEN_SNAPSHOT));
+ } else {
+ hBox.getChildren().add(new ImageView(ImageRepository.SNAPSHOT));
+ }
+ hBox.getChildren().add(new Label(node.getName()));
+ if (node.getTags() != null && !node.getTags().isEmpty()) {
+ ImageView tagImage = new ImageView(ImageCache.getImage(BrowserTreeCell.class, "/icons/save-and-restore/snapshot-tags.png"));
+ tagImage.setFitHeight(13);
+ tagImage.setPreserveRatio(true);
+ hBox.getChildren().add(tagImage);
+ }
+ if(saveAndRestoreController != null){
+ setContextMenu(new ContextMenuSnapshot(saveAndRestoreController));
+ }
break;
case COMPOSITE_SNAPSHOT:
- setContextMenu(compositeSnapshotContextMenu);
+ hBox.getChildren().add(new ImageView(ImageRepository.COMPOSITE_SNAPSHOT));
+ hBox.getChildren().add(new Label(node.getName()));
+ if (node.getTags() != null && !node.getTags().isEmpty()) {
+ ImageView tagImage = new ImageView(ImageCache.getImage(BrowserTreeCell.class, "/icons/save-and-restore/snapshot-tags.png"));
+ tagImage.setFitHeight(13);
+ tagImage.setPreserveRatio(true);
+ getChildren().add(tagImage);
+ }
+ if(saveAndRestoreController != null){
+ setContextMenu(new ContextMenuCompositeSnapshot(saveAndRestoreController));
+ }
break;
case CONFIGURATION:
- setContextMenu(configurationContextMenu);
+ hBox.getChildren().add(new ImageView(ImageRepository.CONFIGURATION));
+ hBox.getChildren().add(new Label(node.getName()));
+ if(saveAndRestoreController != null){
+ setContextMenu(new ContextMenuConfiguration(saveAndRestoreController));
+ }
break;
- case FOLDER: ;
+ case FOLDER:
+ hBox.getChildren().add(new ImageView(ImageRepository.FOLDER));
+ hBox.getChildren().add(new Label(node.getName()));
if (node.getUniqueId().equals(Node.ROOT_FOLDER_UNIQUE_ID)) {
setTooltip(new Tooltip(SaveAndRestoreService.getInstance().getServiceIdentifier()));
- setContextMenu(rootFolderContextMenu);
- } else {
- setContextMenu(folderContextMenu);
+ ContextMenu rootFolderContextMenu = new ContextMenu();
+ MenuItem newRootFolderMenuItem = new MenuItem(Messages.contextMenuNewFolder, new ImageView(ImageRepository.FOLDER));
+ newRootFolderMenuItem.setOnAction(ae -> saveAndRestoreController.createNewFolder());
+ rootFolderContextMenu.getItems().add(newRootFolderMenuItem);
+ rootFolderContextMenu.setOnShowing(event -> {
+ if (saveAndRestoreController.getUserIdentity().isNull().get()) {
+ Platform.runLater(() -> rootFolderContextMenu.hide());
+ }
+ });
+ if(saveAndRestoreController != null){
+ setContextMenu(rootFolderContextMenu);
+ }
+
+ } else if (saveAndRestoreController != null){
+ setContextMenu(new ContextMenuFolder(saveAndRestoreController));
}
break;
}
setGraphic(hBox);
}
-
- public class CellGraphic extends HBox{
- private String text;
- private Label nameLabel;
- public CellGraphic(Node node){
- nameLabel = new Label(node.getName());
- switch (node.getNodeType()) {
- case SNAPSHOT:
- if (node.hasTag(Tag.GOLDEN)) {
- getChildren().add(new ImageView(ImageRepository.GOLDEN_SNAPSHOT));
- } else {
- getChildren().add(new ImageView(ImageRepository.SNAPSHOT));
- }
- getChildren().add(new Label(node.getName()));
- if (node.getTags() != null && !node.getTags().isEmpty()) {
- ImageView tagImage = new ImageView(ImageCache.getImage(BrowserTreeCell.class, "/icons/save-and-restore/snapshot-tags.png"));
- tagImage.setFitHeight(13);
- tagImage.setPreserveRatio(true);
- getChildren().add(tagImage);
- }
- break;
- case COMPOSITE_SNAPSHOT:
- getChildren().add(new ImageView(ImageRepository.COMPOSITE_SNAPSHOT));
- getChildren().add(nameLabel);
- if (node.getTags() != null && !node.getTags().isEmpty()) {
- ImageView tagImage = new ImageView(ImageCache.getImage(BrowserTreeCell.class, "/icons/save-and-restore/snapshot-tags.png"));
- tagImage.setFitHeight(13);
- tagImage.setPreserveRatio(true);
- getChildren().add(tagImage);
- }
- break;
- case CONFIGURATION:
- getChildren().add(new ImageView(ImageRepository.CONFIGURATION));
- getChildren().add(nameLabel);
- break;
- case FOLDER:
- String name = node.getName();
- if (node.getUniqueId().equals(Node.ROOT_FOLDER_UNIQUE_ID) && saveAndRestoreController.userIdentity.isNotNull().get()){
- name += " (" + saveAndRestoreController.userIdentity.get() + ")";
- }
- setText(name);
- getChildren().add(new ImageView(ImageRepository.FOLDER));
- getChildren().add(nameLabel);
- break;
- }
- }
-
- public void setText(String text){
- if(text != null){
- nameLabel.textProperty().set(text);
- }
- }
- }
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java
index 974ef4d1e3..e904215ee2 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java
@@ -84,7 +84,10 @@ public ContextMenuBase(SaveAndRestoreController saveAndRestoreController) {
copyUniqueIdToClipboardMenuItem.setOnAction(ae -> saveAndRestoreController.copyUniqueNodeIdToClipboard());
copyUniqueIdToClipboardMenuItem.disableProperty().bind(multipleNodesSelectedProperty);
- setOnShowing(event -> runChecks());
+ // Controller may be null, e.g. when adding PVs from channel table
+ if(saveAndRestoreController != null){
+ setOnShowing(event -> runChecks());
+ }
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuFolder.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuFolder.java
index c495d4c070..4b42dff695 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuFolder.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuFolder.java
@@ -18,7 +18,7 @@
package org.phoebus.applications.saveandrestore.ui;
-import javafx.application.Platform;
+import javafx.beans.binding.Bindings;
import javafx.scene.control.MenuItem;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
@@ -40,18 +40,26 @@ public ContextMenuFolder(SaveAndRestoreController saveAndRestoreController) {
MenuItem renameNodeMenuItem = new MenuItem(Messages.contextMenuRename, new ImageView(renameIcon));
renameNodeMenuItem.setOnAction(ae -> saveAndRestoreController.renameNode());
- renameNodeMenuItem.disableProperty().bind(multipleNodesSelectedProperty);
+ renameNodeMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
+ multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(),
+ multipleNodesSelectedProperty, userIsAuthenticatedProperty));
MenuItem newFolderMenuItem = new MenuItem(Messages.contextMenuNewFolder, new ImageView(ImageRepository.FOLDER));
- newFolderMenuItem.disableProperty().bind(multipleNodesSelectedProperty);
+ newFolderMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
+ multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(),
+ multipleNodesSelectedProperty, userIsAuthenticatedProperty));
newFolderMenuItem.setOnAction(ae -> saveAndRestoreController.createNewFolder());
MenuItem newConfigurationMenuItem = new MenuItem(Messages.contextMenuNewConfiguration, new ImageView(ImageRepository.CONFIGURATION));
- newConfigurationMenuItem.disableProperty().bind(multipleNodesSelectedProperty);
+ newConfigurationMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
+ multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(),
+ multipleNodesSelectedProperty, userIsAuthenticatedProperty));
newConfigurationMenuItem.setOnAction(ae -> saveAndRestoreController.createNewConfiguration());
MenuItem newCompositeSnapshotMenuItem = new MenuItem(Messages.contextMenuNewCompositeSnapshot, new ImageView(ImageRepository.COMPOSITE_SNAPSHOT));
- newCompositeSnapshotMenuItem.disableProperty().bind(multipleNodesSelectedProperty);
+ newCompositeSnapshotMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
+ multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(),
+ multipleNodesSelectedProperty, userIsAuthenticatedProperty));
newCompositeSnapshotMenuItem.setOnAction(ae -> saveAndRestoreController.createNewCompositeSnapshot());
ImageView importConfigurationIconImageView = new ImageView(csvImportIcon);
@@ -59,13 +67,17 @@ public ContextMenuFolder(SaveAndRestoreController saveAndRestoreController) {
importConfigurationIconImageView.setFitHeight(18);
MenuItem importConfigurationMenuItem = new MenuItem(Messages.importConfigurationLabel, importConfigurationIconImageView);
- importConfigurationMenuItem.disableProperty().bind(multipleNodesSelectedProperty);
+ importConfigurationMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
+ multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(),
+ multipleNodesSelectedProperty, userIsAuthenticatedProperty));
importConfigurationMenuItem.setOnAction(ae -> saveAndRestoreController.importConfiguration());
Image pasteIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/paste.png");
MenuItem pasteMenuItem = new MenuItem(Messages.paste, new ImageView(pasteIcon));
pasteMenuItem.setOnAction(ae -> saveAndRestoreController.pasteFromClipboard());
- pasteMenuItem.disableProperty().bind(mayPasteProperty.not());
+ pasteMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
+ mayPasteProperty.not().get() || userIsAuthenticatedProperty.not().get(),
+ mayPasteProperty, userIsAuthenticatedProperty));
getItems().addAll(newFolderMenuItem,
renameNodeMenuItem,
@@ -85,12 +97,6 @@ public ContextMenuFolder(SaveAndRestoreController saveAndRestoreController) {
@Override
protected void runChecks() {
super.runChecks();
- // If user is not authenticated then all menu items are non-functional,
- // disable menu completely instead of showing list of disabled menu items.
- if (userIsAuthenticatedProperty.not().get()) {
- Platform.runLater(this::hide);
- } else {
- mayPasteProperty.set(saveAndRestoreController.mayPaste());
- }
+ mayPasteProperty.set(saveAndRestoreController.mayPaste());
}
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
index 416ce5ec50..67ce603901 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
@@ -57,8 +57,6 @@
import org.phoebus.applications.saveandrestore.ui.snapshot.tag.TagUtil;
import org.phoebus.framework.jobs.JobManager;
import org.phoebus.framework.preferences.PhoebusPreferenceService;
-import org.phoebus.security.store.SecureStore;
-import org.phoebus.security.tokens.AuthenticationScope;
import org.phoebus.security.tokens.ScopedAuthenticationToken;
import org.phoebus.ui.dialog.DialogHelper;
import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
@@ -107,12 +105,6 @@ public class SaveAndRestoreController extends SaveAndRestoreBaseController
private final ObjectMapper objectMapper = new ObjectMapper();
- protected ContextMenu folderContextMenu;
- protected ContextMenu configurationContextMenu;
- protected ContextMenu snapshotContextMenu;
- protected ContextMenu rootFolderContextMenu;
- protected ContextMenu compositeSnapshotContextMenu;
-
protected MultipleSelectionModel> browserSelectionModel;
private static final String TREE_STATE = "tree_state";
@@ -168,23 +160,6 @@ public void initialize(URL url, ResourceBundle resourceBundle) {
filtersComboBox.disableProperty().bind(filterEnabledProperty.not());
filterEnabledProperty.addListener((observable, oldValue, newValue) -> filterEnabledChanged(newValue));
- folderContextMenu = new ContextMenuFolder(this);
- configurationContextMenu = new ContextMenuConfiguration(this);
-
- rootFolderContextMenu = new ContextMenu();
- MenuItem newRootFolderMenuItem = new MenuItem(Messages.contextMenuNewFolder, new ImageView(ImageRepository.FOLDER));
- newRootFolderMenuItem.setOnAction(ae -> createNewFolder());
- rootFolderContextMenu.getItems().add(newRootFolderMenuItem);
- rootFolderContextMenu.setOnShowing(event -> {
- if (userIdentity.isNull().get()) {
- Platform.runLater(() -> rootFolderContextMenu.hide());
- }
- });
-
- snapshotContextMenu = new ContextMenuSnapshot(this);
-
- compositeSnapshotContextMenu = new ContextMenuCompositeSnapshot(this);
-
treeView.setEditable(true);
treeView.setOnMouseClicked(me -> {
@@ -203,9 +178,7 @@ public void initialize(URL url, ResourceBundle resourceBundle) {
saveAndRestoreService.addNodeAddedListener(this);
saveAndRestoreService.addFilterChangeListener(this);
- treeView.setCellFactory(p -> new BrowserTreeCell(folderContextMenu,
- configurationContextMenu, snapshotContextMenu, rootFolderContextMenu, compositeSnapshotContextMenu,
- this));
+ treeView.setCellFactory(p -> new BrowserTreeCell(this));
progressIndicator.visibleProperty().bind(disabledUi);
disabledUi.addListener((observable, oldValue, newValue) -> treeView.setDisable(newValue));
@@ -260,6 +233,12 @@ public Filter fromString(String s) {
// considered in paste actions.
Clipboard.getSystemClipboard().clear();
+ userIdentity.addListener((a, b, c) -> {
+ String name = c == null ? "Root folder" :
+ "Root folder (" + userIdentity.get() + ")";
+ treeView.getRoot().setValue(Node.builder().uniqueId(Node.ROOT_FOLDER_UNIQUE_ID).name(name).build());
+ });
+
loadTreeData();
}
@@ -1389,7 +1368,6 @@ public boolean hasSameParent() {
for (int i = 1; i < selectedItems.size(); i++) {
TreeItem treeItem = selectedItems.get(i);
if (!treeItem.getParent().getValue().getUniqueId().equals(parentNodeOfFirst.getUniqueId())) {
- System.out.println(parentNodeOfFirst.getUniqueId() + " " + treeItem.getParent().getValue().getUniqueId());
return false;
}
}
@@ -1440,7 +1418,6 @@ public boolean compareSnapshotsPossible() {
@Override
public void secureStoreChanged(List validTokens) {
super.secureStoreChanged(validTokens);
- ((BrowserTreeCell.CellGraphic)treeView.getRoot().getGraphic()).setText(userIdentity.get());
tabPane.getTabs().forEach(t -> {
((SaveAndRestoreTab) t).secureStoreChanged(validTokens);
});
From b706e0c2c3e8ee51b888ea524fd1d0683a7ee148 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Thu, 14 Sep 2023 15:24:54 +0200
Subject: [PATCH 21/42] Removed unused UI elements
---
.../applications/saveandrestore/Messages.java | 1 -
.../ConfigurationController.java | 5 ----
.../SnapshotControlsViewController.java | 13 +++------
.../saveandrestore/messages.properties | 2 --
.../ui/configuration/ConfigurationEditor.fxml | 24 ---------------
.../ui/snapshot/SnapshotControlsView.fxml | 29 ++-----------------
6 files changed, 7 insertions(+), 67 deletions(-)
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java
index 51ad6f62be..9a3411ba7e 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java
@@ -24,7 +24,6 @@ public class Messages {
public static String alertContinue;
public static String alertAddingPVsToConfiguration;
public static String authenticationFailed;
- public static String authenticatedUserNone;
public static String baseSetpoint;
public static String buttonSearch;
public static String cannotCompareHeader;
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java
index 1e8f82109c..11587af979 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java
@@ -103,8 +103,6 @@ public class ConfigurationController extends SaveAndRestoreBaseController implem
private Label configurationLastModifiedDateField;
@FXML
private Label createdByField;
- @FXML
- private Label authenticatedUserId;
@FXML
private Pane addPVsPane;
@@ -134,8 +132,6 @@ public ConfigurationController(ConfigurationTab configurationTab) {
this.configurationTab = configurationTab;
}
- private final SimpleStringProperty authenticatedUserProperty = new SimpleStringProperty(Messages.authenticatedUserNone);
-
@FXML
public void initialize() {
@@ -247,7 +243,6 @@ public void updateItem(String item, boolean empty) {
}
});
- authenticatedUserId.textProperty().bind(authenticatedUserProperty);
addPVsPane.disableProperty().bind(userIdentity.isNull());
SaveAndRestoreService.getInstance().addNodeChangeListener(this);
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsViewController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsViewController.java
index f812dfb416..b0da33a7d9 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsViewController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsViewController.java
@@ -40,15 +40,13 @@
import org.phoebus.applications.saveandrestore.model.NodeType;
import org.phoebus.applications.saveandrestore.model.event.SaveAndRestoreEventReceiver;
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreBaseController;
-import org.phoebus.security.store.SecureStore;
-import org.phoebus.security.tokens.AuthenticationScope;
-import org.phoebus.security.tokens.ScopedAuthenticationToken;
import org.phoebus.ui.docking.DockPane;
import org.phoebus.util.time.TimestampFormats;
-import java.util.*;
-import java.util.logging.Level;
-import java.util.logging.Logger;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.ServiceLoader;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -110,9 +108,6 @@ public class SnapshotControlsViewController extends SaveAndRestoreBaseController
@FXML
private ToolBar filterToolbar;
- @FXML
- private Label authenticatedUserId;
-
private List> regexPatterns = new ArrayList<>();
protected final SimpleStringProperty snapshotNameProperty = new SimpleStringProperty();
diff --git a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties
index 01dd9d5de7..d31b3acb26 100644
--- a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties
+++ b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties
@@ -2,8 +2,6 @@ add=Add
alertContinue=Do you wish to continue?
alertAddingPVsToConfiguration=Adding PV to configuration
authenticationFailed=Authentication Failed
-authenticatedUserLabel=Authenticated User
-authenticatedUserNone=
baseSetpoint=Base Setpoint
browseForLocation=Browse to select location
buttonSearch=Open snapshot/tag search window
diff --git a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationEditor.fxml b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationEditor.fxml
index cbdaa03ca1..0080db9c83 100644
--- a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationEditor.fxml
+++ b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationEditor.fxml
@@ -4,7 +4,6 @@
-
@@ -41,28 +40,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -110,7 +87,6 @@
-
diff --git a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsView.fxml b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsView.fxml
index ff76a9ca05..9e80e79d3c 100644
--- a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsView.fxml
+++ b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsView.fxml
@@ -23,7 +23,6 @@
-
@@ -43,14 +42,14 @@
-
+
-
-
+
+
@@ -71,28 +70,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
From a31349058f40fe2d179b079417e7c9e367dce943 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Thu, 14 Sep 2023 16:01:23 +0200
Subject: [PATCH 22/42] Updated documentation due to authentication in
save&restore
---
app/save-and-restore/app/doc/index.rst | 7 +++
docs/source/preference_properties.rst | 74 +++++++++++++++++--------
services/save-and-restore/doc/index.rst | 11 ++++
3 files changed, 70 insertions(+), 22 deletions(-)
diff --git a/app/save-and-restore/app/doc/index.rst b/app/save-and-restore/app/doc/index.rst
index 1d85ada7c3..adffdd1deb 100644
--- a/app/save-and-restore/app/doc/index.rst
+++ b/app/save-and-restore/app/doc/index.rst
@@ -11,6 +11,9 @@ The application uses the save-and-restore service deployed on the network such t
HTTP(s). The URL of the service is specified in the save-and-restore.properties file, or in the settings file
pointed to on the command line.
+Actions that create, modify or delete data are protected by the service. User must sign in through the
+Crendentials Manager application. See also below.
+
Nodes and node types
--------------------
@@ -392,7 +395,11 @@ The items of this context menu offers actions associated with a PV, which is sim
other applications. However, user should be aware that the "Data Browser" item will launch the Data Browser app for
the selected PV *around the point in time defined by the PV timestamp*.
+Authentication
+--------------
+In order to create, modify or delete data (configurations, snapshots) user must sign in through the Credentials Manager application. The UI will
+enable/disable elements based on the authentication status. The restore operation is also protected by authentication.
diff --git a/docs/source/preference_properties.rst b/docs/source/preference_properties.rst
index 9821669738..59bd8f8e8f 100644
--- a/docs/source/preference_properties.rst
+++ b/docs/source/preference_properties.rst
@@ -622,7 +622,7 @@ File ../../app/display/model/src/main/resources/display_model_preferences.proper
# When writing a display file, skip properties that are still at default values?
skip_defaults=true
-
+
# Add a comment containing the date, time, and username when saving an OPI in the Display Editor.
enable_saved_on_comments=true
@@ -671,7 +671,7 @@ File ../../app/display/representation/src/main/resources/display_representation_
# Length limit for tool tips
# Tool tips that are too long can be a problem
# on some window systems.
- tooltip_length=150
+ tooltip_length=200
# Timeout for load / unload of Embedded Widget content [ms]
embedded_timeout=5000
@@ -1034,6 +1034,10 @@ File ../../core/logbook/src/main/resources/logbook_preferences.properties::
# should auto generate a title (e.g. "Display Screenshot...").
auto_title=true
+ # Determines if a log entry created from context menu (e.g. display or data browser)
+ # should auto generate a body (e.g. "Display Screenshot...").
+ auto_body=true
+
# Determines if a log entry created from context menu (e.g. display or data browser)
# should auto generate properties (e.g. "resources.file").
auto_property=false
@@ -1076,6 +1080,11 @@ File ../../app/logbook/olog/ui/src/main/resources/log_olog_ui_preferences.proper
# groups will not be shown.
log_entry_groups_support=false
+ # Log entry update support. If set to false user will not be able to update log entries
+ # , and consequently UI elements and views related to updating log entry and viewing log history
+ # will not be displayed.
+ log_entry_update_support=true
+
# Comma separated list of "hidden" properties. For instance, properties that serve internal
# business logic, but should not be rendered in the properties view.
hidden_properties=Log Entry Group
@@ -1395,32 +1404,29 @@ File ../../app/pvtree/src/main/resources/pv_tree_preferences.properties::
saveandrestore
--------------
-File ../../app/save-and-restore/service/src/main/resources/client_preferences.properties::
+File ../../app/save-and-restore/app/src/main/resources/save_and_restore_preferences.properties::
- #
- # Copyright (C) 2020 European Spallation Source ERIC.
- #
- # This program is free software; you can redistribute it and/or
- # modify it under the terms of the GNU General Public License
- # as published by the Free Software Foundation; either version 2
- # of the License, or (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- #
-
# -----------------------------------------------
# Package org.phoebus.applications.saveandrestore
# -----------------------------------------------
+ # Sort snapshots in reverse order of created time. Last item comes first.
+ sortSnapshotsTimeReversed=false
+
+ # Read timeout (in ms) when taking snapshot
+ readTimeout=5000
+
+ # Limit used in "paginated" search, i.e. the number of search results per page
+ search_result_page_size=30
+
+ # Default save-and-restore search query. Used unless a saved query is located.
+ default_search_query=tags=golden
+
+ # If declared add a date automatically in the name of the snapshot "Take Snapshot"
+ #default_snapshot_name_date_format=yyyy-MM-dd HH:mm:ss
+
# The URL to the save-and-restore service
- jmasar.service.url=http://localhost:8080
+ jmasar.service.url=http://localhost:8080/save-restore
# Read timeout (in ms) used by the Jersey client
httpClient.readTimeout=1000
@@ -1768,6 +1774,30 @@ File ../../core/ui/src/main/resources/phoebus_ui_preferences.properties::
alarm_area_panel_undefined_severity_text_color=192,192,192
alarm_area_panel_undefined_severity_background_color=200,0,200,200
+
+ # When Picture- and/or Symbol widgets are present in an OPI,
+ # zooming in under Windows using the D3D graphics library can
+ # cause excessive VRAM usage. Setting a cache hint can work as
+ # a workaround. Since it has been observed that the cache hints
+ # also can cause graphical errors, the setting of a cache hint
+ # is a configurable option, which must explicitly be set to
+ # have effect.
+ #
+ # The setting defaults to the default caching behavior.
+ #
+ # Valid options are:
+ # "" (the empty string) or "NONE" - The default caching behavior: caching is DISABLED, and the cache hint is set to "CacheHint.DEFAULT".
+ # "DEFAULT" - Caching is ENABLED, and the cache hint is set to "CacheHint.DEFAULT".
+ # "SPEED" - Based on very limited testing, this option seems to work the best as a workaround for the excessive VRAM usage.
+ # "QUALITY"
+ # "SCALE" - This option has been observed to cause graphical errors on several systems: rotated widgets have been observed to be translated instead of rotated.
+ # "ROTATE"
+ # "SCALE_AND_ROTATE"
+ #
+ # If an invalid option is entered, a warning is logged, and the
+ # default caching behavior is used (i.e., caching is DISABLED,
+ # and the cache hint is set to "CacheHint.DEFAULT").
+ cache_hint_for_picture_and_symbol_widgets=
update
diff --git a/services/save-and-restore/doc/index.rst b/services/save-and-restore/doc/index.rst
index 6344675c24..ccc7fc2396 100644
--- a/services/save-and-restore/doc/index.rst
+++ b/services/save-and-restore/doc/index.rst
@@ -723,6 +723,17 @@ Body:
}
]
+Authentication
+--------------
+
+All non-GET endpoints are subject to authentication, i.e. clients must send a basic authentication header. The
+service can be configured to delegate authentication to Active Directory or remote or local LDAP. For demo and test
+purposes hard coded credentials are found in the ``WebSecurityConfig`` class. See the file ``application.properties``
+for information on how to select authentication method.
+
+There is no authorization mechanism, i.e. all authenticated users can perform all actions through the protected
+endpoints. Authorization may be added in future releases.
+
Migration
---------
From 2cfa77219a0af4615ae4331ad420429e264eb5ea Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Mon, 25 Sep 2023 08:23:56 +0200
Subject: [PATCH 23/42] Renamed a few settings/variables to make more sense
---
.../web/config/LDAPAuthoritiesMapper.java | 35 +++++++++++++++
.../web/config/WebSecurityConfig.java | 45 ++++++++++++++++---
.../web/controllers/NodeController.java | 33 +++++++++++++-
.../src/main/resources/application.properties | 30 ++++++++++---
4 files changed, 130 insertions(+), 13 deletions(-)
create mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/LDAPAuthoritiesMapper.java
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/LDAPAuthoritiesMapper.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/LDAPAuthoritiesMapper.java
new file mode 100644
index 0000000000..12f7d3c7ff
--- /dev/null
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/LDAPAuthoritiesMapper.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package org.phoebus.service.saveandrestore.web.config;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
+
+import java.util.Collection;
+
+public class LDAPAuthoritiesMapper implements GrantedAuthoritiesMapper {
+
+ @Override
+ public Collection extends GrantedAuthority> mapAuthorities(Collection extends GrantedAuthority> authorities) {
+
+
+ return null;
+ }
+}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
index 07a6ceea94..5067980e4e 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
@@ -10,8 +10,10 @@
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer;
+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.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@@ -23,7 +25,12 @@
import java.util.logging.Level;
import java.util.logging.Logger;
+@EnableWebSecurity
@Configuration
+@EnableGlobalMethodSecurity(
+ prePostEnabled = true,
+ securedEnabled = true,
+ jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
@@ -75,11 +82,19 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${auth.impl:demo}")
String authenitcationImplementation;
+ @Value("${role.user:sar-user}")
+ private String roleUser;
+
+ @Value("${role.superuser:sar-superuser}")
+ private String roleSuperUser;
+
+ @Value("${role.admin:sar-admin}")
+ private String roleAdmin;
+
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().anyRequest().authenticated();
- AuthenticationManager am = authenticationManager();
http.addFilterBefore(new SessionFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
}
@@ -93,8 +108,6 @@ public void configure(WebSecurity web) {
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
- DefaultSpringSecurityContextSource contextSource;
- DefaultLdapAuthoritiesPopulator myAuthPopulator;
switch(authenitcationImplementation){
case "ad":
ActiveDirectoryLdapAuthenticationProvider adProvider = new ActiveDirectoryLdapAuthenticationProvider(ad_domain, ad_url);
@@ -103,14 +116,15 @@ public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(adProvider);
break;
case "ldap":
- contextSource = new DefaultSpringSecurityContextSource(ldap_url);
+ DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(ldap_url);
if (ldap_manager_dn != null && !ldap_manager_dn.isEmpty() && ldap_manager_password != null && !ldap_manager_password.isEmpty()) {
contextSource.setUserDn(ldap_manager_dn);
contextSource.setPassword(ldap_manager_password);
}
+ contextSource.setBase(ldap_base_dn);
contextSource.afterPropertiesSet();
- myAuthPopulator = new DefaultLdapAuthoritiesPopulator(contextSource, ldap_groups_search_base);
+ DefaultLdapAuthoritiesPopulator myAuthPopulator = new DefaultLdapAuthoritiesPopulator(contextSource, ldap_groups_search_base);
myAuthPopulator.setGroupSearchFilter(ldap_groups_search_pattern);
myAuthPopulator.setSearchSubtree(true);
myAuthPopulator.setIgnorePartialResultException(true);
@@ -126,6 +140,7 @@ public void configure(AuthenticationManagerBuilder auth) throws Exception {
if (ldap_user_search_base != null && !ldap_user_search_base.isEmpty()) {
configurer.userSearchBase(ldap_user_search_base);
}
+ //configurer.authoritiesMapper(new LDAPAuthoritiesMapper());
configurer.contextSource(contextSource);
break;
case "ldap_embedded":
@@ -146,8 +161,9 @@ public void configure(AuthenticationManagerBuilder auth) throws Exception {
break;
case "demo":
auth.inMemoryAuthentication()
- .withUser("admin").password(encoder().encode("adminPass")).roles("ADMIN").and()
- .withUser("user").password(encoder().encode("userPass")).roles("USER");
+ .withUser("admin").password(encoder().encode("adminPass")).roles(roleAdmin()).and()
+ .withUser("user").password(encoder().encode("userPass")).roles(roleUser()).and()
+ .withUser("superuser").password(encoder().encode("superUserPass")).roles(roleSuperUser());
break;
default:
Logger.getLogger(WebSecurityConfig.class.getName())
@@ -178,4 +194,19 @@ public ObjectMapper objectMapper() {
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
+
+ @Bean
+ public String roleUser(){
+ return roleUser.toUpperCase();
+ }
+
+ @Bean
+ public String roleSuperUser(){
+ return roleSuperUser.toUpperCase();
+ }
+
+ @Bean
+ public String roleAdmin(){
+ return roleAdmin.toUpperCase();
+ }
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
index 6666cd2c6c..c3ae5be352 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
@@ -23,6 +23,7 @@
import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
@@ -40,6 +41,9 @@ public class NodeController extends BaseController {
@Autowired
private NodeDAO nodeDAO;
+ @Autowired
+ public String roleAdmin; // This MUST be public!!!
+
private final Logger logger = Logger.getLogger(NodeController.class.getName());
/**
@@ -132,11 +136,38 @@ public List getChildNodes(@PathVariable final String uniqueNodeId) {
*/
@SuppressWarnings("unused")
@DeleteMapping(value = "/node/{uniqueNodeId}", produces = JSON)
- public void deleteNode(@PathVariable final String uniqueNodeId) {
+ @PreAuthorize("hasRole(this.role2) or this.mayDelete(#uniqueNodeId, #principal)")
+ public void deleteNode(@PathVariable final String uniqueNodeId, Principal principal) {
logger.info("Deleting node with unique id " + uniqueNodeId);
nodeDAO.deleteNode(uniqueNodeId);
}
+ /**
+ * NOTE: this method MUST be public!
+ *
+ * An authenticated user may delete a node if the following hold true:
+ *
+ *
User identity is same as the the target {@link Node}'s user id.
+ *
Target {@link Node} is a snapshot.
+ *
Target {@link Node} is not a snapshot, but has no child nodes.
+ *
+ *
+ * @param nodeId Unique node id identifying the target of the user's delete operation.
+ * @param principal Identifies user.
+ * @return false if user may not delete the {@link Node}.
+ */
+ @SuppressWarnings("unused")
+ public boolean mayDelete(String nodeId, Principal principal){
+ Node node = nodeDAO.getNode(nodeId);
+ if(!node.getUserName().equals(principal.getName())){
+ return false;
+ }
+ if(node.getNodeType().equals(NodeType.CONFIGURATION) || node.getNodeType().equals(NodeType.FOLDER)){
+ return nodeDAO.getChildNodes(node.getUniqueId()).isEmpty();
+ }
+ return true;
+ }
+
/**
* Updates a {@link Node}. The purpose is to support modification of name or comment/description, or both. Modification of
* node type is not supported.
diff --git a/services/save-and-restore/src/main/resources/application.properties b/services/save-and-restore/src/main/resources/application.properties
index 3a00604b8d..53cfae4a3d 100644
--- a/services/save-and-restore/src/main/resources/application.properties
+++ b/services/save-and-restore/src/main/resources/application.properties
@@ -35,17 +35,32 @@ ad.url = ldap://127.0.0.1
ad.domain = test.com
############## LDAP - External ##############
-ldap.enabled = false
-ldap.urls = ldaps://controlns02.nsls2.bnl.gov/dc=nsls2,dc=bnl,dc=gov
-ldap.base.dn = dc=nsls2,dc=bnl,dc=gov
+#ldap.urls = ldaps://controlns02.nsls2.bnl.gov/dc=nsls2,dc=bnl,dc=gov
+#ldap.base.dn = dc=nsls2,dc=bnl,dc=gov
+#ldap.user.search.base=
+# User search pattern, e.g. uid={0},ou=People. No default value as LDAP environment may not
+# support user search by pattern.
+#ldap.user.dn.pattern=
+# User search filter, e.g. (&(objectClass=person)(SAMAccountName={0})). No default value as LDAP environment
+# may not support user search by filter.
+#ldap.user.search.filter=
+#ldap.groups.search.base = ou=Group
+#ldap.groups.search.pattern = (memberUid= {1})
+# dn of manager account, may be required for group search
+#ldap.manager.dn=
+# password of account
+#ldap.manager.password=
+
+ldap.urls=ldaps://ldap-cslab.cslab.esss.lu.se
+ldap.base.dn = dc=esss,dc=lu,dc=se
ldap.user.search.base=
# User search pattern, e.g. uid={0},ou=People. No default value as LDAP environment may not
# support user search by pattern.
-ldap.user.dn.pattern=
+ldap.user.dn.pattern=uid={0},ou=Users
# User search filter, e.g. (&(objectClass=person)(SAMAccountName={0})). No default value as LDAP environment
# may not support user search by filter.
ldap.user.search.filter=
-ldap.groups.search.base = ou=Group
+ldap.groups.search.base = ou=Groups
ldap.groups.search.pattern = (memberUid= {1})
# dn of manager account, may be required for group search
ldap.manager.dn=
@@ -73,4 +88,9 @@ spring.ldap.embedded.validation.enabled=false
# demo - Hard coded, see WebSecurityConfig class
auth.impl = demo
+############## Authorization Roles ################
+
+role.user=sar-user
+role.superuser=sar-superuser
+role.admin=ICS Division
From 7854d68ca8a07ac67bcfa5a1fb40bbaae789367b Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Tue, 10 Oct 2023 10:43:49 +0200
Subject: [PATCH 24/42] Documentation for save&restore authorization
---
app/save-and-restore/app/doc/index.rst | 22 ++++++++++++++++------
docs/source/preference_properties.rst | 2 ++
services/save-and-restore/doc/index.rst | 20 ++++++++++++++++----
3 files changed, 34 insertions(+), 10 deletions(-)
diff --git a/app/save-and-restore/app/doc/index.rst b/app/save-and-restore/app/doc/index.rst
index adffdd1deb..c10f23ed96 100644
--- a/app/save-and-restore/app/doc/index.rst
+++ b/app/save-and-restore/app/doc/index.rst
@@ -395,11 +395,21 @@ The items of this context menu offers actions associated with a PV, which is sim
other applications. However, user should be aware that the "Data Browser" item will launch the Data Browser app for
the selected PV *around the point in time defined by the PV timestamp*.
-Authentication
---------------
-
-In order to create, modify or delete data (configurations, snapshots) user must sign in through the Credentials Manager application. The UI will
-enable/disable elements based on the authentication status. The restore operation is also protected by authentication.
-
+Authentication and Authorization
+--------------------------------
+
+Authorization uses a role-based approach like so:
+
+* Unauthenticated users may read data, i.e. browse the tree and view configurations and snapshots.
+* Role0:
+ * Create/update and save configurations
+ * Take and save snapshots.
+ * Delete nodes if user id matches and:
+ * Node is a snapshot
+ * Node is configuration or folder with no child nodes
+* Role1: +perform restore operation
+* Role2: no restrictions
+
+Actual roles are defined and managed on the service. Role (group) membership is managed in Active Directory or LDAP.
diff --git a/docs/source/preference_properties.rst b/docs/source/preference_properties.rst
index 59bd8f8e8f..ad5a30aab8 100644
--- a/docs/source/preference_properties.rst
+++ b/docs/source/preference_properties.rst
@@ -165,7 +165,9 @@ File ../../app/alarm/logging-ui/src/main/resources/alarm_logging_preferences.pro
# Package org.phoebus.applications.alarm.logging.ui
# -------------------------------------------------
+ # The URL of the REST API exposed by the alarm logger service (not the elasticsearch port as it was prior to Phoebus 4.0)
service_uri = http://localhost:9000
+
results_max_size = 10000
diff --git a/services/save-and-restore/doc/index.rst b/services/save-and-restore/doc/index.rst
index ccc7fc2396..f81ea11542 100644
--- a/services/save-and-restore/doc/index.rst
+++ b/services/save-and-restore/doc/index.rst
@@ -723,16 +723,28 @@ Body:
}
]
-Authentication
---------------
+Authentication and Authorization
+--------------------------------
All non-GET endpoints are subject to authentication, i.e. clients must send a basic authentication header. The
service can be configured to delegate authentication to Active Directory or remote or local LDAP. For demo and test
purposes hard coded credentials are found in the ``WebSecurityConfig`` class. See the file ``application.properties``
for information on how to select authentication method.
-There is no authorization mechanism, i.e. all authenticated users can perform all actions through the protected
-endpoints. Authorization may be added in future releases.
+Authorization uses a role-based approach like so:
+
+* Unauthenticated users may read data, i.e. access GET endpoints.
+* Role0:
+ * Create/update and save configurations
+ * Take and save snapshots.
+ * Delete nodes if user id matches and:
+ * Node is a snapshot
+ * Node is configuration or folder with no child nodes
+* Role1: +perform restore operation
+* Role2: no restrictions
+
+Roles must be defined as groups in Active Directory or LDAP. Role/group names can be configured in ``application.properties``.
+
Migration
---------
From affe1ab953a535cd6be4cff18c9b4a9faed37b97 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Wed, 11 Oct 2023 15:55:44 +0200
Subject: [PATCH 25/42] Updating NodeControllerTest due to authorization
---
app/save-and-restore/app/doc/index.rst | 8 +-
services/save-and-restore/doc/index.rst | 6 +-
services/save-and-restore/pom.xml | 4 +
.../web/config/WebSecurityConfig.java | 98 +++++-
.../web/controllers/BaseController.java | 10 +
.../controllers/ConfigurationController.java | 2 +
.../web/controllers/NodeController.java | 35 ++-
.../web/controllers/StructureController.java | 2 +
.../src/main/resources/application.properties | 17 +-
.../src/main/resources/sar.ldif | 65 +++-
.../web/config/ControllersTestConfig.java | 54 +++-
.../web/controllers/NodeControllerTest.java | 293 +++++++++++++++---
.../resources/test_application.properties | 1 +
13 files changed, 502 insertions(+), 93 deletions(-)
diff --git a/app/save-and-restore/app/doc/index.rst b/app/save-and-restore/app/doc/index.rst
index c10f23ed96..970a3c1763 100644
--- a/app/save-and-restore/app/doc/index.rst
+++ b/app/save-and-restore/app/doc/index.rst
@@ -401,15 +401,15 @@ Authentication and Authorization
Authorization uses a role-based approach like so:
* Unauthenticated users may read data, i.e. browse the tree and view configurations and snapshots.
-* Role0:
+* Role "user":
* Create/update and save configurations
* Take and save snapshots.
* Delete nodes if user id matches and:
* Node is a snapshot
* Node is configuration or folder with no child nodes
-* Role1: +perform restore operation
-* Role2: no restrictions
+* Role "superuser": +perform restore operation
+* Role "admin": no restrictions
-Actual roles are defined and managed on the service. Role (group) membership is managed in Active Directory or LDAP.
+Roles are defined and managed on the service. Role (group) membership is managed in Active Directory or LDAP.
diff --git a/services/save-and-restore/doc/index.rst b/services/save-and-restore/doc/index.rst
index f81ea11542..d7d56dc9fe 100644
--- a/services/save-and-restore/doc/index.rst
+++ b/services/save-and-restore/doc/index.rst
@@ -734,14 +734,14 @@ for information on how to select authentication method.
Authorization uses a role-based approach like so:
* Unauthenticated users may read data, i.e. access GET endpoints.
-* Role0:
+* Role "user":
* Create/update and save configurations
* Take and save snapshots.
* Delete nodes if user id matches and:
* Node is a snapshot
* Node is configuration or folder with no child nodes
-* Role1: +perform restore operation
-* Role2: no restrictions
+* Role "superuser": +perform restore operation
+* Role "admin": no restrictions
Roles must be defined as groups in Active Directory or LDAP. Role/group names can be configured in ``application.properties``.
diff --git a/services/save-and-restore/pom.xml b/services/save-and-restore/pom.xml
index ac969996ea..3c898a3407 100644
--- a/services/save-and-restore/pom.xml
+++ b/services/save-and-restore/pom.xml
@@ -87,6 +87,10 @@
org.springframework.securityspring-security-ldap
+
+ com.unboundid
+ unboundid-ldapsdk
+ org.springframework.securityspring-security-test
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
index 5067980e4e..9ec997317d 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
@@ -7,6 +7,8 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.http.HttpMethod;
+import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
+import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer;
@@ -31,6 +33,7 @@
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
+@SuppressWarnings("unused")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
@@ -86,11 +89,35 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private String roleUser;
@Value("${role.superuser:sar-superuser}")
- private String roleSuperUser;
+ private String roleSuperuser;
@Value("${role.admin:sar-admin}")
private String roleAdmin;
+ @Value("${demo.user:user}")
+ private String demoUser;
+
+ @Value("${demo.user.password:userPass}")
+ private String demoUserPassword;
+
+ @Value("${demo.superuser:superuser}")
+ private String demoSuperuser;
+
+ @Value("${demo.superuser.password:superuserPass}")
+ private String demoSuperuserPassword;
+
+ @Value("${demo.admin:admin}")
+ private String demoAdmin;
+
+ @Value("${demo.admin.password:adminPass}")
+ private String demoAdminPassword;
+
+ @Value("${demo.readOnly:johndoe}")
+ private String demoReadOnly;
+
+ @Value("${demo.readOnly.password:1234}")
+ private String demoReadOnlyPassword;
+
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
@@ -161,9 +188,9 @@ public void configure(AuthenticationManagerBuilder auth) throws Exception {
break;
case "demo":
auth.inMemoryAuthentication()
- .withUser("admin").password(encoder().encode("adminPass")).roles(roleAdmin()).and()
- .withUser("user").password(encoder().encode("userPass")).roles(roleUser()).and()
- .withUser("superuser").password(encoder().encode("superUserPass")).roles(roleSuperUser());
+ .withUser(demoAdmin).password(encoder().encode(demoAdminPassword)).roles(roleAdmin()).and()
+ .withUser(demoUser).password(encoder().encode(demoUserPassword)).roles(roleUser()).and()
+ .withUser(demoSuperuser).password(encoder().encode(demoSuperuserPassword)).roles(roleSuperuser());
break;
default:
Logger.getLogger(WebSecurityConfig.class.getName())
@@ -201,12 +228,71 @@ public String roleUser(){
}
@Bean
- public String roleSuperUser(){
- return roleSuperUser.toUpperCase();
+ public String roleSuperuser(){
+ return roleSuperuser.toUpperCase();
}
@Bean
public String roleAdmin(){
return roleAdmin.toUpperCase();
}
+
+ @Bean("demoUser")
+ public String demoUser(){
+ return demoUser;
+ }
+
+ @Bean("demoUserPassword")
+ public String demoUserPassword(){
+ return demoUserPassword;
+ }
+
+ @Bean("demoSuperuser")
+ public String demoSuperuser(){
+ return demoSuperuser;
+ }
+
+ @Bean("demoSuperuserPassword")
+ public String demoSuperuserPassword(){
+ return demoSuperuserPassword;
+ }
+
+ @Bean("demoAdmin")
+ public String demoAdmin(){
+ return demoAdmin;
+ }
+
+ @Bean("demoAdminPassword")
+ public String demoAdminPassword(){
+ return demoAdminPassword;
+ }
+
+ @Bean("demoReadOnly")
+ public String demoReadOnly(){
+ return demoReadOnly;
+ }
+
+ @Bean("demoReadOnlyPassword")
+ public String demoReadOnlyPassword(){
+ return demoReadOnlyPassword;
+ }
+
+
+ /**
+ * Configures role hierarchy, i.e. user -> superuser -> admin. Do not remove this {@link Bean}!
+ *
+ *
NOTE!
+ * Some Spring Security documentation will state that "and" can be used instead of new-line char to
+ * separate rule items. But that does NOT work, at least not with the Spring Security version used in this project.
+ *
+ * @return A {@link RoleHierarchy} object.
+ */
+ @Bean
+ public RoleHierarchy roleHierarchy() {
+ RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
+ hierarchy.setHierarchy("ROLE_" + roleAdmin.toUpperCase() + " > ROLE_" + roleSuperuser.toUpperCase() + "\n" +
+ "ROLE_" + roleSuperuser.toUpperCase() +" > ROLE_" + roleUser.toUpperCase() + "\n" +
+ "ROLE_" + roleAdmin.toUpperCase() + " > ROLE_" + roleUser.toUpperCase());
+ return hierarchy;
+ }
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java
index e49fe8a961..02de8abce9 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java
@@ -20,6 +20,7 @@
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
@@ -46,6 +47,15 @@ public abstract class BaseController {
private final Logger logger = Logger.getLogger(BaseController.class.getName());
+ @Autowired
+ public String roleAdmin; // This MUST be public!!!
+
+ @Autowired
+ public String roleSuperuser; // This MUST be public!!!
+
+ @Autowired
+ public String roleUser; // This MUST be public!!!
+
/**
* Intercepts {@link SnapshotNotFoundException} and triggers a {@link HttpStatus#NOT_FOUND}.
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java
index 9df5f7d8ca..3df04099cf 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java
@@ -21,6 +21,7 @@
import org.phoebus.applications.saveandrestore.model.ConfigurationData;
import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@@ -42,6 +43,7 @@ public class ConfigurationController extends BaseController {
@SuppressWarnings("unused")
@PutMapping(produces = JSON)
+ @PreAuthorize("hasRole(this.roleUser)")
public Configuration createConfiguration(@RequestParam(value = "parentNodeId") String parentNodeId,
@RequestBody Configuration configuration,
Principal principal) {
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
index c3ae5be352..960d143425 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
@@ -41,9 +41,6 @@ public class NodeController extends BaseController {
@Autowired
private NodeDAO nodeDAO;
- @Autowired
- public String roleAdmin; // This MUST be public!!!
-
private final Logger logger = Logger.getLogger(NodeController.class.getName());
/**
@@ -64,12 +61,10 @@ public class NodeController extends BaseController {
*/
@SuppressWarnings("unused")
@PutMapping(value = "/node", produces = JSON)
+ @PreAuthorize("hasRole(this.roleUser)")
public Node createNode(@RequestParam(name = "parentNodeId") String parentsUniqueId,
@RequestBody final Node node,
Principal principal) {
- if (node.getUserName() == null || node.getUserName().isEmpty()) {
- throw new IllegalArgumentException("User name must be non-null and of non-zero length");
- }
if (node.getName() == null || node.getName().isEmpty()) {
throw new IllegalArgumentException("Node name must be non-null and of non-zero length");
}
@@ -136,7 +131,7 @@ public List getChildNodes(@PathVariable final String uniqueNodeId) {
*/
@SuppressWarnings("unused")
@DeleteMapping(value = "/node/{uniqueNodeId}", produces = JSON)
- @PreAuthorize("hasRole(this.role2) or this.mayDelete(#uniqueNodeId, #principal)")
+ @PreAuthorize("hasRole(this.roleAdmin) or this.mayDelete(#uniqueNodeId, #principal)")
public void deleteNode(@PathVariable final String uniqueNodeId, Principal principal) {
logger.info("Deleting node with unique id " + uniqueNodeId);
nodeDAO.deleteNode(uniqueNodeId);
@@ -144,13 +139,13 @@ public void deleteNode(@PathVariable final String uniqueNodeId, Principal princi
/**
* NOTE: this method MUST be public!
- *
- * An authenticated user may delete a node if the following hold true:
+ *
+ * An authenticated user may delete a node if User identity is same as the target {@link Node}'s user id and:
*
- *
User identity is same as the the target {@link Node}'s user id.
*
Target {@link Node} is a snapshot.
*
Target {@link Node} is not a snapshot, but has no child nodes.
*
+ *
*
* @param nodeId Unique node id identifying the target of the user's delete operation.
* @param principal Identifies user.
@@ -168,6 +163,25 @@ public boolean mayDelete(String nodeId, Principal principal){
return true;
}
+ /**
+ * NOTE: this method MUST be public!
+ *
+ *
+ * An authenticated user may update a node if user identity is same as the target {@link Node}'s user id.
+ *
+ * An authenticated user may update a configuration if user identity is same as the target {@link Node}'s user id.
+ *
+ *
+ * @param configuration {@link Configuration} identifying the target of the user's update operation.
+ * @param principal Identifies user.
+ * @return false if user may not update the {@link Node}.
+ */
+ @SuppressWarnings("unused")
+ public boolean mayUpdate(Configuration configuration, Principal principal){
+ Node configNode = nodeDAO.getNode(configuration.getConfigurationNode().getUniqueId());
+ return configNode.getUserName().equals(principal.getName());
}
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
index 960d143425..d80181558b 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
@@ -170,16 +170,13 @@ public boolean mayDelete(String nodeId, Principal principal){
* An authenticated user may update a node if user identity is same as the target {@link Node}'s user id.
*
*
- * @param node {@link Node} identifying the target of the user's delete operation.
+ * @param node {@link Node} identifying the target of the user's update operation.
* @param principal Identifies user.
- * @return false if user may not delete the {@link Node}.
+ * @return false if user may not update the {@link Node}.
*/
@SuppressWarnings("unused")
public boolean mayUpdate(Node node, Principal principal){
- if(!nodeDAO.getNode(node.getUniqueId()).getUserName().equals(principal.getName())){
- return false;
- }
- return true;
+ return nodeDAO.getNode(node.getUniqueId()).getUserName().equals(principal.getName());
}
/**
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java
index eb5e028062..b2e7cca255 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java
@@ -206,6 +206,61 @@ public Configuration answer(InvocationOnMock invocation) {
objectMapper.readValue(result.getResponse().getContentAsString(), Configuration.class);
}
+ @Test
+ public void testUpdateConfig() throws Exception{
+ reset(nodeDAO);
+
+ Node config = Node.builder().nodeType(NodeType.CONFIGURATION).name("config").uniqueId("hhh")
+ .userName("user").build();
+
+ Configuration configuration = new Configuration();
+ configuration.setConfigurationNode(config);
+
+ String confurationAsString = objectMapper.writeValueAsString(configuration);
+
+ Configuration updatedConfiguration = new Configuration();
+ updatedConfiguration.setConfigurationNode(Node.builder().nodeType(NodeType.CONFIGURATION).name("config").uniqueId("hhh")
+ .userName(demoAdmin).build());
+
+ when(nodeDAO.updateConfiguration(updatedConfiguration)).thenReturn(updatedConfiguration);
+
+ MockHttpServletRequestBuilder request = post("/config")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON)
+ .content(confurationAsString);
+
+ MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))
+ .andReturn();
+
+ // Make sure response contains expected data
+ objectMapper.readValue(result.getResponse().getContentAsString(), Configuration.class);
+
+ when(nodeDAO.getNode("hhh")).thenReturn(Node.builder().nodeType(NodeType.CONFIGURATION).userName("notUser").build());
+
+ request = post("/config")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(confurationAsString);
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ when(nodeDAO.getNode("hhh")).thenReturn(Node.builder().nodeType(NodeType.CONFIGURATION).userName("notUser").build());
+
+ request = post("/config")
+ .header(HttpHeaders.AUTHORIZATION, superuserAuthorization)
+ .contentType(JSON)
+ .content(confurationAsString);
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = post("/config")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON)
+ .content(confurationAsString);
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+ }
+
@Test
public void testCreateNodeBadRequests() throws Exception {
reset(nodeDAO);
From c7dd2c8dcb30fc43f254d3ea2b3401b56b4928bb Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Thu, 12 Oct 2023 15:18:42 +0200
Subject: [PATCH 27/42] Authorization for saving and updating snapshots
---
.../saveandrestore/model/Snapshot.java | 13 ++
.../controllers/ConfigurationController.java | 5 +-
.../web/controllers/NodeController.java | 2 +-
.../web/controllers/SnapshotController.java | 25 ++-
.../web/controllers/NodeControllerTest.java | 15 ++
.../controllers/SnapshotControllerTest.java | 203 ++++++++++++++++++
6 files changed, 249 insertions(+), 14 deletions(-)
create mode 100644 services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java
diff --git a/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/Snapshot.java b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/Snapshot.java
index 5555c5292b..3fa214a795 100644
--- a/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/Snapshot.java
+++ b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/Snapshot.java
@@ -43,4 +43,17 @@ public SnapshotData getSnapshotData() {
public void setSnapshotData(SnapshotData snapshotData) {
this.snapshotData = snapshotData;
}
+
+ @Override
+ public boolean equals(Object other){
+ if(!(other instanceof Snapshot)){
+ return false;
+ }
+ return snapshotNode.equals(((Snapshot) other).getSnapshotNode());
+ }
+
+ @Override
+ public int hashCode(){
+ return snapshotNode.hashCode();
+ }
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java
index fa760a8f9b..47d1f7151c 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java
@@ -60,12 +60,11 @@ public ConfigurationData getConfigurationData(@PathVariable String uniqueId) {
@SuppressWarnings("unused")
@PostMapping(produces = JSON)
- @PreAuthorize("hasRole(this.roleAdmin) or this.mayUpdate(#configuration, #principal)")
+ @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleAdmin) or this.mayUpdate(#configuration, #principal))")
public Configuration updateConfiguration(@RequestBody Configuration configuration,
Principal principal) {
configuration.getConfigurationNode().setUserName(principal.getName());
- Configuration c = nodeDAO.updateConfiguration(configuration);
- return c;
+ return nodeDAO.updateConfiguration(configuration);
}
/**
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
index d80181558b..1816a6c0f7 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
@@ -195,7 +195,7 @@ public boolean mayUpdate(Node node, Principal principal){
*/
@SuppressWarnings("unused")
@PostMapping(value = "/node", produces = JSON)
- @PreAuthorize("hasRole(this.roleUser) and this.mayUpdate(#nodeToUpdate, #principal)")
+ @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and this.mayUpdate(#nodeToUpdate, #principal))")
public Node updateNode(@RequestParam(value = "customTimeForMigration", required = false, defaultValue = "false") String customTimeForMigration,
@RequestBody Node nodeToUpdate,
Principal principal) {
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java
index 8b5c8de8bf..d99f5de0e4 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java
@@ -17,21 +17,14 @@
*/
package org.phoebus.service.saveandrestore.web.controllers;
-import org.phoebus.applications.saveandrestore.model.CompositeSnapshot;
-import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData;
import org.phoebus.applications.saveandrestore.model.Node;
+import org.phoebus.applications.saveandrestore.model.NodeType;
import org.phoebus.applications.saveandrestore.model.Snapshot;
import org.phoebus.applications.saveandrestore.model.SnapshotData;
-import org.phoebus.applications.saveandrestore.model.SnapshotItem;
import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
import java.security.Principal;
import java.util.List;
@@ -54,10 +47,22 @@ public List getAllSnapshots() {
}
@PutMapping(value = "/snapshot", produces = JSON)
+ @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and this.maySave(#snapshot, #principal))")
public Snapshot saveSnapshot(@RequestParam(value = "parentNodeId") String parentNodeId,
@RequestBody Snapshot snapshot,
Principal principal) {
+ if(!snapshot.getSnapshotNode().getNodeType().equals(NodeType.SNAPSHOT)){
+ throw new IllegalArgumentException("Snapshot node of wrong type");
+ }
snapshot.getSnapshotNode().setUserName(principal.getName());
return nodeDAO.saveSnapshot(parentNodeId, snapshot);
}
+
+ public boolean maySave(Snapshot snapshot, Principal principal){
+ if(snapshot.getSnapshotNode().getUniqueId() == null){
+ return true;
+ }
+ Node node = nodeDAO.getNode(snapshot.getSnapshotNode().getUniqueId());
+ return node.getUserName().equals(principal.getName());
+ }
}
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java
index b2e7cca255..5ee30aa4e5 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java
@@ -244,6 +244,13 @@ public void testUpdateConfig() throws Exception{
mockMvc.perform(request).andExpect(status().isForbidden());
+ request = post("/config")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON)
+ .content(confurationAsString);
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
when(nodeDAO.getNode("hhh")).thenReturn(Node.builder().nodeType(NodeType.CONFIGURATION).userName("notUser").build());
request = post("/config")
@@ -621,6 +628,14 @@ public void testUpdateNode() throws Exception {
.content(objectMapper.writeValueAsString(node));
mockMvc.perform(request).andExpect(status().isForbidden());
+ request = post("/node")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .param("customTimeForMigration", "false")
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(node));
+ mockMvc.perform(request).andExpect(status().isOk());
+
+
request = post("/node")
.param("customTimeForMigration", "false")
.contentType(JSON)
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java
new file mode 100644
index 0000000000..23e096895c
--- /dev/null
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2023 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package org.phoebus.service.saveandrestore.web.controllers;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.phoebus.applications.saveandrestore.model.Configuration;
+import org.phoebus.applications.saveandrestore.model.Node;
+import org.phoebus.applications.saveandrestore.model.NodeType;
+import org.phoebus.applications.saveandrestore.model.Snapshot;
+import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
+import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+
+import static org.phoebus.service.saveandrestore.web.controllers.BaseController.JSON;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = ControllersTestConfig.class)
+@TestPropertySource(locations = "classpath:test_application.properties")
+@WebMvcTest(NodeController.class)
+public class SnapshotControllerTest {
+
+ @Autowired
+ private NodeDAO nodeDAO;
+
+ @Autowired
+ private String userAuthorization;
+
+ @Autowired
+ private String superuserAuthorization;
+
+ @Autowired
+ private String adminAuthorization;
+
+ @Autowired
+ private String readOnlyAuthorization;
+
+ private ObjectMapper objectMapper = new ObjectMapper();
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Autowired
+ private String demoUser;
+
+ @Autowired
+ private String demoSuperuser;
+
+ @Autowired
+ private String demoAdmin;
+
+ @Test
+ public void testSaveSnapshotWrongNodeType() throws Exception {
+ Snapshot snapshot = new Snapshot();
+ snapshot.setSnapshotNode(Node.builder().nodeType(NodeType.FOLDER).build());
+
+ MockHttpServletRequestBuilder request = put("/snapshot?parentNodeId=a")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(snapshot));
+
+ mockMvc.perform(request).andExpect(status().isBadRequest());
+ }
+
+ @Test
+ public void testSaveSnapshotNoParentNodeId() throws Exception {
+ Snapshot snapshot = new Snapshot();
+ snapshot.setSnapshotNode(Node.builder().nodeType(NodeType.SNAPSHOT).build());
+
+ MockHttpServletRequestBuilder request = put("/snapshot")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(snapshot));
+
+ mockMvc.perform(request).andExpect(status().isBadRequest());
+ }
+
+ @Test
+ public void testSaveNewSnapshot() throws Exception {
+ Node node = Node.builder().nodeType(NodeType.SNAPSHOT).userName(demoUser).build();
+ Snapshot snapshot = new Snapshot();
+ snapshot.setSnapshotNode(node);
+
+ String snapshotString = objectMapper.writeValueAsString(snapshot);
+
+ when(nodeDAO.saveSnapshot(Mockito.any(String.class), Mockito.any(Snapshot.class)))
+ .thenAnswer((Answer) invocation -> snapshot);
+
+ MockHttpServletRequestBuilder request = put("/snapshot?parentNodeId=a")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(snapshotString);
+
+ MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))
+ .andReturn();
+
+ // Make sure response contains expected data
+ objectMapper.readValue(result.getResponse().getContentAsString(), Snapshot.class);
+
+ request = put("/snapshot?parentNodeId=a")
+ .contentType(JSON)
+ .content(snapshotString);
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = put("/snapshot?parentNodeId=a")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON)
+ .content(snapshotString);
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = put("/snapshot?parentNodeId=a")
+ .header(HttpHeaders.AUTHORIZATION, superuserAuthorization)
+ .contentType(JSON)
+ .content(snapshotString);
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = put("/snapshot?parentNodeId=a")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON)
+ .content(snapshotString);
+ mockMvc.perform(request).andExpect(status().isOk());
+ }
+
+ @Test
+ public void testUpdateSnapshot() throws Exception{
+ Node node = Node.builder().uniqueId("s").nodeType(NodeType.SNAPSHOT).userName(demoUser).build();
+ Snapshot snapshot = new Snapshot();
+ snapshot.setSnapshotNode(node);
+
+ String snapshotString = objectMapper.writeValueAsString(snapshot);
+
+ when(nodeDAO.getNode("s")).thenReturn(node);
+ when(nodeDAO.saveSnapshot(Mockito.any(String.class), Mockito.any(Snapshot.class)))
+ .thenAnswer((Answer) invocation -> snapshot);
+
+ MockHttpServletRequestBuilder request = put("/snapshot?parentNodeId=a")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(snapshotString);
+
+ MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))
+ .andReturn();
+
+ // Make sure response contains expected data
+ objectMapper.readValue(result.getResponse().getContentAsString(), Snapshot.class);
+
+ request = put("/snapshot?parentNodeId=a")
+ .contentType(JSON)
+ .content(snapshotString);
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = put("/snapshot?parentNodeId=a")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON)
+ .content(snapshotString);
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = put("/snapshot?parentNodeId=a")
+ .header(HttpHeaders.AUTHORIZATION, superuserAuthorization)
+ .contentType(JSON)
+ .content(snapshotString);
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = put("/snapshot?parentNodeId=a")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON)
+ .content(snapshotString);
+ mockMvc.perform(request).andExpect(status().isOk());
+ }
+}
From 206af33e62042777d80fd29f64b54ecb8f22b032 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Thu, 12 Oct 2023 17:27:38 +0200
Subject: [PATCH 28/42] Authorization for composite snapshots
---
.../model/CompositeSnapshot.java | 13 ++
.../web/config/LDAPAuthoritiesMapper.java | 35 ----
.../web/config/WebSecurityConfig.java | 4 +-
.../CompositeSnapshotController.java | 19 +-
.../CompositeSnapshotControllerTest.java | 164 +++++++++++++++++-
.../controllers/SnapshotControllerTest.java | 6 +-
6 files changed, 190 insertions(+), 51 deletions(-)
delete mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/LDAPAuthoritiesMapper.java
diff --git a/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/CompositeSnapshot.java b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/CompositeSnapshot.java
index 02246a8cf0..90db4a3b15 100644
--- a/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/CompositeSnapshot.java
+++ b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/CompositeSnapshot.java
@@ -45,6 +45,19 @@ public void setCompositeSnapshotData(CompositeSnapshotData compositeSnapshotData
this.compositeSnapshotData = compositeSnapshotData;
}
+ @Override
+ public boolean equals(Object other){
+ if(!(other instanceof CompositeSnapshot)){
+ return false;
+ }
+ return compositeSnapshotNode.equals(((CompositeSnapshot) other).getCompositeSnapshotNode());
+ }
+
+ @Override
+ public int hashCode(){
+ return compositeSnapshotNode.hashCode();
+ }
+
public static CompositeSnapshot.Builder builder(){
return new CompositeSnapshot.Builder();
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/LDAPAuthoritiesMapper.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/LDAPAuthoritiesMapper.java
deleted file mode 100644
index 12f7d3c7ff..0000000000
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/LDAPAuthoritiesMapper.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2020 European Spallation Source ERIC.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- */
-
-package org.phoebus.service.saveandrestore.web.config;
-
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
-
-import java.util.Collection;
-
-public class LDAPAuthoritiesMapper implements GrantedAuthoritiesMapper {
-
- @Override
- public Collection extends GrantedAuthority> mapAuthorities(Collection extends GrantedAuthority> authorities) {
-
-
- return null;
- }
-}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
index 9ec997317d..78c37aeb64 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
@@ -167,7 +167,6 @@ public void configure(AuthenticationManagerBuilder auth) throws Exception {
if (ldap_user_search_base != null && !ldap_user_search_base.isEmpty()) {
configurer.userSearchBase(ldap_user_search_base);
}
- //configurer.authoritiesMapper(new LDAPAuthoritiesMapper());
configurer.contextSource(contextSource);
break;
case "ldap_embedded":
@@ -190,7 +189,8 @@ public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser(demoAdmin).password(encoder().encode(demoAdminPassword)).roles(roleAdmin()).and()
.withUser(demoUser).password(encoder().encode(demoUserPassword)).roles(roleUser()).and()
- .withUser(demoSuperuser).password(encoder().encode(demoSuperuserPassword)).roles(roleSuperuser());
+ .withUser(demoSuperuser).password(encoder().encode(demoSuperuserPassword)).roles(roleSuperuser()).and()
+ .withUser(demoReadOnly).password(encoder().encode(demoReadOnlyPassword)).roles();
break;
default:
Logger.getLogger(WebSecurityConfig.class.getName())
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
index 32461f63d1..f5ef4200a3 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
@@ -19,12 +19,10 @@
package org.phoebus.service.saveandrestore.web.controllers;
-import org.phoebus.applications.saveandrestore.model.CompositeSnapshot;
-import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData;
-import org.phoebus.applications.saveandrestore.model.Node;
-import org.phoebus.applications.saveandrestore.model.SnapshotItem;
+import org.phoebus.applications.saveandrestore.model.*;
import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@@ -44,20 +42,33 @@ public class CompositeSnapshotController extends BaseController {
private NodeDAO nodeDAO;
@PutMapping(value = "/composite-snapshot", produces = JSON)
+ @PreAuthorize("hasRole(this.roleUser)")
public CompositeSnapshot createCompositeSnapshot(@RequestParam(value = "parentNodeId") String parentNodeId,
@RequestBody CompositeSnapshot compositeSnapshot,
Principal principal) {
+ if(!compositeSnapshot.getCompositeSnapshotNode().getNodeType().equals(NodeType.COMPOSITE_SNAPSHOT)){
+ throw new IllegalArgumentException("Composite snapshot node of wrong type");
+ }
compositeSnapshot.getCompositeSnapshotNode().setUserName(principal.getName());
return nodeDAO.createCompositeSnapshot(parentNodeId, compositeSnapshot);
}
@PostMapping(value = "/composite-snapshot", produces = JSON)
+ @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and this.mayUpdate(#compositeSnapshot, #principal))")
public CompositeSnapshot updateCompositeSnapshot(@RequestBody CompositeSnapshot compositeSnapshot,
Principal principal) {
+ if(!compositeSnapshot.getCompositeSnapshotNode().getNodeType().equals(NodeType.COMPOSITE_SNAPSHOT)){
+ throw new IllegalArgumentException("Composite snapshot node of wrong type");
+ }
compositeSnapshot.getCompositeSnapshotNode().setUserName(principal.getName());
return nodeDAO.updateCompositeSnapshot(compositeSnapshot);
}
+ public boolean mayUpdate(CompositeSnapshot compositeSnapshot, Principal principal){
+ Node node = nodeDAO.getNode(compositeSnapshot.getCompositeSnapshotNode().getUniqueId());
+ return node.getUserName().equals(principal.getName());
+ }
+
@GetMapping(value = "/composite-snapshot/{uniqueId}", produces = JSON)
public CompositeSnapshotData getCompositeSnapshotData(@PathVariable String uniqueId) {
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java
index 4dd0979d39..1d68817832 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java
@@ -34,6 +34,7 @@
import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.HttpHeaders;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
@@ -56,9 +57,24 @@
@WebMvcTest(NodeController.class)
public class CompositeSnapshotControllerTest {
+ @Autowired
+ private String userAuthorization;
+
+ @Autowired
+ private String superuserAuthorization;
+
+ @Autowired
+ private String adminAuthorization;
+
+ @Autowired
+ private String readOnlyAuthorization;
+
@Autowired
private NodeDAO nodeDAO;
+ @Autowired
+ private String demoUser;
+
@Autowired
private MockMvc mockMvc;
@@ -81,8 +97,12 @@ public void testCreateCompositeSnapshot() throws Exception {
when(nodeDAO.createCompositeSnapshot(Mockito.any(String.class), Mockito.any(CompositeSnapshot.class))).thenReturn(compositeSnapshot);
- MockHttpServletRequestBuilder request = put("/composite-snapshot?parentNodeId=id").contentType(JSON)
- .content(objectMapper.writeValueAsString(compositeSnapshot));
+ String compositeSnapshotString = objectMapper.writeValueAsString(compositeSnapshot);
+
+ MockHttpServletRequestBuilder request = put("/composite-snapshot?parentNodeId=id")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(compositeSnapshotString);
MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))
.andReturn();
@@ -91,16 +111,65 @@ public void testCreateCompositeSnapshot() throws Exception {
// Make sure response contains expected data
objectMapper.readValue(s, CompositeSnapshot.class);
+ request = put("/composite-snapshot?parentNodeId=id")
+ .header(HttpHeaders.AUTHORIZATION, superuserAuthorization)
+ .contentType(JSON)
+ .content(compositeSnapshotString);
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = put("/composite-snapshot?parentNodeId=id")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON)
+ .content(compositeSnapshotString);
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = put("/composite-snapshot?parentNodeId=id")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON)
+ .content(compositeSnapshotString);
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = put("/composite-snapshot?parentNodeId=id")
+ .contentType(JSON)
+ .content(compositeSnapshotString);
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
reset(nodeDAO);
}
+ @Test
+ public void testCreateCompositeSnapshotWrongNodeType() throws Exception{
+ Node node = Node.builder().uniqueId("c").nodeType(NodeType.SNAPSHOT).build();
+ CompositeSnapshot compositeSnapshot1 = new CompositeSnapshot();
+ compositeSnapshot1.setCompositeSnapshotNode(node);
+
+ MockHttpServletRequestBuilder request = put("/composite-snapshot")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(compositeSnapshot1));
+ mockMvc.perform(request).andExpect(status().isBadRequest());
+ }
+
@Test
public void testUpdateCompositeSnapshot() throws Exception {
- when(nodeDAO.updateCompositeSnapshot(Mockito.any(CompositeSnapshot.class))).thenReturn(compositeSnapshot);
+ Node node = Node.builder().uniqueId("c").nodeType(NodeType.COMPOSITE_SNAPSHOT).userName(demoUser).build();
+ CompositeSnapshot compositeSnapshot1 = new CompositeSnapshot();
+ compositeSnapshot1.setCompositeSnapshotNode(node);
+
+ String compositeSnapshotString = objectMapper.writeValueAsString(compositeSnapshot1);
- MockHttpServletRequestBuilder request = post("/composite-snapshot").contentType(JSON)
- .content(objectMapper.writeValueAsString(compositeSnapshot));
+ when(nodeDAO.updateCompositeSnapshot(compositeSnapshot1)).thenReturn(compositeSnapshot1);
+ when(nodeDAO.getNode("c")).thenReturn(node);
+
+ MockHttpServletRequestBuilder request = post("/composite-snapshot")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(compositeSnapshotString);
MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))
.andReturn();
@@ -109,9 +178,62 @@ public void testUpdateCompositeSnapshot() throws Exception {
// Make sure response contains expected data
objectMapper.readValue(s, CompositeSnapshot.class);
+ when(nodeDAO.getNode("c")).thenReturn(Node.builder().nodeType(NodeType.COMPOSITE_SNAPSHOT).uniqueId("c").userName("notUser").build());
+
+ request = post("/composite-snapshot")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(compositeSnapshotString);
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = post("/composite-snapshot")
+ .header(HttpHeaders.AUTHORIZATION, superuserAuthorization)
+ .contentType(JSON)
+ .content(compositeSnapshotString);
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = post("/composite-snapshot")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON)
+ .content(compositeSnapshotString);
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = post("/composite-snapshot")
+ .contentType(JSON)
+ .content(compositeSnapshotString);
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ when(nodeDAO.getNode("c")).thenReturn(Node.builder().nodeType(NodeType.COMPOSITE_SNAPSHOT).uniqueId("c").userName("notUser").build());
+
+ request = post("/composite-snapshot")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON)
+ .content(compositeSnapshotString);
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
reset(nodeDAO);
}
+ @Test
+ public void testUpdateCompositeSnapshotWrongNodeType() throws Exception{
+ Node node = Node.builder().uniqueId("c").userName(demoUser).nodeType(NodeType.SNAPSHOT).build();
+ CompositeSnapshot compositeSnapshot1 = new CompositeSnapshot();
+ compositeSnapshot1.setCompositeSnapshotNode(node);
+
+ when(nodeDAO.getNode("c")).thenReturn(node);
+
+ MockHttpServletRequestBuilder request = post("/composite-snapshot")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(compositeSnapshot1));
+ mockMvc.perform(request).andExpect(status().isBadRequest());
+ }
+
@Test
public void testGetCompositeSnapshotData() throws Exception {
CompositeSnapshotData compositeSnapshotData = new CompositeSnapshotData();
@@ -178,7 +300,9 @@ public void testGetCompositeSnapshotConsistency() throws Exception {
when(nodeDAO.checkForPVNameDuplicates(Mockito.any(List.class))).thenReturn(List.of("ref"));
- MockHttpServletRequestBuilder request = get("/composite-snapshot-consistency-check").contentType(JSON)
+ MockHttpServletRequestBuilder request = post("/composite-snapshot-consistency-check")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
.content(objectMapper.writeValueAsString(List.of("id")));
MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))
@@ -189,6 +313,34 @@ public void testGetCompositeSnapshotConsistency() throws Exception {
objectMapper.readValue(s, new TypeReference>() {
});
+ request = post("/composite-snapshot-consistency-check")
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("id")));
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+
+ request = post("/composite-snapshot-consistency-check")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("id")));
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = post("/composite-snapshot-consistency-check")
+ .header(HttpHeaders.AUTHORIZATION, superuserAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("id")));
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = post("/composite-snapshot-consistency-check")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("id")));
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
reset(nodeDAO);
}
}
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java
index 23e096895c..a5203fa766 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java
@@ -23,9 +23,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
-import org.phoebus.applications.saveandrestore.model.Configuration;
import org.phoebus.applications.saveandrestore.model.Node;
import org.phoebus.applications.saveandrestore.model.NodeType;
import org.phoebus.applications.saveandrestore.model.Snapshot;
@@ -41,11 +39,11 @@
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+import static org.mockito.Mockito.when;
import static org.phoebus.service.saveandrestore.web.controllers.BaseController.JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-import static org.mockito.Mockito.*;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ControllersTestConfig.class)
@@ -155,7 +153,7 @@ public void testSaveNewSnapshot() throws Exception {
}
@Test
- public void testUpdateSnapshot() throws Exception{
+ public void testUpdateSnapshot() throws Exception {
Node node = Node.builder().uniqueId("s").nodeType(NodeType.SNAPSHOT).userName(demoUser).build();
Snapshot snapshot = new Snapshot();
snapshot.setSnapshotNode(node);
From fbe19d29d659c3c55f9f41a990a0e24cabb26915 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Fri, 13 Oct 2023 09:47:22 +0200
Subject: [PATCH 29/42] Authorization for save&restore filters
---
.../CompositeSnapshotController.java | 12 +++
.../web/controllers/FilterController.java | 27 +++++-
.../web/controllers/SnapshotController.java | 12 +++
.../web/controllers/FilterControllerTest.java | 95 +++++++++++++++++--
.../controllers/SnapshotControllerTest.java | 5 -
5 files changed, 139 insertions(+), 12 deletions(-)
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
index f5ef4200a3..9a4cbb6594 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
@@ -64,6 +64,18 @@ public CompositeSnapshot updateCompositeSnapshot(@RequestBody CompositeSnapshot
return nodeDAO.updateCompositeSnapshot(compositeSnapshot);
}
+ /**
+ * NOTE: this method MUST be public!
+ *
+ *
+ * An authenticated user may save a composite snapshot, and update if user identity is same as the target's
+ * composite snapshot {@link Node}.
+ *
+ *
+ * @param compositeSnapshot {@link CompositeSnapshot} identifying the target of the user's update operation.
+ * @param principal Identifies user.
+ * @return false if user may not update the {@link CompositeSnapshot}.
+ */
public boolean mayUpdate(CompositeSnapshot compositeSnapshot, Principal principal){
Node node = nodeDAO.getNode(compositeSnapshot.getCompositeSnapshotNode().getUniqueId());
return node.getUserName().equals(principal.getName());
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java
index 6e58e541d4..9667fce494 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java
@@ -22,10 +22,12 @@
import org.phoebus.applications.saveandrestore.model.search.Filter;
import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
import java.util.List;
+import java.util.Optional;
@RestController
public class FilterController extends BaseController {
@@ -43,12 +45,34 @@ public class FilterController extends BaseController {
*/
@SuppressWarnings("unused")
@PutMapping(value = "/filter", produces = JSON)
+ @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and this.maySaveOrDelete(#filter.getName(), #principal))")
public Filter saveFilter(@RequestBody final Filter filter,
Principal principal) {
filter.setUser(principal.getName());
return nodeDAO.saveFilter(filter);
}
+
+ /**
+ * NOTE: this method MUST be public!
+ *
+ *
+ * An authenticated user may save a filter, and update if user identity is same as the target's
+ * name field.
+ *
+ *
+ * @param filterName Unique name identifying the target of the user's update operation.
+ * @param principal Identifies user.
+ * @return false if user may not update the {@link Filter}.
+ */
+ @SuppressWarnings("unused")
+ public boolean maySaveOrDelete(String filterName, Principal principal) {
+ Optional filter1 =
+ nodeDAO.getAllFilters().stream().filter(f ->
+ f.getName().equals(filterName)).findFirst();
+ return filter1.map(value -> value.getUser().equals(principal.getName())).orElse(true);
+ }
+
@SuppressWarnings("unused")
@GetMapping(value = "/filters", produces = JSON)
public List getAllFilters() {
@@ -57,7 +81,8 @@ public List getAllFilters() {
@SuppressWarnings("unused")
@DeleteMapping(value = "/filter/{name}")
- public void deleteFilter(@PathVariable final String name) {
+ @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and this.maySaveOrDelete(#name, #principal))")
+ public void deleteFilter(@PathVariable final String name, Principal principal) {
nodeDAO.deleteFilter(name);
}
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java
index d99f5de0e4..c13da7658a 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java
@@ -58,6 +58,18 @@ public Snapshot saveSnapshot(@RequestParam(value = "parentNodeId") String parent
return nodeDAO.saveSnapshot(parentNodeId, snapshot);
}
+ /**
+ * NOTE: this method MUST be public!
+ *
+ *
+ * An authenticated user may save a snapshot, and update if user identity is same as the target's
+ * snapshot {@link Node}.
+ *
+ *
+ * @param snapshot {@link Snapshot} identifying the target of the user's update operation.
+ * @param principal Identifies user.
+ * @return false if user may not update the {@link Snapshot}.
+ */
public boolean maySave(Snapshot snapshot, Principal principal){
if(snapshot.getSnapshotNode().getUniqueId() == null){
return true;
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java
index 1cc9d41e92..6478b33f37 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java
@@ -28,6 +28,7 @@
import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.HttpHeaders;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
@@ -39,15 +40,14 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import static org.phoebus.service.saveandrestore.web.controllers.BaseController.JSON;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ControllersTestConfig.class)
@WebMvcTest(NodeController.class)
+
public class FilterControllerTest {
@Autowired
@@ -59,6 +59,21 @@ public class FilterControllerTest {
@Autowired
private ObjectMapper objectMapper;
+ @Autowired
+ private String userAuthorization;
+
+ @Autowired
+ private String superuserAuthorization;
+
+ @Autowired
+ private String adminAuthorization;
+
+ @Autowired
+ private String readOnlyAuthorization;
+
+ @Autowired
+ private String demoUser;
+
@Test
public void testSaveFilter() throws Exception {
@@ -66,10 +81,14 @@ public void testSaveFilter() throws Exception {
filter.setName("name");
filter.setQueryString("query");
+ String filterString = objectMapper.writeValueAsString(filter);
+
when(nodeDAO.saveFilter(Mockito.any(Filter.class))).thenReturn(filter);
- MockHttpServletRequestBuilder request = put("/filter").contentType(JSON)
- .content(objectMapper.writeValueAsString(filter));
+ MockHttpServletRequestBuilder request = put("/filter")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(filterString);
MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))
.andReturn();
@@ -77,12 +96,76 @@ public void testSaveFilter() throws Exception {
String s = result.getResponse().getContentAsString();
// Make sure response contains expected data
objectMapper.readValue(s, Filter.class);
+
+ request = put("/filter")
+ .header(HttpHeaders.AUTHORIZATION, superuserAuthorization)
+ .contentType(JSON)
+ .content(filterString);
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = put("/filter")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON)
+ .content(filterString);
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = put("/filter")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON)
+ .content(filterString);
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = put("/filter")
+ .contentType(JSON)
+ .content(filterString);
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
}
@Test
public void testDeleteFilter() throws Exception {
- MockHttpServletRequestBuilder request = delete("/filter/name").contentType(JSON);
+ Filter filter = new Filter();
+ filter.setName("name");
+ filter.setQueryString("query");
+ filter.setUser(demoUser);
+
+ when(nodeDAO.getAllFilters()).thenReturn(List.of(filter));
+
+ MockHttpServletRequestBuilder request = delete("/filter/name")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON);
mockMvc.perform(request).andExpect(status().isOk());
+
+ request = delete("/filter/name")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON);
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = delete("/filter/name")
+ .header(HttpHeaders.AUTHORIZATION, superuserAuthorization)
+ .contentType(JSON);
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = delete("/filter/name")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON);
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = delete("/filter/name")
+ .contentType(JSON);
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ filter.setUser("notUser");
+ when(nodeDAO.getAllFilters()).thenReturn(List.of(filter));
+
+ request = delete("/filter/name")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON);
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
}
@Test
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java
index a5203fa766..2eb8b0e582 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java
@@ -74,11 +74,6 @@ public class SnapshotControllerTest {
@Autowired
private String demoUser;
- @Autowired
- private String demoSuperuser;
-
- @Autowired
- private String demoAdmin;
@Test
public void testSaveSnapshotWrongNodeType() throws Exception {
From 8406c85c7a8d1fbb6bd73790f087a393c64e7937 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Fri, 13 Oct 2023 10:05:58 +0200
Subject: [PATCH 30/42] Clarifications in docs about save&restore authorization
details
---
app/save-and-restore/app/doc/index.rst | 16 ++++++++++------
services/save-and-restore/doc/index.rst | 14 +++++++++-----
2 files changed, 19 insertions(+), 11 deletions(-)
diff --git a/app/save-and-restore/app/doc/index.rst b/app/save-and-restore/app/doc/index.rst
index 970a3c1763..eb90d00997 100644
--- a/app/save-and-restore/app/doc/index.rst
+++ b/app/save-and-restore/app/doc/index.rst
@@ -400,13 +400,17 @@ Authentication and Authorization
Authorization uses a role-based approach like so:
-* Unauthenticated users may read data, i.e. browse the tree and view configurations and snapshots.
+* Unauthenticated users may read data, i.e. browse the tree and view configurations, snapshots, search and view filters.
* Role "user":
- * Create/update and save configurations
- * Take and save snapshots.
- * Delete nodes if user id matches and:
- * Node is a snapshot
- * Node is configuration or folder with no child nodes
+ * Create and save configurations
+ * Create and save snapshots
+ * Create and save composite snapshots
+ * Create and save filters
+ * Update and delete objects if user name matches object's user id and:
+ * Object is a snapshot and not referenced in a composite snapshot node
+ * Object is a composite snapshot node
+ * Object is configuration or folder node with no child nodes
+ * Object is a filter
* Role "superuser": +perform restore operation
* Role "admin": no restrictions
diff --git a/services/save-and-restore/doc/index.rst b/services/save-and-restore/doc/index.rst
index d7d56dc9fe..ef6eb8273d 100644
--- a/services/save-and-restore/doc/index.rst
+++ b/services/save-and-restore/doc/index.rst
@@ -735,11 +735,15 @@ Authorization uses a role-based approach like so:
* Unauthenticated users may read data, i.e. access GET endpoints.
* Role "user":
- * Create/update and save configurations
- * Take and save snapshots.
- * Delete nodes if user id matches and:
- * Node is a snapshot
- * Node is configuration or folder with no child nodes
+ * Create and save configurations
+ * Create and save snapshots.
+ * Create and save composite snapshots
+ * Create and save filters
+ * Update and delete objects if user name matches object's user id and:
+ * Object is a snapshot node and not referenced in a composite snapshot node
+ * Object is a composite snapshot node
+ * Object is configuration or folder node with no child nodes
+ * Object is a filter
* Role "superuser": +perform restore operation
* Role "admin": no restrictions
From 57d53ef7d7e943700d5b7bd3c50ed8c6f4fca326 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Fri, 13 Oct 2023 13:00:24 +0200
Subject: [PATCH 31/42] Save&restore delete operation must check if all nodes
selected by user may be deleted
---
.../client/SaveAndRestoreClient.java | 2 --
.../client/SaveAndRestoreJerseyClient.java | 14 ++++-----
.../ui/SaveAndRestoreService.java | 6 ++--
services/save-and-restore/doc/index.rst | 2 +-
.../persistence/dao/NodeDAO.java | 12 ++++++--
.../impl/elasticsearch/ElasticsearchDAO.java | 29 +++++++++++++++----
.../web/controllers/NodeController.java | 25 ++++++++++++++++
7 files changed, 66 insertions(+), 24 deletions(-)
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java
index 6576c92155..600a8e5f9f 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java
@@ -95,8 +95,6 @@ public interface SaveAndRestoreClient {
*/
Node updateNode(Node nodeToUpdate, boolean customTimeForMigration);
- void deleteNode(String uniqueNodeId);
-
/**
* Deletes a list of {@link Node}s
*
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java
index 827855bb17..0e3313f42a 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java
@@ -22,7 +22,6 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
-
import com.sun.jersey.api.client.*;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
@@ -246,20 +245,17 @@ private ClientResponse getCall(String relativeUrl) {
}
@Override
- public void deleteNode(String uniqueNodeId) {
- WebResource webResource = getClient().resource(jmasarServiceUrl + "/node/" + uniqueNodeId);
- ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).delete(ClientResponse.class);
+ public void deleteNodes(List nodeIds) {
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/node");
+ ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
+ .entity(nodeIds, CONTENT_TYPE_JSON)
+ .delete(ClientResponse.class);
if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = response.getEntity(String.class);
throw new SaveAndRestoreClientException("Failed : HTTP error code : " + response.getStatus() + ", error message: " + message);
}
}
- @Override
- public void deleteNodes(List nodeIds) {
- nodeIds.forEach(this::deleteNode);
- }
-
@Override
public List getAllTags() {
ClientResponse response = getCall("/tags");
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java
index dc664c7638..553890a217 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java
@@ -313,7 +313,7 @@ public Filter saveFilter(Filter filter) throws Exception {
*/
public List getAllFilters() throws Exception {
Future> future =
- executor.submit(() -> saveAndRestoreClient.getAllFilters());
+ executor.submit(saveAndRestoreClient::getAllFilters);
return future.get();
}
@@ -338,7 +338,7 @@ public List addTag(TagData tagData) throws Exception {
Future> future =
executor.submit(() -> saveAndRestoreClient.addTag(tagData));
List updatedNodes = future.get();
- updatedNodes.forEach(n -> notifyNodeChangeListeners(n));
+ updatedNodes.forEach(this::notifyNodeChangeListeners);
return updatedNodes;
}
@@ -353,7 +353,7 @@ public List deleteTag(TagData tagData) throws Exception {
Future> future =
executor.submit(() -> saveAndRestoreClient.deleteTag(tagData));
List updatedNodes = future.get();
- updatedNodes.forEach(n -> notifyNodeChangeListeners(n));
+ updatedNodes.forEach(this::notifyNodeChangeListeners);
return updatedNodes;
}
diff --git a/services/save-and-restore/doc/index.rst b/services/save-and-restore/doc/index.rst
index ef6eb8273d..0e96b56cb5 100644
--- a/services/save-and-restore/doc/index.rst
+++ b/services/save-and-restore/doc/index.rst
@@ -736,7 +736,7 @@ Authorization uses a role-based approach like so:
* Unauthenticated users may read data, i.e. access GET endpoints.
* Role "user":
* Create and save configurations
- * Create and save snapshots.
+ * Create and save snapshots
* Create and save composite snapshots
* Create and save filters
* Update and delete objects if user name matches object's user id and:
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/NodeDAO.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/NodeDAO.java
index af68d8122f..a9814db4df 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/NodeDAO.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/NodeDAO.java
@@ -63,14 +63,20 @@ public interface NodeDAO {
List getNodes(List uniqueNodeIds);
/**
- * Deletes a {@link Node}, folder or configuration. If the node is a folder, the
- * entire sub-tree of the folder is deleted, including the snapshots associated
- * with configurations in the sub-tree.
+ * This is deprecated, use {@link #deleteNodes} instead.
*
* @param nodeId The unique id of the node to delete.
*/
+ @Deprecated
void deleteNode(String nodeId);
+ /**
+ * Checks that each of the node ids passed to this method exist, and that none of them
+ * is the root node. If check passes all nodes are deleted.
+ * @param nodeIds List of (existing) node ids.
+ */
+ void deleteNodes(List nodeIds);
+
/**
* Creates a new node in the tree.
*
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAO.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAO.java
index 78bba6ae03..17e389f9dc 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAO.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAO.java
@@ -42,6 +42,7 @@
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
+import java.lang.annotation.Inherited;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -124,15 +125,31 @@ public List getNodes(List uniqueNodeIds) {
return nodes;
}
+ /**
+ * {@inheritDoc}
+ */
@Override
+ @Deprecated
public void deleteNode(String nodeId) {
- Node nodeToDelete = getNode(nodeId);
- if (nodeToDelete == null) {
- throw new NodeNotFoundException("Cannot delete non-existing node");
- } else if (nodeToDelete.getUniqueId().equals(ROOT_FOLDER_UNIQUE_ID)) {
- throw new IllegalArgumentException("Root node cannot be deleted");
+ deleteNodes(List.of(nodeId));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void deleteNodes(List nodeIds){
+ List nodes = new ArrayList<>();
+ for(String nodeId : nodeIds){
+ Node nodeToDelete = getNode(nodeId);
+ if (nodeToDelete == null) {
+ throw new NodeNotFoundException("Cannot delete non-existing node");
+ } else if (nodeToDelete.getUniqueId().equals(ROOT_FOLDER_UNIQUE_ID)) {
+ throw new IllegalArgumentException("Root node cannot be deleted");
+ }
+ nodes.add(nodeToDelete);
}
- deleteNode(nodeToDelete);
+ nodes.forEach(this::deleteNode);
}
@Override
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
index 1816a6c0f7..05a361cf2d 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
@@ -132,11 +132,36 @@ public List getChildNodes(@PathVariable final String uniqueNodeId) {
@SuppressWarnings("unused")
@DeleteMapping(value = "/node/{uniqueNodeId}", produces = JSON)
@PreAuthorize("hasRole(this.roleAdmin) or this.mayDelete(#uniqueNodeId, #principal)")
+ @Deprecated
public void deleteNode(@PathVariable final String uniqueNodeId, Principal principal) {
logger.info("Deleting node with unique id " + uniqueNodeId);
nodeDAO.deleteNode(uniqueNodeId);
}
+ @SuppressWarnings("unused")
+ @DeleteMapping(value = "/node", produces = JSON)
+ @PreAuthorize("hasRole(this.roleAdmin) or this.mayDelete(#nodeIds, #principal)")
+ public void deleteNodes(@RequestBody List nodeIds, Principal principal) {
+ nodeDAO.deleteNodes(nodeIds);
+ }
+
+ /**
+ * NOTE: this method MUST be public!
+ *
+ * Checks if all the nodes provided to this method can be deleted by the user.
+ *
+ * @return true only if all if the nodes can be deleted by the user.
+ */
+ @SuppressWarnings("unused")
+ public boolean mayDelete(List nodeIds, Principal principal){
+ for (String nodeId : nodeIds){
+ if(!mayDelete(nodeId, principal)){
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* NOTE: this method MUST be public!
*
From b81169a6f899c0093f5d5619b94dadbf0a6c0643 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Mon, 16 Oct 2023 09:55:03 +0200
Subject: [PATCH 32/42] Minor bug fixes in save&restore tree view tool tip
---
.../saveandrestore/ui/BrowserTreeCell.java | 13 +++++++++----
.../saveandrestore/ui/SaveAndRestoreController.java | 12 ++++++------
.../saveandrestore/ui/SaveAndRestoreService.java | 2 +-
.../dao/impl/elasticsearch/ElasticsearchDAO.java | 3 ++-
.../web/controllers/ConfigurationController.java | 3 ++-
5 files changed, 20 insertions(+), 13 deletions(-)
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java
index 23eb555205..a90b98c556 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java
@@ -21,9 +21,7 @@
import javafx.application.Platform;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
-import javafx.scene.input.ClipboardContent;
-import javafx.scene.input.Dragboard;
-import javafx.scene.input.TransferMode;
+import javafx.scene.input.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import org.phoebus.applications.saveandrestore.Messages;
@@ -126,6 +124,12 @@ public BrowserTreeCell(SaveAndRestoreController saveAndRestoreController) {
event.setDropCompleted(true);
event.consume();
});
+
+ // This is to suppress expansion of the TreeItem on double-click.
+ addEventFilter(MouseEvent.MOUSE_PRESSED, (MouseEvent e) -> {
+ if (e.getClickCount() % 2 == 0 && e.getButton().equals(MouseButton.PRIMARY))
+ e.consume();
+ });
}
@Override
@@ -150,7 +154,8 @@ public void updateItem(Node node, boolean empty) {
stringBuilder.append(comment).append(System.lineSeparator());
}
if (node.getCreated() != null) { // Happens if configuration management is accessed from context menu
- stringBuilder.append(TimestampFormats.SECONDS_FORMAT.format(node.getCreated().toInstant())).append(" (").append(node.getUserName()).append(")");
+ stringBuilder.append(TimestampFormats.SECONDS_FORMAT
+ .format(node.getLastModified() != null ? node.getLastModified().toInstant() : node.getCreated().toInstant())).append(" (").append(node.getUserName()).append(")");
}
// Tooltip with at least date and user id is set on all tree items
setTooltip(new Tooltip(stringBuilder.toString()));
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
index 67ce603901..dfae860eb7 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java
@@ -757,12 +757,12 @@ public void nodeChanged(Node node) {
}
nodeSubjectToUpdate.setValue(node);
// Folder and configuration node changes may include structure changes, so expand to force update.
- if (nodeSubjectToUpdate.getValue().getNodeType().equals(NodeType.FOLDER) ||
- nodeSubjectToUpdate.getValue().getNodeType().equals(NodeType.CONFIGURATION)) {
- if (nodeSubjectToUpdate.getParent() != null) { // null means root folder as it has no parent
- nodeSubjectToUpdate.getParent().getChildren().sort(treeNodeComparator);
- }
- expandTreeNode(nodeSubjectToUpdate);
+ if(nodeSubjectToUpdate.isExpanded() && (nodeSubjectToUpdate.getValue().getNodeType().equals(NodeType.FOLDER) ||
+ nodeSubjectToUpdate.getValue().getNodeType().equals(NodeType.CONFIGURATION))){
+ if (nodeSubjectToUpdate.getParent() != null) { // null means root folder as it has no parent
+ nodeSubjectToUpdate.getParent().getChildren().sort(treeNodeComparator);
+ }
+ expandTreeNode(nodeSubjectToUpdate);
}
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java
index 553890a217..37637f018d 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java
@@ -145,7 +145,7 @@ public Configuration updateConfiguration(Configuration configuration) throws Exc
Future future = executor.submit(() -> saveAndRestoreClient.updateConfiguration(configuration));
Configuration updatedConfiguration = future.get();
// Associated configuration Node may have a new name
- notifyNodeChangeListeners(configuration.getConfigurationNode());
+ notifyNodeChangeListeners(updatedConfiguration.getConfigurationNode());
return updatedConfiguration;
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAO.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAO.java
index 17e389f9dc..b46afb6d9b 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAO.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAO.java
@@ -697,9 +697,10 @@ public Configuration updateConfiguration(Configuration configuration) {
Node existingConfigurationNode = getNode(configuration.getConfigurationNode().getUniqueId());
- // Set name and description, even if unchanged.
+ // Set name, description and user even if unchanged.
existingConfigurationNode.setName(configuration.getConfigurationNode().getName());
existingConfigurationNode.setDescription(configuration.getConfigurationNode().getDescription());
+ existingConfigurationNode.setUserName(configuration.getConfigurationNode().getUserName());
// Update last modified date
existingConfigurationNode.setLastModified(new Date());
existingConfigurationNode = updateNode(existingConfigurationNode, false);
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java
index 47d1f7151c..462d845e61 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java
@@ -64,7 +64,8 @@ public ConfigurationData getConfigurationData(@PathVariable String uniqueId) {
public Configuration updateConfiguration(@RequestBody Configuration configuration,
Principal principal) {
configuration.getConfigurationNode().setUserName(principal.getName());
- return nodeDAO.updateConfiguration(configuration);
+ Configuration c = nodeDAO.updateConfiguration(configuration);
+ return c;
}
/**
From 247dd27374455a6d4b546264dae078fd2a2bac53 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Fri, 20 Oct 2023 22:01:20 +0200
Subject: [PATCH 33/42] Fix failing unit test
---
.../saveandrestore/web/controllers/FilterControllerTest.java | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java
index 6478b33f37..e0019d4fe9 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java
@@ -38,6 +38,7 @@
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
import static org.phoebus.service.saveandrestore.web.controllers.BaseController.JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
@@ -77,9 +78,12 @@ public class FilterControllerTest {
@Test
public void testSaveFilter() throws Exception {
+ reset(nodeDAO);
+
Filter filter = new Filter();
filter.setName("name");
filter.setQueryString("query");
+ filter.setUser("user");
String filterString = objectMapper.writeValueAsString(filter);
From e329987296c7a68d03d863c19973338c216a0a28 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Sat, 21 Oct 2023 12:37:30 +0200
Subject: [PATCH 34/42] Fixing additional build issues
---
.../saveandrestore/web/config/WebSecurityConfig.java | 4 +---
.../saveandrestore/web/controllers/NodeController.java | 6 +++---
.../saveandrestore/web/controllers/TagController.java | 1 +
.../web/controllers/NodeControllerTest.java | 2 ++
.../web/controllers/TagControllerTest.java | 9 +++++++++
5 files changed, 16 insertions(+), 6 deletions(-)
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
index 78c37aeb64..5a9e6c2767 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
@@ -279,12 +279,10 @@ public String demoReadOnlyPassword(){
/**
- * Configures role hierarchy, i.e. user -> superuser -> admin. Do not remove this {@link Bean}!
- *
+ * Configures role hierarchy, i.e. user - superuser - admin. Do not remove this {@link Bean}!
*
NOTE!
* Some Spring Security documentation will state that "and" can be used instead of new-line char to
* separate rule items. But that does NOT work, at least not with the Spring Security version used in this project.
- *
* @return A {@link RoleHierarchy} object.
*/
@Bean
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
index 05a361cf2d..c395268d9a 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
@@ -128,6 +128,7 @@ public List getChildNodes(@PathVariable final String uniqueNodeId) {
*
*
* @param uniqueNodeId The non-zero id of the node to delete
+ * @param principal {@link Principal} of authenticated user.
*/
@SuppressWarnings("unused")
@DeleteMapping(value = "/node/{uniqueNodeId}", produces = JSON)
@@ -149,7 +150,8 @@ public void deleteNodes(@RequestBody List nodeIds, Principal principal)
* NOTE: this method MUST be public!
*
* Checks if all the nodes provided to this method can be deleted by the user.
- *
+ * @param nodeIds The list of {@link Node} ids subject to the check.
+ * @param principal {@link Principal} of authenticated user.
* @return true only if all if the nodes can be deleted by the user.
*/
@SuppressWarnings("unused")
@@ -164,13 +166,11 @@ public boolean mayDelete(List nodeIds, Principal principal){
/**
* NOTE: this method MUST be public!
- *
* An authenticated user may delete a node if User identity is same as the target {@link Node}'s user id and:
*
*
Target {@link Node} is a snapshot.
*
Target {@link Node} is not a snapshot, but has no child nodes.
*
- *
*
* @param nodeId Unique node id identifying the target of the user's delete operation.
* @param principal Identifies user.
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java
index efd7e4f958..a9bc15c447 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java
@@ -53,6 +53,7 @@ public List getTags() {
* Adds a {@link Tag} to specified list of target {@link Node}s. The {@link Tag} contained
* in tagData must be non-null, and its name must be non-null and non-empty.
* @param tagData See {@link TagData}
+ * @param principal {@link Principal} of authenticated user.
* @return The list of updated {@link Node}s
*/
@PostMapping("/tags")
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java
index 5ee30aa4e5..b37367f46b 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java
@@ -352,6 +352,8 @@ public void testDeleteFolder() throws Exception {
mockMvc.perform(request).andExpect(status().isForbidden());
+ when(nodeDAO.getNode("a")).thenReturn(Node.builder().uniqueId("a").userName(demoUser).build());
+
request =
delete("/node/a")
.header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization);
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java
index dd78e6becd..46d55cddfa 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java
@@ -30,6 +30,7 @@
import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.HttpHeaders;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
@@ -57,6 +58,9 @@ public class TagControllerTest {
@Autowired
private NodeDAO nodeDAO;
+ @Autowired
+ private String userAuthorization;
+
private ObjectMapper objectMapper = new ObjectMapper();
@Test
@@ -88,6 +92,7 @@ public void testAddTag() throws Exception{
when(nodeDAO.addTag(tagData)).thenReturn(List.of(node));
MockHttpServletRequestBuilder request = post("/tags").contentType(JSON)
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
.content(objectMapper.writeValueAsString(tagData));
MvcResult result = mockMvc.perform(request)
.andExpect(status().isOk()).andExpect(content().contentType(JSON))
@@ -104,6 +109,7 @@ public void testAddTagBadData() throws Exception{
TagData tagData = new TagData();
tagData.setUniqueNodeIds(List.of("uniqueId"));
MockHttpServletRequestBuilder request = post("/tags").contentType(JSON)
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
.content(objectMapper.writeValueAsString(tagData));
mockMvc.perform(request)
.andExpect(status().isBadRequest());
@@ -112,6 +118,7 @@ public void testAddTagBadData() throws Exception{
tag.setName(null);
tagData.setTag(tag);
request = post("/tags").contentType(JSON)
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
.content(objectMapper.writeValueAsString(tagData));
mockMvc.perform(request)
.andExpect(status().isBadRequest());
@@ -119,6 +126,7 @@ public void testAddTagBadData() throws Exception{
tag.setName("");
tagData.setTag(tag);
request = post("/tags").contentType(JSON)
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
.content(objectMapper.writeValueAsString(tagData));
mockMvc.perform(request)
.andExpect(status().isBadRequest());
@@ -127,6 +135,7 @@ public void testAddTagBadData() throws Exception{
tagData.setTag(tag);
tagData.setUniqueNodeIds(null);
request = post("/tags").contentType(JSON)
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
.content(objectMapper.writeValueAsString(tagData));
mockMvc.perform(request)
.andExpect(status().isBadRequest());
From 3af48171f1395a9a4c84e38d45cf4aca29e1dec4 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Fri, 27 Oct 2023 14:34:06 +0200
Subject: [PATCH 35/42] Testing different authorization config for save&restore
---
.../security/UserNotAuthorizedException.java | 27 +++++++++++
.../web/config/WebSecurityConfig.java | 45 +++++++++++++------
.../web/controllers/BaseController.java | 12 ++++-
.../web/controllers/NodeController.java | 12 ++++-
4 files changed, 79 insertions(+), 17 deletions(-)
create mode 100644 app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/security/UserNotAuthorizedException.java
diff --git a/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/security/UserNotAuthorizedException.java b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/security/UserNotAuthorizedException.java
new file mode 100644
index 0000000000..b8aafa19e4
--- /dev/null
+++ b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/security/UserNotAuthorizedException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package org.phoebus.applications.saveandrestore.model.security;
+
+public class UserNotAuthorizedException extends RuntimeException{
+
+ public UserNotAuthorizedException(String message){
+ super(message);
+ }
+}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
index 5a9e6c2767..1e6490662e 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
@@ -22,17 +22,27 @@
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
+import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
@EnableWebSecurity
@Configuration
+
+/*
@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
+
+ */
+
@SuppressWarnings("unused")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@@ -118,13 +128,20 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${demo.readOnly.password:1234}")
private String demoReadOnlyPassword;
+
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
+ http.authorizeRequests().antMatchers(HttpMethod.DELETE, "/node/**").hasRole(roleUser.toUpperCase());
+ http.authorizeRequests().antMatchers(HttpMethod.POST, "/node/**").hasRole(roleUser.toUpperCase());
+ http.authorizeRequests().antMatchers(HttpMethod.PUT, "/node/**").hasRole(roleUser.toUpperCase());
+ http.authorizeRequests().antMatchers(HttpMethod.POST, "/snapshot/**").authenticated();
http.authorizeRequests().anyRequest().authenticated();
http.addFilterBefore(new SessionFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
}
+
+
@Override
public void configure(WebSecurity web) {
// The below lists exceptions for authentication.
@@ -132,10 +149,9 @@ public void configure(WebSecurity web) {
web.ignoring().antMatchers(HttpMethod.POST, "/**/login*");
}
-
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
- switch(authenitcationImplementation){
+ switch (authenitcationImplementation) {
case "ad":
ActiveDirectoryLdapAuthenticationProvider adProvider = new ActiveDirectoryLdapAuthenticationProvider(ad_domain, ad_url);
adProvider.setConvertSubErrorCodesToExceptions(true);
@@ -223,57 +239,57 @@ public ObjectMapper objectMapper() {
}
@Bean
- public String roleUser(){
+ public String roleUser() {
return roleUser.toUpperCase();
}
@Bean
- public String roleSuperuser(){
+ public String roleSuperuser() {
return roleSuperuser.toUpperCase();
}
@Bean
- public String roleAdmin(){
+ public String roleAdmin() {
return roleAdmin.toUpperCase();
}
@Bean("demoUser")
- public String demoUser(){
+ public String demoUser() {
return demoUser;
}
@Bean("demoUserPassword")
- public String demoUserPassword(){
+ public String demoUserPassword() {
return demoUserPassword;
}
@Bean("demoSuperuser")
- public String demoSuperuser(){
+ public String demoSuperuser() {
return demoSuperuser;
}
@Bean("demoSuperuserPassword")
- public String demoSuperuserPassword(){
+ public String demoSuperuserPassword() {
return demoSuperuserPassword;
}
@Bean("demoAdmin")
- public String demoAdmin(){
+ public String demoAdmin() {
return demoAdmin;
}
@Bean("demoAdminPassword")
- public String demoAdminPassword(){
+ public String demoAdminPassword() {
return demoAdminPassword;
}
@Bean("demoReadOnly")
- public String demoReadOnly(){
+ public String demoReadOnly() {
return demoReadOnly;
}
@Bean("demoReadOnlyPassword")
- public String demoReadOnlyPassword(){
+ public String demoReadOnlyPassword() {
return demoReadOnlyPassword;
}
@@ -283,13 +299,14 @@ public String demoReadOnlyPassword(){
*
NOTE!
* Some Spring Security documentation will state that "and" can be used instead of new-line char to
* separate rule items. But that does NOT work, at least not with the Spring Security version used in this project.
+ *
* @return A {@link RoleHierarchy} object.
*/
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_" + roleAdmin.toUpperCase() + " > ROLE_" + roleSuperuser.toUpperCase() + "\n" +
- "ROLE_" + roleSuperuser.toUpperCase() +" > ROLE_" + roleUser.toUpperCase() + "\n" +
+ "ROLE_" + roleSuperuser.toUpperCase() + " > ROLE_" + roleUser.toUpperCase() + "\n" +
"ROLE_" + roleAdmin.toUpperCase() + " > ROLE_" + roleUser.toUpperCase());
return hierarchy;
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java
index 02de8abce9..4e24cd2c12 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java
@@ -20,6 +20,7 @@
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
+import org.phoebus.applications.saveandrestore.model.security.UserNotAuthorizedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -56,6 +57,9 @@ public abstract class BaseController {
@Autowired
public String roleUser; // This MUST be public!!!
+ @Autowired
+ public String demoAdmin;
+
/**
* Intercepts {@link SnapshotNotFoundException} and triggers a {@link HttpStatus#NOT_FOUND}.
@@ -95,7 +99,13 @@ public ResponseEntity handleNodeNotFoundException(HttpServletRequest req
log(exception);
return new ResponseEntity<>(exception.getMessage(), HttpStatus.NOT_FOUND);
}
-
+
+ @ExceptionHandler(UserNotAuthorizedException.class)
+ public ResponseEntity handleUserNotAuthorizedException(HttpServletRequest req, UserNotAuthorizedException exception){
+ log(exception);
+ return new ResponseEntity<>(exception.getMessage(), HttpStatus.FORBIDDEN);
+ }
+
private void log(Throwable throwable) {
logger.log(Level.INFO, "Intercepted " + throwable.getClass().getName(), throwable);
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
index c395268d9a..271da6b4ad 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
@@ -20,10 +20,12 @@
import org.phoebus.applications.saveandrestore.model.Node;
import org.phoebus.applications.saveandrestore.model.NodeType;
import org.phoebus.applications.saveandrestore.model.Tag;
+import org.phoebus.applications.saveandrestore.model.security.UserNotAuthorizedException;
import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
@@ -132,17 +134,23 @@ public List getChildNodes(@PathVariable final String uniqueNodeId) {
*/
@SuppressWarnings("unused")
@DeleteMapping(value = "/node/{uniqueNodeId}", produces = JSON)
- @PreAuthorize("hasRole(this.roleAdmin) or this.mayDelete(#uniqueNodeId, #principal)")
+ //@PreAuthorize("hasRole(this.roleAdmin) or this.mayDelete(#uniqueNodeId, #principal)")
@Deprecated
public void deleteNode(@PathVariable final String uniqueNodeId, Principal principal) {
+ if(!principal.getName().equals(demoAdmin) && !mayDelete(uniqueNodeId, principal)){
+ throw new UserNotAuthorizedException("User not authorized to delete node");
+ }
logger.info("Deleting node with unique id " + uniqueNodeId);
nodeDAO.deleteNode(uniqueNodeId);
}
@SuppressWarnings("unused")
@DeleteMapping(value = "/node", produces = JSON)
- @PreAuthorize("hasRole(this.roleAdmin) or this.mayDelete(#nodeIds, #principal)")
+ //@PreAuthorize("hasRole(this.roleAdmin) or this.mayDelete(#nodeIds, #principal)")
public void deleteNodes(@RequestBody List nodeIds, Principal principal) {
+ if(!principal.getName().equals(demoAdmin) && !mayDelete(nodeIds, principal)){
+ throw new UserNotAuthorizedException("User not authorized to delete nodes");
+ }
nodeDAO.deleteNodes(nodeIds);
}
From 40435224ffdda2c9abf92d094dfd5f25909daf79 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Wed, 8 Nov 2023 15:17:49 +0100
Subject: [PATCH 36/42] Making save&restore authorization/authentication
optional
---
.../CredentialsManagementApp.java | 3 +-
.../saveandrestore/Preferences.java | 3 +
.../SaveAndRestoreAuthenticationProvider.java | 6 +
.../client/SaveAndRestoreJerseyClient.java | 6 +-
.../saveandrestore/ui/ContextMenuBase.java | 2 +-
.../save_and_restore_preferences.properties | 5 +-
.../ServiceAuthenticationProvider.java | 13 +-
services/save-and-restore/pom.xml | 1 -
.../web/config/AuthEnabledCondition.java | 41 +++
.../web/config/SessionFilter.java | 102 --------
.../web/config/WebSecurityConfig.java | 233 ++++++++----------
.../controllers/AuthenticationController.java | 8 +-
.../web/controllers/AuthorizationHelper.java | 110 +++++++++
.../CompositeSnapshotController.java | 20 +-
.../web/controllers/NodeController.java | 74 +-----
.../src/main/resources/application.properties | 49 ++--
.../src/main/resources/sar.ldif | 19 --
.../elasticsearch/ElasticsearchDAOTest.java | 2 +
.../web/config/WebConfigTest.java | 2 +
.../AppMetaDataControllerTest.java | 2 +
.../CompositeSnapshotControllerTest.java | 35 +--
.../web/controllers/FilterControllerTest.java | 24 +-
.../web/controllers/HelpResourceTest.java | 2 +
.../web/controllers/NodeControllerTest.java | 49 +---
.../web/controllers/SearchControllerTest.java | 4 +-
.../controllers/SnapshotControllerTest.java | 21 +-
.../web/controllers/TagControllerTest.java | 4 +-
.../resources/test_application.properties | 1 +
28 files changed, 351 insertions(+), 490 deletions(-)
create mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AuthEnabledCondition.java
delete mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/SessionFilter.java
create mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java
diff --git a/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementApp.java b/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementApp.java
index 55700d2d86..7a322f016a 100644
--- a/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementApp.java
+++ b/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementApp.java
@@ -60,7 +60,8 @@ public String getDisplayName() {
@Override
public AppInstance create() {
List authenticationProviders =
- ServiceLoader.load(ServiceAuthenticationProvider.class).stream().map(Provider::get).collect(Collectors.toList());
+ ServiceLoader.load(ServiceAuthenticationProvider.class).stream().map(Provider::get)
+ .filter(ServiceAuthenticationProvider::isActive).collect(Collectors.toList());
try {
SecureStore secureStore = new SecureStore();
new CredentialsManagementStage(authenticationProviders, secureStore).show();
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Preferences.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Preferences.java
index 2453b12734..fb026ac839 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Preferences.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Preferences.java
@@ -31,6 +31,9 @@ public class Preferences {
@Preference
public static String default_snapshot_name_date_format;
+ @Preference
+ public static boolean authentication_enabled;
+
static
{
AnnotatedPreferences.initialize(Preferences.class, "/save_and_restore_preferences.properties");
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java
index 98c35263a7..fd18b5eae6 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java
@@ -19,6 +19,7 @@
package org.phoebus.applications.saveandrestore.authentication;
+import org.phoebus.applications.saveandrestore.Preferences;
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService;
import org.phoebus.security.authorization.ServiceAuthenticationProvider;
import org.phoebus.security.tokens.AuthenticationScope;
@@ -52,4 +53,9 @@ public void logout(String token) {
public AuthenticationScope getAuthenticationScope(){
return AuthenticationScope.SAVE_AND_RESTORE;
}
+
+ @Override
+ public boolean isActive(){
+ return Preferences.authentication_enabled;
+ }
}
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java
index 0e3313f42a..3dc85f2ca1 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java
@@ -102,8 +102,10 @@ private Client getClient() {
String password = scopedAuthenticationToken.getPassword();
httpBasicAuthFilter = new HTTPBasicAuthFilter(username, password);
client.addFilter(httpBasicAuthFilter);
- } else if (httpBasicAuthFilter != null) {
- client.removeFilter(httpBasicAuthFilter);
+ } else {//if (httpBasicAuthFilter != null) {
+ //client.removeFilter(httpBasicAuthFilter);
+ httpBasicAuthFilter = new HTTPBasicAuthFilter(System.getProperty("user.name"), "password");
+ client.addFilter(httpBasicAuthFilter);
}
} catch (Exception e) {
logger.log(Level.WARNING, "Unable to retrieve credentials from secure store", e);
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java
index e904215ee2..579dabf34e 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java
@@ -76,7 +76,7 @@ public ContextMenuBase(SaveAndRestoreController saveAndRestoreController) {
deleteNodesMenuItem = new MenuItem(Messages.contextMenuDelete, new ImageView(ImageRepository.DELETE));
deleteNodesMenuItem.setOnAction(ae -> saveAndRestoreController.deleteNodes());
deleteNodesMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
- userIsAuthenticatedProperty.not().get() ||
+ //userIsAuthenticatedProperty.not().get() ||
hasSameParentProperty.not().get(),
userIsAuthenticatedProperty, hasSameParentProperty));
diff --git a/app/save-and-restore/app/src/main/resources/save_and_restore_preferences.properties b/app/save-and-restore/app/src/main/resources/save_and_restore_preferences.properties
index 1f933c89aa..1545280157 100644
--- a/app/save-and-restore/app/src/main/resources/save_and_restore_preferences.properties
+++ b/app/save-and-restore/app/src/main/resources/save_and_restore_preferences.properties
@@ -24,4 +24,7 @@ jmasar.service.url=http://localhost:8080/save-restore
httpClient.readTimeout=1000
# Connect timeout in (ms) used by the Jersey client
-httpClient.connectTimeout=1000
\ No newline at end of file
+httpClient.connectTimeout=1000
+
+# Authentication/authorization enabled/disabled
+authentication_enabled=false
\ No newline at end of file
diff --git a/core/security/src/main/java/org/phoebus/security/authorization/ServiceAuthenticationProvider.java b/core/security/src/main/java/org/phoebus/security/authorization/ServiceAuthenticationProvider.java
index 0362400c8b..691fb1581f 100644
--- a/core/security/src/main/java/org/phoebus/security/authorization/ServiceAuthenticationProvider.java
+++ b/core/security/src/main/java/org/phoebus/security/authorization/ServiceAuthenticationProvider.java
@@ -35,7 +35,7 @@ public interface ServiceAuthenticationProvider {
/**
* Signs out user from the service.
- * @param token User name or other type of token (e.g. session cookie).
+ * @param token Username or other type of token (e.g. session cookie).
*/
void logout(String token);
@@ -46,9 +46,18 @@ public interface ServiceAuthenticationProvider {
* {@link org.phoebus.security.store.SecureStore}. Such keys are stored in
* lower case in the key store that backs {@link org.phoebus.security.store.SecureStore}, and
* is a behavior defined by the encryption scheme implementation.
- * Consequently an identity like "UPPER" will be persisted as "upper", i.e. case insensitivity
+ * Consequently, an identity like "UPPER" will be persisted as "upper", i.e. case insensitivity
* must be considered when defining an identity.
* @return Service name
*/
AuthenticationScope getAuthenticationScope();
+
+ /**
+ * Indicates if a provider is active. Inactive providers suggest authentication is disabled or should
+ * not be accessible in the credentials management UI.
+ * @return true if the authentication provider is active, otherwise false.
+ */
+ default boolean isActive(){
+ return true;
+ }
}
diff --git a/services/save-and-restore/pom.xml b/services/save-and-restore/pom.xml
index 3c898a3407..17ea04ef41 100644
--- a/services/save-and-restore/pom.xml
+++ b/services/save-and-restore/pom.xml
@@ -105,7 +105,6 @@
org.springframework.bootspring-boot-starter-logging
- ${spring.boot.version}org.apache.logging.log4j
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AuthEnabledCondition.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AuthEnabledCondition.java
new file mode 100644
index 0000000000..8063e2f9d9
--- /dev/null
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AuthEnabledCondition.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package org.phoebus.service.saveandrestore.web.config;
+
+import org.springframework.context.annotation.Condition;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+
+/**
+ * {@link Condition} subclass used to determine if authentication/authorization is enabled through an
+ * application property setting.
+ */
+public class AuthEnabledCondition implements Condition {
+ /**
+ * @param context the condition context
+ * @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
+ * or {@link org.springframework.core.type.MethodMetadata method} being checked
+ * @return true if application property auth.impl is anything other than none.
+ */
+ @Override
+ public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
+ return !"none".equalsIgnoreCase(context.getEnvironment().getProperty("auth.impl").trim());
+ }
+}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/SessionFilter.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/SessionFilter.java
deleted file mode 100644
index 2db87594c2..0000000000
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/SessionFilter.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2020 European Spallation Source ERIC.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- */
-
-package org.phoebus.service.saveandrestore.web.config;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.config.annotation.web.builders.WebSecurity;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.web.filter.GenericFilterBean;
-
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Base64;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Filter that will take care of authenticating requests that come with
- * a basic authentication header.
- *
- * This class should not be instantiated as a bean in the application configuration. If it is, the
- * doFilter() method will be called for each endpoint URI, effectively defeating the purpose of the
- * configuration of ignored URI patterns set up in the Spring Security context, see
- * {@link WebSecurityConfig#configure(WebSecurity)}.
- */
-public class SessionFilter extends GenericFilterBean {
-
- private AuthenticationManager authenticationManager;
- private ObjectMapper objectMapper;
-
- public SessionFilter(AuthenticationManager authenticationManager) {
- this.authenticationManager = authenticationManager;
- objectMapper = new ObjectMapper();
- }
-
- /**
- * @param request A {@link ServletRequest}
- * @param response A {@link ServletResponse}
- * @param filterChain The {@link FilterChain} to which this implementation contributes.
- * @throws IOException May be thrown by upstream filters.
- * @throws ServletException May be thrown by upstream filters.
- */
- @Override
- public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
- HttpServletRequest httpServletRequest = (HttpServletRequest) request;
-
- String basicAuthenticationHeader = httpServletRequest.getHeader("Authorization");
- String[] usernameAndPassword = getUsernameAndPassword(basicAuthenticationHeader);
- if (usernameAndPassword == null) {
- SecurityContextHolder.getContext().setAuthentication(null);
- } else {
- Authentication authentication = new UsernamePasswordAuthenticationToken(usernameAndPassword[0],
- usernameAndPassword[1]);
- try {
- authentication = authenticationManager.authenticate(authentication);
- SecurityContextHolder.getContext().setAuthentication(authentication);
- } catch (AuthenticationException e) {
- Logger.getLogger(SessionFilter.class.getName())
- .log(Level.FINE, String.format("User %s not authenticated through authorization header",
- usernameAndPassword[0]));
- }
- }
-
- filterChain.doFilter(request, response);
- }
-
- protected String[] getUsernameAndPassword(String authorization) {
- if (authorization != null && authorization.toLowerCase().startsWith("basic")) {
- // Authorization: Basic base64credentials
- String base64Credentials = authorization.substring("Basic".length()).trim();
- byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
- String credentials = new String(credDecoded, StandardCharsets.UTF_8);
- // credentials = username:password
- return credentials.split(":", 2);
- }
- return null;
- }
-}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
index 1e6490662e..532302e9c9 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
@@ -3,48 +3,38 @@
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Scope;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.*;
+import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.http.HttpMethod;
+import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer;
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.config.annotation.web.configuration.WebSecurityCustomizer;
+import org.springframework.security.config.ldap.LdapBindAuthenticationManagerFactory;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
-import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
+import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
+import org.springframework.security.ldap.userdetails.PersonContextMapper;
import org.springframework.security.web.SecurityFilterChain;
-import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
-import org.springframework.security.web.util.matcher.RequestMatcher;
-import javax.servlet.*;
-import javax.servlet.http.HttpServletRequest;
-import java.io.IOException;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-@EnableWebSecurity
@Configuration
-
-/*
+@EnableWebSecurity
@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
-
- */
-
@SuppressWarnings("unused")
-public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+public class WebSecurityConfig {
/**
* External Active Directory configuration properties
@@ -70,29 +60,15 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
String ldap_manager_dn;
@Value("${ldap.manager.password}")
String ldap_manager_password;
- @Value("${ldap.user.search.base}")
+ @Value("${ldap.user.search.base:invalid}")
String ldap_user_search_base;
- @Value("${ldap.user.search.filter}")
+ @Value("${ldap.user.search.filter:invalid}")
String ldap_user_search_filter;
- /**
- * Embedded LDAP configuration properties
- */
- @Value("${embedded_ldap.urls:ldaps://localhost:389/}")
- String embedded_ldap_url;
- @Value("${embedded_ldap.base.dn}")
- String embedded_ldap_base_dn;
- @Value("${embedded_ldap.user.dn.pattern}")
- String embedded_ldap_user_dn_pattern;
- @Value("${embedded_ldap.groups.search.base}")
- String embedded_ldap_groups_search_base;
- @Value("${embedded_ldap.groups.search.pattern}")
- String embedded_ldap_groups_search_pattern;
-
/**
* Authentication implementation.
*/
- @Value("${auth.impl:demo}")
+ @Value("${auth.impl:none}")
String authenitcationImplementation;
@Value("${role.user:sar-user}")
@@ -128,105 +104,93 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${demo.readOnly.password:1234}")
private String demoReadOnlyPassword;
+ @Bean
+ public WebSecurityCustomizer ignoringCustomizer() {
+ return web -> {
+ // The below lists exceptions for authentication.
+ web.ignoring().antMatchers(HttpMethod.GET, "/**");
+ web.ignoring().antMatchers(HttpMethod.POST, "/**/login*");
+ };
+ }
- @Override
- protected void configure(HttpSecurity http) throws Exception {
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
- http.authorizeRequests().antMatchers(HttpMethod.DELETE, "/node/**").hasRole(roleUser.toUpperCase());
- http.authorizeRequests().antMatchers(HttpMethod.POST, "/node/**").hasRole(roleUser.toUpperCase());
- http.authorizeRequests().antMatchers(HttpMethod.PUT, "/node/**").hasRole(roleUser.toUpperCase());
- http.authorizeRequests().antMatchers(HttpMethod.POST, "/snapshot/**").authenticated();
- http.authorizeRequests().anyRequest().authenticated();
- http.addFilterBefore(new SessionFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
+ if ("none".equalsIgnoreCase(authenitcationImplementation.trim())) {
+ http.anonymous();
+ } else {
+ http.authorizeRequests().anyRequest().authenticated();
+ http.httpBasic();
+ }
+ return http.build();
}
+ @Bean
+ @Conditional(LdapAuthCondition.class)
+ public DefaultSpringSecurityContextSource contextSourceFactoryBeanLdap() {
+ DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(ldap_url);
+ if (ldap_manager_dn != null && !ldap_manager_dn.isEmpty() && ldap_manager_password != null && !ldap_manager_password.isEmpty()) {
+ contextSource.setUserDn(ldap_manager_dn);
+ contextSource.setPassword(ldap_manager_password);
+ }
+ contextSource.setBase(ldap_base_dn);
+ return contextSource;
+ }
-
- @Override
- public void configure(WebSecurity web) {
- // The below lists exceptions for authentication.
- web.ignoring().antMatchers(HttpMethod.GET, "/**");
- web.ignoring().antMatchers(HttpMethod.POST, "/**/login*");
+ @Bean
+ @Conditional(LdapAuthCondition.class)
+ public AuthenticationManager ldapAuthenticationManager(
+ BaseLdapPathContextSource contextSource) {
+ LdapBindAuthenticationManagerFactory factory =
+ new LdapBindAuthenticationManagerFactory(contextSource);
+ factory.setUserDnPatterns(ldap_user_dn_pattern);
+ factory.setUserDetailsContextMapper(new PersonContextMapper());
+
+ factory.setLdapAuthoritiesPopulator(authorities(contextSource));
+ return factory.createAuthenticationManager();
}
- @Override
- public void configure(AuthenticationManagerBuilder auth) throws Exception {
- switch (authenitcationImplementation) {
- case "ad":
- ActiveDirectoryLdapAuthenticationProvider adProvider = new ActiveDirectoryLdapAuthenticationProvider(ad_domain, ad_url);
- adProvider.setConvertSubErrorCodesToExceptions(true);
- adProvider.setUseAuthenticationRequestCredentials(true);
- auth.authenticationProvider(adProvider);
- break;
- case "ldap":
- DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(ldap_url);
- if (ldap_manager_dn != null && !ldap_manager_dn.isEmpty() && ldap_manager_password != null && !ldap_manager_password.isEmpty()) {
- contextSource.setUserDn(ldap_manager_dn);
- contextSource.setPassword(ldap_manager_password);
- }
- contextSource.setBase(ldap_base_dn);
- contextSource.afterPropertiesSet();
-
- DefaultLdapAuthoritiesPopulator myAuthPopulator = new DefaultLdapAuthoritiesPopulator(contextSource, ldap_groups_search_base);
- myAuthPopulator.setGroupSearchFilter(ldap_groups_search_pattern);
- myAuthPopulator.setSearchSubtree(true);
- myAuthPopulator.setIgnorePartialResultException(true);
-
- LdapAuthenticationProviderConfigurer configurer = auth.ldapAuthentication()
- .ldapAuthoritiesPopulator(myAuthPopulator);
- if (ldap_user_dn_pattern != null && !ldap_user_dn_pattern.isEmpty()) {
- configurer.userDnPatterns(ldap_user_dn_pattern);
- }
- if (ldap_user_search_filter != null && !ldap_user_search_filter.isEmpty()) {
- configurer.userSearchFilter(ldap_user_search_filter);
- }
- if (ldap_user_search_base != null && !ldap_user_search_base.isEmpty()) {
- configurer.userSearchBase(ldap_user_search_base);
- }
- configurer.contextSource(contextSource);
- break;
- case "ldap_embedded":
- contextSource = new DefaultSpringSecurityContextSource(embedded_ldap_url);
- contextSource.afterPropertiesSet();
-
- myAuthPopulator
- = new DefaultLdapAuthoritiesPopulator(contextSource, embedded_ldap_groups_search_base);
- myAuthPopulator.setGroupSearchFilter(embedded_ldap_groups_search_pattern);
- myAuthPopulator.setSearchSubtree(true);
- myAuthPopulator.setIgnorePartialResultException(true);
-
- auth.ldapAuthentication()
- .userDnPatterns(embedded_ldap_user_dn_pattern)
- .ldapAuthoritiesPopulator(myAuthPopulator)
- .groupSearchBase("ou=Group")
- .contextSource(contextSource);
- break;
- case "demo":
- auth.inMemoryAuthentication()
- .withUser(demoAdmin).password(encoder().encode(demoAdminPassword)).roles(roleAdmin()).and()
- .withUser(demoUser).password(encoder().encode(demoUserPassword)).roles(roleUser()).and()
- .withUser(demoSuperuser).password(encoder().encode(demoSuperuserPassword)).roles(roleSuperuser()).and()
- .withUser(demoReadOnly).password(encoder().encode(demoReadOnlyPassword)).roles();
- break;
- default:
- Logger.getLogger(WebSecurityConfig.class.getName())
- .log(Level.SEVERE, "Authentication Implementation \"" + authenitcationImplementation + "\" not supported");
- throw new IllegalArgumentException("Authentication Implementation \"" + authenitcationImplementation + "\" not supported");
+ @Bean
+ @Conditional(LdapAuthCondition.class)
+ public LdapAuthoritiesPopulator authorities(BaseLdapPathContextSource contextSource) {
+ DefaultLdapAuthoritiesPopulator myAuthPopulator = new DefaultLdapAuthoritiesPopulator(contextSource, ldap_groups_search_base);
+ myAuthPopulator.setGroupSearchFilter(ldap_groups_search_pattern);
+ myAuthPopulator.setSearchSubtree(true);
+ myAuthPopulator.setIgnorePartialResultException(true);
+ LdapAuthenticationProviderConfigurer configurer = new LdapAuthenticationProviderConfigurer();
+ if (ldap_user_dn_pattern != null && !ldap_user_dn_pattern.isEmpty()) {
+ configurer.userDnPatterns(ldap_user_dn_pattern);
+ }
+ if (ldap_user_search_filter != null && !ldap_user_search_filter.isEmpty()) {
+ configurer.userSearchFilter(ldap_user_search_filter);
}
+ if (ldap_user_search_base != null && !ldap_user_search_base.isEmpty()) {
+ configurer.userSearchBase(ldap_user_search_base);
+ }
+ configurer.contextSource(contextSource);
+ return myAuthPopulator;
}
@Bean
- public PasswordEncoder encoder() {
- return new BCryptPasswordEncoder();
+ @ConditionalOnProperty(name = "auth.impl", havingValue = "demo")
+ public AuthenticationManager demoAuthenticationManager(AuthenticationManagerBuilder auth) throws Exception {
+ return new AuthenticationManagerBuilder(new ObjectPostProcessor<>() {
+ @Override
+ public O postProcess(O object) {
+ return object;
+ }
+ }).inMemoryAuthentication()
+ .passwordEncoder(encoder())
+ .withUser(demoAdmin).password(encoder().encode(demoAdminPassword)).roles(roleAdmin()).and()
+ .withUser(demoUser).password(encoder().encode(demoUserPassword)).roles(roleUser()).and()
+ .withUser(demoSuperuser).password(encoder().encode(demoSuperuserPassword)).roles(roleSuperuser()).and()
+ .withUser(demoReadOnly).password(encoder().encode(demoReadOnlyPassword)).roles().and().and().build();
}
@Bean
- public AuthenticationManager authenticationManager() {
- try {
- return super.authenticationManager();
- } catch (Exception e) {
- return null;
- }
+ @Scope("singleton")
+ public PasswordEncoder encoder() {
+ return new BCryptPasswordEncoder();
}
@SuppressWarnings("unused")
@@ -305,9 +269,26 @@ public String demoReadOnlyPassword() {
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
- hierarchy.setHierarchy("ROLE_" + roleAdmin.toUpperCase() + " > ROLE_" + roleSuperuser.toUpperCase() + "\n" +
- "ROLE_" + roleSuperuser.toUpperCase() + " > ROLE_" + roleUser.toUpperCase() + "\n" +
- "ROLE_" + roleAdmin.toUpperCase() + " > ROLE_" + roleUser.toUpperCase());
+ hierarchy.setHierarchy("ROLE_" + roleAdmin.toUpperCase() + " > ROLE_" + roleUser.toUpperCase());
return hierarchy;
}
+
+ /**
+ * {@link Condition} subclass used to select ldap and ldap_embedded
+ * authentication/authorization provider.
+ */
+ private static class LdapAuthCondition implements Condition {
+ /**
+ * @param context the condition context
+ * @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
+ * or {@link org.springframework.core.type.MethodMetadata method} being checked
+ * @return true if application property auth.impl is ldap
+ * or ldap_embedded, otherwise false.
+ */
+ @Override
+ public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
+ String testValue = context.getEnvironment().getProperty("auth.impl");
+ return "ldap".equals(testValue) || "ldap_embedded".equals(testValue);
+ }
+ }
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java
index f7c60da540..2b68764e48 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java
@@ -20,7 +20,10 @@
package org.phoebus.service.saveandrestore.web.controllers;
import org.phoebus.applications.saveandrestore.model.UserData;
+import org.phoebus.service.saveandrestore.web.config.AuthEnabledCondition;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Conditional;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
@@ -37,9 +40,9 @@
@SuppressWarnings("unused")
@RestController
+@Conditional(AuthEnabledCondition.class)
public class AuthenticationController extends BaseController {
-
@Autowired
private AuthenticationManager authenticationManager;
@@ -54,7 +57,8 @@ public class AuthenticationController extends BaseController {
@PostMapping(value = "login")
public ResponseEntity login(@RequestParam(value = "username") String userName,
@RequestParam(value = "password") String password) {
- Authentication authentication = new UsernamePasswordAuthenticationToken(userName, password);
+ Authentication authentication =
+ new UsernamePasswordAuthenticationToken(userName, password);
try {
authentication = authenticationManager.authenticate(authentication);
} catch (AuthenticationException e) {
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java
new file mode 100644
index 0000000000..76829c1ff2
--- /dev/null
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package org.phoebus.service.saveandrestore.web.controllers;
+
+import org.phoebus.applications.saveandrestore.model.CompositeSnapshot;
+import org.phoebus.applications.saveandrestore.model.Node;
+import org.phoebus.applications.saveandrestore.model.NodeType;
+import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.security.Principal;
+import java.util.List;
+
+/**
+ * {@link Service} class implementing domain specific authorization rules in order to
+ * grant or deny access to certain REST endpoints.
+ */
+@Service("authorizationHelper")
+public class AuthorizationHelper {
+
+ @Autowired
+ private NodeDAO nodeDAO;
+
+ /**
+ * Checks if all the nodes provided to this method can be deleted by the user.
+ *
+ * @param nodeIds The list of {@link Node} ids subject to the check.
+ * @param principal {@link Principal} of authenticated user.
+ * @return true only if all if the nodes can be deleted by the user.
+ */
+ @SuppressWarnings("unused")
+ public boolean mayDelete(List nodeIds, Principal principal) {
+ for (String nodeId : nodeIds) {
+ if (!mayDelete(nodeId, principal)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * An authenticated user may delete a node if User identity is same as the target {@link Node}'s user id and:
+ *
+ *
Target {@link Node} is a snapshot.
+ *
Target {@link Node} is not a snapshot, but has no child nodes.
+ *
+ *
+ * @param nodeId Unique node id identifying the target of the user's delete operation.
+ * @param principal Identifies user.
+ * @return false if user may not delete the {@link Node}.
+ */
+ @SuppressWarnings("unused")
+ public boolean mayDelete(String nodeId, Principal principal) {
+ Node node = nodeDAO.getNode(nodeId);
+ if (!node.getUserName().equals(principal.getName())) {
+ return false;
+ }
+ if (node.getNodeType().equals(NodeType.CONFIGURATION) || node.getNodeType().equals(NodeType.FOLDER)) {
+ return nodeDAO.getChildNodes(node.getUniqueId()).isEmpty();
+ }
+ return true;
+ }
+
+ /**
+ * An authenticated user may update a node if user identity is same as the target {@link Node}'s user id.
+ *
+ * @param node {@link Node} identifying the target of the user's update operation.
+ * @param principal Identifies user.
+ * @return false if user may not update the {@link Node}.
+ */
+ @SuppressWarnings("unused")
+ public boolean mayUpdate(Node node, Principal principal) {
+ return nodeDAO.getNode(node.getUniqueId()).getUserName().equals(principal.getName());
+ }
+
+ /**
+ *
+ * An authenticated user may save a composite snapshot, and update if user identity is same as the target's
+ * composite snapshot {@link Node}.
+ *
+ *
+ * @param compositeSnapshot {@link CompositeSnapshot} identifying the target of the user's update operation.
+ * @param principal Identifies user.
+ * @return false if user may not update the {@link CompositeSnapshot}.
+ */
+ @SuppressWarnings("unused")
+ public boolean mayUpdate(CompositeSnapshot compositeSnapshot, Principal principal) {
+ Node node = nodeDAO.getNode(compositeSnapshot.getCompositeSnapshotNode().getUniqueId());
+ return node.getUserName().equals(principal.getName());
+ }
+
+}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
index 9a4cbb6594..278dc1aea8 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
@@ -54,7 +54,7 @@ public CompositeSnapshot createCompositeSnapshot(@RequestParam(value = "parentNo
}
@PostMapping(value = "/composite-snapshot", produces = JSON)
- @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and this.mayUpdate(#compositeSnapshot, #principal))")
+ @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and @authorizationHelper.mayUpdate(#compositeSnapshot, #principal))")
public CompositeSnapshot updateCompositeSnapshot(@RequestBody CompositeSnapshot compositeSnapshot,
Principal principal) {
if(!compositeSnapshot.getCompositeSnapshotNode().getNodeType().equals(NodeType.COMPOSITE_SNAPSHOT)){
@@ -64,24 +64,6 @@ public CompositeSnapshot updateCompositeSnapshot(@RequestBody CompositeSnapshot
return nodeDAO.updateCompositeSnapshot(compositeSnapshot);
}
- /**
- * NOTE: this method MUST be public!
- *
- *
- * An authenticated user may save a composite snapshot, and update if user identity is same as the target's
- * composite snapshot {@link Node}.
- *
- *
- * @param compositeSnapshot {@link CompositeSnapshot} identifying the target of the user's update operation.
- * @param principal Identifies user.
- * @return false if user may not update the {@link CompositeSnapshot}.
- */
- public boolean mayUpdate(CompositeSnapshot compositeSnapshot, Principal principal){
- Node node = nodeDAO.getNode(compositeSnapshot.getCompositeSnapshotNode().getUniqueId());
- return node.getUserName().equals(principal.getName());
- }
-
-
@GetMapping(value = "/composite-snapshot/{uniqueId}", produces = JSON)
public CompositeSnapshotData getCompositeSnapshotData(@PathVariable String uniqueId) {
return nodeDAO.getCompositeSnapshotData(uniqueId);
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
index 271da6b4ad..fd936b067a 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
@@ -20,12 +20,12 @@
import org.phoebus.applications.saveandrestore.model.Node;
import org.phoebus.applications.saveandrestore.model.NodeType;
import org.phoebus.applications.saveandrestore.model.Tag;
-import org.phoebus.applications.saveandrestore.model.security.UserNotAuthorizedException;
import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
@@ -134,84 +134,20 @@ public List getChildNodes(@PathVariable final String uniqueNodeId) {
*/
@SuppressWarnings("unused")
@DeleteMapping(value = "/node/{uniqueNodeId}", produces = JSON)
- //@PreAuthorize("hasRole(this.roleAdmin) or this.mayDelete(#uniqueNodeId, #principal)")
+ @PreAuthorize("hasRole(this.roleAdmin) or @authorizationHelper.mayDelete(#uniqueNodeId, #principal)")
@Deprecated
public void deleteNode(@PathVariable final String uniqueNodeId, Principal principal) {
- if(!principal.getName().equals(demoAdmin) && !mayDelete(uniqueNodeId, principal)){
- throw new UserNotAuthorizedException("User not authorized to delete node");
- }
logger.info("Deleting node with unique id " + uniqueNodeId);
nodeDAO.deleteNode(uniqueNodeId);
}
@SuppressWarnings("unused")
@DeleteMapping(value = "/node", produces = JSON)
- //@PreAuthorize("hasRole(this.roleAdmin) or this.mayDelete(#nodeIds, #principal)")
+ @PreAuthorize("hasRole(this.roleAdmin) or @authorizationHelper.mayDelete(#nodeIds, #principal)")
public void deleteNodes(@RequestBody List nodeIds, Principal principal) {
- if(!principal.getName().equals(demoAdmin) && !mayDelete(nodeIds, principal)){
- throw new UserNotAuthorizedException("User not authorized to delete nodes");
- }
nodeDAO.deleteNodes(nodeIds);
}
- /**
- * NOTE: this method MUST be public!
- *
- * Checks if all the nodes provided to this method can be deleted by the user.
- * @param nodeIds The list of {@link Node} ids subject to the check.
- * @param principal {@link Principal} of authenticated user.
- * @return true only if all if the nodes can be deleted by the user.
- */
- @SuppressWarnings("unused")
- public boolean mayDelete(List nodeIds, Principal principal){
- for (String nodeId : nodeIds){
- if(!mayDelete(nodeId, principal)){
- return false;
- }
- }
- return true;
- }
-
- /**
- * NOTE: this method MUST be public!
- * An authenticated user may delete a node if User identity is same as the target {@link Node}'s user id and:
- *
- *
Target {@link Node} is a snapshot.
- *
Target {@link Node} is not a snapshot, but has no child nodes.
- *
- *
- * @param nodeId Unique node id identifying the target of the user's delete operation.
- * @param principal Identifies user.
- * @return false if user may not delete the {@link Node}.
- */
- @SuppressWarnings("unused")
- public boolean mayDelete(String nodeId, Principal principal){
- Node node = nodeDAO.getNode(nodeId);
- if(!node.getUserName().equals(principal.getName())){
- return false;
- }
- if(node.getNodeType().equals(NodeType.CONFIGURATION) || node.getNodeType().equals(NodeType.FOLDER)){
- return nodeDAO.getChildNodes(node.getUniqueId()).isEmpty();
- }
- return true;
- }
-
- /**
- * NOTE: this method MUST be public!
- *
- *
- * An authenticated user may update a node if user identity is same as the target {@link Node}'s user id.
- *
- *
- * @param node {@link Node} identifying the target of the user's update operation.
- * @param principal Identifies user.
- * @return false if user may not update the {@link Node}.
- */
- @SuppressWarnings("unused")
- public boolean mayUpdate(Node node, Principal principal){
- return nodeDAO.getNode(node.getUniqueId()).getUserName().equals(principal.getName());
- }
-
/**
* Updates a {@link Node}. The purpose is to support modification of name or comment/description, or both. Modification of
* node type is not supported.
@@ -228,7 +164,7 @@ public boolean mayUpdate(Node node, Principal principal){
*/
@SuppressWarnings("unused")
@PostMapping(value = "/node", produces = JSON)
- @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and this.mayUpdate(#nodeToUpdate, #principal))")
+ @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and @authorizationHelper.mayUpdate(#nodeToUpdate, #principal))")
public Node updateNode(@RequestParam(value = "customTimeForMigration", required = false, defaultValue = "false") String customTimeForMigration,
@RequestBody Node nodeToUpdate,
Principal principal) {
diff --git a/services/save-and-restore/src/main/resources/application.properties b/services/save-and-restore/src/main/resources/application.properties
index 11e8e732c1..de3d8b7c4f 100644
--- a/services/save-and-restore/src/main/resources/application.properties
+++ b/services/save-and-restore/src/main/resources/application.properties
@@ -1,4 +1,4 @@
-logging.level.org.springframework=INFO
+logging.level.org.springframework=DEBUG
app.version=@project.version@
app.name=@project.artifactId@
@@ -35,45 +35,34 @@ ad.url = ldap://127.0.0.1
ad.domain = test.com
############## LDAP - External ##############
-#ldap.urls = ldaps://controlns02.nsls2.bnl.gov/dc=nsls2,dc=bnl,dc=gov
-#ldap.base.dn = dc=nsls2,dc=bnl,dc=gov
+# If uncommenting in this section, make sure
+# to comment out in LDAP - Embedded section
+#############################################
+#ldap.urls=ldaps://ldap-cslab.cslab.esss.lu.se
+#ldap.base.dn = dc=esss,dc=lu,dc=se
#ldap.user.search.base=
# User search pattern, e.g. uid={0},ou=People. No default value as LDAP environment may not
# support user search by pattern.
-#ldap.user.dn.pattern=
+#ldap.user.dn.pattern=uid={0},ou=Users
# User search filter, e.g. (&(objectClass=person)(SAMAccountName={0})). No default value as LDAP environment
# may not support user search by filter.
#ldap.user.search.filter=
-#ldap.groups.search.base = ou=Group
+#ldap.groups.search.base = ou=Groups
#ldap.groups.search.pattern = (memberUid= {1})
# dn of manager account, may be required for group search
-#ldap.manager.dn=
-# password of account
-#ldap.manager.password=
-
-ldap.urls=ldaps://ldap-cslab.cslab.esss.lu.se
-ldap.base.dn = dc=esss,dc=lu,dc=se
-ldap.user.search.base=
-# User search pattern, e.g. uid={0},ou=People. No default value as LDAP environment may not
-# support user search by pattern.
-ldap.user.dn.pattern=uid={0},ou=Users
-# User search filter, e.g. (&(objectClass=person)(SAMAccountName={0})). No default value as LDAP environment
-# may not support user search by filter.
-ldap.user.search.filter=
-ldap.groups.search.base = ou=Groups
-ldap.groups.search.pattern = (memberUid= {1})
-# dn of manager account, may be required for group search
ldap.manager.dn=
# password of account
ldap.manager.password=
############## LDAP - Embedded ##############
-#embedded_ldap.enabled = false
-embedded_ldap.urls = ldap://localhost:8389/dc=sar,dc=local
-embedded_ldap.base.dn = dc=sar,dc=local
-embedded_ldap.user.dn.pattern = uid={0},ou=Group
-embedded_ldap.groups.search.base = ou=Group
-embedded_ldap.groups.search.pattern = (memberUid= {1})
+# If uncommenting in this section, make sure
+# to comment out in LDAP - External section
+#############################################
+ldap.urls=ldap://localhost:8389/dc=sar,dc=local
+ldap.base.dn = dc=sar,dc=local
+ldap.user.dn.pattern = uid={0},ou=Group
+ldap.groups.search.base = ou=Group
+ldap.groups.search.pattern = (memberUid= {1})
spring.ldap.embedded.ldif=classpath:sar.ldif
spring.ldap.embedded.base-dn=dc=sar,dc=local
spring.ldap.embedded.port=8389
@@ -82,8 +71,6 @@ spring.ldap.embedded.validation.enabled=false
############## Demo credentials ##############
demo.user=user
demo.user.password=userPass
-demo.superuser=superuser
-demo.superuser.password=superuserPass
demo.admin=admin
demo.admin.password=adminPass
demo.readOnly=johndoe
@@ -95,11 +82,11 @@ demo.readOnly.password=1234
# ldap - Probably Open LDAP
# ldap_embedded - Embedded LDAP. Config in sar.ldif
# demo - Hard coded, see WebSecurityConfig class
-auth.impl = demo
+# none - No authentication and authorization
+auth.impl = none
############## Authorization Roles ################
role.user=sar-user
-role.superuser=sar-superuser
role.admin=sar-admin
diff --git a/services/save-and-restore/src/main/resources/sar.ldif b/services/save-and-restore/src/main/resources/sar.ldif
index 77730d1ed4..91133bfb6a 100644
--- a/services/save-and-restore/src/main/resources/sar.ldif
+++ b/services/save-and-restore/src/main/resources/sar.ldif
@@ -23,14 +23,6 @@ gidNumber: 27001
uidNumber: 27001
memberUid: user
-dn: cn=sar-superuser,ou=Group,dc=sar,dc=local
-cn: sar-superuser
-objectClass: posixGroup
-description: save-n-restore superuser
-gidNumber: 27002
-uidNumber: 27002
-memberUid: superuser
-
dn: cn=sar-admin,ou=Group,dc=sar,dc=local
cn: sar-admin
objectClass: posixGroup
@@ -50,17 +42,6 @@ uidNumber: 23004
gidNumber: 23004
homeDirectory: /dev/null
-dn: uid=superuser,ou=Group,dc=sar,dc=local
-uid: superuser
-objectClass: account
-objectClass: posixAccount
-description: User with sar-user role
-cn: superuser
-userPassword: superuserPass
-uidNumber: 23004
-gidNumber: 23004
-homeDirectory: /dev/null
-
dn: uid=johndoe,ou=Group,dc=sar,dc=local
uid: johndoe
objectClass: account
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAOTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAOTest.java
index 21a6a7d8af..ae2e5f1f16 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAOTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAOTest.java
@@ -36,6 +36,7 @@
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
@@ -56,6 +57,7 @@
@EnableConfigurationProperties
@ContextHierarchy({@ContextConfiguration(classes = {ElasticTestConfig.class})})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class})
+@TestPropertySource(locations = "classpath:test_application.properties")
@Profile("IT")
public class ElasticsearchDAOTest {
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/WebConfigTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/WebConfigTest.java
index cefd95c9e9..da1005650f 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/WebConfigTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/WebConfigTest.java
@@ -25,10 +25,12 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@ContextHierarchy({@ContextConfiguration(classes = {WebConfiguration.class, ControllersTestConfig.class})})
+@TestPropertySource(locations = "classpath:test_application.properties")
@SuppressWarnings("unused")
public class WebConfigTest {
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/AppMetaDataControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/AppMetaDataControllerTest.java
index 522be2e636..d49f0912cf 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/AppMetaDataControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/AppMetaDataControllerTest.java
@@ -26,6 +26,7 @@
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
@@ -42,6 +43,7 @@
@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class})})
@WebMvcTest(AppMetaDataControllerTest.class)
@ExtendWith(SpringExtension.class)
+@TestPropertySource(locations = "classpath:test_application.properties")
@SuppressWarnings("unused")
public class AppMetaDataControllerTest {
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java
index 1d68817832..c5b66238df 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java
@@ -36,6 +36,7 @@
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
@@ -54,15 +55,13 @@
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ControllersTestConfig.class)
-@WebMvcTest(NodeController.class)
+@WebMvcTest(CompositeSnapshotController.class)
+@TestPropertySource(locations = "classpath:test_application.properties")
public class CompositeSnapshotControllerTest {
@Autowired
private String userAuthorization;
- @Autowired
- private String superuserAuthorization;
-
@Autowired
private String adminAuthorization;
@@ -111,13 +110,6 @@ public void testCreateCompositeSnapshot() throws Exception {
// Make sure response contains expected data
objectMapper.readValue(s, CompositeSnapshot.class);
- request = put("/composite-snapshot?parentNodeId=id")
- .header(HttpHeaders.AUTHORIZATION, superuserAuthorization)
- .contentType(JSON)
- .content(compositeSnapshotString);
-
- mockMvc.perform(request).andExpect(status().isOk());
-
request = put("/composite-snapshot?parentNodeId=id")
.header(HttpHeaders.AUTHORIZATION, adminAuthorization)
.contentType(JSON)
@@ -136,7 +128,7 @@ public void testCreateCompositeSnapshot() throws Exception {
.contentType(JSON)
.content(compositeSnapshotString);
- mockMvc.perform(request).andExpect(status().isForbidden());
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
reset(nodeDAO);
}
@@ -187,13 +179,6 @@ public void testUpdateCompositeSnapshot() throws Exception {
mockMvc.perform(request).andExpect(status().isForbidden());
- request = post("/composite-snapshot")
- .header(HttpHeaders.AUTHORIZATION, superuserAuthorization)
- .contentType(JSON)
- .content(compositeSnapshotString);
-
- mockMvc.perform(request).andExpect(status().isForbidden());
-
request = post("/composite-snapshot")
.header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
.contentType(JSON)
@@ -205,7 +190,7 @@ public void testUpdateCompositeSnapshot() throws Exception {
.contentType(JSON)
.content(compositeSnapshotString);
- mockMvc.perform(request).andExpect(status().isForbidden());
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
when(nodeDAO.getNode("c")).thenReturn(Node.builder().nodeType(NodeType.COMPOSITE_SNAPSHOT).uniqueId("c").userName("notUser").build());
@@ -316,8 +301,7 @@ public void testGetCompositeSnapshotConsistency() throws Exception {
request = post("/composite-snapshot-consistency-check")
.contentType(JSON)
.content(objectMapper.writeValueAsString(List.of("id")));
-
- mockMvc.perform(request).andExpect(status().isForbidden());
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
request = post("/composite-snapshot-consistency-check")
@@ -327,13 +311,6 @@ public void testGetCompositeSnapshotConsistency() throws Exception {
mockMvc.perform(request).andExpect(status().isOk());
- request = post("/composite-snapshot-consistency-check")
- .header(HttpHeaders.AUTHORIZATION, superuserAuthorization)
- .contentType(JSON)
- .content(objectMapper.writeValueAsString(List.of("id")));
-
- mockMvc.perform(request).andExpect(status().isOk());
-
request = post("/composite-snapshot-consistency-check")
.header(HttpHeaders.AUTHORIZATION, adminAuthorization)
.contentType(JSON)
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java
index e0019d4fe9..dccca5a33e 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java
@@ -30,6 +30,7 @@
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
@@ -47,8 +48,8 @@
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ControllersTestConfig.class)
-@WebMvcTest(NodeController.class)
-
+@WebMvcTest(FilterController.class)
+@TestPropertySource(locations = "classpath:test_application.properties")
public class FilterControllerTest {
@Autowired
@@ -63,9 +64,6 @@ public class FilterControllerTest {
@Autowired
private String userAuthorization;
- @Autowired
- private String superuserAuthorization;
-
@Autowired
private String adminAuthorization;
@@ -101,13 +99,6 @@ public void testSaveFilter() throws Exception {
// Make sure response contains expected data
objectMapper.readValue(s, Filter.class);
- request = put("/filter")
- .header(HttpHeaders.AUTHORIZATION, superuserAuthorization)
- .contentType(JSON)
- .content(filterString);
-
- mockMvc.perform(request).andExpect(status().isOk());
-
request = put("/filter")
.header(HttpHeaders.AUTHORIZATION, adminAuthorization)
.contentType(JSON)
@@ -126,7 +117,7 @@ public void testSaveFilter() throws Exception {
.contentType(JSON)
.content(filterString);
- mockMvc.perform(request).andExpect(status().isForbidden());
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
}
@Test
@@ -148,11 +139,6 @@ public void testDeleteFilter() throws Exception {
.contentType(JSON);
mockMvc.perform(request).andExpect(status().isOk());
- request = delete("/filter/name")
- .header(HttpHeaders.AUTHORIZATION, superuserAuthorization)
- .contentType(JSON);
- mockMvc.perform(request).andExpect(status().isForbidden());
-
request = delete("/filter/name")
.header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
.contentType(JSON);
@@ -160,7 +146,7 @@ public void testDeleteFilter() throws Exception {
request = delete("/filter/name")
.contentType(JSON);
- mockMvc.perform(request).andExpect(status().isForbidden());
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
filter.setUser("notUser");
when(nodeDAO.getAllFilters()).thenReturn(List.of(filter));
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/HelpResourceTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/HelpResourceTest.java
index f6df59f368..3843053690 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/HelpResourceTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/HelpResourceTest.java
@@ -25,6 +25,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
@@ -35,6 +36,7 @@
@ExtendWith(SpringExtension.class)
@WebMvcTest(HelpResource.class)
@ContextConfiguration(classes = ControllersTestConfig.class)
+@TestPropertySource(locations = "classpath:test_application.properties")
public class HelpResourceTest{
@Autowired
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java
index b37367f46b..4b433a4fa5 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java
@@ -89,18 +89,12 @@ public class NodeControllerTest {
@Autowired
private String demoUser;
- @Autowired
- private String demoSuperuser;
-
@Autowired
private String demoAdmin;
@Autowired
private String userAuthorization;
- @Autowired
- private String superuserAuthorization;
-
@Autowired
private String adminAuthorization;
@@ -145,12 +139,6 @@ public void testCreateFolder() throws Exception {
.content(content);
mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON));
- request = put("/node?parentNodeId=a")
- .header(HttpHeaders.AUTHORIZATION, superuserAuthorization)
- .contentType(JSON)
- .content(content);
- mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON));
-
request = put("/node?parentNodeId=a")
.contentType(JSON)
.header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
@@ -253,13 +241,6 @@ public void testUpdateConfig() throws Exception{
when(nodeDAO.getNode("hhh")).thenReturn(Node.builder().nodeType(NodeType.CONFIGURATION).userName("notUser").build());
- request = post("/config")
- .header(HttpHeaders.AUTHORIZATION, superuserAuthorization)
- .contentType(JSON)
- .content(confurationAsString);
-
- mockMvc.perform(request).andExpect(status().isForbidden());
-
request = post("/config")
.header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
.contentType(JSON)
@@ -350,7 +331,7 @@ public void testDeleteFolder() throws Exception {
MockHttpServletRequestBuilder request =
delete("/node/a");
- mockMvc.perform(request).andExpect(status().isForbidden());
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
when(nodeDAO.getNode("a")).thenReturn(Node.builder().uniqueId("a").userName(demoUser).build());
@@ -415,22 +396,6 @@ public void testDeleteFolder() throws Exception {
when(nodeDAO.getNode("a")).thenReturn(Node.builder().uniqueId("a").nodeType(NodeType.CONFIGURATION).userName(demoUser).build());
when(nodeDAO.getChildNodes("a")).thenReturn(List.of(Node.builder().build()));
- request =
- delete("/node/a")
- .header(HttpHeaders.AUTHORIZATION, superuserAuthorization);
- mockMvc.perform(request).andExpect(status().isForbidden());
-
- when(nodeDAO.getNode("a")).thenReturn(Node.builder().uniqueId("a").nodeType(NodeType.FOLDER).userName(demoUser).build());
- when(nodeDAO.getChildNodes("a")).thenReturn(List.of(Node.builder().build()));
-
- request =
- delete("/node/a")
- .header(HttpHeaders.AUTHORIZATION, superuserAuthorization);
- mockMvc.perform(request).andExpect(status().isForbidden());
-
- when(nodeDAO.getNode("a")).thenReturn(Node.builder().uniqueId("a").nodeType(NodeType.CONFIGURATION).userName(demoUser).build());
- when(nodeDAO.getChildNodes("a")).thenReturn(List.of(Node.builder().build()));
-
request =
delete("/node/a")
.header(HttpHeaders.AUTHORIZATION, adminAuthorization);
@@ -535,14 +500,6 @@ public void testMoveNode() throws Exception {
mockMvc.perform(request).andExpect(status().isForbidden());
- request = post("/move")
- .header(HttpHeaders.AUTHORIZATION, superuserAuthorization)
- .contentType(JSON)
- .content(objectMapper.writeValueAsString(Arrays.asList("a")))
- .param("to", "b")
- .param("username", "username");
-
- mockMvc.perform(request).andExpect(status().isForbidden());
request = post("/move")
.header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
@@ -559,7 +516,7 @@ public void testMoveNode() throws Exception {
.param("to", "b")
.param("username", "username");
- mockMvc.perform(request).andExpect(status().isForbidden());
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
}
@Test
@@ -642,7 +599,7 @@ public void testUpdateNode() throws Exception {
.param("customTimeForMigration", "false")
.contentType(JSON)
.content(objectMapper.writeValueAsString(node));
- mockMvc.perform(request).andExpect(status().isForbidden());
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
request = post("/node")
.header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java
index a65d0ea781..e5a3948b57 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java
@@ -30,6 +30,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
@@ -49,7 +50,8 @@
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ControllersTestConfig.class)
-@WebMvcTest(NodeController.class)
+@WebMvcTest(SearchController.class)
+@TestPropertySource(locations = "classpath:test_application.properties")
public class SearchControllerTest {
@Autowired
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java
index 2eb8b0e582..b7a8ac4cde 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java
@@ -48,7 +48,7 @@
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ControllersTestConfig.class)
@TestPropertySource(locations = "classpath:test_application.properties")
-@WebMvcTest(NodeController.class)
+@WebMvcTest(SnapshotController.class)
public class SnapshotControllerTest {
@Autowired
@@ -57,9 +57,6 @@ public class SnapshotControllerTest {
@Autowired
private String userAuthorization;
- @Autowired
- private String superuserAuthorization;
-
@Autowired
private String adminAuthorization;
@@ -126,7 +123,7 @@ public void testSaveNewSnapshot() throws Exception {
request = put("/snapshot?parentNodeId=a")
.contentType(JSON)
.content(snapshotString);
- mockMvc.perform(request).andExpect(status().isForbidden());
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
request = put("/snapshot?parentNodeId=a")
.header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
@@ -134,12 +131,6 @@ public void testSaveNewSnapshot() throws Exception {
.content(snapshotString);
mockMvc.perform(request).andExpect(status().isForbidden());
- request = put("/snapshot?parentNodeId=a")
- .header(HttpHeaders.AUTHORIZATION, superuserAuthorization)
- .contentType(JSON)
- .content(snapshotString);
- mockMvc.perform(request).andExpect(status().isOk());
-
request = put("/snapshot?parentNodeId=a")
.header(HttpHeaders.AUTHORIZATION, adminAuthorization)
.contentType(JSON)
@@ -173,7 +164,7 @@ public void testUpdateSnapshot() throws Exception {
request = put("/snapshot?parentNodeId=a")
.contentType(JSON)
.content(snapshotString);
- mockMvc.perform(request).andExpect(status().isForbidden());
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
request = put("/snapshot?parentNodeId=a")
.header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
@@ -181,12 +172,6 @@ public void testUpdateSnapshot() throws Exception {
.content(snapshotString);
mockMvc.perform(request).andExpect(status().isForbidden());
- request = put("/snapshot?parentNodeId=a")
- .header(HttpHeaders.AUTHORIZATION, superuserAuthorization)
- .contentType(JSON)
- .content(snapshotString);
- mockMvc.perform(request).andExpect(status().isForbidden());
-
request = put("/snapshot?parentNodeId=a")
.header(HttpHeaders.AUTHORIZATION, adminAuthorization)
.contentType(JSON)
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java
index 46d55cddfa..d4606db8a0 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java
@@ -32,6 +32,7 @@
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
@@ -49,7 +50,8 @@
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ControllersTestConfig.class)
-@WebMvcTest(NodeController.class)
+@WebMvcTest(TagController.class)
+@TestPropertySource(locations = "classpath:test_application.properties")
public class TagControllerTest {
@Autowired
diff --git a/services/save-and-restore/src/test/resources/test_application.properties b/services/save-and-restore/src/test/resources/test_application.properties
index 547d620710..97e817e74d 100644
--- a/services/save-and-restore/src/test/resources/test_application.properties
+++ b/services/save-and-restore/src/test/resources/test_application.properties
@@ -18,3 +18,4 @@ elasticsearch.snapshot_node.index:test_saveandrestore_snapshot
elasticsearch.composite_snapshot_node.index=test_saveandrestore_composite_snapshot
elasticsearch.filter.index:test_saveandrestore_filter
+auth.impl = demo
From 1a6c789132b2aaa6a66857ff8195fb997f6b7967 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Wed, 8 Nov 2023 19:54:03 +0100
Subject: [PATCH 37/42] Authentication/authorization for save&restore tag
management
---
.../applications/saveandrestore/Messages.java | 2 +
.../ui/ContextMenuSnapshot.java | 6 +--
.../ui/snapshot/tag/TagUtil.java | 14 +++++++
.../saveandrestore/messages.properties | 2 +
.../web/controllers/AuthorizationHelper.java | 37 ++++++++++++++-----
.../web/controllers/TagController.java | 14 ++++---
6 files changed, 58 insertions(+), 17 deletions(-)
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java
index 9a3411ba7e..c15a64f8cd 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java
@@ -70,9 +70,11 @@ public class Messages {
public static String duplicatePVNamesFoundInSelection;
public static String editFilter;
public static String errorActionFailed;
+ public static String errorAddTagFailed;
public static String errorCreateFolderFailed;
public static String errorCreateConfigurationFailed;
public static String errorDeleteNodeFailed;
+ public static String errorDeleteTagFailed;
public static String errorGeneric;
public static String errorUnableToRetrieveData;
public static String exportConfigurationLabel;
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java
index 4a4086b687..371e7b8e0e 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java
@@ -60,7 +60,7 @@ public ContextMenuSnapshot(SaveAndRestoreController saveAndRestoreController) {
tagWithComment = new Menu(Messages.contextMenuTagsWithComment, snapshotTagsWithCommentIconImage);
tagWithComment.setOnShowing(event -> saveAndRestoreController.tagWithComment(tagWithComment));
tagWithComment.disableProperty().bind(Bindings.createBooleanBinding(() ->
- multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(),
+ multipleNodesSelectedProperty.get(), //|| userIsAuthenticatedProperty.not().get(),
multipleNodesSelectedProperty, userIsAuthenticatedProperty));
MenuItem addTagWithCommentMenuItem = TagWidget.AddTagWithCommentMenuItem();
@@ -84,8 +84,8 @@ public ContextMenuSnapshot(SaveAndRestoreController saveAndRestoreController) {
tagGoldenMenuItem = new MenuItem(Messages.contextMenuTagAsGolden, new ImageView(ImageRepository.SNAPSHOT));
tagGoldenMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
- multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get() || mayTagOrUntagGoldenProperty.not().get(),
- multipleNodesSelectedProperty, userIsAuthenticatedProperty, mayTagOrUntagGoldenProperty));
+ multipleNodesSelectedProperty.get() || /*userIsAuthenticatedProperty.not().get() ||*/ mayTagOrUntagGoldenProperty.not().get(),
+ multipleNodesSelectedProperty, /*userIsAuthenticatedProperty,*/ mayTagOrUntagGoldenProperty));
Image copyIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/copy.png");
MenuItem copyMenuItem = new MenuItem(Messages.copy, new ImageView(copyIcon));
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/tag/TagUtil.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/tag/TagUtil.java
index 749b0ec806..75666e05cb 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/tag/TagUtil.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/tag/TagUtil.java
@@ -44,7 +44,9 @@
import org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotNewTagDialog;
import org.phoebus.framework.autocomplete.ProposalService;
import org.phoebus.ui.autocomplete.AutocompleteMenu;
+import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
+import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
@@ -132,6 +134,9 @@ public static void tagWithComment(Menu parentMenu,
List updatedNodes = SaveAndRestoreService.getInstance().deleteTag(tagData);
callback.accept(updatedNodes);
} catch (Exception e) {
+ ExceptionDetailsErrorDialog.openError(Messages.errorGeneric,
+ Messages.errorDeleteTagFailed,
+ e);
Logger.getLogger(TagUtil.class.getName()).log(Level.WARNING, "Failed to remove tag from node", e);
}
}
@@ -168,6 +173,9 @@ public static List addTag(Li
tagData.setUniqueNodeIds(selectedNodeIds);
updatedNodes.addAll(SaveAndRestoreService.getInstance().addTag(tagData));
} catch (Exception e) {
+ ExceptionDetailsErrorDialog.openError(Messages.errorGeneric,
+ Messages.errorAddTagFailed,
+ e);
Logger.getLogger(TagUtil.class.getName()).log(Level.WARNING, "Failed to add tag to node");
}
});
@@ -213,6 +221,9 @@ public static boolean configureGoldenItem(Listtrue only if all if the nodes can be deleted by the user.
*/
- @SuppressWarnings("unused")
public boolean mayDelete(List nodeIds, Principal principal) {
for (String nodeId : nodeIds) {
if (!mayDelete(nodeId, principal)) {
@@ -67,7 +71,6 @@ public boolean mayDelete(List nodeIds, Principal principal) {
* @param principal Identifies user.
* @return false if user may not delete the {@link Node}.
*/
- @SuppressWarnings("unused")
public boolean mayDelete(String nodeId, Principal principal) {
Node node = nodeDAO.getNode(nodeId);
if (!node.getUserName().equals(principal.getName())) {
@@ -86,7 +89,6 @@ public boolean mayDelete(String nodeId, Principal principal) {
* @param principal Identifies user.
* @return false if user may not update the {@link Node}.
*/
- @SuppressWarnings("unused")
public boolean mayUpdate(Node node, Principal principal) {
return nodeDAO.getNode(node.getUniqueId()).getUserName().equals(principal.getName());
}
@@ -101,10 +103,27 @@ public boolean mayUpdate(Node node, Principal principal) {
* @param principal Identifies user.
* @return false if user may not update the {@link CompositeSnapshot}.
*/
- @SuppressWarnings("unused")
+
public boolean mayUpdate(CompositeSnapshot compositeSnapshot, Principal principal) {
- Node node = nodeDAO.getNode(compositeSnapshot.getCompositeSnapshotNode().getUniqueId());
- return node.getUserName().equals(principal.getName());
+ return isOwner(compositeSnapshot.getCompositeSnapshotNode().getUniqueId(), principal.getName());
+ }
+
+ public boolean mayAddOrDeleteTag(TagData tagData, Authentication authentication){
+ Tag tag = tagData.getTag();
+ List roles = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
+ if(tag.getName().equals(Tag.GOLDEN)){
+ return roles.contains(roleAdmin);
+ }
+ for(String nodeId : tagData.getUniqueNodeIds()){
+ if(!isOwner(nodeId, authentication.getName())){
+ return false;
+ }
+ }
+ return true;
}
+ private boolean isOwner(String nodeId, String username){
+ Node node = nodeDAO.getNode(nodeId);
+ return node.getUserName().equals(username);
+ }
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java
index a9bc15c447..36abce2101 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java
@@ -26,6 +26,8 @@
import org.phoebus.applications.saveandrestore.model.TagData;
import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
@@ -53,19 +55,20 @@ public List getTags() {
* Adds a {@link Tag} to specified list of target {@link Node}s. The {@link Tag} contained
* in tagData must be non-null, and its name must be non-null and non-empty.
* @param tagData See {@link TagData}
- * @param principal {@link Principal} of authenticated user.
+ * @param authentication {@link Authentication} of authenticated user.
* @return The list of updated {@link Node}s
*/
@PostMapping("/tags")
+ @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and @authorizationHelper.mayAddOrDeleteTag(#tagData, #authentication))")
public List addTag(@RequestBody TagData tagData,
- Principal principal) {
+ Authentication authentication) {
if (tagData.getTag() == null ||
tagData.getTag().getName() == null ||
tagData.getTag().getName().isEmpty() ||
tagData.getUniqueNodeIds() == null) {
throw new IllegalArgumentException("Cannot add tag, data invalid");
}
- tagData.getTag().setUserName(principal.getName());
+ tagData.getTag().setUserName(authentication.getName());
return nodeDAO.addTag(tagData);
}
@@ -76,12 +79,13 @@ public List addTag(@RequestBody TagData tagData,
* @return The list of updated {@link Node}s
*/
@DeleteMapping("/tags")
- public List deleteTag(@RequestBody TagData tagData) {
+ @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and @authorizationHelper.mayAddOrDeleteTag(#tagData, #authentication))")
+ public List deleteTag(@RequestBody TagData tagData, Authentication authentication) {
if (tagData.getTag() == null ||
tagData.getTag().getName() == null ||
tagData.getTag().getName().isEmpty() ||
tagData.getUniqueNodeIds() == null) {
- throw new IllegalArgumentException("Cannot add tag, data invalid");
+ throw new IllegalArgumentException("Cannot delete tag, data invalid");
}
return nodeDAO.deleteTag(tagData);
}
From db129719b72bae76cd3cd50345852851c2c11356 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Thu, 9 Nov 2023 16:36:29 +0100
Subject: [PATCH 38/42] Remaining pieces for save&restore
authentication/authorization: filters
---
.../client/SaveAndRestoreJerseyClient.java | 40 ++++--
.../config/AnonymousWebSecurityConfig.java | 109 +++++++++++++++
.../web/config/WebSecurityConfig.java | 119 ++--------------
.../web/controllers/AuthorizationHelper.java | 41 +++++-
.../web/controllers/BaseController.java | 129 ++++++++----------
.../web/controllers/FilterController.java | 25 +---
.../web/controllers/TagController.java | 26 ++--
.../dao/impl/elasticsearch/DAOTest.java | 4 +-
.../elasticsearch/ElasticsearchDAOTest.java | 21 +--
.../web/config/ControllersTestConfig.java | 30 ++--
.../web/controllers/TagControllerTest.java | 11 +-
11 files changed, 290 insertions(+), 265 deletions(-)
create mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AnonymousWebSecurityConfig.java
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java
index 3dc85f2ca1..aac19ca992 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java
@@ -181,7 +181,9 @@ public List getChildNodes(String uniqueNodeId) throws SaveAndRestoreClient
@Override
public Node createNewNode(String parentNodeId, Node node) {
- WebResource webResource = getClient().resource(jmasarServiceUrl + "/node").queryParam("parentNodeId", parentNodeId);
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/node")
+ .queryParam("parentNodeId", parentNodeId) // Request parameter username is needed in case authorization/authentication is disabled.
+ .queryParam("username", System.getProperty("user.name"));
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(node, CONTENT_TYPE_JSON)
.put(ClientResponse.class);
@@ -205,7 +207,9 @@ public Node updateNode(Node nodeToUpdate) {
@Override
public Node updateNode(Node nodeToUpdate, boolean customTimeForMigration) {
WebResource webResource = getClient().resource(jmasarServiceUrl + "/node")
- .queryParam("customTimeForMigration", customTimeForMigration ? "true" : "false");
+ .queryParam("customTimeForMigration", customTimeForMigration ? "true" : "false")
+ // Request parameter username is needed in case authorization/authentication is disabled.
+ .queryParam("username", System.getProperty("user.name"));
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(nodeToUpdate, CONTENT_TYPE_JSON)
@@ -343,7 +347,9 @@ public ConfigurationData getConfigurationData(String nodeId) {
public Configuration createConfiguration(String parentNodeId, Configuration configuration) {
WebResource webResource =
getClient().resource(jmasarServiceUrl + "/config")
- .queryParam("parentNodeId", parentNodeId);
+ .queryParam("parentNodeId", parentNodeId)
+ // Request parameter username is needed in case authorization/authentication is disabled.
+ .queryParam("username", System.getProperty("user.name"));
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(configuration, CONTENT_TYPE_JSON)
.put(ClientResponse.class);
@@ -361,7 +367,9 @@ public Configuration createConfiguration(String parentNodeId, Configuration conf
@Override
public Configuration updateConfiguration(Configuration configuration) {
- WebResource webResource = getClient().resource(jmasarServiceUrl + "/config");
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/config")
+ // Request parameter username is needed in case authorization/authentication is disabled.
+ .queryParam("username", System.getProperty("user.name"));
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(configuration, CONTENT_TYPE_JSON)
@@ -388,7 +396,9 @@ public SnapshotData getSnapshotData(String nodeId) {
public Snapshot saveSnapshot(String parentNodeId, Snapshot snapshot) {
WebResource webResource =
getClient().resource(jmasarServiceUrl + "/snapshot")
- .queryParam("parentNodeId", parentNodeId);
+ .queryParam("parentNodeId", parentNodeId)
+ // Request parameter username is needed in case authorization/authentication is disabled.
+ .queryParam("username", System.getProperty("user.name"));
ClientResponse response;
try {
response = webResource.accept(CONTENT_TYPE_JSON)
@@ -413,7 +423,9 @@ public Snapshot saveSnapshot(String parentNodeId, Snapshot snapshot) {
public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeSnapshot compositeSnapshot) {
WebResource webResource =
getClient().resource(jmasarServiceUrl + "/composite-snapshot")
- .queryParam("parentNodeId", parentNodeId);
+ .queryParam("parentNodeId", parentNodeId)
+ // Request parameter username is needed in case authorization/authentication is disabled.
+ .queryParam("username", System.getProperty("user.name"));
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(compositeSnapshot, CONTENT_TYPE_JSON)
.put(ClientResponse.class);
@@ -451,7 +463,9 @@ public List checkCompositeSnapshotConsistency(List snapshotNodeI
@Override
public CompositeSnapshot updateCompositeSnapshot(CompositeSnapshot compositeSnapshot) {
- WebResource webResource = getClient().resource(jmasarServiceUrl + "/composite-snapshot");
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/composite-snapshot")
+ // Request parameter username is needed in case authorization/authentication is disabled.
+ .queryParam("username", System.getProperty("user.name"));
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(compositeSnapshot, CONTENT_TYPE_JSON)
@@ -488,8 +502,9 @@ public SearchResult search(MultivaluedMap searchParams) {
@Override
public Filter saveFilter(Filter filter) {
- WebResource webResource = getClient().resource(jmasarServiceUrl + "/filter");
-
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/filter")
+ // Request parameter username is needed in case authorization/authentication is disabled.
+ .queryParam("username", System.getProperty("user.name"));
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(filter, CONTENT_TYPE_JSON)
.put(ClientResponse.class);
@@ -542,15 +557,18 @@ public void deleteFilter(String name) {
}
/**
- * Adds a tag to a list of unique node ids, see {@link TagData}
+ * Adds a tag to a list of unique node ids, see {@link TagData}.
*
* @param tagData see {@link TagData}
* @return A list of updated {@link Node}s. This may contain fewer elements than the list of unique node ids
* passed in the tagData parameter.
*/
public List addTag(TagData tagData) {
+
WebResource webResource =
- getClient().resource(jmasarServiceUrl + "/tags");
+ getClient().resource(jmasarServiceUrl + "/tags")
+ // Request parameter username is needed in case authorization/authentication is disabled.
+ .queryParam("username", System.getProperty("user.name"));
ClientResponse response;
try {
response = webResource.accept(CONTENT_TYPE_JSON)
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AnonymousWebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AnonymousWebSecurityConfig.java
new file mode 100644
index 0000000000..efd62f40d4
--- /dev/null
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AnonymousWebSecurityConfig.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package org.phoebus.service.saveandrestore.web.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.SecurityFilterChain;
+
+/**
+ * Web security configuration instantiated if authorization/authentication is disabled.
+ * Despite its name it still needs to define authorization/authentication related values and beans.
+ */
+@Configuration
+@ConditionalOnProperty(name = "auth.impl", havingValue = "none")
+@SuppressWarnings("unused")
+public class AnonymousWebSecurityConfig {
+
+ @Value("${role.user:sar-user}")
+ public String roleUser;
+
+ @Value("${role.admin:sar-admin}")
+ public String roleAdmin;
+
+ @Value("${demo.user:user}")
+ public String demoUser;
+
+ @Bean
+ public String roleUser() {
+ return roleUser.toUpperCase();
+ }
+
+ @Bean
+ public String roleAdmin() {
+ return roleAdmin.toUpperCase();
+ }
+
+ @Bean
+ public String demoUser(){
+ return demoUser;
+ }
+
+ @Bean
+ public String demoUserPassword(){
+ return demoUserPassword;
+ }
+
+ @Bean
+ public String demoAdmin(){
+ return demoAdmin;
+ }
+
+ @Bean
+ public String demoAdminPassword(){
+ return demoAdminPassword;
+ }
+
+ @Bean
+ public String demoReadOnly(){
+ return demoReadOnly;
+ }
+
+ @Bean
+ public String demoReadOnlyPassword(){
+ return demoReadOnlyPassword;
+ }
+
+ @Value("${demo.user.password:userPass}")
+ public String demoUserPassword;
+
+ @Value("${demo.admin:admin}")
+ public String demoAdmin;
+
+ @Value("${demo.admin.password:adminPass}")
+ public String demoAdminPassword;
+
+ @Value("${demo.readOnly:johndoe}")
+ public String demoReadOnly;
+
+ @Value("${demo.readOnly.password:1234}")
+ public String demoReadOnlyPassword;
+
+
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ http.csrf().disable();
+ http.anonymous();
+ return http.build();
+ }
+}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
index 532302e9c9..95ecef6dac 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
@@ -29,12 +29,16 @@
@Configuration
@EnableWebSecurity
-@EnableGlobalMethodSecurity(
- prePostEnabled = true,
- securedEnabled = true,
- jsr250Enabled = true)
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+@Conditional(AuthEnabledCondition.class)
@SuppressWarnings("unused")
-public class WebSecurityConfig {
+public class WebSecurityConfig extends AnonymousWebSecurityConfig {
+
+ /**
+ * Authentication implementation.
+ */
+ @Value("${auth.impl:none}")
+ protected String authenitcationImplementation;
/**
* External Active Directory configuration properties
@@ -65,45 +69,6 @@ public class WebSecurityConfig {
@Value("${ldap.user.search.filter:invalid}")
String ldap_user_search_filter;
- /**
- * Authentication implementation.
- */
- @Value("${auth.impl:none}")
- String authenitcationImplementation;
-
- @Value("${role.user:sar-user}")
- private String roleUser;
-
- @Value("${role.superuser:sar-superuser}")
- private String roleSuperuser;
-
- @Value("${role.admin:sar-admin}")
- private String roleAdmin;
-
- @Value("${demo.user:user}")
- private String demoUser;
-
- @Value("${demo.user.password:userPass}")
- private String demoUserPassword;
-
- @Value("${demo.superuser:superuser}")
- private String demoSuperuser;
-
- @Value("${demo.superuser.password:superuserPass}")
- private String demoSuperuserPassword;
-
- @Value("${demo.admin:admin}")
- private String demoAdmin;
-
- @Value("${demo.admin.password:adminPass}")
- private String demoAdminPassword;
-
- @Value("${demo.readOnly:johndoe}")
- private String demoReadOnly;
-
- @Value("${demo.readOnly.password:1234}")
- private String demoReadOnlyPassword;
-
@Bean
public WebSecurityCustomizer ignoringCustomizer() {
return web -> {
@@ -116,12 +81,9 @@ public WebSecurityCustomizer ignoringCustomizer() {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
- if ("none".equalsIgnoreCase(authenitcationImplementation.trim())) {
- http.anonymous();
- } else {
- http.authorizeRequests().anyRequest().authenticated();
- http.httpBasic();
- }
+ http.authorizeRequests().anyRequest().authenticated();
+ http.httpBasic();
+
return http.build();
}
@@ -183,7 +145,6 @@ public O postProcess(O object) {
.passwordEncoder(encoder())
.withUser(demoAdmin).password(encoder().encode(demoAdminPassword)).roles(roleAdmin()).and()
.withUser(demoUser).password(encoder().encode(demoUserPassword)).roles(roleUser()).and()
- .withUser(demoSuperuser).password(encoder().encode(demoSuperuserPassword)).roles(roleSuperuser()).and()
.withUser(demoReadOnly).password(encoder().encode(demoReadOnlyPassword)).roles().and().and().build();
}
@@ -202,62 +163,6 @@ public ObjectMapper objectMapper() {
return objectMapper;
}
- @Bean
- public String roleUser() {
- return roleUser.toUpperCase();
- }
-
- @Bean
- public String roleSuperuser() {
- return roleSuperuser.toUpperCase();
- }
-
- @Bean
- public String roleAdmin() {
- return roleAdmin.toUpperCase();
- }
-
- @Bean("demoUser")
- public String demoUser() {
- return demoUser;
- }
-
- @Bean("demoUserPassword")
- public String demoUserPassword() {
- return demoUserPassword;
- }
-
- @Bean("demoSuperuser")
- public String demoSuperuser() {
- return demoSuperuser;
- }
-
- @Bean("demoSuperuserPassword")
- public String demoSuperuserPassword() {
- return demoSuperuserPassword;
- }
-
- @Bean("demoAdmin")
- public String demoAdmin() {
- return demoAdmin;
- }
-
- @Bean("demoAdminPassword")
- public String demoAdminPassword() {
- return demoAdminPassword;
- }
-
- @Bean("demoReadOnly")
- public String demoReadOnly() {
- return demoReadOnly;
- }
-
- @Bean("demoReadOnlyPassword")
- public String demoReadOnlyPassword() {
- return demoReadOnlyPassword;
- }
-
-
/**
* Configures role hierarchy, i.e. user - superuser - admin. Do not remove this {@link Bean}!
*
NOTE!
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java
index 193e04bd1b..182080cf28 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java
@@ -20,14 +20,18 @@
package org.phoebus.service.saveandrestore.web.controllers;
import org.phoebus.applications.saveandrestore.model.*;
+import org.phoebus.applications.saveandrestore.model.search.Filter;
import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
+import org.phoebus.service.saveandrestore.web.config.AuthEnabledCondition;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Conditional;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;
import java.security.Principal;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Collectors;
/**
@@ -35,6 +39,7 @@
* grant or deny access to certain REST endpoints.
*/
@Service("authorizationHelper")
+@Conditional(AuthEnabledCondition.class)
@SuppressWarnings("unused")
public class AuthorizationHelper {
@@ -103,12 +108,24 @@ public boolean mayUpdate(Node node, Principal principal) {
* @param principal Identifies user.
* @return false if user may not update the {@link CompositeSnapshot}.
*/
-
public boolean mayUpdate(CompositeSnapshot compositeSnapshot, Principal principal) {
return isOwner(compositeSnapshot.getCompositeSnapshotNode().getUniqueId(), principal.getName());
}
+ /**
+ * An authenticated user may add or delete {@link Tag}s if user identity is same as the target's
+ * snapshot {@link Node}. However, to add or delete golden tag user must have admin privileges.
+ * @param tagData {@link TagData} containing {@link Node} ids and {@link Tag} name.
+ * @param authentication {@link Authentication} providing username and roles.
+ * @return true if {@link Tag} can be added or deleted.
+ */
public boolean mayAddOrDeleteTag(TagData tagData, Authentication authentication){
+ if (tagData.getTag() == null ||
+ tagData.getTag().getName() == null ||
+ tagData.getTag().getName().isEmpty() ||
+ tagData.getUniqueNodeIds() == null) {
+ throw new IllegalArgumentException("Cannot add tag, data invalid");
+ }
Tag tag = tagData.getTag();
List roles = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
if(tag.getName().equals(Tag.GOLDEN)){
@@ -126,4 +143,26 @@ private boolean isOwner(String nodeId, String username){
Node node = nodeDAO.getNode(nodeId);
return node.getUserName().equals(username);
}
+
+ /**
+ *
+ *
+ * An authenticated user may save a filter, and update/delete if user identity is same as the target's
+ * name field.
+ *
+ *
+ * @param filterName Unique name identifying the target of the user's update operation.
+ * @param principal Identifies user.
+ * @return false if user may not update the {@link Filter}.
+ */
+ public boolean maySaveOrDeleteFilter(String filterName, Principal principal) {
+ Optional filter1 =
+ nodeDAO.getAllFilters().stream().filter(f ->
+ f.getName().equals(filterName)).findFirst();
+ // If the filter does not (yet) exist, save is OK
+ if(filter1.isEmpty()){
+ return true;
+ }
+ return filter1.map(value -> value.getUser().equals(principal.getName())).orElse(true);
+ }
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java
index 4e24cd2c12..6d2d8f4ceb 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java
@@ -1,36 +1,33 @@
-/**
+/**
* Copyright (C) 2018 European Spallation Source ERIC.
- *
+ *
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.phoebus.service.saveandrestore.web.controllers;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-
import org.phoebus.applications.saveandrestore.model.security.UserNotAuthorizedException;
+import org.phoebus.service.saveandrestore.NodeNotFoundException;
+import org.phoebus.service.saveandrestore.SnapshotNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
-import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-import org.phoebus.service.saveandrestore.NodeNotFoundException;
-import org.phoebus.service.saveandrestore.SnapshotNotFoundException;
-
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -43,70 +40,64 @@
@RestController
@SuppressWarnings("unused")
public abstract class BaseController {
-
- public static final String JSON = "application/json";
-
- private final Logger logger = Logger.getLogger(BaseController.class.getName());
- @Autowired
- public String roleAdmin; // This MUST be public!!!
+ public static final String JSON = "application/json";
+
+ private final Logger logger = Logger.getLogger(BaseController.class.getName());
+
+ @Autowired
+ public String roleAdmin; // This MUST be public!!!
- @Autowired
- public String roleSuperuser; // This MUST be public!!!
+ @Autowired
+ public String roleUser; // This MUST be public!!!
- @Autowired
- public String roleUser; // This MUST be public!!!
- @Autowired
- public String demoAdmin;
+ /**
+ * Intercepts {@link SnapshotNotFoundException} and triggers a {@link HttpStatus#NOT_FOUND}.
+ * @param req The servlet request
+ * @param exception The exception to intercept
+ * @return A {@link ResponseEntity} carrying the underlying exception message.
+ */
+ @ExceptionHandler(SnapshotNotFoundException.class)
+ public ResponseEntity handleSnapshotNotFoundException(HttpServletRequest req,
+ SnapshotNotFoundException exception) {
+ log(exception);
+ return new ResponseEntity<>(exception.getMessage(), HttpStatus.NOT_FOUND);
+ }
+ /**
+ * Intercepts {@link IllegalArgumentException} and triggers a {@link HttpStatus#BAD_REQUEST}.
+ * @param req The servlet request
+ * @param exception The exception to intercept
+ * @return A {@link ResponseEntity} carrying the underlying exception message.
+ */
+ @ExceptionHandler(IllegalArgumentException.class)
+ public ResponseEntity handleIllegalArgumentException(HttpServletRequest req,
+ IllegalArgumentException exception) {
+ log(exception);
+ return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
+ }
- /**
- * Intercepts {@link SnapshotNotFoundException} and triggers a {@link HttpStatus#NOT_FOUND}.
- * @param req The servlet request
- * @param exception The exception to intercept
- * @return A {@link ResponseEntity} carrying the underlying exception message.
- */
- @ExceptionHandler(SnapshotNotFoundException.class)
- public ResponseEntity handleSnapshotNotFoundException(HttpServletRequest req,
- SnapshotNotFoundException exception) {
- log(exception);
- return new ResponseEntity<>(exception.getMessage(), HttpStatus.NOT_FOUND);
- }
-
- /**
- * Intercepts {@link IllegalArgumentException} and triggers a {@link HttpStatus#BAD_REQUEST}.
- * @param req The servlet request
- * @param exception The exception to intercept
- * @return A {@link ResponseEntity} carrying the underlying exception message.
- */
- @ExceptionHandler(IllegalArgumentException.class)
- public ResponseEntity handleIllegalArgumentException(HttpServletRequest req,
- IllegalArgumentException exception) {
- log(exception);
- return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
- }
-
- /**
- * Intercepts {@link NodeNotFoundException} and triggers a {@link HttpStatus#NOT_FOUND}.
- * @param req The {@link HttpServlet} request
- * @param exception The exception to intercept
- * @return A {@link ResponseEntity} carrying the underlying exception message.
- */
- @ExceptionHandler(NodeNotFoundException.class)
- public ResponseEntity handleNodeNotFoundException(HttpServletRequest req,
- NodeNotFoundException exception) {
- log(exception);
- return new ResponseEntity<>(exception.getMessage(), HttpStatus.NOT_FOUND);
- }
+ /**
+ * Intercepts {@link NodeNotFoundException} and triggers a {@link HttpStatus#NOT_FOUND}.
+ * @param req The {@link HttpServlet} request
+ * @param exception The exception to intercept
+ * @return A {@link ResponseEntity} carrying the underlying exception message.
+ */
+ @ExceptionHandler(NodeNotFoundException.class)
+ public ResponseEntity handleNodeNotFoundException(HttpServletRequest req,
+ NodeNotFoundException exception) {
+ log(exception);
+ return new ResponseEntity<>(exception.getMessage(), HttpStatus.NOT_FOUND);
+ }
- @ExceptionHandler(UserNotAuthorizedException.class)
- public ResponseEntity handleUserNotAuthorizedException(HttpServletRequest req, UserNotAuthorizedException exception){
- log(exception);
- return new ResponseEntity<>(exception.getMessage(), HttpStatus.FORBIDDEN);
- }
+ @ExceptionHandler(UserNotAuthorizedException.class)
+ public ResponseEntity handleUserNotAuthorizedException(HttpServletRequest req, UserNotAuthorizedException exception) {
+ log(exception);
+ return new ResponseEntity<>(exception.getMessage(), HttpStatus.FORBIDDEN);
+ }
- private void log(Throwable throwable) {
- logger.log(Level.INFO, "Intercepted " + throwable.getClass().getName(), throwable);
- }
+ private void log(Throwable throwable) {
+ logger.log(Level.INFO, "Intercepted " + throwable.getClass().getName(), throwable);
+ }
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java
index 9667fce494..d702f3cddc 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java
@@ -45,34 +45,13 @@ public class FilterController extends BaseController {
*/
@SuppressWarnings("unused")
@PutMapping(value = "/filter", produces = JSON)
- @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and this.maySaveOrDelete(#filter.getName(), #principal))")
+ @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and @authorizationHelper.maySaveOrDeleteFilter(#filter.getName(), #principal))")
public Filter saveFilter(@RequestBody final Filter filter,
Principal principal) {
filter.setUser(principal.getName());
return nodeDAO.saveFilter(filter);
}
-
- /**
- * NOTE: this method MUST be public!
- *
- *
- * An authenticated user may save a filter, and update if user identity is same as the target's
- * name field.
- *
- *
- * @param filterName Unique name identifying the target of the user's update operation.
- * @param principal Identifies user.
- * @return false if user may not update the {@link Filter}.
- */
- @SuppressWarnings("unused")
- public boolean maySaveOrDelete(String filterName, Principal principal) {
- Optional filter1 =
- nodeDAO.getAllFilters().stream().filter(f ->
- f.getName().equals(filterName)).findFirst();
- return filter1.map(value -> value.getUser().equals(principal.getName())).orElse(true);
- }
-
@SuppressWarnings("unused")
@GetMapping(value = "/filters", produces = JSON)
public List getAllFilters() {
@@ -81,7 +60,7 @@ public List getAllFilters() {
@SuppressWarnings("unused")
@DeleteMapping(value = "/filter/{name}")
- @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and this.maySaveOrDelete(#name, #principal))")
+ @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and @authorizationHelper.maySaveOrDeleteFilter(#name, #principal))")
public void deleteFilter(@PathVariable final String name, Principal principal) {
nodeDAO.deleteFilter(name);
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java
index 36abce2101..84d0a1d2b6 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java
@@ -30,11 +30,10 @@
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
-import java.security.Principal;
import java.util.List;
/**
- * {@link TagController} class for supporting RESTful APIs for tag
+ * {@link TagController} class for supporting REST-ful APIs for tag
*
* @author Genie Jhang
*/
@@ -54,39 +53,34 @@ public List getTags() {
/**
* Adds a {@link Tag} to specified list of target {@link Node}s. The {@link Tag} contained
* in tagData must be non-null, and its name must be non-null and non-empty.
- * @param tagData See {@link TagData}
+ *
+ * @param tagData See {@link TagData}
+ * @param userName Must be non-null and non-empty if authentication/authorization is disabled.
* @param authentication {@link Authentication} of authenticated user.
* @return The list of updated {@link Node}s
*/
@PostMapping("/tags")
@PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and @authorizationHelper.mayAddOrDeleteTag(#tagData, #authentication))")
public List addTag(@RequestBody TagData tagData,
+ @RequestParam(name = "username", required = false) String userName,
Authentication authentication) {
- if (tagData.getTag() == null ||
- tagData.getTag().getName() == null ||
- tagData.getTag().getName().isEmpty() ||
- tagData.getUniqueNodeIds() == null) {
- throw new IllegalArgumentException("Cannot add tag, data invalid");
+ if(authentication == null && (userName == null || userName.isEmpty())){
+ throw new IllegalArgumentException("Cannot determine username for tag");
}
- tagData.getTag().setUserName(authentication.getName());
+ tagData.getTag().setUserName(authentication == null ? userName : authentication.getName());
return nodeDAO.addTag(tagData);
}
/**
* Removes a {@link Tag} from specified list of target {@link Node}s. The {@link Tag} contained
- * * in tagData must be non-null, and its name must be non-null and non-empty.
+ * * in tagData must be non-null, and its name must be non-null and non-empty.
+ *
* @param tagData See {@link TagData}
* @return The list of updated {@link Node}s
*/
@DeleteMapping("/tags")
@PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and @authorizationHelper.mayAddOrDeleteTag(#tagData, #authentication))")
public List deleteTag(@RequestBody TagData tagData, Authentication authentication) {
- if (tagData.getTag() == null ||
- tagData.getTag().getName() == null ||
- tagData.getTag().getName().isEmpty() ||
- tagData.getUniqueNodeIds() == null) {
- throw new IllegalArgumentException("Cannot delete tag, data invalid");
- }
return nodeDAO.deleteTag(tagData);
}
}
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/DAOTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/DAOTest.java
index 07ec4a3c76..47c92f0b42 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/DAOTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/DAOTest.java
@@ -24,12 +24,12 @@
import java.util.Arrays;
import java.util.List;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
public class DAOTest {
@Test
- public void testExtractDuplicatePVNames(){
+ public void testExtractDuplicatePVNames() {
List allPVNames = Arrays.asList("a", "b", "c", "d", "D", "a", "B", "a", "b");
List duplicates = new ElasticsearchDAO().extractDuplicates(allPVNames);
assertEquals(2, duplicates.size());
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAOTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAOTest.java
index ae2e5f1f16..606c9d4884 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAOTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAOTest.java
@@ -22,12 +22,7 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
-import org.phoebus.applications.saveandrestore.model.ConfigPv;
-import org.phoebus.applications.saveandrestore.model.ConfigurationData;
-import org.phoebus.applications.saveandrestore.model.Node;
-import org.phoebus.applications.saveandrestore.model.NodeType;
-import org.phoebus.applications.saveandrestore.model.SnapshotData;
-import org.phoebus.applications.saveandrestore.model.SnapshotItem;
+import org.phoebus.applications.saveandrestore.model.*;
import org.phoebus.service.saveandrestore.model.ESTreeNode;
import org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch.ElasticsearchDAO.NodeNameComparator;
import org.springframework.beans.factory.annotation.Autowired;
@@ -46,12 +41,8 @@
import java.util.UUID;
import java.util.stream.Collectors;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import static org.mockito.Mockito.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.when;
@ExtendWith(SpringExtension.class)
@EnableConfigurationProperties
@@ -184,7 +175,7 @@ public void testMayCopySnapshot() {
ConfigPv configPvb = ConfigPv.builder().pvName("b").build();
ConfigPv configPvc = ConfigPv.builder().pvName("c").build();
ConfigPv configPvd = ConfigPv.builder().pvName("d").build();
- ConfigPv configPve= ConfigPv.builder().pvName("e").build();
+ ConfigPv configPve = ConfigPv.builder().pvName("e").build();
SnapshotData snapshotData = new SnapshotData();
snapshotData.setSnapshotItems(Arrays.asList(SnapshotItem.builder().configPv(configPva).build(),
@@ -228,7 +219,7 @@ public void testMayCopySnapshot() {
}
@Test
- public void testDetermineNewNodeName(){
+ public void testDetermineNewNodeName() {
Node n1 = Node.builder().uniqueId("abc").name("abc").build();
Node n2 = Node.builder().uniqueId("def").name("def").build();
Node n3 = Node.builder().uniqueId("ABC copy").name("ABC copy").build();
@@ -358,7 +349,7 @@ public void testDetermineNewNodeName3() {
}
@Test
- public void nodeNameComparatorTest(){
+ public void nodeNameComparatorTest() {
List sorted = Arrays.asList("abc", "abc copy").stream().sorted(new NodeNameComparator()).collect(Collectors.toList());
assertEquals("abc", sorted.get(0));
sorted = Arrays.asList("abc copy", "abc").stream().sorted(new NodeNameComparator()).collect(Collectors.toList());
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java
index 3ee6f1a245..8e29211f0d 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java
@@ -27,10 +27,11 @@
import org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch.SnapshotDataRepository;
import org.phoebus.service.saveandrestore.search.SearchUtil;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringBootConfiguration;
-import org.springframework.context.annotation.*;
-import org.springframework.test.context.TestPropertySource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.Profile;
import org.springframework.util.Base64Utils;
@SpringBootConfiguration
@@ -46,12 +47,6 @@ public class ControllersTestConfig {
@Autowired
private String demoUserPassword;
- @Autowired
- private String demoSuperuser;
-
- @Autowired
- private String demoSuperuserPassword;
-
@Autowired
private String demoAdmin;
@@ -107,22 +102,17 @@ public SearchUtil searchUtil() {
}
@Bean("userAuthorization")
- public String userAuthorization(){
- return "Basic " + Base64Utils.encodeToString((demoUser +":" + demoUserPassword).getBytes());
- }
-
- @Bean("superuserAuthorization")
- public String superuserAuthorization(){
- return "Basic " + Base64Utils.encodeToString((demoSuperuser +":" + demoSuperuserPassword).getBytes());
+ public String userAuthorization() {
+ return "Basic " + Base64Utils.encodeToString((demoUser + ":" + demoUserPassword).getBytes());
}
@Bean("adminAuthorization")
- public String adminAuthorization(){
- return "Basic " + Base64Utils.encodeToString((demoAdmin +":" + demoAdminPassword).getBytes());
+ public String adminAuthorization() {
+ return "Basic " + Base64Utils.encodeToString((demoAdmin + ":" + demoAdminPassword).getBytes());
}
@Bean("readOnlyAuthorization")
- public String readOnlyAuthorization(){
- return "Basic " + Base64Utils.encodeToString((demoReadOnly +":" + demoReadOnlyPassword).getBytes());
+ public String readOnlyAuthorization() {
+ return "Basic " + Base64Utils.encodeToString((demoReadOnly + ":" + demoReadOnlyPassword).getBytes());
}
}
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java
index d4606db8a0..b7bf3a6b1e 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java
@@ -63,6 +63,9 @@ public class TagControllerTest {
@Autowired
private String userAuthorization;
+ @Autowired
+ private String demoUser;
+
private ObjectMapper objectMapper = new ObjectMapper();
@Test
@@ -86,11 +89,13 @@ public void testAddTag() throws Exception{
Tag tag = new Tag();
tag.setName("tag");
- Node node = Node.builder().name("name").uniqueId("uniqueId").tags(List.of(tag)).build();
+ Node node = Node.builder().name("name").uniqueId("uniqueId").userName(demoUser).tags(List.of(tag)).build();
TagData tagData = new TagData();
tagData.setTag(tag);
tagData.setUniqueNodeIds(List.of("uniqueId"));
+
+ when(nodeDAO.getNode("uniqueId")).thenReturn(node);
when(nodeDAO.addTag(tagData)).thenReturn(List.of(node));
MockHttpServletRequestBuilder request = post("/tags").contentType(JSON)
@@ -110,6 +115,10 @@ public void testAddTagBadData() throws Exception{
TagData tagData = new TagData();
tagData.setUniqueNodeIds(List.of("uniqueId"));
+
+ Node node = Node.builder().name("name").uniqueId("uniqueId").userName(demoUser).build();
+ when(nodeDAO.getNode("uniqueId")).thenReturn(node);
+
MockHttpServletRequestBuilder request = post("/tags").contentType(JSON)
.header(HttpHeaders.AUTHORIZATION, userAuthorization)
.content(objectMapper.writeValueAsString(tagData));
From 6b594b149c16d6b5a51b2e3a3b59be56e3486cfa Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Mon, 13 Nov 2023 15:01:39 +0100
Subject: [PATCH 39/42] Updated authorization strategy: authentication
mandatory, authorization optional
---
.../CredentialsManagementApp.java | 2 +-
.../saveandrestore/Preferences.java | 3 -
.../SaveAndRestoreAuthenticationProvider.java | 4 -
.../save_and_restore_preferences.properties | 3 -
.../ServiceAuthenticationProvider.java | 8 --
.../config/AnonymousWebSecurityConfig.java | 109 ----------------
.../web/config/WebSecurityConfig.java | 78 ++++++++++-
.../controllers/AuthenticationController.java | 1 -
.../web/controllers/AuthorizationHelper.java | 121 +++++++++++++-----
.../CompositeSnapshotController.java | 2 +-
.../web/controllers/FilterController.java | 4 +-
.../web/controllers/NodeController.java | 44 ++++++-
.../web/controllers/TagController.java | 18 +--
.../src/main/resources/application.properties | 6 +-
.../web/controllers/NodeControllerTest.java | 44 +++----
.../resources/test_application.properties | 2 +
16 files changed, 237 insertions(+), 212 deletions(-)
delete mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AnonymousWebSecurityConfig.java
diff --git a/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementApp.java b/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementApp.java
index 7a322f016a..4f55f13e5f 100644
--- a/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementApp.java
+++ b/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementApp.java
@@ -61,7 +61,7 @@ public String getDisplayName() {
public AppInstance create() {
List authenticationProviders =
ServiceLoader.load(ServiceAuthenticationProvider.class).stream().map(Provider::get)
- .filter(ServiceAuthenticationProvider::isActive).collect(Collectors.toList());
+ .collect(Collectors.toList());
try {
SecureStore secureStore = new SecureStore();
new CredentialsManagementStage(authenticationProviders, secureStore).show();
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Preferences.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Preferences.java
index fb026ac839..2453b12734 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Preferences.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Preferences.java
@@ -31,9 +31,6 @@ public class Preferences {
@Preference
public static String default_snapshot_name_date_format;
- @Preference
- public static boolean authentication_enabled;
-
static
{
AnnotatedPreferences.initialize(Preferences.class, "/save_and_restore_preferences.properties");
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java
index fd18b5eae6..1ce2a3e074 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java
@@ -54,8 +54,4 @@ public AuthenticationScope getAuthenticationScope(){
return AuthenticationScope.SAVE_AND_RESTORE;
}
- @Override
- public boolean isActive(){
- return Preferences.authentication_enabled;
- }
}
diff --git a/app/save-and-restore/app/src/main/resources/save_and_restore_preferences.properties b/app/save-and-restore/app/src/main/resources/save_and_restore_preferences.properties
index 1545280157..082fcab433 100644
--- a/app/save-and-restore/app/src/main/resources/save_and_restore_preferences.properties
+++ b/app/save-and-restore/app/src/main/resources/save_and_restore_preferences.properties
@@ -25,6 +25,3 @@ httpClient.readTimeout=1000
# Connect timeout in (ms) used by the Jersey client
httpClient.connectTimeout=1000
-
-# Authentication/authorization enabled/disabled
-authentication_enabled=false
\ No newline at end of file
diff --git a/core/security/src/main/java/org/phoebus/security/authorization/ServiceAuthenticationProvider.java b/core/security/src/main/java/org/phoebus/security/authorization/ServiceAuthenticationProvider.java
index 691fb1581f..9e807e0c69 100644
--- a/core/security/src/main/java/org/phoebus/security/authorization/ServiceAuthenticationProvider.java
+++ b/core/security/src/main/java/org/phoebus/security/authorization/ServiceAuthenticationProvider.java
@@ -52,12 +52,4 @@ public interface ServiceAuthenticationProvider {
*/
AuthenticationScope getAuthenticationScope();
- /**
- * Indicates if a provider is active. Inactive providers suggest authentication is disabled or should
- * not be accessible in the credentials management UI.
- * @return true if the authentication provider is active, otherwise false.
- */
- default boolean isActive(){
- return true;
- }
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AnonymousWebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AnonymousWebSecurityConfig.java
deleted file mode 100644
index efd62f40d4..0000000000
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AnonymousWebSecurityConfig.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2023 European Spallation Source ERIC.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- */
-
-package org.phoebus.service.saveandrestore.web.config;
-
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.web.SecurityFilterChain;
-
-/**
- * Web security configuration instantiated if authorization/authentication is disabled.
- * Despite its name it still needs to define authorization/authentication related values and beans.
- */
-@Configuration
-@ConditionalOnProperty(name = "auth.impl", havingValue = "none")
-@SuppressWarnings("unused")
-public class AnonymousWebSecurityConfig {
-
- @Value("${role.user:sar-user}")
- public String roleUser;
-
- @Value("${role.admin:sar-admin}")
- public String roleAdmin;
-
- @Value("${demo.user:user}")
- public String demoUser;
-
- @Bean
- public String roleUser() {
- return roleUser.toUpperCase();
- }
-
- @Bean
- public String roleAdmin() {
- return roleAdmin.toUpperCase();
- }
-
- @Bean
- public String demoUser(){
- return demoUser;
- }
-
- @Bean
- public String demoUserPassword(){
- return demoUserPassword;
- }
-
- @Bean
- public String demoAdmin(){
- return demoAdmin;
- }
-
- @Bean
- public String demoAdminPassword(){
- return demoAdminPassword;
- }
-
- @Bean
- public String demoReadOnly(){
- return demoReadOnly;
- }
-
- @Bean
- public String demoReadOnlyPassword(){
- return demoReadOnlyPassword;
- }
-
- @Value("${demo.user.password:userPass}")
- public String demoUserPassword;
-
- @Value("${demo.admin:admin}")
- public String demoAdmin;
-
- @Value("${demo.admin.password:adminPass}")
- public String demoAdminPassword;
-
- @Value("${demo.readOnly:johndoe}")
- public String demoReadOnly;
-
- @Value("${demo.readOnly.password:1234}")
- public String demoReadOnlyPassword;
-
-
- @Bean
- public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
- http.csrf().disable();
- http.anonymous();
- return http.build();
- }
-}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
index 95ecef6dac..bbbee58ddf 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
@@ -30,9 +30,8 @@
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
-@Conditional(AuthEnabledCondition.class)
@SuppressWarnings("unused")
-public class WebSecurityConfig extends AnonymousWebSecurityConfig {
+public class WebSecurityConfig {
/**
* Authentication implementation.
@@ -69,6 +68,74 @@ public class WebSecurityConfig extends AnonymousWebSecurityConfig {
@Value("${ldap.user.search.filter:invalid}")
String ldap_user_search_filter;
+ @Value("${role.user:sar-user}")
+ public String roleUser;
+
+ @Value("${role.admin:sar-admin}")
+ public String roleAdmin;
+
+ @Value("${demo.user:user}")
+ public String demoUser;
+
+ @Value("${demo.user.password:userPass}")
+ public String demoUserPassword;
+
+ @Value("${demo.admin:admin}")
+ public String demoAdmin;
+
+ @Value("${demo.admin.password:adminPass}")
+ public String demoAdminPassword;
+
+ @Value("${demo.readOnly:johndoe}")
+ public String demoReadOnly;
+
+ @Value("${demo.readOnly.password:1234}")
+ public String demoReadOnlyPassword;
+
+ @Bean
+ public String roleUser() {
+ return roleUser.toUpperCase();
+ }
+
+ @Bean
+ public String roleAdmin() {
+ return roleAdmin.toUpperCase();
+ }
+
+ @Bean
+ public String demoUser(){
+ return demoUser;
+ }
+
+ @Bean
+ public String demoUserPassword(){
+ return demoUserPassword;
+ }
+
+ @Bean
+ public String demoAdmin(){
+ return demoAdmin;
+ }
+
+ @Bean
+ public String demoAdminPassword(){
+ return demoAdminPassword;
+ }
+
+ @Bean
+ public String demoReadOnly(){
+ return demoReadOnly;
+ }
+
+ @Bean
+ public String demoReadOnlyPassword(){
+ return demoReadOnlyPassword;
+ }
+
+ @Bean
+ public String authenticationImplementation(){
+ return authenitcationImplementation;
+ }
@Bean
public WebSecurityCustomizer ignoringCustomizer() {
return web -> {
@@ -81,7 +148,12 @@ public WebSecurityCustomizer ignoringCustomizer() {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
- http.authorizeRequests().anyRequest().authenticated();
+ if("none".equalsIgnoreCase(authenitcationImplementation.trim())){
+ http.authorizeRequests().antMatchers("/**").permitAll();
+ }
+ else{
+ http.authorizeRequests().anyRequest().authenticated();
+ }
http.httpBasic();
return http.build();
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java
index 2b68764e48..c4d4f0c887 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java
@@ -40,7 +40,6 @@
@SuppressWarnings("unused")
@RestController
-@Conditional(AuthEnabledCondition.class)
public class AuthenticationController extends BaseController {
@Autowired
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java
index 182080cf28..446987fbb5 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java
@@ -24,12 +24,20 @@
import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
import org.phoebus.service.saveandrestore.web.config.AuthEnabledCondition;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Conditional;
+import org.springframework.security.access.expression.SecurityExpressionRoot;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Service;
import java.security.Principal;
+import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -39,26 +47,42 @@
* grant or deny access to certain REST endpoints.
*/
@Service("authorizationHelper")
-@Conditional(AuthEnabledCondition.class)
@SuppressWarnings("unused")
public class AuthorizationHelper {
+ @Autowired
+ private AuthenticationManager authenticationManager;
+
@Autowired
private NodeDAO nodeDAO;
@Autowired
private String roleAdmin;
+ @Autowired
+ private String roleUser;
+
+ @Value("${authorization.bypass:true}")
+ public boolean bypassAuthorization;
+
/**
- * Checks if all the nodes provided to this method can be deleted by the user.
+ * Checks if all the nodes provided to this method can be deleted by the user. User with admin privileges is always
+ * permitted to delete, while a user not having required role may never delete.
*
* @param nodeIds The list of {@link Node} ids subject to the check.
- * @param principal {@link Principal} of authenticated user.
+ * @param methodSecurityExpressionOperations {@link MethodSecurityExpressionOperations} Spring managed object
+ * queried for authorization.
* @return true only if all if the nodes can be deleted by the user.
*/
- public boolean mayDelete(List nodeIds, Principal principal) {
+ public boolean mayDelete(List nodeIds, MethodSecurityExpressionOperations methodSecurityExpressionOperations) {
+ if(bypassAuthorization || methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleAdmin)){
+ return true;
+ }
+ if(!methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleUser)){
+ return false;
+ }
for (String nodeId : nodeIds) {
- if (!mayDelete(nodeId, principal)) {
+ if (!mayDelete(nodeId, ((User)methodSecurityExpressionOperations.getAuthentication().getPrincipal()).getUsername())) {
return false;
}
}
@@ -66,19 +90,19 @@ public boolean mayDelete(List nodeIds, Principal principal) {
}
/**
- * An authenticated user may delete a node if User identity is same as the target {@link Node}'s user id and:
+ * An authenticated user may delete id user identity is same as the target {@link Node}'s user unique id and:
*
*
Target {@link Node} is a snapshot.
*
Target {@link Node} is not a snapshot, but has no child nodes.
*
*
- * @param nodeId Unique node id identifying the target of the user's delete operation.
- * @param principal Identifies user.
+ * @param nodeId The target {@link Node}'s unique node id.
+ * @param userName {@link MethodSecurityExpressionOperations} Username of authenticated user.
* @return false if user may not delete the {@link Node}.
*/
- public boolean mayDelete(String nodeId, Principal principal) {
+ private boolean mayDelete(String nodeId, String userName) {
Node node = nodeDAO.getNode(nodeId);
- if (!node.getUserName().equals(principal.getName())) {
+ if (!node.getUserName().equals(userName)){
return false;
}
if (node.getNodeType().equals(NodeType.CONFIGURATION) || node.getNodeType().equals(NodeType.FOLDER)) {
@@ -88,62 +112,81 @@ public boolean mayDelete(String nodeId, Principal principal) {
}
/**
- * An authenticated user may update a node if user identity is same as the target {@link Node}'s user id.
+ * An authenticated user may update a node if user has admin privileges, or
+ * if user identity is same as the target {@link Node}'s user id.
*
- * @param node {@link Node} identifying the target of the user's update operation.
- * @param principal Identifies user.
+ * @param node {@link Node} identifying the target of the user's update operation.
+ * @param methodSecurityExpressionOperations {@link MethodSecurityExpressionOperations} Spring managed object
+ * queried for authorization.
* @return false if user may not update the {@link Node}.
*/
- public boolean mayUpdate(Node node, Principal principal) {
- return nodeDAO.getNode(node.getUniqueId()).getUserName().equals(principal.getName());
+ public boolean mayUpdate(Node node, MethodSecurityExpressionOperations methodSecurityExpressionOperations) {
+ if(bypassAuthorization || methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleAdmin)){
+ return true;
+ }
+ if(!methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleUser)){
+ return false;
+ }
+ return nodeDAO.getNode(node.getUniqueId()).getUserName()
+ .equals(((User)methodSecurityExpressionOperations.getAuthentication().getPrincipal()).getUsername());
}
/**
- *
- * An authenticated user may save a composite snapshot, and update if user identity is same as the target's
- * composite snapshot {@link Node}.
- *
+ * An authenticated user may update a composite snapshot if user has admin privileges, or
+ * if user identity is same as the target {@link Node}'s user id.
*
* @param compositeSnapshot {@link CompositeSnapshot} identifying the target of the user's update operation.
- * @param principal Identifies user.
+ * @param methodSecurityExpressionOperations {@link MethodSecurityExpressionOperations} Spring managed object
+ * queried for authorization.
* @return false if user may not update the {@link CompositeSnapshot}.
*/
- public boolean mayUpdate(CompositeSnapshot compositeSnapshot, Principal principal) {
- return isOwner(compositeSnapshot.getCompositeSnapshotNode().getUniqueId(), principal.getName());
+ public boolean mayUpdate(CompositeSnapshot compositeSnapshot, MethodSecurityExpressionOperations methodSecurityExpressionOperations) {
+ if(bypassAuthorization || methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleAdmin)){
+ return true;
+ }
+ if(!methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleUser)){
+ return false;
+ }
+ return nodeDAO.getNode(compositeSnapshot.getCompositeSnapshotNode().getUniqueId()).getUserName()
+ .equals(((User)methodSecurityExpressionOperations.getAuthentication().getPrincipal()).getUsername());
}
/**
* An authenticated user may add or delete {@link Tag}s if user identity is same as the target's
* snapshot {@link Node}. However, to add or delete golden tag user must have admin privileges.
+ *
* @param tagData {@link TagData} containing {@link Node} ids and {@link Tag} name.
- * @param authentication {@link Authentication} providing username and roles.
+ * @param methodSecurityExpressionOperations {@link MethodSecurityExpressionOperations} Spring managed object
+ * queried for authorization.
* @return true if {@link Tag} can be added or deleted.
*/
- public boolean mayAddOrDeleteTag(TagData tagData, Authentication authentication){
+ public boolean mayAddOrDeleteTag(TagData tagData, MethodSecurityExpressionOperations methodSecurityExpressionOperations){
if (tagData.getTag() == null ||
tagData.getTag().getName() == null ||
tagData.getTag().getName().isEmpty() ||
tagData.getUniqueNodeIds() == null) {
throw new IllegalArgumentException("Cannot add tag, data invalid");
}
+ if(bypassAuthorization || methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleAdmin)){
+ return true;
+ }
+ if(!methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleUser)){
+ return false;
+ }
Tag tag = tagData.getTag();
- List roles = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
if(tag.getName().equals(Tag.GOLDEN)){
- return roles.contains(roleAdmin);
+ return methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleAdmin);
}
+ String username = ((User)methodSecurityExpressionOperations.getAuthentication().getPrincipal()).getUsername();
for(String nodeId : tagData.getUniqueNodeIds()){
- if(!isOwner(nodeId, authentication.getName())){
+ Node node = nodeDAO.getNode(nodeId);
+ if(!node.getUserName().equals(username)){
return false;
}
}
return true;
}
- private boolean isOwner(String nodeId, String username){
- Node node = nodeDAO.getNode(nodeId);
- return node.getUserName().equals(username);
- }
-
/**
*
*
*
* @param filterName Unique name identifying the target of the user's update operation.
- * @param principal Identifies user.
+ * @param methodSecurityExpressionOperations {@link MethodSecurityExpressionOperations} Spring managed object
+ * queried for authorization.
* @return false if user may not update the {@link Filter}.
*/
- public boolean maySaveOrDeleteFilter(String filterName, Principal principal) {
+ public boolean maySaveOrDeleteFilter(String filterName, MethodSecurityExpressionOperations methodSecurityExpressionOperations) {
+ if(bypassAuthorization || methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleAdmin)){
+ return true;
+ }
+ if(!methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleUser)){
+ return false;
+ }
Optional filter1 =
nodeDAO.getAllFilters().stream().filter(f ->
f.getName().equals(filterName)).findFirst();
@@ -163,6 +213,7 @@ public boolean maySaveOrDeleteFilter(String filterName, Principal principal) {
if(filter1.isEmpty()){
return true;
}
- return filter1.map(value -> value.getUser().equals(principal.getName())).orElse(true);
+ String username = ((User)methodSecurityExpressionOperations.getAuthentication().getPrincipal()).getUsername();
+ return filter1.map(value -> value.getUser().equals(username)).orElse(true);
}
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
index 278dc1aea8..359fa67e11 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
@@ -54,7 +54,7 @@ public CompositeSnapshot createCompositeSnapshot(@RequestParam(value = "parentNo
}
@PostMapping(value = "/composite-snapshot", produces = JSON)
- @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and @authorizationHelper.mayUpdate(#compositeSnapshot, #principal))")
+ @PreAuthorize("@authorizationHelper.mayUpdate(#compositeSnapshot, #root)")
public CompositeSnapshot updateCompositeSnapshot(@RequestBody CompositeSnapshot compositeSnapshot,
Principal principal) {
if(!compositeSnapshot.getCompositeSnapshotNode().getNodeType().equals(NodeType.COMPOSITE_SNAPSHOT)){
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java
index d702f3cddc..11ca952fc5 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/FilterController.java
@@ -45,7 +45,7 @@ public class FilterController extends BaseController {
*/
@SuppressWarnings("unused")
@PutMapping(value = "/filter", produces = JSON)
- @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and @authorizationHelper.maySaveOrDeleteFilter(#filter.getName(), #principal))")
+ @PreAuthorize("@authorizationHelper.maySaveOrDeleteFilter(#filter.getName(), #root)")
public Filter saveFilter(@RequestBody final Filter filter,
Principal principal) {
filter.setUser(principal.getName());
@@ -60,7 +60,7 @@ public List getAllFilters() {
@SuppressWarnings("unused")
@DeleteMapping(value = "/filter/{name}")
- @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and @authorizationHelper.maySaveOrDeleteFilter(#name, #principal))")
+ @PreAuthorize("@authorizationHelper.maySaveOrDeleteFilter(#name, #root)")
public void deleteFilter(@PathVariable final String name, Principal principal) {
nodeDAO.deleteFilter(name);
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
index fd936b067a..be62d65422 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
@@ -23,6 +23,8 @@
import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
+import org.springframework.security.access.expression.SecurityExpressionRoot;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -130,21 +132,39 @@ public List getChildNodes(@PathVariable final String uniqueNodeId) {
*
*
* @param uniqueNodeId The non-zero id of the node to delete
- * @param principal {@link Principal} of authenticated user.
+ * @param authentication {@link Authentication} of authenticated user.
*/
+ /*
@SuppressWarnings("unused")
@DeleteMapping(value = "/node/{uniqueNodeId}", produces = JSON)
- @PreAuthorize("hasRole(this.roleAdmin) or @authorizationHelper.mayDelete(#uniqueNodeId, #principal)")
+ @PreAuthorize("hasRole(this.roleAdmin) or @authorizationHelper.mayDelete(#uniqueNodeId, #authentication)")
@Deprecated
- public void deleteNode(@PathVariable final String uniqueNodeId, Principal principal) {
+ public void deleteNode(@PathVariable final String uniqueNodeId, Authentication authentication) {
logger.info("Deleting node with unique id " + uniqueNodeId);
nodeDAO.deleteNode(uniqueNodeId);
}
+ */
+
+ /**
+ * Deletes all {@link Node}s contained in the provided list.
+ *
+ * Checks are made to make sure user may delete
+ * the {@link Node}s, see {@link AuthorizationHelper}. If the checks fail on any of the {@link Node} ids,
+ * checks are aborted and client will receive an HTTP 403 response.
+ *
+ * Note that the {@link PreAuthorize} annotations calls a helper method in {@link AuthorizationHelper}, using
+ * the list of {@link Node} ids and a Spring injected object - root - used to check
+ * authorities of the user.
+ *
+ * Note also that an unauthenticated user (e.g. no basic authentication header in client's request) will
+ * receive a HTTP 401 response, i.e. the {@link PreAuthorize} check is not invoked.
+ * @param nodeIds
+ */
@SuppressWarnings("unused")
@DeleteMapping(value = "/node", produces = JSON)
- @PreAuthorize("hasRole(this.roleAdmin) or @authorizationHelper.mayDelete(#nodeIds, #principal)")
- public void deleteNodes(@RequestBody List nodeIds, Principal principal) {
+ @PreAuthorize("@authorizationHelper.mayDelete(#nodeIds, #root)")
+ public void deleteNodes(@RequestBody List nodeIds) {
nodeDAO.deleteNodes(nodeIds);
}
@@ -152,6 +172,17 @@ public void deleteNodes(@RequestBody List nodeIds, Principal principal)
* Updates a {@link Node}. The purpose is to support modification of name or comment/description, or both. Modification of
* node type is not supported.
*
+ *
+ * Checks are made to make sure user may update
+ * the {@link Node}, see {@link AuthorizationHelper}. If the checks fail client will receive an HTTP 403 response.
+ *
+ * Note that the {@link PreAuthorize} annotations calls a helper method in {@link AuthorizationHelper}, using
+ * the of {@link Node} id and a Spring injected object - root - used to check
+ * authorities of the user.
+ *
+ * Note also that an unauthenticated user (e.g. no basic authentication header in client's request) will
+ * receive a HTTP 401 response, i.e. the {@link PreAuthorize} check is not invoked.
+ *
*
* A {@link HttpStatus#BAD_REQUEST} is returned if a node of the same name and type already exists in the parent folder,
* or if the node in question is the root node (0).
@@ -164,13 +195,14 @@ public void deleteNodes(@RequestBody List nodeIds, Principal principal)
*/
@SuppressWarnings("unused")
@PostMapping(value = "/node", produces = JSON)
- @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and @authorizationHelper.mayUpdate(#nodeToUpdate, #principal))")
+ @PreAuthorize("@authorizationHelper.mayUpdate(#nodeToUpdate, #root)")
public Node updateNode(@RequestParam(value = "customTimeForMigration", required = false, defaultValue = "false") String customTimeForMigration,
@RequestBody Node nodeToUpdate,
Principal principal) {
if (!areTagsValid(nodeToUpdate)) {
throw new IllegalArgumentException("Node may not contain golden tag");
}
+ nodeToUpdate.setUserName(principal.getName());
return nodeDAO.updateNode(nodeToUpdate, Boolean.valueOf(customTimeForMigration));
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java
index 84d0a1d2b6..45c10ddf71 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java
@@ -30,6 +30,7 @@
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
+import java.security.Principal;
import java.util.List;
/**
@@ -55,19 +56,14 @@ public List getTags() {
* in tagData must be non-null, and its name must be non-null and non-empty.
*
* @param tagData See {@link TagData}
- * @param userName Must be non-null and non-empty if authentication/authorization is disabled.
- * @param authentication {@link Authentication} of authenticated user.
+ * @param principal {@link Principal} of authenticated user.
* @return The list of updated {@link Node}s
*/
@PostMapping("/tags")
- @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and @authorizationHelper.mayAddOrDeleteTag(#tagData, #authentication))")
+ @PreAuthorize("@authorizationHelper.mayAddOrDeleteTag(#tagData, #root)")
public List addTag(@RequestBody TagData tagData,
- @RequestParam(name = "username", required = false) String userName,
- Authentication authentication) {
- if(authentication == null && (userName == null || userName.isEmpty())){
- throw new IllegalArgumentException("Cannot determine username for tag");
- }
- tagData.getTag().setUserName(authentication == null ? userName : authentication.getName());
+ Principal principal) {
+ tagData.getTag().setUserName(principal.getName());
return nodeDAO.addTag(tagData);
}
@@ -79,8 +75,8 @@ public List addTag(@RequestBody TagData tagData,
* @return The list of updated {@link Node}s
*/
@DeleteMapping("/tags")
- @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and @authorizationHelper.mayAddOrDeleteTag(#tagData, #authentication))")
- public List deleteTag(@RequestBody TagData tagData, Authentication authentication) {
+ @PreAuthorize("@authorizationHelper.mayAddOrDeleteTag(#tagData, #root)")
+ public List deleteTag(@RequestBody TagData tagData) {
return nodeDAO.deleteTag(tagData);
}
}
diff --git a/services/save-and-restore/src/main/resources/application.properties b/services/save-and-restore/src/main/resources/application.properties
index de3d8b7c4f..c04f141083 100644
--- a/services/save-and-restore/src/main/resources/application.properties
+++ b/services/save-and-restore/src/main/resources/application.properties
@@ -82,8 +82,10 @@ demo.readOnly.password=1234
# ldap - Probably Open LDAP
# ldap_embedded - Embedded LDAP. Config in sar.ldif
# demo - Hard coded, see WebSecurityConfig class
-# none - No authentication and authorization
-auth.impl = none
+auth.impl = demo
+
+###### Bypass authorization (but not authentication) #######
+auth.bypass = true
############## Authorization Roles ################
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java
index 4b433a4fa5..41e7ec7878 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java
@@ -328,36 +328,28 @@ public void testGetSnapshotsForNonExistingConfig() throws Exception {
*/
@Test
public void testDeleteFolder() throws Exception {
- MockHttpServletRequestBuilder request =
- delete("/node/a");
-
- mockMvc.perform(request).andExpect(status().isUnauthorized());
- when(nodeDAO.getNode("a")).thenReturn(Node.builder().uniqueId("a").userName(demoUser).build());
-
- request =
- delete("/node/a")
- .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization);
- mockMvc.perform(request).andExpect(status().isForbidden());
+ //.header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ // .contentType(JSON).content(objectMapper.writeValueAsString(config));
- request =
- delete("/node/a")
- .header(HttpHeaders.AUTHORIZATION, adminAuthorization);
- mockMvc.perform(request).andExpect(status().isOk());
+ MockHttpServletRequestBuilder request =
+ post("/node");
- reset(nodeDAO);
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
when(nodeDAO.getNode("a")).thenReturn(Node.builder().uniqueId("a").userName(demoUser).build());
request =
- delete("/node/a")
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")))
.header(HttpHeaders.AUTHORIZATION, userAuthorization);
mockMvc.perform(request).andExpect(status().isOk());
when(nodeDAO.getNode("a")).thenReturn(Node.builder().uniqueId("a").userName("notDemoUser").build());
request =
- delete("/node/a")
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")))
.header(HttpHeaders.AUTHORIZATION, userAuthorization);
mockMvc.perform(request).andExpect(status().isForbidden());
@@ -365,7 +357,8 @@ public void testDeleteFolder() throws Exception {
when(nodeDAO.getChildNodes("a")).thenReturn(Collections.emptyList());
request =
- delete("/node/a")
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")))
.header(HttpHeaders.AUTHORIZATION, userAuthorization);
mockMvc.perform(request).andExpect(status().isOk());
@@ -373,7 +366,8 @@ public void testDeleteFolder() throws Exception {
when(nodeDAO.getChildNodes("a")).thenReturn(Collections.emptyList());
request =
- delete("/node/a")
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")))
.header(HttpHeaders.AUTHORIZATION, userAuthorization);
mockMvc.perform(request).andExpect(status().isOk());
@@ -381,7 +375,8 @@ public void testDeleteFolder() throws Exception {
when(nodeDAO.getChildNodes("a")).thenReturn(List.of(Node.builder().build()));
request =
- delete("/node/a")
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")))
.header(HttpHeaders.AUTHORIZATION, userAuthorization);
mockMvc.perform(request).andExpect(status().isForbidden());
@@ -389,7 +384,8 @@ public void testDeleteFolder() throws Exception {
when(nodeDAO.getChildNodes("a")).thenReturn(List.of(Node.builder().build()));
request =
- delete("/node/a")
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")))
.header(HttpHeaders.AUTHORIZATION, userAuthorization);
mockMvc.perform(request).andExpect(status().isForbidden());
@@ -397,7 +393,8 @@ public void testDeleteFolder() throws Exception {
when(nodeDAO.getChildNodes("a")).thenReturn(List.of(Node.builder().build()));
request =
- delete("/node/a")
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")))
.header(HttpHeaders.AUTHORIZATION, adminAuthorization);
mockMvc.perform(request).andExpect(status().isOk());
@@ -405,7 +402,8 @@ public void testDeleteFolder() throws Exception {
when(nodeDAO.getChildNodes("a")).thenReturn(List.of(Node.builder().build()));
request =
- delete("/node/a")
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")))
.header(HttpHeaders.AUTHORIZATION, adminAuthorization);
mockMvc.perform(request).andExpect(status().isOk());
}
diff --git a/services/save-and-restore/src/test/resources/test_application.properties b/services/save-and-restore/src/test/resources/test_application.properties
index 97e817e74d..dc4073e83b 100644
--- a/services/save-and-restore/src/test/resources/test_application.properties
+++ b/services/save-and-restore/src/test/resources/test_application.properties
@@ -19,3 +19,5 @@ elasticsearch.composite_snapshot_node.index=test_saveandrestore_composite_snapsh
elasticsearch.filter.index:test_saveandrestore_filter
auth.impl = demo
+
+authorization.bypass=false
From 9b626bfdbc395bb402d08a9f36a1cc7e47d3bf38 Mon Sep 17 00:00:00 2001
From: georgweiss
Date: Tue, 14 Nov 2023 14:06:09 +0100
Subject: [PATCH 40/42] Final pieces of the save&restore AA
---
.../CredentialsManagementController.java | 25 ++-
.../CredentialsManagementStage.java | 1 +
.../client/SaveAndRestoreClient.java | 4 +-
.../client/SaveAndRestoreJerseyClient.java | 68 +++---
.../saveandrestore/ui/ContextMenuBase.java | 2 +-
.../ui/ContextMenuSnapshot.java | 6 +-
.../ui/SaveAndRestoreService.java | 9 +-
.../ui/snapshot/SnapshotController.java | 23 +-
.../ui/snapshot/SnapshotControlsView.fxml | 5 +-
.../security/UserNotAuthorizedException.java | 27 ---
docs/source/preference_properties.rst | 17 +-
services/save-and-restore/doc/index.rst | 33 ++-
.../migration/MigrateRdbToElastic.java | 2 +-
.../persistence/dao/NodeDAO.java | 11 +-
.../impl/elasticsearch/ElasticsearchDAO.java | 23 +-
.../web/config/WebSecurityConfig.java | 10 +-
.../web/controllers/AuthorizationHelper.java | 103 +++++++--
.../web/controllers/BaseController.java | 7 -
.../CompositeSnapshotController.java | 2 +-
.../controllers/ConfigurationController.java | 24 +-
.../web/controllers/NodeController.java | 2 +-
.../web/controllers/SnapshotController.java | 31 +--
.../web/controllers/StructureController.java | 3 +-
.../web/controllers/TagController.java | 3 +-
.../src/main/resources/application.properties | 4 +-
.../persistence/dao/impl/DAOTestIT.java | 108 ++++++---
...positeSnapshotControllerPermitAllTest.java | 144 ++++++++++++
.../CompositeSnapshotControllerTest.java | 2 +-
.../ConfigurationControllerPermitAllTest.java | 127 +++++++++++
.../ConfigurationControllerTest.java | 160 ++++++++++++++
.../FilterControllerPermitAllTest.java | 137 ++++++++++++
.../NodeControllerPermitAllTest.java | 206 ++++++++++++++++++
.../web/controllers/NodeControllerTest.java | 156 ++++---------
.../web/controllers/SearchControllerTest.java | 4 +-
.../SnapshotControllerPermitAllTest.java | 171 +++++++++++++++
.../controllers/SnapshotControllerTest.java | 69 +++++-
.../StructureControllerPermitAllTest.java | 155 +++++++++++++
.../controllers/StructureControllerTest.java | 196 +++++++++++++++++
.../TagControllerPermitAllTest.java | 161 ++++++++++++++
.../web/controllers/TagControllerTest.java | 77 ++++++-
.../resources/test_application.properties | 23 +-
.../test_application_permit_all.properties | 2 +
42 files changed, 1979 insertions(+), 364 deletions(-)
delete mode 100644 app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/security/UserNotAuthorizedException.java
create mode 100644 services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerPermitAllTest.java
create mode 100644 services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationControllerPermitAllTest.java
create mode 100644 services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationControllerTest.java
create mode 100644 services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerPermitAllTest.java
create mode 100644 services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerPermitAllTest.java
create mode 100644 services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerPermitAllTest.java
create mode 100644 services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/StructureControllerPermitAllTest.java
create mode 100644 services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/StructureControllerTest.java
create mode 100644 services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerPermitAllTest.java
create mode 100644 services/save-and-restore/src/test/resources/test_application_permit_all.properties
diff --git a/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementController.java b/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementController.java
index cd1af5cada..1aa73f5786 100644
--- a/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementController.java
+++ b/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementController.java
@@ -31,6 +31,8 @@
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
+import javafx.scene.input.KeyCode;
+import javafx.stage.Stage;
import javafx.util.Callback;
import org.phoebus.framework.jobs.JobManager;
import org.phoebus.security.authorization.ServiceAuthenticationProvider;
@@ -75,6 +77,8 @@ public class CredentialsManagementController {
private static final Logger LOGGER = Logger.getLogger(CredentialsManagementController.class.getName());
private final List authenticationProviders;
+ private Stage stage;
+
public CredentialsManagementController(List authenticationProviders, SecureStore secureStore) {
this.authenticationProviders = authenticationProviders;
this.secureStore = secureStore;
@@ -139,6 +143,11 @@ public void logOutFromAll() {
}
}
+ /**
+ * Attempts to sign in user based on provided credentials. If sign-in succeeds, this method will close the
+ * associated UI.
+ * @param serviceItem The {@link ServiceItem} defining the scope, and implicitly the authentication service.
+ */
private void login(ServiceItem serviceItem){
try {
serviceItem.getServiceAuthenticationProvider().authenticate(serviceItem.getUsername(), serviceItem.getPassword());
@@ -146,11 +155,10 @@ private void login(ServiceItem serviceItem){
secureStore.setScopedAuthentication(new ScopedAuthenticationToken(serviceItem.getAuthenticationScope(),
serviceItem.getUsername(),
serviceItem.getPassword()));
+ stage.close();
} catch (Exception exception) {
LOGGER.log(Level.WARNING, "Failed to store credentials", exception);
}
- updateTable();
-
} catch (Exception exception) {
LOGGER.log(Level.WARNING, "Failed to login to service", exception);
ExceptionDetailsErrorDialog.openError(parent, "Login Failure", "Failed to login to service", exception);
@@ -252,7 +260,7 @@ public boolean isLoginAction(){
return loginAction;
}
}
- private static class UsernameTableCell extends TableCell{
+ private class UsernameTableCell extends TableCell{
private final TextField textField = new TextField();
public UsernameTableCell(){
@@ -279,7 +287,7 @@ protected void updateItem(String item, final boolean empty)
}
}
- private static class PasswordTableCell extends TableCell{
+ private class PasswordTableCell extends TableCell{
private final PasswordField passwordField = new PasswordField();
public PasswordTableCell(){
@@ -301,8 +309,17 @@ protected void updateItem(String item, final boolean empty)
// Disable field if user is logged in.
passwordField.disableProperty().set(!getTableRow().getItem().loginAction);
}
+ passwordField.setOnKeyPressed(keyEvent -> {
+ if (keyEvent.getCode() == KeyCode.ENTER) {
+ CredentialsManagementController.this.login(getTableRow().getItem());
+ }
+ });
setGraphic(passwordField);
}
}
}
+
+ public void setStage(Stage stage){
+ this.stage = stage;
+ }
}
diff --git a/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementStage.java b/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementStage.java
index 6749e1175e..ef51b00f04 100644
--- a/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementStage.java
+++ b/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementStage.java
@@ -50,6 +50,7 @@ public CredentialsManagementStage(List authentica
CredentialsManagementController controller =
(CredentialsManagementController) clazz.getConstructor(List.class, SecureStore.class)
.newInstance(authenticationProviders, secureStore);
+ controller.setStage(this);
return controller;
} catch (Exception e) {
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java
index 600a8e5f9f..a95994721d 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java
@@ -151,7 +151,9 @@ public interface SaveAndRestoreClient {
SnapshotData getSnapshotData(String uniqueId);
- Snapshot saveSnapshot(String parentNodeId, Snapshot snapshot);
+ Snapshot createSnapshot(String parentNodeId, Snapshot snapshot);
+
+ Snapshot updateSnapshot(Snapshot snapshot);
/**
* Creates a new {@link CompositeSnapshot}.
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java
index aac19ca992..ae9cf53c92 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java
@@ -102,10 +102,8 @@ private Client getClient() {
String password = scopedAuthenticationToken.getPassword();
httpBasicAuthFilter = new HTTPBasicAuthFilter(username, password);
client.addFilter(httpBasicAuthFilter);
- } else {//if (httpBasicAuthFilter != null) {
- //client.removeFilter(httpBasicAuthFilter);
- httpBasicAuthFilter = new HTTPBasicAuthFilter(System.getProperty("user.name"), "password");
- client.addFilter(httpBasicAuthFilter);
+ } else if (httpBasicAuthFilter != null) {
+ client.removeFilter(httpBasicAuthFilter);
}
} catch (Exception e) {
logger.log(Level.WARNING, "Unable to retrieve credentials from secure store", e);
@@ -182,8 +180,7 @@ public List getChildNodes(String uniqueNodeId) throws SaveAndRestoreClient
@Override
public Node createNewNode(String parentNodeId, Node node) {
WebResource webResource = getClient().resource(jmasarServiceUrl + "/node")
- .queryParam("parentNodeId", parentNodeId) // Request parameter username is needed in case authorization/authentication is disabled.
- .queryParam("username", System.getProperty("user.name"));
+ .queryParam("parentNodeId", parentNodeId);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(node, CONTENT_TYPE_JSON)
.put(ClientResponse.class);
@@ -207,9 +204,7 @@ public Node updateNode(Node nodeToUpdate) {
@Override
public Node updateNode(Node nodeToUpdate, boolean customTimeForMigration) {
WebResource webResource = getClient().resource(jmasarServiceUrl + "/node")
- .queryParam("customTimeForMigration", customTimeForMigration ? "true" : "false")
- // Request parameter username is needed in case authorization/authentication is disabled.
- .queryParam("username", System.getProperty("user.name"));
+ .queryParam("customTimeForMigration", customTimeForMigration ? "true" : "false");
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(nodeToUpdate, CONTENT_TYPE_JSON)
@@ -347,9 +342,7 @@ public ConfigurationData getConfigurationData(String nodeId) {
public Configuration createConfiguration(String parentNodeId, Configuration configuration) {
WebResource webResource =
getClient().resource(jmasarServiceUrl + "/config")
- .queryParam("parentNodeId", parentNodeId)
- // Request parameter username is needed in case authorization/authentication is disabled.
- .queryParam("username", System.getProperty("user.name"));
+ .queryParam("parentNodeId", parentNodeId);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(configuration, CONTENT_TYPE_JSON)
.put(ClientResponse.class);
@@ -367,9 +360,7 @@ public Configuration createConfiguration(String parentNodeId, Configuration conf
@Override
public Configuration updateConfiguration(Configuration configuration) {
- WebResource webResource = getClient().resource(jmasarServiceUrl + "/config")
- // Request parameter username is needed in case authorization/authentication is disabled.
- .queryParam("username", System.getProperty("user.name"));
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/config");
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(configuration, CONTENT_TYPE_JSON)
@@ -393,12 +384,10 @@ public SnapshotData getSnapshotData(String nodeId) {
}
@Override
- public Snapshot saveSnapshot(String parentNodeId, Snapshot snapshot) {
+ public Snapshot createSnapshot(String parentNodeId, Snapshot snapshot) {
WebResource webResource =
getClient().resource(jmasarServiceUrl + "/snapshot")
- .queryParam("parentNodeId", parentNodeId)
- // Request parameter username is needed in case authorization/authentication is disabled.
- .queryParam("username", System.getProperty("user.name"));
+ .queryParam("parentNodeId", parentNodeId);
ClientResponse response;
try {
response = webResource.accept(CONTENT_TYPE_JSON)
@@ -419,13 +408,36 @@ public Snapshot saveSnapshot(String parentNodeId, Snapshot snapshot) {
return response.getEntity(Snapshot.class);
}
+ @Override
+ public Snapshot updateSnapshot(Snapshot snapshot) {
+ WebResource webResource =
+ getClient().resource(jmasarServiceUrl + "/snapshot");
+ ClientResponse response;
+ try {
+ response = webResource.accept(CONTENT_TYPE_JSON)
+ .entity(snapshot, CONTENT_TYPE_JSON)
+ .post(ClientResponse.class);
+ } catch (UniformInterfaceException e) {
+ throw new RuntimeException(e);
+ }
+ if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
+ String message = Messages.searchFailed;
+ try {
+ message = new String(response.getEntityInputStream().readAllBytes());
+ } catch (IOException e) {
+ logger.log(Level.WARNING, "Unable to parse response", e);
+ }
+ throw new SaveAndRestoreClientException(message);
+ }
+ return response.getEntity(Snapshot.class);
+ }
+
+
@Override
public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeSnapshot compositeSnapshot) {
WebResource webResource =
getClient().resource(jmasarServiceUrl + "/composite-snapshot")
- .queryParam("parentNodeId", parentNodeId)
- // Request parameter username is needed in case authorization/authentication is disabled.
- .queryParam("username", System.getProperty("user.name"));
+ .queryParam("parentNodeId", parentNodeId);
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(compositeSnapshot, CONTENT_TYPE_JSON)
.put(ClientResponse.class);
@@ -463,9 +475,7 @@ public List checkCompositeSnapshotConsistency(List snapshotNodeI
@Override
public CompositeSnapshot updateCompositeSnapshot(CompositeSnapshot compositeSnapshot) {
- WebResource webResource = getClient().resource(jmasarServiceUrl + "/composite-snapshot")
- // Request parameter username is needed in case authorization/authentication is disabled.
- .queryParam("username", System.getProperty("user.name"));
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/composite-snapshot");
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(compositeSnapshot, CONTENT_TYPE_JSON)
@@ -502,9 +512,7 @@ public SearchResult search(MultivaluedMap searchParams) {
@Override
public Filter saveFilter(Filter filter) {
- WebResource webResource = getClient().resource(jmasarServiceUrl + "/filter")
- // Request parameter username is needed in case authorization/authentication is disabled.
- .queryParam("username", System.getProperty("user.name"));
+ WebResource webResource = getClient().resource(jmasarServiceUrl + "/filter");
ClientResponse response = webResource.accept(CONTENT_TYPE_JSON)
.entity(filter, CONTENT_TYPE_JSON)
.put(ClientResponse.class);
@@ -566,9 +574,7 @@ public void deleteFilter(String name) {
public List addTag(TagData tagData) {
WebResource webResource =
- getClient().resource(jmasarServiceUrl + "/tags")
- // Request parameter username is needed in case authorization/authentication is disabled.
- .queryParam("username", System.getProperty("user.name"));
+ getClient().resource(jmasarServiceUrl + "/tags");
ClientResponse response;
try {
response = webResource.accept(CONTENT_TYPE_JSON)
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java
index 579dabf34e..e904215ee2 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java
@@ -76,7 +76,7 @@ public ContextMenuBase(SaveAndRestoreController saveAndRestoreController) {
deleteNodesMenuItem = new MenuItem(Messages.contextMenuDelete, new ImageView(ImageRepository.DELETE));
deleteNodesMenuItem.setOnAction(ae -> saveAndRestoreController.deleteNodes());
deleteNodesMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
- //userIsAuthenticatedProperty.not().get() ||
+ userIsAuthenticatedProperty.not().get() ||
hasSameParentProperty.not().get(),
userIsAuthenticatedProperty, hasSameParentProperty));
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java
index 371e7b8e0e..4a4086b687 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java
@@ -60,7 +60,7 @@ public ContextMenuSnapshot(SaveAndRestoreController saveAndRestoreController) {
tagWithComment = new Menu(Messages.contextMenuTagsWithComment, snapshotTagsWithCommentIconImage);
tagWithComment.setOnShowing(event -> saveAndRestoreController.tagWithComment(tagWithComment));
tagWithComment.disableProperty().bind(Bindings.createBooleanBinding(() ->
- multipleNodesSelectedProperty.get(), //|| userIsAuthenticatedProperty.not().get(),
+ multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(),
multipleNodesSelectedProperty, userIsAuthenticatedProperty));
MenuItem addTagWithCommentMenuItem = TagWidget.AddTagWithCommentMenuItem();
@@ -84,8 +84,8 @@ public ContextMenuSnapshot(SaveAndRestoreController saveAndRestoreController) {
tagGoldenMenuItem = new MenuItem(Messages.contextMenuTagAsGolden, new ImageView(ImageRepository.SNAPSHOT));
tagGoldenMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() ->
- multipleNodesSelectedProperty.get() || /*userIsAuthenticatedProperty.not().get() ||*/ mayTagOrUntagGoldenProperty.not().get(),
- multipleNodesSelectedProperty, /*userIsAuthenticatedProperty,*/ mayTagOrUntagGoldenProperty));
+ multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get() || mayTagOrUntagGoldenProperty.not().get(),
+ multipleNodesSelectedProperty, userIsAuthenticatedProperty, mayTagOrUntagGoldenProperty));
Image copyIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/copy.png");
MenuItem copyMenuItem = new MenuItem(Messages.copy, new ImageView(copyIcon));
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java
index 37637f018d..4b7ea4c361 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java
@@ -235,7 +235,14 @@ public Snapshot saveSnapshot(Node configurationNode, Snapshot snapshot) throws E
return snapshotItem;
}).collect(Collectors.toList());
snapshot.getSnapshotData().setSnapshotItems(beautifiedItems);
- Future future = executor.submit(() -> saveAndRestoreClient.saveSnapshot(configurationNode.getUniqueId(), snapshot));
+ Future future = executor.submit(() -> {
+ if(snapshot.getSnapshotNode().getUniqueId() == null){
+ return saveAndRestoreClient.createSnapshot(configurationNode.getUniqueId(), snapshot);
+ }
+ else{
+ return saveAndRestoreClient.updateSnapshot(snapshot);
+ }
+ });
Snapshot updatedSnapshot = future.get();
// Notify listeners as the configuration node has a new child node.
notifyNodeChangeListeners(configurationNode);
diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java
index 25ad0d710d..20d024e70b 100644
--- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java
+++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java
@@ -34,8 +34,6 @@
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService;
import org.phoebus.applications.saveandrestore.ui.VNoData;
import org.phoebus.framework.jobs.JobManager;
-import org.phoebus.security.store.SecureStore;
-import org.phoebus.security.tokens.AuthenticationScope;
import org.phoebus.security.tokens.ScopedAuthenticationToken;
import org.phoebus.ui.dialog.DialogHelper;
import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
@@ -153,13 +151,22 @@ public void saveSnapshot(ActionEvent actionEvent) {
List snapshotItems = snapshotProperty.get().getSnapshotData().getSnapshotItems();
SnapshotData snapshotData = new SnapshotData();
snapshotData.setSnapshotItems(snapshotItems);
- Snapshot snapshot = new Snapshot();
+ Snapshot snapshot = snapshotProperty.get();
+ // Creating new or updating existing (e.g. name change)?
+ if (snapshot == null) {
+ snapshot = new Snapshot();
+ snapshot.setSnapshotNode(Node.builder().nodeType(NodeType.SNAPSHOT)
+ .name(snapshotControlsViewController.getSnapshotNameProperty().get())
+ .description(snapshotControlsViewController.getSnapshotCommentProperty().get()).build());
+ } else {
+ snapshot.getSnapshotNode().setName(snapshotControlsViewController.getSnapshotNameProperty().get());
+ snapshot.getSnapshotNode().setDescription(snapshotControlsViewController.getSnapshotCommentProperty().get());
+ }
snapshot.setSnapshotData(snapshotData);
- snapshot.setSnapshotNode(Node.builder().nodeType(NodeType.SNAPSHOT)
- .name(snapshotControlsViewController.getSnapshotNameProperty().get())
- .description(snapshotControlsViewController.getSnapshotCommentProperty().get()).build());
+
try {
snapshot = SaveAndRestoreService.getInstance().saveSnapshot(configurationNode, snapshot);
+ snapshotProperty.set(snapshot);
Node _snapshotNode = snapshot.getSnapshotNode();
javafx.scene.Node jfxNode = (javafx.scene.Node) actionEvent.getSource();
String userData = (String) jfxNode.getUserData();
@@ -363,7 +370,7 @@ public void addSnapshot(Node snapshotNode) {
Snapshot snapshot = getSnapshotFromService(snapshotNode);
snapshotTableViewController.addSnapshot(snapshot);
} catch (Exception e) {
- e.printStackTrace();
+ Logger.getLogger(SnapshotController.class.getName()).log(Level.WARNING, "Failed to add snapshot", e);
} finally {
disabledUi.set(false);
}
@@ -392,7 +399,7 @@ private Snapshot getSnapshotFromService(Node snapshotNode) throws Exception {
}
@Override
- public void secureStoreChanged(List validTokens){
+ public void secureStoreChanged(List validTokens) {
snapshotControlsViewController.secureStoreChanged(validTokens);
}
}
\ No newline at end of file
diff --git a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsView.fxml b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsView.fxml
index 9e80e79d3c..d855662974 100644
--- a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsView.fxml
+++ b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsView.fxml
@@ -44,12 +44,13 @@
+
+
-
-
+
diff --git a/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/security/UserNotAuthorizedException.java b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/security/UserNotAuthorizedException.java
deleted file mode 100644
index b8aafa19e4..0000000000
--- a/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/security/UserNotAuthorizedException.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 European Spallation Source ERIC.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- */
-
-package org.phoebus.applications.saveandrestore.model.security;
-
-public class UserNotAuthorizedException extends RuntimeException{
-
- public UserNotAuthorizedException(String message){
- super(message);
- }
-}
diff --git a/docs/source/preference_properties.rst b/docs/source/preference_properties.rst
index ad5a30aab8..8a770612aa 100644
--- a/docs/source/preference_properties.rst
+++ b/docs/source/preference_properties.rst
@@ -24,10 +24,12 @@ File ../../app/alarm/model/src/main/resources/alarm_preferences.properties::
# A file to configure the properites of kafka clients
kafka_properties=
- # Name of alarm tree root
+ # Name of alarm tree root.
+ # Configures the alarm configuration used by the alarm server.
+ # For the UI, it sets the default alarm configuration.
config_name=Accelerator
- # Names of selectable alarm configurations
+ # Names of selectable alarm configurations for UI.
# The `config_name` will be used as the default for newly opened tools,
# and if `config_names` is empty, it remains the only option.
# When one or more comma-separated configurations are listed,
@@ -1215,6 +1217,17 @@ File ../../core/pv/src/main/resources/pv_ca_preferences.properties::
# Package org.phoebus.pv.ca
# -------------------------
+ # By default, we use the following preferences settings,
+ # but when the System property "jca.use_env" is "true",
+ # JCA falls back to the EPICS_CA_... environment variables.
+ #
+ # Sites that prefer to use the EPICS_CA_... environment variables
+ # thus need to add
+ #
+ # -Djca.use_env=true
+ #
+ # to their launcher script.
+
# Channel Access address list
addr_list=
diff --git a/services/save-and-restore/doc/index.rst b/services/save-and-restore/doc/index.rst
index 0e96b56cb5..8d9641fa88 100644
--- a/services/save-and-restore/doc/index.rst
+++ b/services/save-and-restore/doc/index.rst
@@ -724,37 +724,46 @@ Body:
]
Authentication and Authorization
---------------------------------
+================================
All non-GET endpoints are subject to authentication, i.e. clients must send a basic authentication header. The
service can be configured to delegate authentication to Active Directory or remote or local LDAP. For demo and test
purposes hard coded credentials are found in the ``WebSecurityConfig`` class. See the file ``application.properties``
for information on how to select authentication method.
+Two roles are defined, "sar-user" and "sar-admin". The actual name of these roles can be customizable in ``application.properties``,
+and must match role/group names in LDAP or Active Directory.
+
Authorization uses a role-based approach like so:
* Unauthenticated users may read data, i.e. access GET endpoints.
-* Role "user":
- * Create and save configurations
- * Create and save snapshots
- * Create and save composite snapshots
- * Create and save filters
+* Save-and-restore role "sar-user":
+ * Create and update configurations
+ * Create and update snapshots
+ * Create and update composite snapshots
+ * Create and update filters
+ * Create and update tags, except GOLDEN tag
* Update and delete objects if user name matches object's user id and:
* Object is a snapshot node and not referenced in a composite snapshot node
* Object is a composite snapshot node
* Object is configuration or folder node with no child nodes
* Object is a filter
-* Role "superuser": +perform restore operation
-* Role "admin": no restrictions
+ * Object is a tag
+* Save-and-restore role "sar-admin": no restrictions
+
-Roles must be defined as groups in Active Directory or LDAP. Role/group names can be configured in ``application.properties``.
+Enabled authentication, disabled authorization
+----------------------------------------------
+The application property ``authorization.permitall`` (default ``true``) can be used to bypass all authorization. In
+this case authentication is still required for protected endpoints, but user need not be associated with
+a save-and-restore role/group.
Migration
----------
+=========
-Commit ``48e17a380b660d59b79cec4d2bd908c0d78eeeae`` of the service code base is about changing the persistence
-component from a RDB engine to Elasticsearch. Sites using save-and-restore with an RDB engine may migrate
+From commit ``48e17a380b660d59b79cec4d2bd908c0d78eeeae`` of the service code base the persistence
+layer is moved from RDB engine to Elasticsearch. Sites using save-and-restore with an RDB engine may migrate
data using the below procedure.
Terminology: "source host" is the host running the legacy service instance using a RDB engine,
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/migration/MigrateRdbToElastic.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/migration/MigrateRdbToElastic.java
index c10096264f..c033b64882 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/migration/MigrateRdbToElastic.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/migration/MigrateRdbToElastic.java
@@ -188,7 +188,7 @@ private void createSnapshot(RestTemplate restTemplate,
snapshotData.setUniqueId(legacySnapshotNode.getUniqueId());
snapshot.setSnapshotData(snapshotData);
snapshotCount++;
- elasticsearchDAO.saveSnapshot(newConfigurationNode.getUniqueId(), snapshot);
+ elasticsearchDAO.createSnapshot(newConfigurationNode.getUniqueId(), snapshot);
}
}
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/NodeDAO.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/NodeDAO.java
index a9814db4df..0fa5aa4304 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/NodeDAO.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/NodeDAO.java
@@ -134,7 +134,16 @@ public interface NodeDAO {
* @param snapshot The {@link Snapshot} data.
* @return The persisted {@link Snapshot} data.
*/
- Snapshot saveSnapshot(String parentNodeId, Snapshot snapshot);
+ Snapshot createSnapshot(String parentNodeId, Snapshot snapshot);
+
+ /**
+ * Updates a {@link Snapshot} with respect to name, description/comment. No other properties of the
+ * node can be modified, but last updated date will be set accordingly.
+ *
+ * @param snapshot The {@link Snapshot} subject to update.
+ * @return The {@link Snapshot} object as read from the persistence implementation.
+ */
+ Snapshot updateSnapshot(Snapshot snapshot);
/**
* Updates a {@link Node} with respect to name, description/comment and tags. No other properties of the
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAO.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAO.java
index b46afb6d9b..1eecf1845e 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAO.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAO.java
@@ -723,7 +723,7 @@ public ConfigurationData getConfigurationData(String uniqueId) {
}
@Override
- public Snapshot saveSnapshot(String parentNodeId, Snapshot snapshot) {
+ public Snapshot createSnapshot(String parentNodeId, Snapshot snapshot) {
SnapshotData sanitizedSnapshotData = removeDuplicateSnapshotItems(snapshot.getSnapshotData());
snapshot.setSnapshotData(sanitizedSnapshotData);
@@ -747,6 +747,27 @@ public Snapshot saveSnapshot(String parentNodeId, Snapshot snapshot) {
return newSnapshot;
}
+ @Override
+ public Snapshot updateSnapshot(Snapshot snapshot) {
+
+ SnapshotData sanitizedSnapshotData = removeDuplicateSnapshotItems(snapshot.getSnapshotData());
+ snapshot.setSnapshotData(sanitizedSnapshotData);
+
+ snapshot.getSnapshotNode().setNodeType(NodeType.SNAPSHOT); // Force node type
+ SnapshotData newSnapshotData;
+ try {
+ newSnapshotData = snapshotDataRepository.save(snapshot.getSnapshotData());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ Snapshot newSnapshot = new Snapshot();
+ newSnapshot.setSnapshotData(newSnapshotData);
+ newSnapshot.setSnapshotNode(snapshot.getSnapshotNode());
+
+ return newSnapshot;
+ }
+
@Override
public SnapshotData getSnapshotData(String uniqueId) {
Optional snapshotData = snapshotDataRepository.findById(uniqueId);
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
index bbbee58ddf..1b90f4b65e 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
@@ -36,7 +36,7 @@ public class WebSecurityConfig {
/**
* Authentication implementation.
*/
- @Value("${auth.impl:none}")
+ @Value("${auth.impl:demo}")
protected String authenitcationImplementation;
/**
@@ -148,14 +148,8 @@ public WebSecurityCustomizer ignoringCustomizer() {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
- if("none".equalsIgnoreCase(authenitcationImplementation.trim())){
- http.authorizeRequests().antMatchers("/**").permitAll();
- }
- else{
- http.authorizeRequests().anyRequest().authenticated();
- }
+ http.authorizeRequests().anyRequest().authenticated();
http.httpBasic();
-
return http.build();
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java
index 446987fbb5..087949bfe1 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java
@@ -22,25 +22,15 @@
import org.phoebus.applications.saveandrestore.model.*;
import org.phoebus.applications.saveandrestore.model.search.Filter;
import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
-import org.phoebus.service.saveandrestore.web.config.AuthEnabledCondition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Conditional;
-import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations;
import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.AuthenticationProvider;
-import org.springframework.security.authorization.AuthorizationManager;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Service;
-import java.security.Principal;
-import java.util.Collection;
import java.util.List;
import java.util.Optional;
-import java.util.stream.Collectors;
/**
* {@link Service} class implementing domain specific authorization rules in order to
@@ -62,8 +52,10 @@ public class AuthorizationHelper {
@Autowired
private String roleUser;
- @Value("${authorization.bypass:true}")
- public boolean bypassAuthorization;
+ @Value("${authorization.permitall:true}")
+ public boolean permitAll;
+
+ private static final String ROLE_PREFIX = "ROLE_";
/**
* Checks if all the nodes provided to this method can be deleted by the user. User with admin privileges is always
@@ -75,10 +67,10 @@ public class AuthorizationHelper {
* @return true only if all if the nodes can be deleted by the user.
*/
public boolean mayDelete(List nodeIds, MethodSecurityExpressionOperations methodSecurityExpressionOperations) {
- if(bypassAuthorization || methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleAdmin)){
+ if(permitAll || methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleAdmin)){
return true;
}
- if(!methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleUser)){
+ if(!methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleUser)){
return false;
}
for (String nodeId : nodeIds) {
@@ -121,10 +113,10 @@ private boolean mayDelete(String nodeId, String userName) {
* @return false if user may not update the {@link Node}.
*/
public boolean mayUpdate(Node node, MethodSecurityExpressionOperations methodSecurityExpressionOperations) {
- if(bypassAuthorization || methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleAdmin)){
+ if(permitAll || methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleAdmin)){
return true;
}
- if(!methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleUser)){
+ if(!methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleUser)){
return false;
}
return nodeDAO.getNode(node.getUniqueId()).getUserName()
@@ -141,16 +133,57 @@ public boolean mayUpdate(Node node, MethodSecurityExpressionOperations methodSec
* @return false if user may not update the {@link CompositeSnapshot}.
*/
public boolean mayUpdate(CompositeSnapshot compositeSnapshot, MethodSecurityExpressionOperations methodSecurityExpressionOperations) {
- if(bypassAuthorization || methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleAdmin)){
+ if(permitAll || methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleAdmin)){
return true;
}
- if(!methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleUser)){
+ if(!methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleUser)){
return false;
}
return nodeDAO.getNode(compositeSnapshot.getCompositeSnapshotNode().getUniqueId()).getUserName()
.equals(((User)methodSecurityExpressionOperations.getAuthentication().getPrincipal()).getUsername());
}
+ /**
+ * An authenticated user may update a configuration if user has admin privileges, or
+ * if user identity is same as the target {@link Node}'s user id.
+ *
+ * @param configuration {@link Configuration} identifying the target of the user's update operation.
+ * @param methodSecurityExpressionOperations {@link MethodSecurityExpressionOperations} Spring managed object
+ * queried for authorization.
+ * @return false if user may not update the {@link Configuration}.
+ */
+ public boolean mayUpdate(Configuration configuration, MethodSecurityExpressionOperations methodSecurityExpressionOperations) {
+ if(permitAll || methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleAdmin)){
+ return true;
+ }
+ if(!methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleUser)){
+ return false;
+ }
+ return nodeDAO.getNode(configuration.getConfigurationNode().getUniqueId()).getUserName()
+ .equals(((User)methodSecurityExpressionOperations.getAuthentication().getPrincipal()).getUsername());
+ }
+
+ /**
+ * An authenticated user may update a snapshot if user has admin privileges, or
+ * if user identity is same as the target {@link Node}'s user id.
+ *
+ * @param snapshot {@link Snapshot} identifying the target of the user's update operation.
+ * @param methodSecurityExpressionOperations {@link MethodSecurityExpressionOperations} Spring managed object
+ * queried for authorization.
+ * @return false if user may not update the {@link Snapshot}.
+ */
+ public boolean mayUpdate(Snapshot snapshot, MethodSecurityExpressionOperations methodSecurityExpressionOperations) {
+ if(permitAll || methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleAdmin)){
+ return true;
+ }
+ if(!methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleUser)){
+ return false;
+ }
+ // If snapshot's node has null id, then this is a
+ return nodeDAO.getNode(snapshot.getSnapshotNode().getUniqueId()).getUserName()
+ .equals(((User)methodSecurityExpressionOperations.getAuthentication().getPrincipal()).getUsername());
+ }
+
/**
* An authenticated user may add or delete {@link Tag}s if user identity is same as the target's
* snapshot {@link Node}. However, to add or delete golden tag user must have admin privileges.
@@ -167,15 +200,15 @@ public boolean mayAddOrDeleteTag(TagData tagData, MethodSecurityExpressionOperat
tagData.getUniqueNodeIds() == null) {
throw new IllegalArgumentException("Cannot add tag, data invalid");
}
- if(bypassAuthorization || methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleAdmin)){
+ if(permitAll || methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleAdmin)){
return true;
}
- if(!methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleUser)){
+ if(!methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleUser)){
return false;
}
Tag tag = tagData.getTag();
if(tag.getName().equals(Tag.GOLDEN)){
- return methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleAdmin);
+ return methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleAdmin);
}
String username = ((User)methodSecurityExpressionOperations.getAuthentication().getPrincipal()).getUsername();
for(String nodeId : tagData.getUniqueNodeIds()){
@@ -200,10 +233,10 @@ public boolean mayAddOrDeleteTag(TagData tagData, MethodSecurityExpressionOperat
* @return false if user may not update the {@link Filter}.
*/
public boolean maySaveOrDeleteFilter(String filterName, MethodSecurityExpressionOperations methodSecurityExpressionOperations) {
- if(bypassAuthorization || methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleAdmin)){
+ if(permitAll || methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleAdmin)){
return true;
}
- if(!methodSecurityExpressionOperations.hasAuthority("ROLE_" + roleUser)){
+ if(!methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleUser)){
return false;
}
Optional filter1 =
@@ -216,4 +249,28 @@ public boolean maySaveOrDeleteFilter(String filterName, MethodSecurityExpression
String username = ((User)methodSecurityExpressionOperations.getAuthentication().getPrincipal()).getUsername();
return filter1.map(value -> value.getUser().equals(username)).orElse(true);
}
+
+ /**
+ * Checks if user is allowed to create an object (node, snapshot...). This is the case if authorization
+ * is disabled or if user has (basic) user role.
+ *
+ * @param methodSecurityExpressionOperations {@link MethodSecurityExpressionOperations} Spring managed object
+ * queried for authorization.
+ * @return false if user may not create the object, otherwise true.
+ */
+ public boolean mayCreate(MethodSecurityExpressionOperations methodSecurityExpressionOperations){
+ return permitAll || methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleUser);
+ }
+
+ /**
+ * Checks if user is allowed to move or copy objects. This is the case if authorization
+ * is disabled or if user has admin role.
+ *
+ * @param methodSecurityExpressionOperations {@link MethodSecurityExpressionOperations} Spring managed object
+ * queried for authorization.
+ * @return false if user may not create the object, otherwise true.
+ */
+ public boolean mayMoveOrCopy(MethodSecurityExpressionOperations methodSecurityExpressionOperations) {
+ return permitAll || methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleAdmin);
+ }
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java
index 6d2d8f4ceb..8a025c82bb 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java
@@ -17,7 +17,6 @@
*/
package org.phoebus.service.saveandrestore.web.controllers;
-import org.phoebus.applications.saveandrestore.model.security.UserNotAuthorizedException;
import org.phoebus.service.saveandrestore.NodeNotFoundException;
import org.phoebus.service.saveandrestore.SnapshotNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
@@ -91,12 +90,6 @@ public ResponseEntity handleNodeNotFoundException(HttpServletRequest req
return new ResponseEntity<>(exception.getMessage(), HttpStatus.NOT_FOUND);
}
- @ExceptionHandler(UserNotAuthorizedException.class)
- public ResponseEntity handleUserNotAuthorizedException(HttpServletRequest req, UserNotAuthorizedException exception) {
- log(exception);
- return new ResponseEntity<>(exception.getMessage(), HttpStatus.FORBIDDEN);
- }
-
private void log(Throwable throwable) {
logger.log(Level.INFO, "Intercepted " + throwable.getClass().getName(), throwable);
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
index 359fa67e11..96690cf79b 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java
@@ -42,7 +42,7 @@ public class CompositeSnapshotController extends BaseController {
private NodeDAO nodeDAO;
@PutMapping(value = "/composite-snapshot", produces = JSON)
- @PreAuthorize("hasRole(this.roleUser)")
+ @PreAuthorize("@authorizationHelper.mayCreate(#root)")
public CompositeSnapshot createCompositeSnapshot(@RequestParam(value = "parentNodeId") String parentNodeId,
@RequestBody CompositeSnapshot compositeSnapshot,
Principal principal) {
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java
index 462d845e61..81813e3a27 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java
@@ -44,7 +44,7 @@ public class ConfigurationController extends BaseController {
@SuppressWarnings("unused")
@PutMapping(produces = JSON)
- @PreAuthorize("hasRole(this.roleUser)")
+ @PreAuthorize("@authorizationHelper.mayCreate(#root)")
public Configuration createConfiguration(@RequestParam(value = "parentNodeId") String parentNodeId,
@RequestBody Configuration configuration,
Principal principal) {
@@ -60,28 +60,10 @@ public ConfigurationData getConfigurationData(@PathVariable String uniqueId) {
@SuppressWarnings("unused")
@PostMapping(produces = JSON)
- @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleAdmin) or this.mayUpdate(#configuration, #principal))")
+ @PreAuthorize("@authorizationHelper.mayUpdate(#configuration, #root)")
public Configuration updateConfiguration(@RequestBody Configuration configuration,
Principal principal) {
configuration.getConfigurationNode().setUserName(principal.getName());
- Configuration c = nodeDAO.updateConfiguration(configuration);
- return c;
- }
-
- /**
- * NOTE: this method MUST be public!
- *
- *
- * An authenticated user may update a configuration if user identity is same as the target {@link Node}'s user id.
- *
- *
- * @param configuration {@link Configuration} identifying the target of the user's update operation.
- * @param principal Identifies user.
- * @return false if user may not update the {@link Node}.
- */
- @SuppressWarnings("unused")
- public boolean mayUpdate(Configuration configuration, Principal principal){
- Node configNode = nodeDAO.getNode(configuration.getConfigurationNode().getUniqueId());
- return configNode.getUserName().equals(principal.getName());
+ return nodeDAO.updateConfiguration(configuration);
}
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
index be62d65422..95ef3471c1 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java
@@ -65,7 +65,7 @@ public class NodeController extends BaseController {
*/
@SuppressWarnings("unused")
@PutMapping(value = "/node", produces = JSON)
- @PreAuthorize("hasRole(this.roleUser)")
+ @PreAuthorize("@authorizationHelper.mayCreate(#root)")
public Node createNode(@RequestParam(name = "parentNodeId") String parentsUniqueId,
@RequestBody final Node node,
Principal principal) {
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java
index c13da7658a..5654233e3c 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java
@@ -47,34 +47,25 @@ public List getAllSnapshots() {
}
@PutMapping(value = "/snapshot", produces = JSON)
- @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and this.maySave(#snapshot, #principal))")
- public Snapshot saveSnapshot(@RequestParam(value = "parentNodeId") String parentNodeId,
+ @PreAuthorize("@authorizationHelper.mayCreate(#root)")
+ public Snapshot createSnapshot(@RequestParam(value = "parentNodeId") String parentNodeId,
@RequestBody Snapshot snapshot,
Principal principal) {
if(!snapshot.getSnapshotNode().getNodeType().equals(NodeType.SNAPSHOT)){
throw new IllegalArgumentException("Snapshot node of wrong type");
}
snapshot.getSnapshotNode().setUserName(principal.getName());
- return nodeDAO.saveSnapshot(parentNodeId, snapshot);
+ return nodeDAO.createSnapshot(parentNodeId, snapshot);
}
- /**
- * NOTE: this method MUST be public!
- *
- *
- * An authenticated user may save a snapshot, and update if user identity is same as the target's
- * snapshot {@link Node}.
- *
- *
- * @param snapshot {@link Snapshot} identifying the target of the user's update operation.
- * @param principal Identifies user.
- * @return false if user may not update the {@link Snapshot}.
- */
- public boolean maySave(Snapshot snapshot, Principal principal){
- if(snapshot.getSnapshotNode().getUniqueId() == null){
- return true;
+ @PostMapping(value = "/snapshot", produces = JSON)
+ @PreAuthorize("@authorizationHelper.mayUpdate(#snapshot, #root)")
+ public Snapshot updateSnapshot(@RequestBody Snapshot snapshot,
+ Principal principal) {
+ if(!snapshot.getSnapshotNode().getNodeType().equals(NodeType.SNAPSHOT)){
+ throw new IllegalArgumentException("Snapshot node of wrong type");
}
- Node node = nodeDAO.getNode(snapshot.getSnapshotNode().getUniqueId());
- return node.getUserName().equals(principal.getName());
+ snapshot.getSnapshotNode().setUserName(principal.getName());
+ return nodeDAO.updateSnapshot(snapshot);
}
}
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/StructureController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/StructureController.java
index 91f121e7b8..4bfc3e1cc8 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/StructureController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/StructureController.java
@@ -53,7 +53,7 @@ public class StructureController extends BaseController {
*/
@SuppressWarnings("unused")
@PostMapping(value = "/move", produces = JSON)
- @PreAuthorize("hasRole(this.roleAdmin)")
+ @PreAuthorize("@authorizationHelper.mayMoveOrCopy(#root)")
public Node moveNodes(@RequestParam(value = "to") String to,
@RequestBody List nodes,
Principal principal) {
@@ -77,6 +77,7 @@ public Node moveNodes(@RequestParam(value = "to") String to,
*/
@SuppressWarnings("unused")
@PostMapping(value = "/copy", produces = JSON)
+ @PreAuthorize("@authorizationHelper.mayMoveOrCopy(#root)")
public Node copyNodes(@RequestParam(value = "to") String to,
@RequestBody List nodes,
Principal principal) {
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java
index 45c10ddf71..2c04dac8df 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java
@@ -27,7 +27,6 @@
import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
@@ -55,7 +54,7 @@ public List getTags() {
* Adds a {@link Tag} to specified list of target {@link Node}s. The {@link Tag} contained
* in tagData must be non-null, and its name must be non-null and non-empty.
*
- * @param tagData See {@link TagData}
+ * @param tagData See {@link TagData}
* @param principal {@link Principal} of authenticated user.
* @return The list of updated {@link Node}s
*/
diff --git a/services/save-and-restore/src/main/resources/application.properties b/services/save-and-restore/src/main/resources/application.properties
index c04f141083..525552efcb 100644
--- a/services/save-and-restore/src/main/resources/application.properties
+++ b/services/save-and-restore/src/main/resources/application.properties
@@ -84,8 +84,8 @@ demo.readOnly.password=1234
# demo - Hard coded, see WebSecurityConfig class
auth.impl = demo
-###### Bypass authorization (but not authentication) #######
-auth.bypass = true
+###### Bypass authorization (but not authentication!) #######
+authorization.permitall = true
############## Authorization Roles ################
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/DAOTestIT.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/DAOTestIT.java
index 2add9a17ee..57cd7446ef 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/DAOTestIT.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/DAOTestIT.java
@@ -271,7 +271,7 @@ public void testGetConfigForSnapshot() {
snapshotData.setSnapshotItems(List.of(item1));
snapshot.setSnapshotData(snapshotData);
- Node newSnapshot = nodeDAO.saveSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot).getSnapshotNode();
+ Node newSnapshot = nodeDAO.createSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot).getSnapshotNode();
config = nodeDAO.getParentNode(newSnapshot.getUniqueId());
@@ -306,7 +306,7 @@ public void testDeleteSnapshotReferencedInCompositeSnapshot() {
snapshot.setSnapshotData(snapshotData);
snapshot.setSnapshotNode(snapshotNode);
- snapshot = nodeDAO.saveSnapshot(configNode.getUniqueId(), snapshot);
+ snapshot = nodeDAO.createSnapshot(configNode.getUniqueId(), snapshot);
Node compositeSnapshotNode = Node.builder().name("My composite snapshot").nodeType(NodeType.COMPOSITE_SNAPSHOT).build();
@@ -352,7 +352,7 @@ public void testUpdateCompositeSnapshot() {
snapshotData.setSnapshotItems(List.of(SnapshotItem.builder().configPv(ConfigPv.builder().pvName("pv1").build()).build()));
snapshot.setSnapshotData(snapshotData);
- snapshot = nodeDAO.saveSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot);
+ snapshot = nodeDAO.createSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot);
CompositeSnapshot compositeSnapshot = new CompositeSnapshot();
compositeSnapshot.setCompositeSnapshotNode(compositeSnapshotNode);
@@ -387,7 +387,7 @@ public void testUpdateCompositeSnapshot() {
snapshotData2.setSnapshotItems(List.of(SnapshotItem.builder().configPv(ConfigPv.builder().pvName("pv2").build()).build()));
snapshot2.setSnapshotData(snapshotData2);
- snapshot2 = nodeDAO.saveSnapshot(configuration2.getConfigurationNode().getUniqueId(), snapshot2);
+ snapshot2 = nodeDAO.createSnapshot(configuration2.getConfigurationNode().getUniqueId(), snapshot2);
compositeSnapshotData = compositeSnapshot.getCompositeSnapshotData();
compositeSnapshotData.setReferencedSnapshotNodes(Arrays.asList(snapshot.getSnapshotNode().getUniqueId(),
@@ -443,7 +443,7 @@ public void testGetAllCompositeSnapshotData() {
Snapshot snapshot = new Snapshot();
snapshot.setSnapshotNode(snapshotNode);
snapshot.setSnapshotData(snapshotData);
- nodeDAO.saveSnapshot(configNode.getUniqueId(), snapshot);
+ nodeDAO.createSnapshot(configNode.getUniqueId(), snapshot);
compositeSnapshotData.setReferencedSnapshotNodes(List.of(snapshotNode.getUniqueId()));
compositeSnapshot.setCompositeSnapshotData(compositeSnapshotData);
@@ -496,7 +496,7 @@ public void testTakeSnapshot() {
snapshotData.setSnapshotItems(List.of(item1));
snapshot.setSnapshotData(snapshotData);
- snapshot = nodeDAO.saveSnapshot(config.getUniqueId(), snapshot);
+ snapshot = nodeDAO.createSnapshot(config.getUniqueId(), snapshot);
List snapshotItems = snapshot.getSnapshotData().getSnapshotItems();
assertEquals(1, snapshotItems.size());
@@ -514,6 +514,64 @@ public void testTakeSnapshot() {
clearAllData();
}
+ @Test
+ public void testUpdateSnapshot(){
+ Node rootNode = nodeDAO.getRootNode();
+ Node folderNode =
+ Node.builder().name("folder").build();
+
+ folderNode = nodeDAO.createNode(rootNode.getUniqueId(), folderNode);
+
+ Node config = Node.builder().name("My config 3").nodeType(NodeType.CONFIGURATION).build();
+
+ Configuration configuration = new Configuration();
+ configuration.setConfigurationNode(config);
+ ConfigurationData configurationData = new ConfigurationData();
+ configurationData.setPvList(List.of(ConfigPv.builder()
+ .pvName("whatever").readbackPvName("readback_whatever").build()));
+ configuration.setConfigurationData(configurationData);
+
+ configuration = nodeDAO.createConfiguration(folderNode.getUniqueId(), configuration);
+
+ SnapshotItem item1 = SnapshotItem.builder().configPv(configuration.getConfigurationData().getPvList().get(0))
+ .value(VDouble.of(7.7, alarm, time, display)).readbackValue(VDouble.of(8.8, alarm, time, display))
+ .build();
+
+ Snapshot snapshot = new Snapshot();
+ snapshot.setSnapshotNode(Node.builder()
+ .name("snapshot name")
+ .userName("user")
+ .description("comment")
+ .build());
+ SnapshotData snapshotData = new SnapshotData();
+ snapshotData.setSnapshotItems(List.of(item1));
+ snapshot.setSnapshotData(snapshotData);
+
+ snapshot = nodeDAO.createSnapshot(config.getUniqueId(), snapshot);
+
+ List snapshotItems = snapshot.getSnapshotData().getSnapshotItems();
+ assertEquals(1, snapshotItems.size());
+ assertEquals(7.7, ((VDouble) snapshotItems.get(0).getValue()).getValue(), 0.01);
+ assertEquals(8.8, ((VDouble) snapshotItems.get(0).getReadbackValue()).getValue(), 0.01);
+
+ List snapshots = nodeDAO.getSnapshots(config.getUniqueId());
+ assertEquals(1, snapshots.size());
+
+ Node snapshotNode = snapshot.getSnapshotNode();
+ snapshotNode.setName("other snapshot name");
+ snapshotNode.setDescription("other comment");
+
+ snapshot.setSnapshotNode(snapshotNode);
+
+ snapshot = nodeDAO.updateSnapshot(snapshot);
+
+ snapshotNode = snapshot.getSnapshotNode();
+ assertEquals("other snapshot name", snapshotNode.getName());
+ assertEquals("other comment", snapshotNode.getDescription());
+
+ clearAllData();
+ }
+
@Test
public void testGetSnapshotsNoSnapshots() {
assertThrows(NodeNotFoundException.class,
@@ -552,7 +610,7 @@ public void testGetSnapshotItemsWithNullPvValues() {
snapshotData.setSnapshotItems(List.of(item1));
snapshot.setSnapshotData(snapshotData);
- snapshot = nodeDAO.saveSnapshot(config.getUniqueId(), snapshot);
+ snapshot = nodeDAO.createSnapshot(config.getUniqueId(), snapshot);
assertEquals(7.7, ((VDouble) snapshot.getSnapshotData().getSnapshotItems().get(0).getValue()).getValue(), 0.01);
assertNull(snapshot.getSnapshotData().getSnapshotItems().get(0).getReadbackValue());
@@ -566,7 +624,7 @@ public void testGetSnapshotItemsWithNullPvValues() {
SnapshotData snapshotData1 = new SnapshotData();
snapshot1.setSnapshotData(snapshotData1);
- snapshot1 = nodeDAO.saveSnapshot(config.getUniqueId(), snapshot1);
+ snapshot1 = nodeDAO.createSnapshot(config.getUniqueId(), snapshot1);
assertNull(snapshot1.getSnapshotData().getSnapshotItems());
@@ -588,7 +646,7 @@ public void testSnapshotTag() {
snapshot.setSnapshotNode(Node.builder().name("testSnapshot").nodeType(NodeType.SNAPSHOT).build());
snapshot.setSnapshotData(new SnapshotData());
- Node snapshotNode = nodeDAO.saveSnapshot(configNode.getUniqueId(), snapshot).getSnapshotNode();
+ Node snapshotNode = nodeDAO.createSnapshot(configNode.getUniqueId(), snapshot).getSnapshotNode();
Tag tag = Tag.builder().name("tag1").comment("comment1").userName("testUser1").build();
snapshotNode.addTag(tag);
@@ -620,7 +678,7 @@ public void testSnapshotTag() {
snapshot2.setSnapshotNode(Node.builder().name("testSnapshot2").nodeType(NodeType.SNAPSHOT).build());
snapshot2.setSnapshotData(new SnapshotData());
- Node snapshotNode2 = nodeDAO.saveSnapshot(configNode.getUniqueId(), snapshot2).getSnapshotNode();
+ Node snapshotNode2 = nodeDAO.createSnapshot(configNode.getUniqueId(), snapshot2).getSnapshotNode();
Tag newTag = Tag.builder().name("newtag").comment("comment1").userName("testUser1").build();
@@ -727,7 +785,7 @@ public void testUpdateConfig() throws Exception {
snapshotData.setSnapshotItems(Arrays.asList(item1, item2));
snapshot.setSnapshotData(snapshotData);
- snapshot = nodeDAO.saveSnapshot(config.getUniqueId(), snapshot);
+ snapshot = nodeDAO.createSnapshot(config.getUniqueId(), snapshot);
// Save another snapshot with same data
Snapshot snapshot1 = new Snapshot();
@@ -736,7 +794,7 @@ public void testUpdateConfig() throws Exception {
snapshotData1.setSnapshotItems(Arrays.asList(item1, item2));
snapshot1.setSnapshotData(snapshotData1);
- nodeDAO.saveSnapshot(config.getUniqueId(), snapshot1);
+ nodeDAO.createSnapshot(config.getUniqueId(), snapshot1);
List snapshotItems = snapshot.getSnapshotData().getSnapshotItems();
@@ -1429,7 +1487,7 @@ public void testCopySnapshotToFolderNotSupported() {
snapshotData.setSnapshotItems(List.of(item1));
snapshot.setSnapshotData(snapshotData);
- snapshot = nodeDAO.saveSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot);
+ snapshot = nodeDAO.createSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot);
String snapshotId = snapshot.getSnapshotNode().getUniqueId();
@@ -1479,7 +1537,7 @@ public void testCopySnapshotToConfiguration() {
snapshotData.setSnapshotItems(List.of(item1));
snapshot.setSnapshotData(snapshotData);
- snapshot = nodeDAO.saveSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot);
+ snapshot = nodeDAO.createSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot);
String snapshotId = snapshot.getSnapshotNode().getUniqueId();
@@ -1527,7 +1585,7 @@ public void testCopySnapshotToConfigurationPvListMismatch() {
snapshotData.setSnapshotItems(List.of(item1));
snapshot.setSnapshotData(snapshotData);
- snapshot = nodeDAO.saveSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot);
+ snapshot = nodeDAO.createSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot);
String snapshotId = snapshot.getSnapshotNode().getUniqueId();
String config2Id = configuration2.getConfigurationNode().getUniqueId();
@@ -1565,7 +1623,7 @@ public void testCopyCompositeSnapshot() {
snapshotData.setSnapshotItems(List.of(item1));
snapshot.setSnapshotData(snapshotData);
- snapshot = nodeDAO.saveSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot);
+ snapshot = nodeDAO.createSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot);
Node folderNode1 = nodeDAO.createNode(rootNode.getUniqueId(),
@@ -1625,7 +1683,7 @@ public void testCopyCompositeSnapshotToConfiguration() {
snapshotData.setSnapshotItems(List.of(item1));
snapshot.setSnapshotData(snapshotData);
- snapshot = nodeDAO.saveSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot);
+ snapshot = nodeDAO.createSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot);
Node config2 = Node.builder().name("My config 4").nodeType(NodeType.CONFIGURATION).build();
@@ -1816,7 +1874,7 @@ public void testGetAllSnapshots() {
snapshotData.setSnapshotItems(List.of(item1));
snapshot.setSnapshotData(snapshotData);
- nodeDAO.saveSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot);
+ nodeDAO.createSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot);
List snapshotNodes = nodeDAO.getAllSnapshots();
assertEquals(1, snapshotNodes.size());
@@ -1908,7 +1966,7 @@ public void testCheckForPVNameDuplicates() {
SnapshotData snapshotData = new SnapshotData();
snapshotData.setSnapshotItems(Arrays.asList(item1, item2));
snapshot.setSnapshotData(snapshotData);
- Node newSnapshot1 = nodeDAO.saveSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot).getSnapshotNode();
+ Node newSnapshot1 = nodeDAO.createSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot).getSnapshotNode();
//************ End create snapshot1 ************/
//************ Create snapshot2 ************/
@@ -1934,7 +1992,7 @@ public void testCheckForPVNameDuplicates() {
SnapshotData snapshotData2 = new SnapshotData();
snapshotData2.setSnapshotItems(Arrays.asList(item12, item22));
snapshot2.setSnapshotData(snapshotData2);
- Node newSnapshot2 = nodeDAO.saveSnapshot(configuration2.getConfigurationNode().getUniqueId(), snapshot2).getSnapshotNode();
+ Node newSnapshot2 = nodeDAO.createSnapshot(configuration2.getConfigurationNode().getUniqueId(), snapshot2).getSnapshotNode();
//************ End create snapshot2 ************/
List duplicates = nodeDAO.checkForPVNameDuplicates(Arrays.asList(snapshot.getSnapshotNode().getUniqueId(),
@@ -1963,7 +2021,7 @@ public void testCheckForPVNameDuplicates() {
SnapshotData snapshotData3 = new SnapshotData();
snapshotData3.setSnapshotItems(Collections.singletonList(item13));
snapshot3.setSnapshotData(snapshotData3);
- Node newSnapshot3 = nodeDAO.saveSnapshot(configuration3.getConfigurationNode().getUniqueId(), snapshot3).getSnapshotNode();
+ Node newSnapshot3 = nodeDAO.createSnapshot(configuration3.getConfigurationNode().getUniqueId(), snapshot3).getSnapshotNode();
//************ End create snapshot3 ************/
duplicates = nodeDAO.checkForPVNameDuplicates(Arrays.asList(snapshot.getSnapshotNode().getUniqueId(),
@@ -2030,7 +2088,7 @@ public void testCheckForRejectedReferencedNodesInCompositeSnapshot() {
SnapshotData snapshotData = new SnapshotData();
snapshotData.setSnapshotItems(Arrays.asList(item1, item2));
snapshot.setSnapshotData(snapshotData);
- Node newSnapshot1 = nodeDAO.saveSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot).getSnapshotNode();
+ Node newSnapshot1 = nodeDAO.createSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot).getSnapshotNode();
//************ End create snapshot1 ************/
//************ Create snapshot2 ************/
@@ -2056,7 +2114,7 @@ public void testCheckForRejectedReferencedNodesInCompositeSnapshot() {
SnapshotData snapshotData2 = new SnapshotData();
snapshotData2.setSnapshotItems(Arrays.asList(item12, item22));
snapshot2.setSnapshotData(snapshotData2);
- Node newSnapshot2 = nodeDAO.saveSnapshot(configuration2.getConfigurationNode().getUniqueId(), snapshot2).getSnapshotNode();
+ Node newSnapshot2 = nodeDAO.createSnapshot(configuration2.getConfigurationNode().getUniqueId(), snapshot2).getSnapshotNode();
//************ End create snapshot2 ************/
@@ -2116,7 +2174,7 @@ public void testGetSnapshotItemsFromCompositeSnapshot() {
SnapshotData snapshotData = new SnapshotData();
snapshotData.setSnapshotItems(Arrays.asList(item1, item2));
snapshot.setSnapshotData(snapshotData);
- Node newSnapshot1 = nodeDAO.saveSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot).getSnapshotNode();
+ Node newSnapshot1 = nodeDAO.createSnapshot(configuration.getConfigurationNode().getUniqueId(), snapshot).getSnapshotNode();
//************ End create snapshot1 ************/
//************ Create snapshot2 ************/
@@ -2142,7 +2200,7 @@ public void testGetSnapshotItemsFromCompositeSnapshot() {
SnapshotData snapshotData2 = new SnapshotData();
snapshotData2.setSnapshotItems(Arrays.asList(item12, item22));
snapshot2.setSnapshotData(snapshotData2);
- Node newSnapshot2 = nodeDAO.saveSnapshot(configuration2.getConfigurationNode().getUniqueId(), snapshot2).getSnapshotNode();
+ Node newSnapshot2 = nodeDAO.createSnapshot(configuration2.getConfigurationNode().getUniqueId(), snapshot2).getSnapshotNode();
//************ End create snapshot2 ************/
//************ Create composite snapshot ************/
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerPermitAllTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerPermitAllTest.java
new file mode 100644
index 0000000000..19cec3db71
--- /dev/null
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerPermitAllTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2020 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package org.phoebus.service.saveandrestore.web.controllers;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mockito;
+import org.phoebus.applications.saveandrestore.model.CompositeSnapshot;
+import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData;
+import org.phoebus.applications.saveandrestore.model.Node;
+import org.phoebus.applications.saveandrestore.model.NodeType;
+import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
+import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+
+import java.util.List;
+
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+import static org.phoebus.service.saveandrestore.web.controllers.BaseController.JSON;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = ControllersTestConfig.class)
+@WebMvcTest(CompositeSnapshotController.class)
+@TestPropertySource(locations = "classpath:test_application_permit_all.properties")
+public class CompositeSnapshotControllerPermitAllTest {
+
+ @Autowired
+ private String readOnlyAuthorization;
+
+ @Autowired
+ private NodeDAO nodeDAO;
+
+ @Autowired
+ private String demoUser;
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ private static CompositeSnapshot compositeSnapshot;
+
+ @BeforeAll
+ public static void init() {
+ compositeSnapshot = new CompositeSnapshot();
+ compositeSnapshot.setCompositeSnapshotNode(Node.builder().nodeType(NodeType.COMPOSITE_SNAPSHOT)
+ .name("name").uniqueId("id").build());
+ CompositeSnapshotData compositeSnapshotData = new CompositeSnapshotData();
+ compositeSnapshotData.setReferencedSnapshotNodes(List.of("ref"));
+ compositeSnapshot.setCompositeSnapshotData(compositeSnapshotData);
+ }
+
+ @Test
+ public void testCreateCompositeSnapshot() throws Exception {
+
+ when(nodeDAO.createCompositeSnapshot(Mockito.any(String.class), Mockito.any(CompositeSnapshot.class))).thenReturn(compositeSnapshot);
+
+ String compositeSnapshotString = objectMapper.writeValueAsString(compositeSnapshot);
+
+ MockHttpServletRequestBuilder request = put("/composite-snapshot?parentNodeId=id")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON)
+ .content(compositeSnapshotString);
+
+ MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))
+ .andReturn();
+
+ String s = result.getResponse().getContentAsString();
+ // Make sure response contains expected data
+ objectMapper.readValue(s, CompositeSnapshot.class);
+
+ request = put("/composite-snapshot?parentNodeId=id")
+ .contentType(JSON)
+ .content(compositeSnapshotString);
+
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+ reset(nodeDAO);
+ }
+
+ @Test
+ public void testUpdateCompositeSnapshot() throws Exception {
+
+ Node node = Node.builder().uniqueId("c").nodeType(NodeType.COMPOSITE_SNAPSHOT).userName(demoUser).build();
+ CompositeSnapshot compositeSnapshot1 = new CompositeSnapshot();
+ compositeSnapshot1.setCompositeSnapshotNode(node);
+
+ String compositeSnapshotString = objectMapper.writeValueAsString(compositeSnapshot1);
+
+ when(nodeDAO.updateCompositeSnapshot(compositeSnapshot1)).thenReturn(compositeSnapshot1);
+ when(nodeDAO.getNode("c")).thenReturn(node);
+
+ MockHttpServletRequestBuilder request = post("/composite-snapshot")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON)
+ .content(compositeSnapshotString);
+
+ MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))
+ .andReturn();
+
+ String s = result.getResponse().getContentAsString();
+ // Make sure response contains expected data
+ objectMapper.readValue(s, CompositeSnapshot.class);
+
+ request = post("/composite-snapshot")
+ .contentType(JSON)
+ .content(compositeSnapshotString);
+
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+
+ reset(nodeDAO);
+ }
+}
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java
index c5b66238df..8d4162cf24 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java
@@ -77,7 +77,7 @@ public class CompositeSnapshotControllerTest {
@Autowired
private MockMvc mockMvc;
- private ObjectMapper objectMapper = new ObjectMapper();
+ private final ObjectMapper objectMapper = new ObjectMapper();
private static CompositeSnapshot compositeSnapshot;
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationControllerPermitAllTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationControllerPermitAllTest.java
new file mode 100644
index 0000000000..502df6b8f3
--- /dev/null
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationControllerPermitAllTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2020 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package org.phoebus.service.saveandrestore.web.controllers;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.phoebus.applications.saveandrestore.model.Configuration;
+import org.phoebus.applications.saveandrestore.model.Node;
+import org.phoebus.applications.saveandrestore.model.NodeType;
+import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
+import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+import static org.phoebus.service.saveandrestore.web.controllers.BaseController.JSON;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = ControllersTestConfig.class)
+@WebMvcTest(ConfigurationController.class)
+@TestPropertySource(locations = "classpath:test_application_permit_all.properties")
+public class ConfigurationControllerPermitAllTest {
+
+ @Autowired
+ private NodeDAO nodeDAO;
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Autowired
+ private ObjectMapper objectMapper;
+
+ @Autowired
+ private String userAuthorization;
+
+ @Autowired
+ private String readOnlyAuthorization;
+
+ @Autowired
+ private String demoUser;
+
+ @Test
+ public void testCreateConfiguration() throws Exception {
+
+ reset(nodeDAO);
+
+ Configuration configuration = new Configuration();
+ configuration.setConfigurationNode(Node.builder().build());
+ MockHttpServletRequestBuilder request = put("/config?parentNodeId=a")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON).content(objectMapper.writeValueAsString(configuration));
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = put("/config?parentNodeId=a")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON).content(objectMapper.writeValueAsString(configuration));
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = put("/config?parentNodeId=a")
+ .contentType(JSON).content(objectMapper.writeValueAsString(configuration));
+
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ public void testUpdateConfiguration() throws Exception {
+
+ Node configurationNode = Node.builder().uniqueId("uniqueId").nodeType(NodeType.CONFIGURATION).userName(demoUser).build();
+
+ Configuration configuration = new Configuration();
+ configuration.setConfigurationNode(configurationNode);
+
+ when(nodeDAO.getNode("uniqueId")).thenReturn(configurationNode);
+
+ MockHttpServletRequestBuilder request = post("/config")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON).content(objectMapper.writeValueAsString(configuration));
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ when(nodeDAO.getNode("uniqueId")).thenReturn(configurationNode);
+
+ request = post("/config")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON).content(objectMapper.writeValueAsString(configuration));
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ when(nodeDAO.getNode("uniqueId")).thenReturn(configurationNode);
+
+ request = post("/config")
+ .contentType(JSON).content(objectMapper.writeValueAsString(configuration));
+
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+
+ }
+}
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationControllerTest.java
new file mode 100644
index 0000000000..4ab5544360
--- /dev/null
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationControllerTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2020 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package org.phoebus.service.saveandrestore.web.controllers;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.phoebus.applications.saveandrestore.model.Configuration;
+import org.phoebus.applications.saveandrestore.model.Node;
+import org.phoebus.applications.saveandrestore.model.NodeType;
+import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
+import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+import static org.phoebus.service.saveandrestore.web.controllers.BaseController.JSON;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = ControllersTestConfig.class)
+@WebMvcTest(ConfigurationController.class)
+@TestPropertySource(locations = "classpath:test_application.properties")
+public class ConfigurationControllerTest {
+
+ @Autowired
+ private NodeDAO nodeDAO;
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Autowired
+ private ObjectMapper objectMapper;
+
+ @Autowired
+ private String userAuthorization;
+
+ @Autowired
+ private String adminAuthorization;
+
+ @Autowired
+ private String readOnlyAuthorization;
+
+ @Autowired
+ private String demoUser;
+
+ @Test
+ public void testCreateConfiguration() throws Exception {
+
+ reset(nodeDAO);
+
+ Configuration configuration = new Configuration();
+ configuration.setConfigurationNode(Node.builder().build());
+ MockHttpServletRequestBuilder request = put("/config?parentNodeId=a")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON).content(objectMapper.writeValueAsString(configuration));
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = put("/config?parentNodeId=a")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON).content(objectMapper.writeValueAsString(configuration));
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = put("/config?parentNodeId=a")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON).content(objectMapper.writeValueAsString(configuration));
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = put("/config?parentNodeId=a")
+ .contentType(JSON).content(objectMapper.writeValueAsString(configuration));
+
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ public void testUpdateConfiguration() throws Exception {
+
+ Node configurationNode = Node.builder().uniqueId("uniqueId").nodeType(NodeType.CONFIGURATION).userName(demoUser).build();
+
+ Configuration configuration = new Configuration();
+ configuration.setConfigurationNode(configurationNode);
+
+ when(nodeDAO.getNode("uniqueId")).thenReturn(configurationNode);
+
+ MockHttpServletRequestBuilder request = post("/config")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON).content(objectMapper.writeValueAsString(configuration));
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ when(nodeDAO.getNode("uniqueId")).thenReturn(configurationNode);
+
+ request = post("/config")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON).content(objectMapper.writeValueAsString(configuration));
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ configurationNode = Node.builder().uniqueId("uniqueId").nodeType(NodeType.CONFIGURATION).userName("someUser").build();
+ configuration.setConfigurationNode(configurationNode);
+
+ when(nodeDAO.getNode("uniqueId")).thenReturn(configurationNode);
+
+ request = post("/config")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON).content(objectMapper.writeValueAsString(configuration));
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ when(nodeDAO.getNode("uniqueId")).thenReturn(configurationNode);
+
+ request = post("/config")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON).content(objectMapper.writeValueAsString(configuration));
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = post("/config")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON).content(objectMapper.writeValueAsString(configuration));
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = post("/config")
+ .contentType(JSON).content(objectMapper.writeValueAsString(configuration));
+
+ mockMvc.perform(request).andExpect(status().isUnauthorized()
+ );
+
+ }
+}
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerPermitAllTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerPermitAllTest.java
new file mode 100644
index 0000000000..0864cf5f2a
--- /dev/null
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerPermitAllTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2020 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package org.phoebus.service.saveandrestore.web.controllers;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mockito;
+import org.phoebus.applications.saveandrestore.model.search.Filter;
+import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
+import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+
+import java.util.List;
+
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+import static org.phoebus.service.saveandrestore.web.controllers.BaseController.JSON;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = ControllersTestConfig.class)
+@WebMvcTest(FilterController.class)
+@TestPropertySource(locations = "classpath:test_application_permit_all.properties")
+public class FilterControllerPermitAllTest {
+
+ @Autowired
+ private NodeDAO nodeDAO;
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Autowired
+ private ObjectMapper objectMapper;
+
+ @Autowired
+ private String userAuthorization;
+
+ @Autowired
+ private String readOnlyAuthorization;
+
+ @Autowired
+ private String demoUser;
+
+ @Test
+ public void testSaveFilter() throws Exception {
+
+ reset(nodeDAO);
+
+ Filter filter = new Filter();
+ filter.setName("name");
+ filter.setQueryString("query");
+ filter.setUser("user");
+
+ String filterString = objectMapper.writeValueAsString(filter);
+
+ when(nodeDAO.saveFilter(Mockito.any(Filter.class))).thenReturn(filter);
+
+ MockHttpServletRequestBuilder request = put("/filter")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(filterString);
+
+ MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))
+ .andReturn();
+
+ String s = result.getResponse().getContentAsString();
+ // Make sure response contains expected data
+ objectMapper.readValue(s, Filter.class);
+
+ request = put("/filter")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON)
+ .content(filterString);
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = put("/filter")
+ .contentType(JSON)
+ .content(filterString);
+
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ public void testDeleteFilter() throws Exception {
+ Filter filter = new Filter();
+ filter.setName("name");
+ filter.setQueryString("query");
+ filter.setUser(demoUser);
+
+ when(nodeDAO.getAllFilters()).thenReturn(List.of(filter));
+
+ MockHttpServletRequestBuilder request = delete("/filter/name")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON);
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = delete("/filter/name")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON);
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = delete("/filter/name")
+ .contentType(JSON);
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+ }
+
+}
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerPermitAllTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerPermitAllTest.java
new file mode 100644
index 0000000000..1f591c298e
--- /dev/null
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerPermitAllTest.java
@@ -0,0 +1,206 @@
+/**
+ * Copyright (C) 2018 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package org.phoebus.service.saveandrestore.web.controllers;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mockito;
+import org.phoebus.applications.saveandrestore.model.Node;
+import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
+import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+
+import java.util.List;
+
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+import static org.phoebus.service.saveandrestore.web.controllers.BaseController.JSON;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = ControllersTestConfig.class)
+@TestPropertySource(locations = "classpath:test_application_permit_all.properties")
+@WebMvcTest(NodeController.class)
+/**
+ * Main purpose of the tests in this class is to verify that REST end points are
+ * maintained, i.e. that URLs are not changed and that they return the correct
+ * data.
+ *
+ * @author Georg Weiss, European Spallation Source
+ *
+ */
+public class NodeControllerPermitAllTest {
+
+ @Autowired
+ private NodeDAO nodeDAO;
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ private static Node folderFromClient;
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Autowired
+ private String demoUser;
+
+ @Autowired
+ private String userAuthorization;
+
+ @Autowired
+ private String readOnlyAuthorization;
+
+ @BeforeAll
+ public static void setUp() {
+ folderFromClient = Node.builder().name("SomeFolder").userName("myusername").uniqueId("11").build();
+ }
+
+ @Test
+ public void testCreateFolder() throws Exception {
+
+ when(nodeDAO.createNode(Mockito.any(String.class), Mockito.any(Node.class))).thenReturn(folderFromClient);
+
+ String content = objectMapper.writeValueAsString(folderFromClient);
+
+ MockHttpServletRequestBuilder request = put("/node?parentNodeId=a")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(content);
+
+ MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))
+ .andReturn();
+
+ String s = result.getResponse().getContentAsString();
+ // Make sure response contains expected data
+ objectMapper.readValue(s, Node.class);
+
+ request = put("/node?parentNodeId=a")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON)
+ .content(content);
+ mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON));
+
+ request = put("/node?parentNodeId=a")
+ .contentType(JSON)
+ .content(content);
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ public void testDeleteFolder() throws Exception {
+
+ MockHttpServletRequestBuilder request =
+ post("/node");
+
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+
+ when(nodeDAO.getNode("a")).thenReturn(Node.builder().uniqueId("a").userName(demoUser).build());
+
+ request =
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")))
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization);
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ when(nodeDAO.getNode("a")).thenReturn(Node.builder().uniqueId("a").userName(demoUser).build());
+
+ request =
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")))
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization);
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ when(nodeDAO.getNode("a")).thenReturn(Node.builder().uniqueId("a").userName(demoUser).build());
+
+ request =
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")))
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization);
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request =
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")));
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ public void testGetFolderIllegalArgument() throws Exception {
+ when(nodeDAO.getNode("a")).thenThrow(IllegalArgumentException.class);
+
+ MockHttpServletRequestBuilder request = get("/node/a");
+
+ mockMvc.perform(request).andExpect(status().isBadRequest());
+
+ }
+
+ @Test
+ public void testUpdateNode() throws Exception {
+
+ reset(nodeDAO);
+
+ Node node = Node.builder().name("foo").uniqueId("a").userName(demoUser).build();
+
+ when(nodeDAO.getNode("a")).thenReturn(node);
+ when(nodeDAO.updateNode(Mockito.any(Node.class), Mockito.anyBoolean())).thenReturn(node);
+
+ MockHttpServletRequestBuilder request = post("/node")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .param("customTimeForMigration", "false")
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(node));
+
+ MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))
+ .andReturn();
+
+ // Make sure response contains expected data
+ objectMapper.readValue(result.getResponse().getContentAsString(), Node.class);
+
+ node = Node.builder().name("foo").uniqueId("a").userName("notDemoUser").build();
+
+ when(nodeDAO.getNode("a")).thenReturn(node);
+ when(nodeDAO.updateNode(Mockito.any(Node.class), Mockito.anyBoolean())).thenReturn(node);
+
+
+ request = post("/node")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .param("customTimeForMigration", "false")
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(node));
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = post("/node")
+ .param("customTimeForMigration", "false")
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(node));
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+ }
+}
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java
index 41e7ec7878..60bc8075cc 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java
@@ -24,7 +24,6 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.phoebus.applications.saveandrestore.model.Configuration;
import org.phoebus.applications.saveandrestore.model.Node;
@@ -51,25 +50,21 @@
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
import static org.phoebus.service.saveandrestore.web.controllers.BaseController.JSON;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-@ExtendWith(SpringExtension.class)
-@ContextConfiguration(classes = ControllersTestConfig.class)
-@TestPropertySource(locations = "classpath:test_application.properties")
-@WebMvcTest(NodeController.class)
/**
* Main purpose of the tests in this class is to verify that REST end points are
* maintained, i.e. that URLs are not changed and that they return the correct
* data.
*
* @author Georg Weiss, European Spallation Source
- *
*/
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = ControllersTestConfig.class)
+@TestPropertySource(locations = "classpath:test_application.properties")
+@WebMvcTest(NodeController.class)
public class NodeControllerTest {
@Autowired
@@ -82,9 +77,7 @@ public class NodeControllerTest {
private static Node config1;
- private static Node snapshot;
-
- private ObjectMapper objectMapper = new ObjectMapper();
+ private final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private String demoUser;
@@ -108,10 +101,6 @@ public static void setUp() {
.userName("myusername").build();
folderFromClient = Node.builder().name("SomeFolder").userName("myusername").uniqueId("11").build();
-
- snapshot = Node.builder().nodeType(NodeType.SNAPSHOT).nodeType(NodeType.SNAPSHOT).name("name")
- .build();
-
}
@Test
@@ -145,10 +134,10 @@ public void testCreateFolder() throws Exception {
.content(content);
mockMvc.perform(request).andExpect(status().isForbidden());
- put("/node?parentNodeId=a")
+ request = put("/node?parentNodeId=a")
.contentType(JSON)
.content(content);
- mockMvc.perform(request).andExpect(status().isForbidden());
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
}
@Test
@@ -176,11 +165,7 @@ public void testCreateConfig() throws Exception {
Configuration configuration = new Configuration();
configuration.setConfigurationNode(config);
- when(nodeDAO.createConfiguration(Mockito.any(String.class), Mockito.any(Configuration.class))).thenAnswer(new Answer() {
- public Configuration answer(InvocationOnMock invocation) {
- return configuration;
- }
- });
+ when(nodeDAO.createConfiguration(Mockito.any(String.class), Mockito.any(Configuration.class))).thenAnswer((Answer) invocation -> configuration);
MockHttpServletRequestBuilder request = put("/config?parentNodeId=p")
.header(HttpHeaders.AUTHORIZATION, userAuthorization)
@@ -195,7 +180,7 @@ public Configuration answer(InvocationOnMock invocation) {
}
@Test
- public void testUpdateConfig() throws Exception{
+ public void testUpdateConfig() throws Exception {
reset(nodeDAO);
Node config = Node.builder().nodeType(NodeType.CONFIGURATION).name("config").uniqueId("hhh")
@@ -274,11 +259,7 @@ public void testCreateNodeBadRequests() throws Exception {
public void testGetChildNodes() throws Exception {
reset(nodeDAO);
- when(nodeDAO.getChildNodes("p")).thenAnswer(new Answer>() {
- public List answer(InvocationOnMock invocation) throws Throwable {
- return Arrays.asList(config1);
- }
- });
+ when(nodeDAO.getChildNodes("p")).thenAnswer((Answer>) invocation -> Collections.singletonList(config1));
MockHttpServletRequestBuilder request = get("/node/p/children").contentType(JSON);
@@ -286,7 +267,7 @@ public List answer(InvocationOnMock invocation) throws Throwable {
.andReturn();
// Make sure response contains expected data
- List childNodes = objectMapper.readValue(result.getResponse().getContentAsString(), new TypeReference>() {
+ List childNodes = objectMapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() {
});
assertEquals(1, childNodes.size());
@@ -322,15 +303,23 @@ public void testGetSnapshotsForNonExistingConfig() throws Exception {
mockMvc.perform(request).andExpect(status().isNotFound());
}
- /**
- * Tests both OK responses and forbidden responses.
- * @throws Exception
- */
@Test
- public void testDeleteFolder() throws Exception {
+ public void testDeleteSnapshot() throws Exception {
+
+ when(nodeDAO.getNode("a")).thenReturn(Node.builder()
+ .nodeType(NodeType.SNAPSHOT)
+ .uniqueId("a").userName(demoUser).build());
+
+ MockHttpServletRequestBuilder request =
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")))
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization);
- //.header(HttpHeaders.AUTHORIZATION, userAuthorization)
- // .contentType(JSON).content(objectMapper.writeValueAsString(config));
+ mockMvc.perform(request).andExpect(status().isOk());
+ }
+
+ @Test
+ public void testDeleteFolder() throws Exception {
MockHttpServletRequestBuilder request =
post("/node");
@@ -353,6 +342,14 @@ public void testDeleteFolder() throws Exception {
.header(HttpHeaders.AUTHORIZATION, userAuthorization);
mockMvc.perform(request).andExpect(status().isForbidden());
+ when(nodeDAO.getNode("a")).thenReturn(Node.builder().uniqueId("a").userName(demoUser).build());
+
+ request =
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")))
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization);
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
when(nodeDAO.getNode("a")).thenReturn(Node.builder().uniqueId("a").nodeType(NodeType.CONFIGURATION).userName(demoUser).build());
when(nodeDAO.getChildNodes("a")).thenReturn(Collections.emptyList());
@@ -468,79 +465,6 @@ public void testGetNonExistingFolder() throws Exception {
}
- @Test
- public void testMoveNode() throws Exception {
- when(nodeDAO.moveNodes(Arrays.asList("a"), "b", demoAdmin))
- .thenReturn(Node.builder().uniqueId("2").uniqueId("a").userName(demoAdmin).build());
-
- MockHttpServletRequestBuilder request = post("/move")
- .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
- .contentType(JSON)
- .content(objectMapper.writeValueAsString(Arrays.asList("a")))
- .param("to", "b")
- .param("username", "username");
-
- MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))
- .andReturn();
-
- // Make sure response contains expected data
- objectMapper.readValue(result.getResponse().getContentAsString(), Node.class);
-
- when(nodeDAO.moveNodes(Arrays.asList("a"), "b", demoUser))
- .thenReturn(Node.builder().uniqueId("2").uniqueId("a").userName(demoUser).build());
-
- request = post("/move")
- .header(HttpHeaders.AUTHORIZATION, userAuthorization)
- .contentType(JSON)
- .content(objectMapper.writeValueAsString(Arrays.asList("a")))
- .param("to", "b")
- .param("username", "username");
-
- mockMvc.perform(request).andExpect(status().isForbidden());
-
-
- request = post("/move")
- .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
- .contentType(JSON)
- .content(objectMapper.writeValueAsString(Arrays.asList("a")))
- .param("to", "b")
- .param("username", "username");
-
- mockMvc.perform(request).andExpect(status().isForbidden());
-
- request = post("/move")
- .contentType(JSON)
- .content(objectMapper.writeValueAsString(Arrays.asList("a")))
- .param("to", "b")
- .param("username", "username");
-
- mockMvc.perform(request).andExpect(status().isUnauthorized());
- }
-
- @Test
- public void testMoveNodeTargetIdEmpty() throws Exception {
- MockHttpServletRequestBuilder request = post("/move")
- .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
- .contentType(JSON)
- .content(objectMapper.writeValueAsString(Arrays.asList("a")))
- .param("to", "")
- .param("username", "user");
-
- mockMvc.perform(request).andExpect(status().isBadRequest());
- }
-
- @Test
- public void testMoveNodeSourceNodeListEmpty() throws Exception {
- MockHttpServletRequestBuilder request = post("/move")
- .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
- .contentType(JSON)
- .content(objectMapper.writeValueAsString(Collections.emptyList()))
- .param("to", "targetId")
- .param("username", "user");
-
- mockMvc.perform(request).andExpect(status().isBadRequest());
- }
-
@Test
public void testGetFolderIllegalArgument() throws Exception {
when(nodeDAO.getNode("a")).thenThrow(IllegalArgumentException.class);
@@ -605,6 +529,12 @@ public void testUpdateNode() throws Exception {
.contentType(JSON)
.content(objectMapper.writeValueAsString(node));
mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = post("/node")
+ .param("customTimeForMigration", "false")
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(node));
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
}
@Test
@@ -617,11 +547,11 @@ public void testGetFromPath() throws Exception {
mockMvc.perform(request).andExpect(status().isBadRequest());
Node node = Node.builder().name("name").uniqueId("uniqueId").build();
- when(nodeDAO.getFromPath("/a/b/c")).thenReturn(Arrays.asList(node));
+ when(nodeDAO.getFromPath("/a/b/c")).thenReturn(Collections.singletonList(node));
request = get("/path?path=/a/b/c");
MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andReturn();
- List nodes = objectMapper.readValue(result.getResponse().getContentAsString(), new TypeReference>() {
+ List nodes = objectMapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() {
});
assertEquals(1, nodes.size());
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java
index e5a3948b57..0e6348b93f 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java
@@ -36,9 +36,7 @@
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-import javax.ws.rs.core.MultivaluedMap;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -64,7 +62,7 @@ public class SearchControllerTest {
private ObjectMapper objectMapper;
@Test
- public void testSearch() throws Exception{
+ public void testSearch() throws Exception {
SearchResult searchResult = new SearchResult();
searchResult.setHitCount(1);
searchResult.setNodes(List.of(Node.builder().name("node").build()));
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerPermitAllTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerPermitAllTest.java
new file mode 100644
index 0000000000..a56b005c04
--- /dev/null
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerPermitAllTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2023 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package org.phoebus.service.saveandrestore.web.controllers;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+import org.phoebus.applications.saveandrestore.model.Node;
+import org.phoebus.applications.saveandrestore.model.NodeType;
+import org.phoebus.applications.saveandrestore.model.Snapshot;
+import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
+import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+
+import java.util.List;
+
+import static org.mockito.Mockito.when;
+import static org.phoebus.service.saveandrestore.web.controllers.BaseController.JSON;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = ControllersTestConfig.class)
+@TestPropertySource(locations = "classpath:test_application_permit_all.properties")
+@WebMvcTest(SnapshotController.class)
+public class SnapshotControllerPermitAllTest {
+
+ @Autowired
+ private NodeDAO nodeDAO;
+
+ @Autowired
+ private String userAuthorization;
+
+ @Autowired
+ private String readOnlyAuthorization;
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Autowired
+ private String demoUser;
+
+ @Test
+ public void testSaveNewSnapshot() throws Exception {
+ Node node = Node.builder().uniqueId("uniqueId").nodeType(NodeType.SNAPSHOT).userName(demoUser).build();
+ Snapshot snapshot = new Snapshot();
+ snapshot.setSnapshotNode(node);
+
+ String snapshotString = objectMapper.writeValueAsString(snapshot);
+
+ when(nodeDAO.getNode("uniqueId")).thenReturn(node);
+ when(nodeDAO.createSnapshot(Mockito.any(String.class), Mockito.any(Snapshot.class)))
+ .thenAnswer((Answer) invocation -> snapshot);
+
+ MockHttpServletRequestBuilder request = put("/snapshot?parentNodeId=a")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(snapshotString);
+
+ MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))
+ .andReturn();
+
+ // Make sure response contains expected data
+ objectMapper.readValue(result.getResponse().getContentAsString(), Snapshot.class);
+
+ request = put("/snapshot?parentNodeId=a")
+ .contentType(JSON)
+ .content(snapshotString);
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+
+ request = put("/snapshot?parentNodeId=a")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON)
+ .content(snapshotString);
+ mockMvc.perform(request).andExpect(status().isOk());
+ }
+
+ @Test
+ public void testDeleteSnapshot() throws Exception{
+
+ when(nodeDAO.getNode("a")).thenReturn(Node.builder()
+ .nodeType(NodeType.SNAPSHOT)
+ .uniqueId("a").userName(demoUser).build());
+
+ MockHttpServletRequestBuilder request =
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")))
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization);
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request =
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")))
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization);
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request =
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")));
+
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ public void testUpdateSnapshot() throws Exception {
+ Node node = Node.builder().uniqueId("s").nodeType(NodeType.SNAPSHOT).userName(demoUser).build();
+ Snapshot snapshot = new Snapshot();
+ snapshot.setSnapshotNode(node);
+
+ String snapshotString = objectMapper.writeValueAsString(snapshot);
+
+ when(nodeDAO.getNode("s")).thenReturn(node);
+ when(nodeDAO.updateSnapshot(Mockito.any(Snapshot.class)))
+ .thenAnswer((Answer) invocation -> snapshot);
+
+ MockHttpServletRequestBuilder request = post("/snapshot")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(snapshotString);
+
+ MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))
+ .andReturn();
+
+ // Make sure response contains expected data
+ objectMapper.readValue(result.getResponse().getContentAsString(), Snapshot.class);
+
+ request = put("/snapshot")
+ .contentType(JSON)
+ .content(snapshotString);
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+
+ request = post("/snapshot")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON)
+ .content(snapshotString);
+ mockMvc.perform(request).andExpect(status().isOk());
+ }
+}
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java
index b7a8ac4cde..5424ce6965 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java
@@ -39,8 +39,12 @@
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+import java.util.List;
+
import static org.mockito.Mockito.when;
import static org.phoebus.service.saveandrestore.web.controllers.BaseController.JSON;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -63,7 +67,7 @@ public class SnapshotControllerTest {
@Autowired
private String readOnlyAuthorization;
- private ObjectMapper objectMapper = new ObjectMapper();
+ private final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private MockMvc mockMvc;
@@ -74,8 +78,13 @@ public class SnapshotControllerTest {
@Test
public void testSaveSnapshotWrongNodeType() throws Exception {
+
+ Node node = Node.builder().uniqueId("uniqueId").userName(demoUser).nodeType(NodeType.FOLDER).build();
+
Snapshot snapshot = new Snapshot();
- snapshot.setSnapshotNode(Node.builder().nodeType(NodeType.FOLDER).build());
+ snapshot.setSnapshotNode(node);
+
+ when(nodeDAO.getNode("uniqueId")).thenReturn(node);
MockHttpServletRequestBuilder request = put("/snapshot?parentNodeId=a")
.header(HttpHeaders.AUTHORIZATION, userAuthorization)
@@ -99,14 +108,15 @@ public void testSaveSnapshotNoParentNodeId() throws Exception {
}
@Test
- public void testSaveNewSnapshot() throws Exception {
- Node node = Node.builder().nodeType(NodeType.SNAPSHOT).userName(demoUser).build();
+ public void testCreateSnapshot() throws Exception {
+ Node node = Node.builder().uniqueId("uniqueId").nodeType(NodeType.SNAPSHOT).userName(demoUser).build();
Snapshot snapshot = new Snapshot();
snapshot.setSnapshotNode(node);
String snapshotString = objectMapper.writeValueAsString(snapshot);
- when(nodeDAO.saveSnapshot(Mockito.any(String.class), Mockito.any(Snapshot.class)))
+ when(nodeDAO.getNode("uniqueId")).thenReturn(node);
+ when(nodeDAO.createSnapshot(Mockito.any(String.class), Mockito.any(Snapshot.class)))
.thenAnswer((Answer) invocation -> snapshot);
MockHttpServletRequestBuilder request = put("/snapshot?parentNodeId=a")
@@ -147,10 +157,10 @@ public void testUpdateSnapshot() throws Exception {
String snapshotString = objectMapper.writeValueAsString(snapshot);
when(nodeDAO.getNode("s")).thenReturn(node);
- when(nodeDAO.saveSnapshot(Mockito.any(String.class), Mockito.any(Snapshot.class)))
+ when(nodeDAO.updateSnapshot(Mockito.any(Snapshot.class)))
.thenAnswer((Answer) invocation -> snapshot);
- MockHttpServletRequestBuilder request = put("/snapshot?parentNodeId=a")
+ MockHttpServletRequestBuilder request = post("/snapshot")
.header(HttpHeaders.AUTHORIZATION, userAuthorization)
.contentType(JSON)
.content(snapshotString);
@@ -161,21 +171,60 @@ public void testUpdateSnapshot() throws Exception {
// Make sure response contains expected data
objectMapper.readValue(result.getResponse().getContentAsString(), Snapshot.class);
- request = put("/snapshot?parentNodeId=a")
+ request = put("/snapshot")
.contentType(JSON)
.content(snapshotString);
mockMvc.perform(request).andExpect(status().isUnauthorized());
- request = put("/snapshot?parentNodeId=a")
+ request = post("/snapshot")
.header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
.contentType(JSON)
.content(snapshotString);
mockMvc.perform(request).andExpect(status().isForbidden());
- request = put("/snapshot?parentNodeId=a")
+ request = post("/snapshot")
.header(HttpHeaders.AUTHORIZATION, adminAuthorization)
.contentType(JSON)
.content(snapshotString);
mockMvc.perform(request).andExpect(status().isOk());
}
+
+ @Test
+ public void testDeleteSnapshot() throws Exception{
+
+ when(nodeDAO.getNode("a")).thenReturn(Node.builder()
+ .nodeType(NodeType.SNAPSHOT)
+ .uniqueId("a").userName(demoUser).build());
+
+ MockHttpServletRequestBuilder request =
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")))
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization);
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request =
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")))
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization);
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ when(nodeDAO.getNode("a")).thenReturn(Node.builder()
+ .nodeType(NodeType.SNAPSHOT)
+ .uniqueId("a").userName("otherUser").build());
+
+ request =
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")))
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization);
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request =
+ delete("/node")
+ .contentType(JSON).content(objectMapper.writeValueAsString(List.of("a")));
+
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+ }
}
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/StructureControllerPermitAllTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/StructureControllerPermitAllTest.java
new file mode 100644
index 0000000000..c82b44e5ae
--- /dev/null
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/StructureControllerPermitAllTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package org.phoebus.service.saveandrestore.web.controllers;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.phoebus.applications.saveandrestore.model.Node;
+import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
+import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+
+import java.util.List;
+
+import static org.mockito.Mockito.when;
+import static org.phoebus.service.saveandrestore.web.controllers.BaseController.JSON;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = ControllersTestConfig.class)
+@TestPropertySource(locations = "classpath:test_application_permit_all.properties")
+@WebMvcTest(StructureController.class)
+public class StructureControllerPermitAllTest {
+
+ @Autowired
+ private NodeDAO nodeDAO;
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Autowired
+ private String demoUser;
+
+ @Autowired
+ private String demoAdmin;
+
+ @Autowired
+ private String userAuthorization;
+
+ @Autowired
+ private String adminAuthorization;
+
+ @Autowired
+ private String readOnlyAuthorization;
+
+
+ @Test
+ public void testMoveNode() throws Exception {
+ when(nodeDAO.moveNodes(List.of("a"), "b", demoAdmin))
+ .thenReturn(Node.builder().uniqueId("2").uniqueId("a").userName(demoAdmin).build());
+
+ MockHttpServletRequestBuilder request = post("/move")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("a")))
+ .param("to", "b")
+ .param("username", "username");
+
+ MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))
+ .andReturn();
+
+ // Make sure response contains expected data
+ objectMapper.readValue(result.getResponse().getContentAsString(), Node.class);
+
+ when(nodeDAO.moveNodes(List.of("a"), "b", demoUser))
+ .thenReturn(Node.builder().uniqueId("2").uniqueId("a").userName(demoUser).build());
+
+ request = post("/move")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("a")))
+ .param("to", "b")
+ .param("username", "username");
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+
+ request = post("/move")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("a")))
+ .param("to", "b")
+ .param("username", "username");
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = post("/move")
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of
+ ("a")))
+ .param("to", "b")
+ .param("username", "username");
+
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ public void testCopyNodes() throws Exception {
+ MockHttpServletRequestBuilder request = post("/copy")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("a")))
+ .param("to", "target");
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = post("/copy")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("a")))
+ .param("to", "target");
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = post("/copy")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("a")))
+ .param("to", "target");
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = post("/copy")
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("a")))
+ .param("to", "target");
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+ }
+}
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/StructureControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/StructureControllerTest.java
new file mode 100644
index 0000000000..df736f8acc
--- /dev/null
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/StructureControllerTest.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2023 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package org.phoebus.service.saveandrestore.web.controllers;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.phoebus.applications.saveandrestore.model.Node;
+import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
+import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.mockito.Mockito.when;
+import static org.phoebus.service.saveandrestore.web.controllers.BaseController.JSON;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = ControllersTestConfig.class)
+@TestPropertySource(locations = "classpath:test_application.properties")
+@WebMvcTest(StructureController.class)
+public class StructureControllerTest {
+
+ @Autowired
+ private NodeDAO nodeDAO;
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Autowired
+ private String demoUser;
+
+ @Autowired
+ private String demoAdmin;
+
+ @Autowired
+ private String userAuthorization;
+
+ @Autowired
+ private String adminAuthorization;
+
+ @Autowired
+ private String readOnlyAuthorization;
+
+
+ @Test
+ public void testMoveNode() throws Exception {
+ when(nodeDAO.moveNodes(List.of("a"), "b", demoAdmin))
+ .thenReturn(Node.builder().uniqueId("2").uniqueId("a").userName(demoAdmin).build());
+
+ MockHttpServletRequestBuilder request = post("/move")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("a")))
+ .param("to", "b")
+ .param("username", "username");
+
+ MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))
+ .andReturn();
+
+ // Make sure response contains expected data
+ objectMapper.readValue(result.getResponse().getContentAsString(), Node.class);
+
+ when(nodeDAO.moveNodes(List.of("a"), "b", demoUser))
+ .thenReturn(Node.builder().uniqueId("2").uniqueId("a").userName(demoUser).build());
+
+ request = post("/move")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("a")))
+ .param("to", "b")
+ .param("username", "username");
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+
+ request = post("/move")
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("a")))
+ .param("to", "b")
+ .param("username", "username");
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = post("/move")
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("a")))
+ .param("to", "b")
+ .param("username", "username");
+
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ public void testMoveNodeSourceNodeListEmpty() throws Exception {
+ MockHttpServletRequestBuilder request = post("/move")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(Collections.emptyList()))
+ .param("to", "targetId")
+ .param("username", "user");
+
+ mockMvc.perform(request).andExpect(status().isBadRequest());
+ }
+
+ @Test
+ public void testMoveNodeTargetIdEmpty() throws Exception {
+ MockHttpServletRequestBuilder request = post("/move")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("a")))
+ .param("to", "")
+ .param("username", "user");
+
+ mockMvc.perform(request).andExpect(status().isBadRequest());
+ }
+
+ @Test
+ public void testCopyNodes() throws Exception {
+ MockHttpServletRequestBuilder request = post("/copy")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("a")))
+ .param("to", "target");
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = post("/copy")
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("a")))
+ .param("to", "target");
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = post("/copy")
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("a")))
+ .param("to", "target");
+ mockMvc.perform(request).andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ public void testCopyNodesBadRequest() throws Exception {
+ Node node = Node.builder().uniqueId("uniqueId").userName(demoUser).build();
+ MockHttpServletRequestBuilder request = post("/copy")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("a")))
+ .param("to", "");
+ mockMvc.perform(request).andExpect(status().isBadRequest());
+
+ request = post("/copy")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(List.of("a")));
+ mockMvc.perform(request).andExpect(status().isBadRequest());
+
+ request = post("/copy")
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .contentType(JSON)
+ .content(objectMapper.writeValueAsString(Collections.emptyList()))
+ .param("to", "target");
+ mockMvc.perform(request).andExpect(status().isBadRequest());
+ }
+}
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerPermitAllTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerPermitAllTest.java
new file mode 100644
index 0000000000..bc11cfc137
--- /dev/null
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerPermitAllTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2020 European Spallation Source ERIC.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+package org.phoebus.service.saveandrestore.web.controllers;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.phoebus.applications.saveandrestore.model.Node;
+import org.phoebus.applications.saveandrestore.model.Tag;
+import org.phoebus.applications.saveandrestore.model.TagData;
+import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO;
+import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+
+import java.util.List;
+
+import static org.mockito.Mockito.when;
+import static org.phoebus.service.saveandrestore.web.controllers.BaseController.JSON;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = ControllersTestConfig.class)
+@WebMvcTest(TagController.class)
+@TestPropertySource(locations = "classpath:test_application_permit_all.properties")
+public class TagControllerPermitAllTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Autowired
+ private NodeDAO nodeDAO;
+
+ @Autowired
+ private String userAuthorization;
+
+ @Autowired
+ private String readOnlyAuthorization;
+
+ @Autowired
+ private String demoUser;
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Test
+ public void testAddTag() throws Exception{
+ Tag tag = new Tag();
+ tag.setName("tag");
+
+ Node node = Node.builder().name("name").uniqueId("uniqueId").userName(demoUser).tags(List.of(tag)).build();
+
+ TagData tagData = new TagData();
+ tagData.setTag(tag);
+ tagData.setUniqueNodeIds(List.of("uniqueId"));
+
+ when(nodeDAO.getNode("uniqueId")).thenReturn(node);
+ when(nodeDAO.addTag(tagData)).thenReturn(List.of(node));
+
+ MockHttpServletRequestBuilder request = post("/tags").contentType(JSON)
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .content(objectMapper.writeValueAsString(tagData));
+ MvcResult result = mockMvc.perform(request)
+ .andExpect(status().isOk()).andExpect(content().contentType(JSON))
+ .andReturn();
+
+ String s = result.getResponse().getContentAsString();
+ // Make sure response contains expected data
+ objectMapper.readValue(s, List.class);
+
+ request = post("/tags").contentType(JSON)
+ .content(objectMapper.writeValueAsString(tagData));
+ mockMvc.perform(request)
+ .andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ public void testGoldenTag() throws Exception{
+ Tag tag = new Tag();
+ tag.setName(Tag.GOLDEN);
+ tag.setUserName(demoUser);
+
+ TagData tagData = new TagData();
+ tagData.setTag(tag);
+ tagData.setUniqueNodeIds(List.of("uniqueId"));
+
+ MockHttpServletRequestBuilder request = post("/tags").contentType(JSON)
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .content(objectMapper.writeValueAsString(tagData));
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = post("/tags").contentType(JSON)
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .content(objectMapper.writeValueAsString(tagData));
+
+ mockMvc.perform(request).andExpect(status().isOk());
+ }
+
+ @Test
+ public void testDeleteTag() throws Exception{
+ Tag tag = new Tag();
+ tag.setName("tag");
+ tag.setUserName(demoUser);
+
+ TagData tagData = new TagData();
+ tagData.setTag(tag);
+ tagData.setUniqueNodeIds(List.of("uniqueId"));
+
+ Node node = Node.builder().name("name").uniqueId("uniqueId").userName("otherUser").tags(List.of(tag)).build();
+
+ when(nodeDAO.getNode("uniqueId")).thenReturn(node);
+
+ MockHttpServletRequestBuilder request = delete("/tags").contentType(JSON)
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .content(objectMapper.writeValueAsString(tagData));
+
+ mockMvc.perform(request)
+ .andExpect(status().isOk());
+
+ request = delete("/tags").contentType(JSON)
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .content(objectMapper.writeValueAsString(tagData));
+
+ mockMvc.perform(request)
+ .andExpect(status().isOk());
+
+ request = delete("/tags").contentType(JSON)
+ .content(objectMapper.writeValueAsString(tagData));
+
+ mockMvc.perform(request)
+ .andExpect(status().isUnauthorized());
+
+ }
+}
diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java
index b7bf3a6b1e..b7f1afb71c 100644
--- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java
+++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java
@@ -22,7 +22,6 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mockito;
import org.phoebus.applications.saveandrestore.model.Node;
import org.phoebus.applications.saveandrestore.model.Tag;
import org.phoebus.applications.saveandrestore.model.TagData;
@@ -40,11 +39,9 @@
import java.util.List;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.when;
import static org.phoebus.service.saveandrestore.web.controllers.BaseController.JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
-
-import static org.springframework.test.web.servlet.MockMvc.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -63,13 +60,19 @@ public class TagControllerTest {
@Autowired
private String userAuthorization;
+ @Autowired
+ private String adminAuthorization;
+
+ @Autowired
+ private String readOnlyAuthorization;
+
@Autowired
private String demoUser;
- private ObjectMapper objectMapper = new ObjectMapper();
+ private final ObjectMapper objectMapper = new ObjectMapper();
@Test
- public void testGetAllTags() throws Exception{
+ public void testGetAllTags() throws Exception {
Tag tag = new Tag();
tag.setName("tag");
List tags = List.of(tag);
@@ -85,7 +88,7 @@ public void testGetAllTags() throws Exception{
}
@Test
- public void testAddTag() throws Exception{
+ public void testAddTag() throws Exception {
Tag tag = new Tag();
tag.setName("tag");
@@ -111,7 +114,37 @@ public void testAddTag() throws Exception{
}
@Test
- public void testAddTagBadData() throws Exception{
+ public void testGoldenTag() throws Exception {
+ Tag tag = new Tag();
+ tag.setName(Tag.GOLDEN);
+ tag.setUserName(demoUser);
+
+ TagData tagData = new TagData();
+ tagData.setTag(tag);
+ tagData.setUniqueNodeIds(List.of("uniqueId"));
+
+ MockHttpServletRequestBuilder request = post("/tags").contentType(JSON)
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .content(objectMapper.writeValueAsString(tagData));
+
+ mockMvc.perform(request).andExpect(status().isOk());
+
+ request = post("/tags").contentType(JSON)
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .content(objectMapper.writeValueAsString(tagData));
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ request = post("/tags").contentType(JSON)
+ .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization)
+ .content(objectMapper.writeValueAsString(tagData));
+
+ mockMvc.perform(request).andExpect(status().isForbidden());
+
+ }
+
+ @Test
+ public void testAddTagBadData() throws Exception {
TagData tagData = new TagData();
tagData.setUniqueNodeIds(List.of("uniqueId"));
@@ -153,7 +186,33 @@ public void testAddTagBadData() throws Exception{
}
@Test
- public void testDeleteTag() throws Exception{
+ public void testDeleteTag() throws Exception {
+ Tag tag = new Tag();
+ tag.setName("tag");
+ tag.setUserName(demoUser);
+
+ TagData tagData = new TagData();
+ tagData.setTag(tag);
+ tagData.setUniqueNodeIds(List.of("uniqueId"));
+
+ Node node = Node.builder().name("name").uniqueId("uniqueId").userName("otherUser").tags(List.of(tag)).build();
+
+ when(nodeDAO.getNode("uniqueId")).thenReturn(node);
+
+ MockHttpServletRequestBuilder request = delete("/tags").contentType(JSON)
+ .header(HttpHeaders.AUTHORIZATION, userAuthorization)
+ .content(objectMapper.writeValueAsString(tagData));
+
+ mockMvc.perform(request)
+ .andExpect(status().isForbidden());
+
+ request = delete("/tags").contentType(JSON)
+ .header(HttpHeaders.AUTHORIZATION, adminAuthorization)
+ .content(objectMapper.writeValueAsString(tagData));
+
+ mockMvc.perform(request)
+ .andExpect(status().isOk()
+ );
}
}
diff --git a/services/save-and-restore/src/test/resources/test_application.properties b/services/save-and-restore/src/test/resources/test_application.properties
index dc4073e83b..cd97c12504 100644
--- a/services/save-and-restore/src/test/resources/test_application.properties
+++ b/services/save-and-restore/src/test/resources/test_application.properties
@@ -1,23 +1,2 @@
-logging.level.org.springframework=INFO
-app.version=@project.version@
-app.name=@project.name@
-
-server.servlet.contextPath=/save-restore
-
-# Elasticsearch connection parameters
-elasticsearch.network.host=localhost
-elasticsearch.http.port=9200
-
-# Do not change this!
-spring.jackson.serialization.write-dates-as-timestamps=false
-
-# The names of the index to use for save&restore
-elasticsearch.tree_node.index=test_saveandrestore_tree
-elasticsearch.configuration_node.index=test_saveandrestore_configuration
-elasticsearch.snapshot_node.index:test_saveandrestore_snapshot
-elasticsearch.composite_snapshot_node.index=test_saveandrestore_composite_snapshot
-elasticsearch.filter.index:test_saveandrestore_filter
-
auth.impl = demo
-
-authorization.bypass=false
+authorization.permitall=false
diff --git a/services/save-and-restore/src/test/resources/test_application_permit_all.properties b/services/save-and-restore/src/test/resources/test_application_permit_all.properties
new file mode 100644
index 0000000000..7e2f457406
--- /dev/null
+++ b/services/save-and-restore/src/test/resources/test_application_permit_all.properties
@@ -0,0 +1,2 @@
+auth.impl = demo
+authorization.permitall=true
From b3178f1ba62831eb9ccbad61bcf3ad747dcadffd Mon Sep 17 00:00:00 2001
From: Kunal Shroff
Date: Tue, 14 Nov 2023 12:20:55 -0500
Subject: [PATCH 41/42] Including active directory Authentication manager
---
.../web/config/WebSecurityConfig.java | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
index 1b90f4b65e..936c1f0893 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
@@ -22,6 +22,7 @@
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
+import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.PersonContextMapper;
@@ -199,6 +200,22 @@ public LdapAuthoritiesPopulator authorities(BaseLdapPathContextSource contextSou
return myAuthPopulator;
}
+ @Bean
+ @ConditionalOnProperty(name = "auth.impl", havingValue = "ad")
+ public AuthenticationManager authenticationProvider() throws Exception {
+ ActiveDirectoryLdapAuthenticationProvider adProvider =
+ new ActiveDirectoryLdapAuthenticationProvider(ad_domain, ad_url);
+ adProvider.setConvertSubErrorCodesToExceptions(true);
+ adProvider.setUseAuthenticationRequestCredentials(true);
+ adProvider.setUserDetailsContextMapper(new PersonContextMapper());
+ return new AuthenticationManagerBuilder(new ObjectPostProcessor<>() {
+ @Override
+ public O postProcess(O object) {
+ return object;
+ }
+ }).authenticationProvider(adProvider).build();
+ }
+
@Bean
@ConditionalOnProperty(name = "auth.impl", havingValue = "demo")
public AuthenticationManager demoAuthenticationManager(AuthenticationManagerBuilder auth) throws Exception {
From 44ebd82d880428839f41ad8f50281bc03ef55002 Mon Sep 17 00:00:00 2001
From: Kunal Shroff
Date: Tue, 14 Nov 2023 12:27:25 -0500
Subject: [PATCH 42/42] Add authority mapper to convert to ROLE_XXX
---
.../service/saveandrestore/web/config/WebSecurityConfig.java | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
index 936c1f0893..090101b991 100644
--- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
+++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java
@@ -19,6 +19,7 @@
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.ldap.LdapBindAuthenticationManagerFactory;
+import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
@@ -208,6 +209,9 @@ public AuthenticationManager authenticationProvider() throws Exception {
adProvider.setConvertSubErrorCodesToExceptions(true);
adProvider.setUseAuthenticationRequestCredentials(true);
adProvider.setUserDetailsContextMapper(new PersonContextMapper());
+ SimpleAuthorityMapper simpleAuthorityMapper = new SimpleAuthorityMapper();
+ simpleAuthorityMapper.setConvertToUpperCase(true);
+ adProvider.setAuthoritiesMapper(simpleAuthorityMapper);
return new AuthenticationManagerBuilder(new ObjectPostProcessor<>() {
@Override
public O postProcess(O object) {