From 40435224ffdda2c9abf92d094dfd5f25909daf79 Mon Sep 17 00:00:00 2001 From: georgweiss <georg.weiss@ess.eu> Date: Wed, 8 Nov 2023 15:17:49 +0100 Subject: [PATCH] Making save&restore authorization/authentication optional --- .../CredentialsManagementApp.java | 3 +- .../saveandrestore/Preferences.java | 3 + .../SaveAndRestoreAuthenticationProvider.java | 6 + .../client/SaveAndRestoreJerseyClient.java | 6 +- .../saveandrestore/ui/ContextMenuBase.java | 2 +- .../save_and_restore_preferences.properties | 5 +- .../ServiceAuthenticationProvider.java | 13 +- services/save-and-restore/pom.xml | 1 - .../web/config/AuthEnabledCondition.java | 41 +++ .../web/config/SessionFilter.java | 102 -------- .../web/config/WebSecurityConfig.java | 233 ++++++++---------- .../controllers/AuthenticationController.java | 8 +- .../web/controllers/AuthorizationHelper.java | 110 +++++++++ .../CompositeSnapshotController.java | 20 +- .../web/controllers/NodeController.java | 74 +----- .../src/main/resources/application.properties | 49 ++-- .../src/main/resources/sar.ldif | 19 -- .../elasticsearch/ElasticsearchDAOTest.java | 2 + .../web/config/WebConfigTest.java | 2 + .../AppMetaDataControllerTest.java | 2 + .../CompositeSnapshotControllerTest.java | 35 +-- .../web/controllers/FilterControllerTest.java | 24 +- .../web/controllers/HelpResourceTest.java | 2 + .../web/controllers/NodeControllerTest.java | 49 +--- .../web/controllers/SearchControllerTest.java | 4 +- .../controllers/SnapshotControllerTest.java | 21 +- .../web/controllers/TagControllerTest.java | 4 +- .../resources/test_application.properties | 1 + 28 files changed, 351 insertions(+), 490 deletions(-) create mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AuthEnabledCondition.java delete mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/SessionFilter.java create mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java diff --git a/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementApp.java b/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementApp.java index 55700d2d86..7a322f016a 100644 --- a/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementApp.java +++ b/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementApp.java @@ -60,7 +60,8 @@ public String getDisplayName() { @Override public AppInstance create() { List<ServiceAuthenticationProvider> authenticationProviders = - ServiceLoader.load(ServiceAuthenticationProvider.class).stream().map(Provider::get).collect(Collectors.toList()); + ServiceLoader.load(ServiceAuthenticationProvider.class).stream().map(Provider::get) + .filter(ServiceAuthenticationProvider::isActive).collect(Collectors.toList()); try { SecureStore secureStore = new SecureStore(); new CredentialsManagementStage(authenticationProviders, secureStore).show(); diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Preferences.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Preferences.java index 2453b12734..fb026ac839 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Preferences.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Preferences.java @@ -31,6 +31,9 @@ public class Preferences { @Preference public static String default_snapshot_name_date_format; + @Preference + public static boolean authentication_enabled; + static { AnnotatedPreferences.initialize(Preferences.class, "/save_and_restore_preferences.properties"); diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java index 98c35263a7..fd18b5eae6 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java @@ -19,6 +19,7 @@ package org.phoebus.applications.saveandrestore.authentication; +import org.phoebus.applications.saveandrestore.Preferences; import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService; import org.phoebus.security.authorization.ServiceAuthenticationProvider; import org.phoebus.security.tokens.AuthenticationScope; @@ -52,4 +53,9 @@ public void logout(String token) { public AuthenticationScope getAuthenticationScope(){ return AuthenticationScope.SAVE_AND_RESTORE; } + + @Override + public boolean isActive(){ + return Preferences.authentication_enabled; + } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java index 0e3313f42a..3dc85f2ca1 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java @@ -102,8 +102,10 @@ private Client getClient() { String password = scopedAuthenticationToken.getPassword(); httpBasicAuthFilter = new HTTPBasicAuthFilter(username, password); client.addFilter(httpBasicAuthFilter); - } else if (httpBasicAuthFilter != null) { - client.removeFilter(httpBasicAuthFilter); + } else {//if (httpBasicAuthFilter != null) { + //client.removeFilter(httpBasicAuthFilter); + httpBasicAuthFilter = new HTTPBasicAuthFilter(System.getProperty("user.name"), "password"); + client.addFilter(httpBasicAuthFilter); } } catch (Exception e) { logger.log(Level.WARNING, "Unable to retrieve credentials from secure store", e); diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java index e904215ee2..579dabf34e 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java @@ -76,7 +76,7 @@ public ContextMenuBase(SaveAndRestoreController saveAndRestoreController) { deleteNodesMenuItem = new MenuItem(Messages.contextMenuDelete, new ImageView(ImageRepository.DELETE)); deleteNodesMenuItem.setOnAction(ae -> saveAndRestoreController.deleteNodes()); deleteNodesMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> - userIsAuthenticatedProperty.not().get() || + //userIsAuthenticatedProperty.not().get() || hasSameParentProperty.not().get(), userIsAuthenticatedProperty, hasSameParentProperty)); diff --git a/app/save-and-restore/app/src/main/resources/save_and_restore_preferences.properties b/app/save-and-restore/app/src/main/resources/save_and_restore_preferences.properties index 1f933c89aa..1545280157 100644 --- a/app/save-and-restore/app/src/main/resources/save_and_restore_preferences.properties +++ b/app/save-and-restore/app/src/main/resources/save_and_restore_preferences.properties @@ -24,4 +24,7 @@ jmasar.service.url=http://localhost:8080/save-restore httpClient.readTimeout=1000 # Connect timeout in (ms) used by the Jersey client -httpClient.connectTimeout=1000 \ No newline at end of file +httpClient.connectTimeout=1000 + +# Authentication/authorization enabled/disabled +authentication_enabled=false \ No newline at end of file diff --git a/core/security/src/main/java/org/phoebus/security/authorization/ServiceAuthenticationProvider.java b/core/security/src/main/java/org/phoebus/security/authorization/ServiceAuthenticationProvider.java index 0362400c8b..691fb1581f 100644 --- a/core/security/src/main/java/org/phoebus/security/authorization/ServiceAuthenticationProvider.java +++ b/core/security/src/main/java/org/phoebus/security/authorization/ServiceAuthenticationProvider.java @@ -35,7 +35,7 @@ public interface ServiceAuthenticationProvider { /** * Signs out user from the service. - * @param token User name or other type of token (e.g. session cookie). + * @param token Username or other type of token (e.g. session cookie). */ void logout(String token); @@ -46,9 +46,18 @@ public interface ServiceAuthenticationProvider { * {@link org.phoebus.security.store.SecureStore}. Such keys are stored in * <b>lower</b> case in the key store that backs {@link org.phoebus.security.store.SecureStore}, and * is a behavior defined by the encryption scheme implementation. - * Consequently an identity like "UPPER" will be persisted as "upper", i.e. case insensitivity + * Consequently, an identity like "UPPER" will be persisted as "upper", i.e. case insensitivity * must be considered when defining an identity. * @return Service name */ AuthenticationScope getAuthenticationScope(); + + /** + * Indicates if a provider is active. Inactive providers suggest authentication is disabled or should + * not be accessible in the credentials management UI. + * @return <code>true</code> if the authentication provider is active, otherwise <code>false</code>. + */ + default boolean isActive(){ + return true; + } } diff --git a/services/save-and-restore/pom.xml b/services/save-and-restore/pom.xml index 3c898a3407..17ea04ef41 100644 --- a/services/save-and-restore/pom.xml +++ b/services/save-and-restore/pom.xml @@ -105,7 +105,6 @@ <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> - <version>${spring.boot.version}</version> <exclusions> <exclusion> <groupId>org.apache.logging.log4j</groupId> diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AuthEnabledCondition.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AuthEnabledCondition.java new file mode 100644 index 0000000000..8063e2f9d9 --- /dev/null +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AuthEnabledCondition.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 European Spallation Source ERIC. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +package org.phoebus.service.saveandrestore.web.config; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * {@link Condition} subclass used to determine if authentication/authorization is enabled through an + * application property setting. + */ +public class AuthEnabledCondition implements Condition { + /** + * @param context the condition context + * @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class} + * or {@link org.springframework.core.type.MethodMetadata method} being checked + * @return <code>true</code> if application property <code>auth.impl</code> is anything other than <code>none</code>. + */ + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return !"none".equalsIgnoreCase(context.getEnvironment().getProperty("auth.impl").trim()); + } +} diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/SessionFilter.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/SessionFilter.java deleted file mode 100644 index 2db87594c2..0000000000 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/SessionFilter.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2020 European Spallation Source ERIC. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -package org.phoebus.service.saveandrestore.web.config; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.annotation.web.builders.WebSecurity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.filter.GenericFilterBean; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Filter that will take care of authenticating requests that come with - * a basic authentication header. - * <p> - * This class should not be instantiated as a bean in the application configuration. If it is, the - * <code>doFilter()</code> method will be called for each endpoint URI, effectively defeating the purpose of the - * configuration of ignored URI patterns set up in the Spring Security context, see - * {@link WebSecurityConfig#configure(WebSecurity)}. - */ -public class SessionFilter extends GenericFilterBean { - - private AuthenticationManager authenticationManager; - private ObjectMapper objectMapper; - - public SessionFilter(AuthenticationManager authenticationManager) { - this.authenticationManager = authenticationManager; - objectMapper = new ObjectMapper(); - } - - /** - * @param request A {@link ServletRequest} - * @param response A {@link ServletResponse} - * @param filterChain The {@link FilterChain} to which this implementation contributes. - * @throws IOException May be thrown by upstream filters. - * @throws ServletException May be thrown by upstream filters. - */ - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { - HttpServletRequest httpServletRequest = (HttpServletRequest) request; - - String basicAuthenticationHeader = httpServletRequest.getHeader("Authorization"); - String[] usernameAndPassword = getUsernameAndPassword(basicAuthenticationHeader); - if (usernameAndPassword == null) { - SecurityContextHolder.getContext().setAuthentication(null); - } else { - Authentication authentication = new UsernamePasswordAuthenticationToken(usernameAndPassword[0], - usernameAndPassword[1]); - try { - authentication = authenticationManager.authenticate(authentication); - SecurityContextHolder.getContext().setAuthentication(authentication); - } catch (AuthenticationException e) { - Logger.getLogger(SessionFilter.class.getName()) - .log(Level.FINE, String.format("User %s not authenticated through authorization header", - usernameAndPassword[0])); - } - } - - filterChain.doFilter(request, response); - } - - protected String[] getUsernameAndPassword(String authorization) { - if (authorization != null && authorization.toLowerCase().startsWith("basic")) { - // Authorization: Basic base64credentials - String base64Credentials = authorization.substring("Basic".length()).trim(); - byte[] credDecoded = Base64.getDecoder().decode(base64Credentials); - String credentials = new String(credDecoded, StandardCharsets.UTF_8); - // credentials = username:password - return credentials.split(":", 2); - } - return null; - } -} diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java index 1e6490662e..532302e9c9 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java @@ -3,48 +3,38 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Scope; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.*; +import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.http.HttpMethod; +import org.springframework.ldap.core.support.BaseLdapPathContextSource; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.config.ldap.LdapBindAuthenticationManagerFactory; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.ldap.DefaultSpringSecurityContextSource; -import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider; import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator; +import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator; +import org.springframework.security.ldap.userdetails.PersonContextMapper; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.util.matcher.RequestMatcher; -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -@EnableWebSecurity @Configuration - -/* +@EnableWebSecurity @EnableGlobalMethodSecurity( prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) - - */ - @SuppressWarnings("unused") -public class WebSecurityConfig extends WebSecurityConfigurerAdapter { +public class WebSecurityConfig { /** * External Active Directory configuration properties @@ -70,29 +60,15 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { String ldap_manager_dn; @Value("${ldap.manager.password}") String ldap_manager_password; - @Value("${ldap.user.search.base}") + @Value("${ldap.user.search.base:invalid}") String ldap_user_search_base; - @Value("${ldap.user.search.filter}") + @Value("${ldap.user.search.filter:invalid}") String ldap_user_search_filter; - /** - * Embedded LDAP configuration properties - */ - @Value("${embedded_ldap.urls:ldaps://localhost:389/}") - String embedded_ldap_url; - @Value("${embedded_ldap.base.dn}") - String embedded_ldap_base_dn; - @Value("${embedded_ldap.user.dn.pattern}") - String embedded_ldap_user_dn_pattern; - @Value("${embedded_ldap.groups.search.base}") - String embedded_ldap_groups_search_base; - @Value("${embedded_ldap.groups.search.pattern}") - String embedded_ldap_groups_search_pattern; - /** * Authentication implementation. */ - @Value("${auth.impl:demo}") + @Value("${auth.impl:none}") String authenitcationImplementation; @Value("${role.user:sar-user}") @@ -128,105 +104,93 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Value("${demo.readOnly.password:1234}") private String demoReadOnlyPassword; + @Bean + public WebSecurityCustomizer ignoringCustomizer() { + return web -> { + // The below lists exceptions for authentication. + web.ignoring().antMatchers(HttpMethod.GET, "/**"); + web.ignoring().antMatchers(HttpMethod.POST, "/**/login*"); + }; + } - @Override - protected void configure(HttpSecurity http) throws Exception { + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable(); - http.authorizeRequests().antMatchers(HttpMethod.DELETE, "/node/**").hasRole(roleUser.toUpperCase()); - http.authorizeRequests().antMatchers(HttpMethod.POST, "/node/**").hasRole(roleUser.toUpperCase()); - http.authorizeRequests().antMatchers(HttpMethod.PUT, "/node/**").hasRole(roleUser.toUpperCase()); - http.authorizeRequests().antMatchers(HttpMethod.POST, "/snapshot/**").authenticated(); - http.authorizeRequests().anyRequest().authenticated(); - http.addFilterBefore(new SessionFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class); + if ("none".equalsIgnoreCase(authenitcationImplementation.trim())) { + http.anonymous(); + } else { + http.authorizeRequests().anyRequest().authenticated(); + http.httpBasic(); + } + return http.build(); } + @Bean + @Conditional(LdapAuthCondition.class) + public DefaultSpringSecurityContextSource contextSourceFactoryBeanLdap() { + DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(ldap_url); + if (ldap_manager_dn != null && !ldap_manager_dn.isEmpty() && ldap_manager_password != null && !ldap_manager_password.isEmpty()) { + contextSource.setUserDn(ldap_manager_dn); + contextSource.setPassword(ldap_manager_password); + } + contextSource.setBase(ldap_base_dn); + return contextSource; + } - - @Override - public void configure(WebSecurity web) { - // The below lists exceptions for authentication. - web.ignoring().antMatchers(HttpMethod.GET, "/**"); - web.ignoring().antMatchers(HttpMethod.POST, "/**/login*"); + @Bean + @Conditional(LdapAuthCondition.class) + public AuthenticationManager ldapAuthenticationManager( + BaseLdapPathContextSource contextSource) { + LdapBindAuthenticationManagerFactory factory = + new LdapBindAuthenticationManagerFactory(contextSource); + factory.setUserDnPatterns(ldap_user_dn_pattern); + factory.setUserDetailsContextMapper(new PersonContextMapper()); + + factory.setLdapAuthoritiesPopulator(authorities(contextSource)); + return factory.createAuthenticationManager(); } - @Override - public void configure(AuthenticationManagerBuilder auth) throws Exception { - switch (authenitcationImplementation) { - case "ad": - ActiveDirectoryLdapAuthenticationProvider adProvider = new ActiveDirectoryLdapAuthenticationProvider(ad_domain, ad_url); - adProvider.setConvertSubErrorCodesToExceptions(true); - adProvider.setUseAuthenticationRequestCredentials(true); - auth.authenticationProvider(adProvider); - break; - case "ldap": - DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(ldap_url); - if (ldap_manager_dn != null && !ldap_manager_dn.isEmpty() && ldap_manager_password != null && !ldap_manager_password.isEmpty()) { - contextSource.setUserDn(ldap_manager_dn); - contextSource.setPassword(ldap_manager_password); - } - contextSource.setBase(ldap_base_dn); - contextSource.afterPropertiesSet(); - - DefaultLdapAuthoritiesPopulator myAuthPopulator = new DefaultLdapAuthoritiesPopulator(contextSource, ldap_groups_search_base); - myAuthPopulator.setGroupSearchFilter(ldap_groups_search_pattern); - myAuthPopulator.setSearchSubtree(true); - myAuthPopulator.setIgnorePartialResultException(true); - - LdapAuthenticationProviderConfigurer configurer = auth.ldapAuthentication() - .ldapAuthoritiesPopulator(myAuthPopulator); - if (ldap_user_dn_pattern != null && !ldap_user_dn_pattern.isEmpty()) { - configurer.userDnPatterns(ldap_user_dn_pattern); - } - if (ldap_user_search_filter != null && !ldap_user_search_filter.isEmpty()) { - configurer.userSearchFilter(ldap_user_search_filter); - } - if (ldap_user_search_base != null && !ldap_user_search_base.isEmpty()) { - configurer.userSearchBase(ldap_user_search_base); - } - configurer.contextSource(contextSource); - break; - case "ldap_embedded": - contextSource = new DefaultSpringSecurityContextSource(embedded_ldap_url); - contextSource.afterPropertiesSet(); - - myAuthPopulator - = new DefaultLdapAuthoritiesPopulator(contextSource, embedded_ldap_groups_search_base); - myAuthPopulator.setGroupSearchFilter(embedded_ldap_groups_search_pattern); - myAuthPopulator.setSearchSubtree(true); - myAuthPopulator.setIgnorePartialResultException(true); - - auth.ldapAuthentication() - .userDnPatterns(embedded_ldap_user_dn_pattern) - .ldapAuthoritiesPopulator(myAuthPopulator) - .groupSearchBase("ou=Group") - .contextSource(contextSource); - break; - case "demo": - auth.inMemoryAuthentication() - .withUser(demoAdmin).password(encoder().encode(demoAdminPassword)).roles(roleAdmin()).and() - .withUser(demoUser).password(encoder().encode(demoUserPassword)).roles(roleUser()).and() - .withUser(demoSuperuser).password(encoder().encode(demoSuperuserPassword)).roles(roleSuperuser()).and() - .withUser(demoReadOnly).password(encoder().encode(demoReadOnlyPassword)).roles(); - break; - default: - Logger.getLogger(WebSecurityConfig.class.getName()) - .log(Level.SEVERE, "Authentication Implementation \"" + authenitcationImplementation + "\" not supported"); - throw new IllegalArgumentException("Authentication Implementation \"" + authenitcationImplementation + "\" not supported"); + @Bean + @Conditional(LdapAuthCondition.class) + public LdapAuthoritiesPopulator authorities(BaseLdapPathContextSource contextSource) { + DefaultLdapAuthoritiesPopulator myAuthPopulator = new DefaultLdapAuthoritiesPopulator(contextSource, ldap_groups_search_base); + myAuthPopulator.setGroupSearchFilter(ldap_groups_search_pattern); + myAuthPopulator.setSearchSubtree(true); + myAuthPopulator.setIgnorePartialResultException(true); + LdapAuthenticationProviderConfigurer configurer = new LdapAuthenticationProviderConfigurer(); + if (ldap_user_dn_pattern != null && !ldap_user_dn_pattern.isEmpty()) { + configurer.userDnPatterns(ldap_user_dn_pattern); + } + if (ldap_user_search_filter != null && !ldap_user_search_filter.isEmpty()) { + configurer.userSearchFilter(ldap_user_search_filter); } + if (ldap_user_search_base != null && !ldap_user_search_base.isEmpty()) { + configurer.userSearchBase(ldap_user_search_base); + } + configurer.contextSource(contextSource); + return myAuthPopulator; } @Bean - public PasswordEncoder encoder() { - return new BCryptPasswordEncoder(); + @ConditionalOnProperty(name = "auth.impl", havingValue = "demo") + public AuthenticationManager demoAuthenticationManager(AuthenticationManagerBuilder auth) throws Exception { + return new AuthenticationManagerBuilder(new ObjectPostProcessor<>() { + @Override + public <O> O postProcess(O object) { + return object; + } + }).inMemoryAuthentication() + .passwordEncoder(encoder()) + .withUser(demoAdmin).password(encoder().encode(demoAdminPassword)).roles(roleAdmin()).and() + .withUser(demoUser).password(encoder().encode(demoUserPassword)).roles(roleUser()).and() + .withUser(demoSuperuser).password(encoder().encode(demoSuperuserPassword)).roles(roleSuperuser()).and() + .withUser(demoReadOnly).password(encoder().encode(demoReadOnlyPassword)).roles().and().and().build(); } @Bean - public AuthenticationManager authenticationManager() { - try { - return super.authenticationManager(); - } catch (Exception e) { - return null; - } + @Scope("singleton") + public PasswordEncoder encoder() { + return new BCryptPasswordEncoder(); } @SuppressWarnings("unused") @@ -305,9 +269,26 @@ public String demoReadOnlyPassword() { @Bean public RoleHierarchy roleHierarchy() { RoleHierarchyImpl hierarchy = new RoleHierarchyImpl(); - hierarchy.setHierarchy("ROLE_" + roleAdmin.toUpperCase() + " > ROLE_" + roleSuperuser.toUpperCase() + "\n" + - "ROLE_" + roleSuperuser.toUpperCase() + " > ROLE_" + roleUser.toUpperCase() + "\n" + - "ROLE_" + roleAdmin.toUpperCase() + " > ROLE_" + roleUser.toUpperCase()); + hierarchy.setHierarchy("ROLE_" + roleAdmin.toUpperCase() + " > ROLE_" + roleUser.toUpperCase()); return hierarchy; } + + /** + * {@link Condition} subclass used to select ldap and ldap_embedded + * authentication/authorization provider. + */ + private static class LdapAuthCondition implements Condition { + /** + * @param context the condition context + * @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class} + * or {@link org.springframework.core.type.MethodMetadata method} being checked + * @return <code>true</code> if application property <code>auth.impl</code> is <code>ldap</code> + * or <code>ldap_embedded</code>, otherwise <code>false</code>. + */ + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + String testValue = context.getEnvironment().getProperty("auth.impl"); + return "ldap".equals(testValue) || "ldap_embedded".equals(testValue); + } + } } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java index f7c60da540..2b68764e48 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java @@ -20,7 +20,10 @@ package org.phoebus.service.saveandrestore.web.controllers; import org.phoebus.applications.saveandrestore.model.UserData; +import org.phoebus.service.saveandrestore.web.config.AuthEnabledCondition; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Conditional; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; @@ -37,9 +40,9 @@ @SuppressWarnings("unused") @RestController +@Conditional(AuthEnabledCondition.class) public class AuthenticationController extends BaseController { - @Autowired private AuthenticationManager authenticationManager; @@ -54,7 +57,8 @@ public class AuthenticationController extends BaseController { @PostMapping(value = "login") public ResponseEntity<UserData> login(@RequestParam(value = "username") String userName, @RequestParam(value = "password") String password) { - Authentication authentication = new UsernamePasswordAuthenticationToken(userName, password); + Authentication authentication = + new UsernamePasswordAuthenticationToken(userName, password); try { authentication = authenticationManager.authenticate(authentication); } catch (AuthenticationException e) { diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java new file mode 100644 index 0000000000..76829c1ff2 --- /dev/null +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2023 European Spallation Source ERIC. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +package org.phoebus.service.saveandrestore.web.controllers; + +import org.phoebus.applications.saveandrestore.model.CompositeSnapshot; +import org.phoebus.applications.saveandrestore.model.Node; +import org.phoebus.applications.saveandrestore.model.NodeType; +import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.security.Principal; +import java.util.List; + +/** + * {@link Service} class implementing domain specific authorization rules in order to + * grant or deny access to certain REST endpoints. + */ +@Service("authorizationHelper") +public class AuthorizationHelper { + + @Autowired + private NodeDAO nodeDAO; + + /** + * Checks if all the nodes provided to this method can be deleted by the user. + * + * @param nodeIds The list of {@link Node} ids subject to the check. + * @param principal {@link Principal} of authenticated user. + * @return <code>true</code> only if <b>all</b> if the nodes can be deleted by the user. + */ + @SuppressWarnings("unused") + public boolean mayDelete(List<String> nodeIds, Principal principal) { + for (String nodeId : nodeIds) { + if (!mayDelete(nodeId, principal)) { + return false; + } + } + return true; + } + + /** + * An authenticated user may delete a node if User identity is same as the target {@link Node}'s user id and: + * <ul> + * <li>Target {@link Node} is a snapshot.</li> + * <li>Target {@link Node} is not a snapshot, but has no child nodes.</li> + * </ul> + * + * @param nodeId Unique node id identifying the target of the user's delete operation. + * @param principal Identifies user. + * @return <code>false</code> if user may not delete the {@link Node}. + */ + @SuppressWarnings("unused") + public boolean mayDelete(String nodeId, Principal principal) { + Node node = nodeDAO.getNode(nodeId); + if (!node.getUserName().equals(principal.getName())) { + return false; + } + if (node.getNodeType().equals(NodeType.CONFIGURATION) || node.getNodeType().equals(NodeType.FOLDER)) { + return nodeDAO.getChildNodes(node.getUniqueId()).isEmpty(); + } + return true; + } + + /** + * An authenticated user may update a node if user identity is same as the target {@link Node}'s user id. + * + * @param node {@link Node} identifying the target of the user's update operation. + * @param principal Identifies user. + * @return <code>false</code> if user may not update the {@link Node}. + */ + @SuppressWarnings("unused") + public boolean mayUpdate(Node node, Principal principal) { + return nodeDAO.getNode(node.getUniqueId()).getUserName().equals(principal.getName()); + } + + /** + * <p> + * An authenticated user may save a composite snapshot, and update if user identity is same as the target's + * composite snapshot {@link Node}. + * </p> + * + * @param compositeSnapshot {@link CompositeSnapshot} identifying the target of the user's update operation. + * @param principal Identifies user. + * @return <code>false</code> if user may not update the {@link CompositeSnapshot}. + */ + @SuppressWarnings("unused") + public boolean mayUpdate(CompositeSnapshot compositeSnapshot, Principal principal) { + Node node = nodeDAO.getNode(compositeSnapshot.getCompositeSnapshotNode().getUniqueId()); + return node.getUserName().equals(principal.getName()); + } + +} diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java index 9a4cbb6594..278dc1aea8 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java @@ -54,7 +54,7 @@ public CompositeSnapshot createCompositeSnapshot(@RequestParam(value = "parentNo } @PostMapping(value = "/composite-snapshot", produces = JSON) - @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and this.mayUpdate(#compositeSnapshot, #principal))") + @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and @authorizationHelper.mayUpdate(#compositeSnapshot, #principal))") public CompositeSnapshot updateCompositeSnapshot(@RequestBody CompositeSnapshot compositeSnapshot, Principal principal) { if(!compositeSnapshot.getCompositeSnapshotNode().getNodeType().equals(NodeType.COMPOSITE_SNAPSHOT)){ @@ -64,24 +64,6 @@ public CompositeSnapshot updateCompositeSnapshot(@RequestBody CompositeSnapshot return nodeDAO.updateCompositeSnapshot(compositeSnapshot); } - /** - * NOTE: this method MUST be public! - * - * <p> - * An authenticated user may save a composite snapshot, and update if user identity is same as the target's - * composite snapshot {@link Node}. - * </p> - * - * @param compositeSnapshot {@link CompositeSnapshot} identifying the target of the user's update operation. - * @param principal Identifies user. - * @return <code>false</code> if user may not update the {@link CompositeSnapshot}. - */ - public boolean mayUpdate(CompositeSnapshot compositeSnapshot, Principal principal){ - Node node = nodeDAO.getNode(compositeSnapshot.getCompositeSnapshotNode().getUniqueId()); - return node.getUserName().equals(principal.getName()); - } - - @GetMapping(value = "/composite-snapshot/{uniqueId}", produces = JSON) public CompositeSnapshotData getCompositeSnapshotData(@PathVariable String uniqueId) { return nodeDAO.getCompositeSnapshotData(uniqueId); diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java index 271da6b4ad..fd936b067a 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java @@ -20,12 +20,12 @@ import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.applications.saveandrestore.model.NodeType; import org.phoebus.applications.saveandrestore.model.Tag; -import org.phoebus.applications.saveandrestore.model.security.UserNotAuthorizedException; import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; import java.security.Principal; @@ -134,84 +134,20 @@ public List<Node> getChildNodes(@PathVariable final String uniqueNodeId) { */ @SuppressWarnings("unused") @DeleteMapping(value = "/node/{uniqueNodeId}", produces = JSON) - //@PreAuthorize("hasRole(this.roleAdmin) or this.mayDelete(#uniqueNodeId, #principal)") + @PreAuthorize("hasRole(this.roleAdmin) or @authorizationHelper.mayDelete(#uniqueNodeId, #principal)") @Deprecated public void deleteNode(@PathVariable final String uniqueNodeId, Principal principal) { - if(!principal.getName().equals(demoAdmin) && !mayDelete(uniqueNodeId, principal)){ - throw new UserNotAuthorizedException("User not authorized to delete node"); - } logger.info("Deleting node with unique id " + uniqueNodeId); nodeDAO.deleteNode(uniqueNodeId); } @SuppressWarnings("unused") @DeleteMapping(value = "/node", produces = JSON) - //@PreAuthorize("hasRole(this.roleAdmin) or this.mayDelete(#nodeIds, #principal)") + @PreAuthorize("hasRole(this.roleAdmin) or @authorizationHelper.mayDelete(#nodeIds, #principal)") public void deleteNodes(@RequestBody List<String> nodeIds, Principal principal) { - if(!principal.getName().equals(demoAdmin) && !mayDelete(nodeIds, principal)){ - throw new UserNotAuthorizedException("User not authorized to delete nodes"); - } nodeDAO.deleteNodes(nodeIds); } - /** - * NOTE: this method MUST be public! - * - * Checks if all the nodes provided to this method can be deleted by the user. - * @param nodeIds The list of {@link Node} ids subject to the check. - * @param principal {@link Principal} of authenticated user. - * @return <code>true</code> only if <b>all</b> if the nodes can be deleted by the user. - */ - @SuppressWarnings("unused") - public boolean mayDelete(List<String> nodeIds, Principal principal){ - for (String nodeId : nodeIds){ - if(!mayDelete(nodeId, principal)){ - return false; - } - } - return true; - } - - /** - * NOTE: this method MUST be public! - * An authenticated user may delete a node if User identity is same as the target {@link Node}'s user id and: - * <ul> - * <li>Target {@link Node} is a snapshot.</li> - * <li>Target {@link Node} is not a snapshot, but has no child nodes.</li> - * </ul> - * - * @param nodeId Unique node id identifying the target of the user's delete operation. - * @param principal Identifies user. - * @return <code>false</code> if user may not delete the {@link Node}. - */ - @SuppressWarnings("unused") - public boolean mayDelete(String nodeId, Principal principal){ - Node node = nodeDAO.getNode(nodeId); - if(!node.getUserName().equals(principal.getName())){ - return false; - } - if(node.getNodeType().equals(NodeType.CONFIGURATION) || node.getNodeType().equals(NodeType.FOLDER)){ - return nodeDAO.getChildNodes(node.getUniqueId()).isEmpty(); - } - return true; - } - - /** - * NOTE: this method MUST be public! - * - * <p> - * An authenticated user may update a node if user identity is same as the target {@link Node}'s user id. - * </p> - * - * @param node {@link Node} identifying the target of the user's update operation. - * @param principal Identifies user. - * @return <code>false</code> if user may not update the {@link Node}. - */ - @SuppressWarnings("unused") - public boolean mayUpdate(Node node, Principal principal){ - return nodeDAO.getNode(node.getUniqueId()).getUserName().equals(principal.getName()); - } - /** * Updates a {@link Node}. The purpose is to support modification of name or comment/description, or both. Modification of * node type is not supported. @@ -228,7 +164,7 @@ public boolean mayUpdate(Node node, Principal principal){ */ @SuppressWarnings("unused") @PostMapping(value = "/node", produces = JSON) - @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and this.mayUpdate(#nodeToUpdate, #principal))") + @PreAuthorize("hasRole(this.roleAdmin) or (hasRole(this.roleUser) and @authorizationHelper.mayUpdate(#nodeToUpdate, #principal))") public Node updateNode(@RequestParam(value = "customTimeForMigration", required = false, defaultValue = "false") String customTimeForMigration, @RequestBody Node nodeToUpdate, Principal principal) { diff --git a/services/save-and-restore/src/main/resources/application.properties b/services/save-and-restore/src/main/resources/application.properties index 11e8e732c1..de3d8b7c4f 100644 --- a/services/save-and-restore/src/main/resources/application.properties +++ b/services/save-and-restore/src/main/resources/application.properties @@ -1,4 +1,4 @@ -logging.level.org.springframework=INFO +logging.level.org.springframework=DEBUG app.version=@project.version@ app.name=@project.artifactId@ @@ -35,45 +35,34 @@ ad.url = ldap://127.0.0.1 ad.domain = test.com ############## LDAP - External ############## -#ldap.urls = ldaps://controlns02.nsls2.bnl.gov/dc=nsls2,dc=bnl,dc=gov -#ldap.base.dn = dc=nsls2,dc=bnl,dc=gov +# If uncommenting in this section, make sure +# to comment out in LDAP - Embedded section +############################################# +#ldap.urls=ldaps://ldap-cslab.cslab.esss.lu.se +#ldap.base.dn = dc=esss,dc=lu,dc=se #ldap.user.search.base= # User search pattern, e.g. uid={0},ou=People. No default value as LDAP environment may not # support user search by pattern. -#ldap.user.dn.pattern= +#ldap.user.dn.pattern=uid={0},ou=Users # User search filter, e.g. (&(objectClass=person)(SAMAccountName={0})). No default value as LDAP environment # may not support user search by filter. #ldap.user.search.filter= -#ldap.groups.search.base = ou=Group +#ldap.groups.search.base = ou=Groups #ldap.groups.search.pattern = (memberUid= {1}) # dn of manager account, may be required for group search -#ldap.manager.dn= -# password of account -#ldap.manager.password= - -ldap.urls=ldaps://ldap-cslab.cslab.esss.lu.se -ldap.base.dn = dc=esss,dc=lu,dc=se -ldap.user.search.base= -# User search pattern, e.g. uid={0},ou=People. No default value as LDAP environment may not -# support user search by pattern. -ldap.user.dn.pattern=uid={0},ou=Users -# User search filter, e.g. (&(objectClass=person)(SAMAccountName={0})). No default value as LDAP environment -# may not support user search by filter. -ldap.user.search.filter= -ldap.groups.search.base = ou=Groups -ldap.groups.search.pattern = (memberUid= {1}) -# dn of manager account, may be required for group search ldap.manager.dn= # password of account ldap.manager.password= ############## LDAP - Embedded ############## -#embedded_ldap.enabled = false -embedded_ldap.urls = ldap://localhost:8389/dc=sar,dc=local -embedded_ldap.base.dn = dc=sar,dc=local -embedded_ldap.user.dn.pattern = uid={0},ou=Group -embedded_ldap.groups.search.base = ou=Group -embedded_ldap.groups.search.pattern = (memberUid= {1}) +# If uncommenting in this section, make sure +# to comment out in LDAP - External section +############################################# +ldap.urls=ldap://localhost:8389/dc=sar,dc=local +ldap.base.dn = dc=sar,dc=local +ldap.user.dn.pattern = uid={0},ou=Group +ldap.groups.search.base = ou=Group +ldap.groups.search.pattern = (memberUid= {1}) spring.ldap.embedded.ldif=classpath:sar.ldif spring.ldap.embedded.base-dn=dc=sar,dc=local spring.ldap.embedded.port=8389 @@ -82,8 +71,6 @@ spring.ldap.embedded.validation.enabled=false ############## Demo credentials ############## demo.user=user demo.user.password=userPass -demo.superuser=superuser -demo.superuser.password=superuserPass demo.admin=admin demo.admin.password=adminPass demo.readOnly=johndoe @@ -95,11 +82,11 @@ demo.readOnly.password=1234 # ldap - Probably Open LDAP # ldap_embedded - Embedded LDAP. Config in sar.ldif # demo - Hard coded, see WebSecurityConfig class -auth.impl = demo +# none - No authentication and authorization +auth.impl = none ############## Authorization Roles ################ role.user=sar-user -role.superuser=sar-superuser role.admin=sar-admin diff --git a/services/save-and-restore/src/main/resources/sar.ldif b/services/save-and-restore/src/main/resources/sar.ldif index 77730d1ed4..91133bfb6a 100644 --- a/services/save-and-restore/src/main/resources/sar.ldif +++ b/services/save-and-restore/src/main/resources/sar.ldif @@ -23,14 +23,6 @@ gidNumber: 27001 uidNumber: 27001 memberUid: user -dn: cn=sar-superuser,ou=Group,dc=sar,dc=local -cn: sar-superuser -objectClass: posixGroup -description: save-n-restore superuser -gidNumber: 27002 -uidNumber: 27002 -memberUid: superuser - dn: cn=sar-admin,ou=Group,dc=sar,dc=local cn: sar-admin objectClass: posixGroup @@ -50,17 +42,6 @@ uidNumber: 23004 gidNumber: 23004 homeDirectory: /dev/null -dn: uid=superuser,ou=Group,dc=sar,dc=local -uid: superuser -objectClass: account -objectClass: posixAccount -description: User with sar-user role -cn: superuser -userPassword: superuserPass -uidNumber: 23004 -gidNumber: 23004 -homeDirectory: /dev/null - dn: uid=johndoe,ou=Group,dc=sar,dc=local uid: johndoe objectClass: account diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAOTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAOTest.java index 21a6a7d8af..ae2e5f1f16 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAOTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAOTest.java @@ -36,6 +36,7 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; @@ -56,6 +57,7 @@ @EnableConfigurationProperties @ContextHierarchy({@ContextConfiguration(classes = {ElasticTestConfig.class})}) @TestExecutionListeners({DependencyInjectionTestExecutionListener.class}) +@TestPropertySource(locations = "classpath:test_application.properties") @Profile("IT") public class ElasticsearchDAOTest { diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/WebConfigTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/WebConfigTest.java index cefd95c9e9..da1005650f 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/WebConfigTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/WebConfigTest.java @@ -25,10 +25,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextHierarchy; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; @ExtendWith(SpringExtension.class) @ContextHierarchy({@ContextConfiguration(classes = {WebConfiguration.class, ControllersTestConfig.class})}) +@TestPropertySource(locations = "classpath:test_application.properties") @SuppressWarnings("unused") public class WebConfigTest { diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/AppMetaDataControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/AppMetaDataControllerTest.java index 522be2e636..d49f0912cf 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/AppMetaDataControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/AppMetaDataControllerTest.java @@ -26,6 +26,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextHierarchy; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -42,6 +43,7 @@ @ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class})}) @WebMvcTest(AppMetaDataControllerTest.class) @ExtendWith(SpringExtension.class) +@TestPropertySource(locations = "classpath:test_application.properties") @SuppressWarnings("unused") public class AppMetaDataControllerTest { diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java index 1d68817832..c5b66238df 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java @@ -36,6 +36,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.HttpHeaders; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -54,15 +55,13 @@ @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = ControllersTestConfig.class) -@WebMvcTest(NodeController.class) +@WebMvcTest(CompositeSnapshotController.class) +@TestPropertySource(locations = "classpath:test_application.properties") public class CompositeSnapshotControllerTest { @Autowired private String userAuthorization; - @Autowired - private String superuserAuthorization; - @Autowired private String adminAuthorization; @@ -111,13 +110,6 @@ public void testCreateCompositeSnapshot() throws Exception { // Make sure response contains expected data objectMapper.readValue(s, CompositeSnapshot.class); - request = put("/composite-snapshot?parentNodeId=id") - .header(HttpHeaders.AUTHORIZATION, superuserAuthorization) - .contentType(JSON) - .content(compositeSnapshotString); - - mockMvc.perform(request).andExpect(status().isOk()); - request = put("/composite-snapshot?parentNodeId=id") .header(HttpHeaders.AUTHORIZATION, adminAuthorization) .contentType(JSON) @@ -136,7 +128,7 @@ public void testCreateCompositeSnapshot() throws Exception { .contentType(JSON) .content(compositeSnapshotString); - mockMvc.perform(request).andExpect(status().isForbidden()); + mockMvc.perform(request).andExpect(status().isUnauthorized()); reset(nodeDAO); } @@ -187,13 +179,6 @@ public void testUpdateCompositeSnapshot() throws Exception { mockMvc.perform(request).andExpect(status().isForbidden()); - request = post("/composite-snapshot") - .header(HttpHeaders.AUTHORIZATION, superuserAuthorization) - .contentType(JSON) - .content(compositeSnapshotString); - - mockMvc.perform(request).andExpect(status().isForbidden()); - request = post("/composite-snapshot") .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization) .contentType(JSON) @@ -205,7 +190,7 @@ public void testUpdateCompositeSnapshot() throws Exception { .contentType(JSON) .content(compositeSnapshotString); - mockMvc.perform(request).andExpect(status().isForbidden()); + mockMvc.perform(request).andExpect(status().isUnauthorized()); when(nodeDAO.getNode("c")).thenReturn(Node.builder().nodeType(NodeType.COMPOSITE_SNAPSHOT).uniqueId("c").userName("notUser").build()); @@ -316,8 +301,7 @@ public void testGetCompositeSnapshotConsistency() throws Exception { request = post("/composite-snapshot-consistency-check") .contentType(JSON) .content(objectMapper.writeValueAsString(List.of("id"))); - - mockMvc.perform(request).andExpect(status().isForbidden()); + mockMvc.perform(request).andExpect(status().isUnauthorized()); request = post("/composite-snapshot-consistency-check") @@ -327,13 +311,6 @@ public void testGetCompositeSnapshotConsistency() throws Exception { mockMvc.perform(request).andExpect(status().isOk()); - request = post("/composite-snapshot-consistency-check") - .header(HttpHeaders.AUTHORIZATION, superuserAuthorization) - .contentType(JSON) - .content(objectMapper.writeValueAsString(List.of("id"))); - - mockMvc.perform(request).andExpect(status().isOk()); - request = post("/composite-snapshot-consistency-check") .header(HttpHeaders.AUTHORIZATION, adminAuthorization) .contentType(JSON) diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java index e0019d4fe9..dccca5a33e 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java @@ -30,6 +30,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.HttpHeaders; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -47,8 +48,8 @@ @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = ControllersTestConfig.class) -@WebMvcTest(NodeController.class) - +@WebMvcTest(FilterController.class) +@TestPropertySource(locations = "classpath:test_application.properties") public class FilterControllerTest { @Autowired @@ -63,9 +64,6 @@ public class FilterControllerTest { @Autowired private String userAuthorization; - @Autowired - private String superuserAuthorization; - @Autowired private String adminAuthorization; @@ -101,13 +99,6 @@ public void testSaveFilter() throws Exception { // Make sure response contains expected data objectMapper.readValue(s, Filter.class); - request = put("/filter") - .header(HttpHeaders.AUTHORIZATION, superuserAuthorization) - .contentType(JSON) - .content(filterString); - - mockMvc.perform(request).andExpect(status().isOk()); - request = put("/filter") .header(HttpHeaders.AUTHORIZATION, adminAuthorization) .contentType(JSON) @@ -126,7 +117,7 @@ public void testSaveFilter() throws Exception { .contentType(JSON) .content(filterString); - mockMvc.perform(request).andExpect(status().isForbidden()); + mockMvc.perform(request).andExpect(status().isUnauthorized()); } @Test @@ -148,11 +139,6 @@ public void testDeleteFilter() throws Exception { .contentType(JSON); mockMvc.perform(request).andExpect(status().isOk()); - request = delete("/filter/name") - .header(HttpHeaders.AUTHORIZATION, superuserAuthorization) - .contentType(JSON); - mockMvc.perform(request).andExpect(status().isForbidden()); - request = delete("/filter/name") .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization) .contentType(JSON); @@ -160,7 +146,7 @@ public void testDeleteFilter() throws Exception { request = delete("/filter/name") .contentType(JSON); - mockMvc.perform(request).andExpect(status().isForbidden()); + mockMvc.perform(request).andExpect(status().isUnauthorized()); filter.setUser("notUser"); when(nodeDAO.getAllFilters()).thenReturn(List.of(filter)); diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/HelpResourceTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/HelpResourceTest.java index f6df59f368..3843053690 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/HelpResourceTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/HelpResourceTest.java @@ -25,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -35,6 +36,7 @@ @ExtendWith(SpringExtension.class) @WebMvcTest(HelpResource.class) @ContextConfiguration(classes = ControllersTestConfig.class) +@TestPropertySource(locations = "classpath:test_application.properties") public class HelpResourceTest{ @Autowired diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java index b37367f46b..4b433a4fa5 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java @@ -89,18 +89,12 @@ public class NodeControllerTest { @Autowired private String demoUser; - @Autowired - private String demoSuperuser; - @Autowired private String demoAdmin; @Autowired private String userAuthorization; - @Autowired - private String superuserAuthorization; - @Autowired private String adminAuthorization; @@ -145,12 +139,6 @@ public void testCreateFolder() throws Exception { .content(content); mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON)); - request = put("/node?parentNodeId=a") - .header(HttpHeaders.AUTHORIZATION, superuserAuthorization) - .contentType(JSON) - .content(content); - mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON)); - request = put("/node?parentNodeId=a") .contentType(JSON) .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization) @@ -253,13 +241,6 @@ public void testUpdateConfig() throws Exception{ when(nodeDAO.getNode("hhh")).thenReturn(Node.builder().nodeType(NodeType.CONFIGURATION).userName("notUser").build()); - request = post("/config") - .header(HttpHeaders.AUTHORIZATION, superuserAuthorization) - .contentType(JSON) - .content(confurationAsString); - - mockMvc.perform(request).andExpect(status().isForbidden()); - request = post("/config") .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization) .contentType(JSON) @@ -350,7 +331,7 @@ public void testDeleteFolder() throws Exception { MockHttpServletRequestBuilder request = delete("/node/a"); - mockMvc.perform(request).andExpect(status().isForbidden()); + mockMvc.perform(request).andExpect(status().isUnauthorized()); when(nodeDAO.getNode("a")).thenReturn(Node.builder().uniqueId("a").userName(demoUser).build()); @@ -415,22 +396,6 @@ public void testDeleteFolder() throws Exception { when(nodeDAO.getNode("a")).thenReturn(Node.builder().uniqueId("a").nodeType(NodeType.CONFIGURATION).userName(demoUser).build()); when(nodeDAO.getChildNodes("a")).thenReturn(List.of(Node.builder().build())); - request = - delete("/node/a") - .header(HttpHeaders.AUTHORIZATION, superuserAuthorization); - mockMvc.perform(request).andExpect(status().isForbidden()); - - when(nodeDAO.getNode("a")).thenReturn(Node.builder().uniqueId("a").nodeType(NodeType.FOLDER).userName(demoUser).build()); - when(nodeDAO.getChildNodes("a")).thenReturn(List.of(Node.builder().build())); - - request = - delete("/node/a") - .header(HttpHeaders.AUTHORIZATION, superuserAuthorization); - mockMvc.perform(request).andExpect(status().isForbidden()); - - when(nodeDAO.getNode("a")).thenReturn(Node.builder().uniqueId("a").nodeType(NodeType.CONFIGURATION).userName(demoUser).build()); - when(nodeDAO.getChildNodes("a")).thenReturn(List.of(Node.builder().build())); - request = delete("/node/a") .header(HttpHeaders.AUTHORIZATION, adminAuthorization); @@ -535,14 +500,6 @@ public void testMoveNode() throws Exception { mockMvc.perform(request).andExpect(status().isForbidden()); - request = post("/move") - .header(HttpHeaders.AUTHORIZATION, superuserAuthorization) - .contentType(JSON) - .content(objectMapper.writeValueAsString(Arrays.asList("a"))) - .param("to", "b") - .param("username", "username"); - - mockMvc.perform(request).andExpect(status().isForbidden()); request = post("/move") .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization) @@ -559,7 +516,7 @@ public void testMoveNode() throws Exception { .param("to", "b") .param("username", "username"); - mockMvc.perform(request).andExpect(status().isForbidden()); + mockMvc.perform(request).andExpect(status().isUnauthorized()); } @Test @@ -642,7 +599,7 @@ public void testUpdateNode() throws Exception { .param("customTimeForMigration", "false") .contentType(JSON) .content(objectMapper.writeValueAsString(node)); - mockMvc.perform(request).andExpect(status().isForbidden()); + mockMvc.perform(request).andExpect(status().isUnauthorized()); request = post("/node") .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization) diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java index a65d0ea781..e5a3948b57 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java @@ -30,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -49,7 +50,8 @@ @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = ControllersTestConfig.class) -@WebMvcTest(NodeController.class) +@WebMvcTest(SearchController.class) +@TestPropertySource(locations = "classpath:test_application.properties") public class SearchControllerTest { @Autowired diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java index 2eb8b0e582..b7a8ac4cde 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java @@ -48,7 +48,7 @@ @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = ControllersTestConfig.class) @TestPropertySource(locations = "classpath:test_application.properties") -@WebMvcTest(NodeController.class) +@WebMvcTest(SnapshotController.class) public class SnapshotControllerTest { @Autowired @@ -57,9 +57,6 @@ public class SnapshotControllerTest { @Autowired private String userAuthorization; - @Autowired - private String superuserAuthorization; - @Autowired private String adminAuthorization; @@ -126,7 +123,7 @@ public void testSaveNewSnapshot() throws Exception { request = put("/snapshot?parentNodeId=a") .contentType(JSON) .content(snapshotString); - mockMvc.perform(request).andExpect(status().isForbidden()); + mockMvc.perform(request).andExpect(status().isUnauthorized()); request = put("/snapshot?parentNodeId=a") .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization) @@ -134,12 +131,6 @@ public void testSaveNewSnapshot() throws Exception { .content(snapshotString); mockMvc.perform(request).andExpect(status().isForbidden()); - request = put("/snapshot?parentNodeId=a") - .header(HttpHeaders.AUTHORIZATION, superuserAuthorization) - .contentType(JSON) - .content(snapshotString); - mockMvc.perform(request).andExpect(status().isOk()); - request = put("/snapshot?parentNodeId=a") .header(HttpHeaders.AUTHORIZATION, adminAuthorization) .contentType(JSON) @@ -173,7 +164,7 @@ public void testUpdateSnapshot() throws Exception { request = put("/snapshot?parentNodeId=a") .contentType(JSON) .content(snapshotString); - mockMvc.perform(request).andExpect(status().isForbidden()); + mockMvc.perform(request).andExpect(status().isUnauthorized()); request = put("/snapshot?parentNodeId=a") .header(HttpHeaders.AUTHORIZATION, readOnlyAuthorization) @@ -181,12 +172,6 @@ public void testUpdateSnapshot() throws Exception { .content(snapshotString); mockMvc.perform(request).andExpect(status().isForbidden()); - request = put("/snapshot?parentNodeId=a") - .header(HttpHeaders.AUTHORIZATION, superuserAuthorization) - .contentType(JSON) - .content(snapshotString); - mockMvc.perform(request).andExpect(status().isForbidden()); - request = put("/snapshot?parentNodeId=a") .header(HttpHeaders.AUTHORIZATION, adminAuthorization) .contentType(JSON) diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java index 46d55cddfa..d4606db8a0 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java @@ -32,6 +32,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.HttpHeaders; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -49,7 +50,8 @@ @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = ControllersTestConfig.class) -@WebMvcTest(NodeController.class) +@WebMvcTest(TagController.class) +@TestPropertySource(locations = "classpath:test_application.properties") public class TagControllerTest { @Autowired diff --git a/services/save-and-restore/src/test/resources/test_application.properties b/services/save-and-restore/src/test/resources/test_application.properties index 547d620710..97e817e74d 100644 --- a/services/save-and-restore/src/test/resources/test_application.properties +++ b/services/save-and-restore/src/test/resources/test_application.properties @@ -18,3 +18,4 @@ elasticsearch.snapshot_node.index:test_saveandrestore_snapshot elasticsearch.composite_snapshot_node.index=test_saveandrestore_composite_snapshot elasticsearch.filter.index:test_saveandrestore_filter +auth.impl = demo