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