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.json jakarta.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-pv 4.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 @@ - + +
      - +
      - +
    + * * @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 @@ -