From e7fd624b1180ddf33a9db7390f8e4423dc507a1b Mon Sep 17 00:00:00 2001
From: sudhakaropsmx <sudhakar@opsmx.io>
Date: Fri, 22 Mar 2024 19:33:38 +0530
Subject: [PATCH] OP-21911: Bugfix sapor gate basic authentication

---
 .../gate/security/basic/BasicAuthConfig.java  | 50 ++++++++++++-------
 .../security/basic/BasicAuthProvider.java     | 26 +++++-----
 .../gate/controllers/RootController.groovy    | 15 ++++--
 .../spinnaker/gate/cache/OesCacheManager.java | 20 --------
 .../gate/cache/OesPlatformCacheManager.java   | 48 ++++++++++++++++++
 .../gate/service/AdminAuthService.java        |  6 +--
 6 files changed, 110 insertions(+), 55 deletions(-)
 create mode 100644 gate-web/src/main/java/com/opsmx/spinnaker/gate/cache/OesPlatformCacheManager.java

diff --git a/gate-basic/src/main/java/com/netflix/spinnaker/gate/security/basic/BasicAuthConfig.java b/gate-basic/src/main/java/com/netflix/spinnaker/gate/security/basic/BasicAuthConfig.java
index ba98fc123f..45428ad5a2 100644
--- a/gate-basic/src/main/java/com/netflix/spinnaker/gate/security/basic/BasicAuthConfig.java
+++ b/gate-basic/src/main/java/com/netflix/spinnaker/gate/security/basic/BasicAuthConfig.java
@@ -18,8 +18,6 @@
 
 import com.netflix.spinnaker.gate.config.AuthConfig;
 import com.netflix.spinnaker.gate.security.SpinnakerAuthConfig;
-import com.netflix.spinnaker.gate.services.OesAuthorizationService;
-import com.netflix.spinnaker.gate.services.PermissionService;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import lombok.extern.slf4j.Slf4j;
@@ -28,10 +26,14 @@
 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationServiceException;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
 import org.springframework.session.web.http.DefaultCookieSerializer;
@@ -43,9 +45,8 @@
 @Slf4j
 public class BasicAuthConfig {
 
-  private final AuthConfig authConfig;
-
-  private final BasicAuthProvider authProvider;
+  @Autowired private final AuthConfig authConfig;
+  @Autowired private final BasicAuthProvider authProvider;
 
   @Autowired DefaultCookieSerializer defaultCookieSerializer;
 
@@ -58,19 +59,16 @@ public class BasicAuthConfig {
   @Value("${security.user.password:}")
   String password;
 
-  @Autowired PermissionService permissionService;
-
   @Autowired
-  public BasicAuthConfig(
-      AuthConfig authConfig,
-      PermissionService permissionService,
-      OesAuthorizationService oesAuthorizationService) {
+  public BasicAuthConfig(AuthConfig authConfig, BasicAuthProvider authProvider) {
     this.authConfig = authConfig;
-    this.authProvider = new BasicAuthProvider(permissionService, oesAuthorizationService);
+    this.authProvider = authProvider;
   }
 
-  @Autowired
-  public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
+  @Bean
+  public AuthenticationManager authManager(HttpSecurity http) throws Exception {
+    AuthenticationManagerBuilder authenticationManagerBuilder =
+        http.getSharedObject(AuthenticationManagerBuilder.class);
     if (name == null || name.isEmpty() || password == null || password.isEmpty()) {
       throw new AuthenticationServiceException(
           "User credentials are not configured properly. Please check username and password are properly configured");
@@ -86,13 +84,15 @@ public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
 
     authProvider.setName(this.name);
     authProvider.setPassword(this.password);
-
-    auth.authenticationProvider(authProvider);
+    authenticationManagerBuilder.authenticationProvider(authProvider);
+    authenticationManagerBuilder.eraseCredentials(false);
+    return authenticationManagerBuilder.build();
   }
 
   @Bean
-  public SecurityFilterChain configure(HttpSecurity http) throws Exception {
+  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
     defaultCookieSerializer.setSameSite(null);
+    http.csrf().disable();
     http.formLogin()
         .and()
         .httpBasic()
@@ -100,4 +100,20 @@ public SecurityFilterChain configure(HttpSecurity http) throws Exception {
     authConfig.configure(http);
     return http.build();
   }
+
+  @Bean
+  public WebSecurityCustomizer webSecurityCustomizer() {
+    return (web) -> {
+      try {
+        authConfig.configure(web);
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    };
+  }
+
+  @Bean
+  public PasswordEncoder passwordEncoder() {
+    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
+  }
 }
diff --git a/gate-basic/src/main/java/com/netflix/spinnaker/gate/security/basic/BasicAuthProvider.java b/gate-basic/src/main/java/com/netflix/spinnaker/gate/security/basic/BasicAuthProvider.java
index 5c609ca47f..296b3dae4a 100644
--- a/gate-basic/src/main/java/com/netflix/spinnaker/gate/security/basic/BasicAuthProvider.java
+++ b/gate-basic/src/main/java/com/netflix/spinnaker/gate/security/basic/BasicAuthProvider.java
@@ -17,13 +17,12 @@
 
 import com.netflix.spinnaker.gate.services.OesAuthorizationService;
 import com.netflix.spinnaker.gate.services.PermissionService;
-import com.netflix.spinnaker.security.User;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.authentication.BadCredentialsException;
@@ -32,8 +31,12 @@
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Component;
 
 @Slf4j
+@Component
 public class BasicAuthProvider implements AuthenticationProvider {
 
   private final PermissionService permissionService;
@@ -46,6 +49,7 @@ public class BasicAuthProvider implements AuthenticationProvider {
   @Setter private String name;
   @Setter private String password;
 
+  @Autowired
   public BasicAuthProvider(
       PermissionService permissionService, OesAuthorizationService oesAuthorizationService) {
     this.permissionService = permissionService;
@@ -61,19 +65,15 @@ public Authentication authenticate(Authentication authentication) throws Authent
     if (!this.name.equals(name) || !this.password.equals(password)) {
       throw new BadCredentialsException("Invalid username/password combination");
     }
-
     log.debug("roles configured for user: {} are roles: {}", name, roles);
-    User user = new User();
-    user.setEmail(name);
-    user.setUsername(name);
-    user.setRoles(Collections.singletonList("USER"));
 
     List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
 
     if (roles != null && !roles.isEmpty() && permissionService != null) {
-      user.setRoles(roles);
-      grantedAuthorities =
-          roles.stream().map(role -> new SimpleGrantedAuthority(role)).collect(Collectors.toList());
+      grantedAuthorities.addAll(
+          roles.stream()
+              .map(role -> new SimpleGrantedAuthority(role))
+              .collect(Collectors.toList()));
       // Updating roles in fiat service
       permissionService.loginWithRoles(name, roles);
       log.info("Platform service enabled value :{}", isPlatformEnabled);
@@ -81,9 +81,11 @@ public Authentication authenticate(Authentication authentication) throws Authent
       if (isPlatformEnabled) {
         oesAuthorizationService.cacheUserGroups(roles, name);
       }
+    } else {
+      grantedAuthorities.add(new SimpleGrantedAuthority("USER"));
     }
-
-    return new UsernamePasswordAuthenticationToken(user, password, grantedAuthorities);
+    UserDetails principal = new User(name, password, grantedAuthorities);
+    return new UsernamePasswordAuthenticationToken(principal, password, grantedAuthorities);
   }
 
   @Override
diff --git a/gate-web/src/main/groovy/com/netflix/spinnaker/gate/controllers/RootController.groovy b/gate-web/src/main/groovy/com/netflix/spinnaker/gate/controllers/RootController.groovy
index afa09bc0a7..6e37c8ac3b 100644
--- a/gate-web/src/main/groovy/com/netflix/spinnaker/gate/controllers/RootController.groovy
+++ b/gate-web/src/main/groovy/com/netflix/spinnaker/gate/controllers/RootController.groovy
@@ -26,13 +26,22 @@ import jakarta.servlet.http.HttpServletResponse
 @Slf4j
 @RestController
 class RootController {
-
+  @Value('${services.deck.base-url:}')
+  URL deckBaseUrl
   @Value('${services.oesui.externalUrl:}')
   String uiBaseUrl
+  @Value('${services.gate:oes-gate}')
+  String gateType
 
   @RequestMapping("/")
   void root(HttpServletResponse response) {
-    log.info("uiBaseUrl : {}", uiBaseUrl)
-    response.sendRedirect(uiBaseUrl + "/application")
+    if(gateType.equalsIgnoreCase("oes-gate")){
+      log.info("uiBaseUrl : {}", uiBaseUrl)
+      response.sendRedirect(uiBaseUrl + "/application")
+    } else {
+      log.info("deckBaseUrl : {}", deckBaseUrl)
+      response.sendRedirect("/applications")
+    }
+
   }
 }
diff --git a/gate-web/src/main/java/com/opsmx/spinnaker/gate/cache/OesCacheManager.java b/gate-web/src/main/java/com/opsmx/spinnaker/gate/cache/OesCacheManager.java
index 9b9190326d..3d1dbfeb38 100644
--- a/gate-web/src/main/java/com/opsmx/spinnaker/gate/cache/OesCacheManager.java
+++ b/gate-web/src/main/java/com/opsmx/spinnaker/gate/cache/OesCacheManager.java
@@ -16,13 +16,9 @@
 
 package com.opsmx.spinnaker.gate.cache;
 
-import com.github.benmanes.caffeine.cache.Caffeine;
-import java.util.concurrent.TimeUnit;
 import lombok.Getter;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
 import org.springframework.cache.CacheManager;
-import org.springframework.cache.caffeine.CaffeineCacheManager;
 import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -34,26 +30,10 @@ public class OesCacheManager {
 
   @Getter private CacheManager concurrentMapCacheManager;
 
-  @Getter private CaffeineCacheManager caffeineCacheManager;
-
-  @Value("${cache.expiryTime:600000}")
-  private String cacheExpiryTimeout;
-
   @Primary
   @Bean(name = "concurrentMapCacheManager")
   public CacheManager concurrentMapCacheManager() {
     concurrentMapCacheManager = new ConcurrentMapCacheManager("datasource");
     return concurrentMapCacheManager;
   }
-
-  @Bean(name = "caffeineCacheManager")
-  public CacheManager cacheManager() {
-
-    CaffeineCacheManager cacheManager = new CaffeineCacheManager("adminAuth");
-    cacheManager.setCaffeine(
-        Caffeine.newBuilder()
-            .expireAfterWrite(Long.parseLong(cacheExpiryTimeout), TimeUnit.MILLISECONDS));
-    caffeineCacheManager = cacheManager;
-    return cacheManager;
-  }
 }
diff --git a/gate-web/src/main/java/com/opsmx/spinnaker/gate/cache/OesPlatformCacheManager.java b/gate-web/src/main/java/com/opsmx/spinnaker/gate/cache/OesPlatformCacheManager.java
new file mode 100644
index 0000000000..be3885a615
--- /dev/null
+++ b/gate-web/src/main/java/com/opsmx/spinnaker/gate/cache/OesPlatformCacheManager.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2024 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.opsmx.spinnaker.gate.cache;
+
+import com.github.benmanes.caffeine.cache.Caffeine;
+import java.util.concurrent.TimeUnit;
+import lombok.Getter;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.caffeine.CaffeineCacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConditionalOnExpression("${services.platform.enabled:false}")
+public class OesPlatformCacheManager {
+
+  @Getter private CaffeineCacheManager caffeineCacheManager;
+
+  @Value("${cache.expiryTime:600000}")
+  private String cacheExpiryTimeout;
+
+  @Bean(name = "caffeineCacheManager")
+  public CacheManager cacheManager() {
+
+    CaffeineCacheManager cacheManager = new CaffeineCacheManager("adminAuth");
+    cacheManager.setCaffeine(
+        Caffeine.newBuilder()
+            .expireAfterWrite(Long.parseLong(cacheExpiryTimeout), TimeUnit.MILLISECONDS));
+    caffeineCacheManager = cacheManager;
+    return cacheManager;
+  }
+}
diff --git a/gate-web/src/main/java/com/opsmx/spinnaker/gate/service/AdminAuthService.java b/gate-web/src/main/java/com/opsmx/spinnaker/gate/service/AdminAuthService.java
index 9fc28ada94..bbdcfa158d 100644
--- a/gate-web/src/main/java/com/opsmx/spinnaker/gate/service/AdminAuthService.java
+++ b/gate-web/src/main/java/com/opsmx/spinnaker/gate/service/AdminAuthService.java
@@ -17,7 +17,7 @@
 package com.opsmx.spinnaker.gate.service;
 
 import com.google.gson.Gson;
-import com.opsmx.spinnaker.gate.cache.OesCacheManager;
+import com.opsmx.spinnaker.gate.cache.OesPlatformCacheManager;
 import com.opsmx.spinnaker.gate.cache.platform.AuthorizationCaching;
 import java.util.Map;
 import java.util.Set;
@@ -35,7 +35,7 @@ public class AdminAuthService implements PlatformCachingService {
 
   private Gson gson = new Gson();
 
-  @Autowired private OesCacheManager oesCacheManager;
+  @Autowired private OesPlatformCacheManager oesPlatformCacheManager;
 
   @Autowired private AuthorizationCaching authorizationCaching;
 
@@ -50,7 +50,7 @@ public void cacheResponse(Object response, String userName) {
   @Override
   public boolean isCacheNotEmpty(String userName) {
 
-    CacheManager cacheManager = oesCacheManager.getCaffeineCacheManager();
+    CacheManager cacheManager = oesPlatformCacheManager.getCaffeineCacheManager();
     CaffeineCache caffeineCache = (CaffeineCache) cacheManager.getCache("adminAuth");
     Set<Object> keySet = caffeineCache.getNativeCache().asMap().keySet();
     return keySet.stream()