Skip to content

Commit

Permalink
feat(gate-web): Add delete session tokens from redis endpoint (#1827)
Browse files Browse the repository at this point in the history
* Add delete session tokens from redis endpoint

delete sessions cache code #publish-snapshot

fix api tests #publish-snapshot

fix sessino service spec test #publish-snapshot

* Update gate-web/src/main/groovy/com/netflix/spinnaker/gate/services/SessionService.groovy

Co-authored-by: Matt Gogerly <6519811+mattgogerly@users.noreply.github.com>

* java rewrite

* spotless apply

---------

Co-authored-by: Matt Gogerly <6519811+mattgogerly@users.noreply.github.com>
  • Loading branch information
juangod-wise and mattgogerly authored Nov 15, 2024
1 parent 1e706c0 commit cae9b39
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ package com.netflix.spinnaker.gate.controllers

import com.netflix.spinnaker.gate.security.SpinnakerUser
import com.netflix.spinnaker.gate.services.PermissionService
import com.netflix.spinnaker.gate.services.SessionService
import com.netflix.spinnaker.security.AuthenticatedRequest
import com.netflix.spinnaker.security.User
import groovy.util.logging.Slf4j
import io.swagger.annotations.ApiOperation
import org.apache.commons.lang3.exception.ExceptionUtils
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.security.access.prepost.PreAuthorize
Expand Down Expand Up @@ -58,10 +58,14 @@ class AuthController {
@Autowired
PermissionService permissionService

SessionService sessionService

@Autowired
AuthController(@Value('${services.deck.base-url:}') URL deckBaseUrl,
@Value('${services.deck.redirect-host-pattern:#{null}}') String redirectHostPattern) {
@Value('${services.deck.redirect-host-pattern:#{null}}') String redirectHostPattern,
@Autowired SessionService sessionService) {
this.deckBaseUrl = deckBaseUrl
this.sessionService = sessionService

if (redirectHostPattern) {
this.redirectHostPattern = Pattern.compile(redirectHostPattern)
Expand Down Expand Up @@ -117,6 +121,16 @@ class AuthController {
permissionService.sync()
}

/**
* On-demand endpoint to purge the session tokens cache
*/
@ApiOperation(value = "Delete session cache")
@RequestMapping(value = "/deleteSessionCache", method = RequestMethod.POST)
@PreAuthorize("@authController.isAdmin()")
void deleteSessionCache() {
sessionService.deleteSpringSessions()
}

@ApiOperation(value = "Redirect to Deck")
@RequestMapping(value = "/redirect", method = RequestMethod.GET)
void redirect(HttpServletResponse response, @RequestParam String to) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2024 Wise PLC
*
* 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.netflix.spinnaker.gate.services;

import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@Component
public class SessionService {

private final JedisPool jedisPool;

public SessionService(@Autowired JedisPool jedisPool) {
this.jedisPool = jedisPool;
}

public void deleteSpringSessions() {
try (Jedis jedis = jedisPool.getResource()) {
Set<String> keys = jedis.keys("spring:session*");

if (!keys.isEmpty()) {
jedis.del(keys.toArray(new String[0]));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@

package com.netflix.spinnaker.gate.controllers

import com.netflix.spinnaker.gate.services.SessionService
import spock.lang.Specification
import spock.lang.Unroll

class AuthControllerSpec extends Specification {
@Unroll
def "should validate redirectUrl against deckBaseUrl or redirectHostPattern"() {
given:
def autoController = new AuthController(deckBaseUrl, redirectHostPattern)
def autoController = new AuthController(deckBaseUrl, redirectHostPattern, null)

expect:
autoController.validDeckRedirect(to) == isValid
Expand All @@ -38,4 +39,19 @@ class AuthControllerSpec extends Specification {
new URL("http://localhost:9000") | "root.net" | "http://spinnaker.root.net:8000" || false
new URL("http://localhost:9000") | ".*\\.root\\.net" | "http://spinnaker.root.net:8000" || true // redirectHostPattern supports regex
}

@Unroll
def "should delete session tokens cache"() {
given:
def sessionServiceMock = Mock(SessionService)
sessionServiceMock.deleteSpringSessions() >> null

def authController = new AuthController(null, null, sessionServiceMock)

when:
authController.deleteSessionCache()

then:
1 * sessionServiceMock.deleteSpringSessions()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2024 Wise PLC
*
* 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.netflix.spinnaker.gate.service;

import com.netflix.spinnaker.gate.services.SessionService;
import com.netflix.spinnaker.kork.jedis.EmbeddedRedis;
import java.util.Set;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;

public class SessionServiceTest {

private static EmbeddedRedis embeddedRedis;

@BeforeAll
public static void setupSpec() {
embeddedRedis = EmbeddedRedis.embed();
}

@AfterAll
public static void tearDown() {
if (embeddedRedis != null) {
embeddedRedis.destroy();
}
}

@Test
public void shouldDeleteSpringSessions() {
// Given
Jedis jedis = embeddedRedis.getJedis();
jedis.set("spring:session:session1", "session1-data");
jedis.set("spring:session:session2", "session2-data");
jedis.set("other:key", "other-data");

SessionService subject = new SessionService(embeddedRedis.getPool());

// When
subject.deleteSpringSessions();

// Then
Set<String> springSessionKeys = jedis.keys("spring:session*");
Set<String> otherKeys = jedis.keys("other:key");

Assertions.assertTrue(
springSessionKeys.isEmpty(), "Spring session keys should have been deleted");
Assertions.assertEquals(1, otherKeys.size(), "Other keys should remain");
}
}

0 comments on commit cae9b39

Please sign in to comment.