mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-13 17:05:43 +01:00
Show session expired warning if the jwt token is expired
This commit is contained in:
@@ -38,6 +38,7 @@ import sonia.scm.SCMContext;
|
|||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.security.AnonymousMode;
|
import sonia.scm.security.AnonymousMode;
|
||||||
import sonia.scm.security.AnonymousToken;
|
import sonia.scm.security.AnonymousToken;
|
||||||
|
import sonia.scm.security.BearerToken;
|
||||||
import sonia.scm.util.HttpUtil;
|
import sonia.scm.util.HttpUtil;
|
||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
import sonia.scm.web.WebTokenGenerator;
|
import sonia.scm.web.WebTokenGenerator;
|
||||||
@@ -49,7 +50,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
import static sonia.scm.web.filter.JwtValidator.isJwtTokenExpired;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles authentication, if a one of the {@link WebTokenGenerator} returns
|
* Handles authentication, if a one of the {@link WebTokenGenerator} returns
|
||||||
@@ -61,23 +62,16 @@ import java.util.Set;
|
|||||||
@Singleton
|
@Singleton
|
||||||
public class AuthenticationFilter extends HttpFilter {
|
public class AuthenticationFilter extends HttpFilter {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(AuthenticationFilter.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* marker for failed authentication
|
* marker for failed authentication
|
||||||
*/
|
*/
|
||||||
private static final String ATTRIBUTE_FAILED_AUTH = "sonia.scm.auth.failed";
|
private static final String ATTRIBUTE_FAILED_AUTH = "sonia.scm.auth.failed";
|
||||||
|
|
||||||
/**
|
|
||||||
* Field description
|
|
||||||
*/
|
|
||||||
private static final String HEADER_AUTHORIZATION = "Authorization";
|
private static final String HEADER_AUTHORIZATION = "Authorization";
|
||||||
|
|
||||||
/**
|
private final Set<WebTokenGenerator> tokenGenerators;
|
||||||
* the logger for AuthenticationFilter
|
protected ScmConfiguration configuration;
|
||||||
*/
|
|
||||||
private static final Logger logger =
|
|
||||||
LoggerFactory.getLogger(AuthenticationFilter.class);
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new basic authenticaton filter.
|
* Constructs a new basic authenticaton filter.
|
||||||
@@ -91,8 +85,6 @@ public class AuthenticationFilter extends HttpFilter {
|
|||||||
this.tokenGenerators = tokenGenerators;
|
this.tokenGenerators = tokenGenerators;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles authentication, if a one of the {@link WebTokenGenerator} returns
|
* Handles authentication, if a one of the {@link WebTokenGenerator} returns
|
||||||
* an {@link AuthenticationToken}.
|
* an {@link AuthenticationToken}.
|
||||||
@@ -104,14 +96,15 @@ public class AuthenticationFilter extends HttpFilter {
|
|||||||
* @throws ServletException
|
* @throws ServletException
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void doFilter(HttpServletRequest request,
|
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||||
HttpServletResponse response, FilterChain chain)
|
|
||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
Subject subject = SecurityUtils.getSubject();
|
Subject subject = SecurityUtils.getSubject();
|
||||||
|
|
||||||
AuthenticationToken token = createToken(request);
|
AuthenticationToken token = createToken(request);
|
||||||
|
|
||||||
if (token != null) {
|
if (token instanceof BearerToken && isJwtTokenExpired(((BearerToken) token).getCredentials())) {
|
||||||
|
handleUnauthorized(request, response, chain);
|
||||||
|
} else if (token != null) {
|
||||||
logger.trace(
|
logger.trace(
|
||||||
"found authentication token on request, start authentication");
|
"found authentication token on request, start authentication");
|
||||||
handleAuthentication(request, response, chain, subject, token);
|
handleAuthentication(request, response, chain, subject, token);
|
||||||
@@ -173,11 +166,8 @@ public class AuthenticationFilter extends HttpFilter {
|
|||||||
* @param response http response
|
* @param response http response
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
protected void sendUnauthorizedError(HttpServletRequest request,
|
protected void sendUnauthorizedError(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||||
HttpServletResponse response)
|
HttpUtil.sendUnauthorized(request, response, configuration.getRealmDescription());
|
||||||
throws IOException {
|
|
||||||
HttpUtil.sendUnauthorized(request, response,
|
|
||||||
configuration.getRealmDescription());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -260,8 +250,6 @@ public class AuthenticationFilter extends HttpFilter {
|
|||||||
response);
|
response);
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns {@code true} if anonymous access is enabled.
|
* Returns {@code true} if anonymous access is enabled.
|
||||||
*
|
*
|
||||||
@@ -270,16 +258,4 @@ public class AuthenticationFilter extends HttpFilter {
|
|||||||
private boolean isAnonymousAccessEnabled() {
|
private boolean isAnonymousAccessEnabled() {
|
||||||
return (configuration != null) && configuration.getAnonymousMode() != AnonymousMode.OFF;
|
return (configuration != null) && configuration.getAnonymousMode() != AnonymousMode.OFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* set of web token generators
|
|
||||||
*/
|
|
||||||
private final Set<WebTokenGenerator> tokenGenerators;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* scm main configuration
|
|
||||||
*/
|
|
||||||
protected ScmConfiguration configuration;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.web.filter;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
public class JwtValidator {
|
||||||
|
|
||||||
|
private JwtValidator() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the jwt token is expired.
|
||||||
|
*
|
||||||
|
* @return {@code true}if the token is expired
|
||||||
|
*/
|
||||||
|
public static boolean isJwtTokenExpired(String raw) {
|
||||||
|
|
||||||
|
boolean expired = false;
|
||||||
|
|
||||||
|
String[] parts = raw.split("\\.");
|
||||||
|
|
||||||
|
if (parts.length > 1) {
|
||||||
|
Base64.Decoder decoder = Base64.getUrlDecoder();
|
||||||
|
String payload = new String(decoder.decode(parts[1]));
|
||||||
|
String[] splitJwt = payload.split(",");
|
||||||
|
|
||||||
|
for (String entry : splitJwt) {
|
||||||
|
if (entry.contains("\"exp\"")) {
|
||||||
|
long expirationTime = Long.parseLong(entry.replaceAll("[^\\d.]", ""));
|
||||||
|
|
||||||
|
if (Instant.now().isAfter(Instant.ofEpochSecond(expirationTime))) {
|
||||||
|
expired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expired;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,8 +24,6 @@
|
|||||||
|
|
||||||
package sonia.scm.web.filter;
|
package sonia.scm.web.filter;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.github.sdorra.shiro.ShiroRule;
|
import com.github.sdorra.shiro.ShiroRule;
|
||||||
import com.github.sdorra.shiro.SubjectAware;
|
import com.github.sdorra.shiro.SubjectAware;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
@@ -38,6 +36,7 @@ import org.junit.runner.RunWith;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
|
import sonia.scm.security.BearerToken;
|
||||||
import sonia.scm.web.WebTokenGenerator;
|
import sonia.scm.web.WebTokenGenerator;
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
@@ -47,191 +46,98 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static org.mockito.Mockito.any;
|
import static org.mockito.Mockito.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
@SubjectAware(configuration = "classpath:sonia/scm/shiro.ini")
|
@SubjectAware(configuration = "classpath:sonia/scm/shiro.ini")
|
||||||
public class AuthenticationFilterTest
|
public class AuthenticationFilterTest {
|
||||||
{
|
|
||||||
|
@Rule
|
||||||
|
public ShiroRule shiro = new ShiroRule();
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private FilterChain chain;
|
||||||
|
@Mock
|
||||||
|
private HttpServletRequest request;
|
||||||
|
@Mock
|
||||||
|
private HttpServletResponse response;
|
||||||
|
|
||||||
|
private ScmConfiguration configuration;
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
* @throws ServletException
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
@SubjectAware(username = "trillian", password = "secret")
|
@SubjectAware(username = "trillian", password = "secret")
|
||||||
public void testDoFilterAuthenticated() throws IOException, ServletException
|
public void testDoFilterAuthenticated() throws IOException, ServletException {
|
||||||
{
|
|
||||||
AuthenticationFilter filter = createAuthenticationFilter();
|
AuthenticationFilter filter = createAuthenticationFilter();
|
||||||
|
|
||||||
filter.doFilter(request, response, chain);
|
filter.doFilter(request, response, chain);
|
||||||
verify(chain).doFilter(any(HttpServletRequest.class),
|
verify(chain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
any(HttpServletResponse.class));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
* @throws ServletException
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testDoFilterUnauthorized() throws IOException, ServletException
|
public void testDoFilterUnauthorized() throws IOException, ServletException {
|
||||||
{
|
|
||||||
AuthenticationFilter filter = createAuthenticationFilter();
|
AuthenticationFilter filter = createAuthenticationFilter();
|
||||||
|
|
||||||
filter.doFilter(request, response, chain);
|
filter.doFilter(request, response, chain);
|
||||||
verify(response).sendError(HttpServletResponse.SC_UNAUTHORIZED,
|
verify(response).sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization Required");
|
||||||
"Authorization Required");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
* @throws ServletException
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testDoFilterWithAuthenticationFailed()
|
public void testDoFilterWithAuthenticationFailed() throws IOException, ServletException {
|
||||||
throws IOException, ServletException
|
AuthenticationFilter filter = createAuthenticationFilter(new DemoWebTokenGenerator("trillian", "sec"));
|
||||||
{
|
|
||||||
AuthenticationFilter filter =
|
|
||||||
createAuthenticationFilter(new DemoWebTokenGenerator("trillian", "sec"));
|
|
||||||
|
|
||||||
filter.doFilter(request, response, chain);
|
filter.doFilter(request, response, chain);
|
||||||
verify(response).sendError(HttpServletResponse.SC_UNAUTHORIZED,
|
|
||||||
"Authorization Required");
|
verify(response).sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization Required");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
* @throws ServletException
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testDoFilterWithAuthenticationSuccess()
|
public void testDoFilterWithAuthenticationSuccess() throws IOException, ServletException {
|
||||||
throws IOException, ServletException
|
AuthenticationFilter filter = createAuthenticationFilter();
|
||||||
{
|
|
||||||
AuthenticationFilter filter =
|
|
||||||
createAuthenticationFilter(new DemoWebTokenGenerator("trillian",
|
|
||||||
"secret"));
|
|
||||||
|
|
||||||
filter.doFilter(request, response, chain);
|
filter.doFilter(request, response, chain);
|
||||||
verify(chain).doFilter(any(HttpServletRequest.class),
|
|
||||||
any(HttpServletResponse.class));
|
verify(response).sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization Required");
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
@Test
|
||||||
|
public void testExpiredBearerToken() throws IOException, ServletException {
|
||||||
|
WebTokenGenerator generator = mock(WebTokenGenerator.class);
|
||||||
|
when(generator.createToken(request)).thenReturn(BearerToken.create(null,
|
||||||
|
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzY21hZG1pbiIsImp0aSI6IjNqUzZ4TzMwUzEiLCJpYXQiOjE1OTY3ODA5Mjg"
|
||||||
|
+ "sImV4cCI6MTU5Njc0NDUyOCwic2NtLW1hbmFnZXIucmVmcmVzaEV4cGlyYXRpb24iOjE1OTY4MjQxMjg2MDIsInNjbS1tYW5h"
|
||||||
|
+ "Z2VyLnBhcmVudFRva2VuSWQiOiIzalM2eE8zMFMxIn0.utZLmzGZr-M6MP19yrd0dgLPkJ0u1xojwHKQi36_QAs"));
|
||||||
|
AuthenticationFilter filter = createAuthenticationFilter(generator);
|
||||||
|
|
||||||
|
filter.doFilter(request, response, chain);
|
||||||
|
verify(response).sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization Required");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp()
|
public void setUp() {
|
||||||
{
|
|
||||||
configuration = new ScmConfiguration();
|
configuration = new ScmConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
private AuthenticationFilter createAuthenticationFilter(WebTokenGenerator... generators) {
|
||||||
|
return new AuthenticationFilter(configuration, ImmutableSet.copyOf(generators));
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param generators
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private AuthenticationFilter createAuthenticationFilter(
|
|
||||||
WebTokenGenerator... generators)
|
|
||||||
{
|
|
||||||
return new AuthenticationFilter(configuration,
|
|
||||||
ImmutableSet.copyOf(generators));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- inner classes --------------------------------------------------------
|
private static class DemoWebTokenGenerator implements WebTokenGenerator {
|
||||||
|
|
||||||
/**
|
private final String username;
|
||||||
* Class description
|
private final String password;
|
||||||
*
|
|
||||||
*
|
|
||||||
* @version Enter version here..., 15/02/21
|
|
||||||
* @author Enter your name here...
|
|
||||||
*/
|
|
||||||
private static class DemoWebTokenGenerator implements WebTokenGenerator
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
public DemoWebTokenGenerator(String username, String password) {
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param username
|
|
||||||
* @param password
|
|
||||||
*/
|
|
||||||
public DemoWebTokenGenerator(String username, String password)
|
|
||||||
{
|
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public AuthenticationToken createToken(HttpServletRequest request)
|
public AuthenticationToken createToken(HttpServletRequest request) {
|
||||||
{
|
|
||||||
return new UsernamePasswordToken(username, password);
|
return new UsernamePasswordToken(username, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields -------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final String password;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final String username;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
@Rule
|
|
||||||
public ShiroRule shiro = new ShiroRule();
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
@Mock
|
|
||||||
private FilterChain chain;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private ScmConfiguration configuration;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
@Mock
|
|
||||||
private HttpServletRequest request;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
@Mock
|
|
||||||
private HttpServletResponse response;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.web.filter;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static sonia.scm.web.filter.JwtValidator.isJwtTokenExpired;
|
||||||
|
|
||||||
|
class JwtValidatorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnFalseIfNotJwtToken() {
|
||||||
|
String raw = "scmadmin.scmadmin.scmadmin";
|
||||||
|
|
||||||
|
boolean result = isJwtTokenExpired(raw);
|
||||||
|
|
||||||
|
assertThat(result).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldValidateExpiredJwtToken() {
|
||||||
|
String raw = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzY21hZG1pbiIsImp0aSI6IjNqUzZ4TzMwUzEiLCJpYXQiOjE1OTY3ODA5Mjgs"
|
||||||
|
+ "ImV4cCI6MTU5Njc0NDUyOCwic2NtLW1hbmFnZXIucmVmcmVzaEV4cGlyYXRpb24iOjE1OTY4MjQxMjg2MDIsInNjbS1tYW5hZ2VyLnB"
|
||||||
|
+ "hcmVudFRva2VuSWQiOiIzalM2eE8zMFMxIn0.utZLmzGZr-M6MP19yrd0dgLPkJ0u1xojwHKQi36_QAs";
|
||||||
|
|
||||||
|
boolean result = isJwtTokenExpired(raw);
|
||||||
|
|
||||||
|
assertThat(result).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldValidateNotExpiredJwtToken() {
|
||||||
|
String raw = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzY21hZG1pbiIsImp0aSI6IjNqUzZ4TzMwUzEiLCJpYXQiOjE1OTY3ODA5Mjgs"
|
||||||
|
+ "ImV4cCI6NTU5Njc0NDUyOCwic2NtLW1hbmFnZXIucmVmcmVzaEV4cGlyYXRpb24iOjE1OTY4MjQxMjg2MDIsInNjbS1tYW5hZ2VyLnB"
|
||||||
|
+ "hcmVudFRva2VuSWQiOiIzalM2eE8zMFMxIn0.cvK4E58734T2PqtEqqhYCInnX_uryUkMhRNX-94riY0";
|
||||||
|
|
||||||
|
boolean result = isJwtTokenExpired(raw);
|
||||||
|
|
||||||
|
assertThat(result).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -60,8 +60,8 @@ class RepositoryRoles extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate = (prevProps: Props) => {
|
componentDidUpdate = (prevProps: Props) => {
|
||||||
const { loading, list, page, rolesLink, location, fetchRolesByPage } = this.props;
|
const { loading, error, list, page, rolesLink, location, fetchRolesByPage } = this.props;
|
||||||
if (list && page && !loading) {
|
if (list && page && !loading && !error) {
|
||||||
const statePage: number = list.page + 1;
|
const statePage: number = list.page + 1;
|
||||||
if (page !== statePage || prevProps.location.search !== location.search) {
|
if (page !== statePage || prevProps.location.search !== location.search) {
|
||||||
fetchRolesByPage(rolesLink, page);
|
fetchRolesByPage(rolesLink, page);
|
||||||
@@ -73,7 +73,7 @@ class RepositoryRoles extends React.Component<Props> {
|
|||||||
const { t, loading, error } = this.props;
|
const { t, loading, error } = this.props;
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <ErrorNotification />;
|
return <ErrorNotification error={error}/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class App extends Component<Props> {
|
|||||||
let content;
|
let content;
|
||||||
const navigation = authenticated ? <PrimaryNavigation links={links} /> : "";
|
const navigation = authenticated ? <PrimaryNavigation links={links} /> : "";
|
||||||
|
|
||||||
if (!authenticated) {
|
if (!authenticated && !loading) {
|
||||||
content = <Login />;
|
content = <Login />;
|
||||||
} else if (loading) {
|
} else if (loading) {
|
||||||
content = <Loading />;
|
content = <Loading />;
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import React from "react";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { WithTranslation, withTranslation } from "react-i18next";
|
import { WithTranslation, withTranslation } from "react-i18next";
|
||||||
import { RouteComponentProps } from "react-router-dom";
|
import { RouteComponentProps } from "react-router-dom";
|
||||||
import { History } from "history";
|
|
||||||
import { Group, PagedCollection } from "@scm-manager/ui-types";
|
import { Group, PagedCollection } from "@scm-manager/ui-types";
|
||||||
import {
|
import {
|
||||||
CreateButton,
|
CreateButton,
|
||||||
@@ -34,7 +33,8 @@ import {
|
|||||||
OverviewPageActions,
|
OverviewPageActions,
|
||||||
Page,
|
Page,
|
||||||
PageActions,
|
PageActions,
|
||||||
urls
|
urls,
|
||||||
|
Loading
|
||||||
} from "@scm-manager/ui-components";
|
} from "@scm-manager/ui-components";
|
||||||
import { getGroupsLink } from "../../modules/indexResource";
|
import { getGroupsLink } from "../../modules/indexResource";
|
||||||
import {
|
import {
|
||||||
@@ -88,6 +88,11 @@ class Groups extends React.Component<Props> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { groups, loading, error, canAddGroups, t } = this.props;
|
const { groups, loading, error, canAddGroups, t } = this.props;
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page title={t("groups.title")} subtitle={t("groups.subtitle")} loading={loading || !groups} error={error}>
|
<Page title={t("groups.title")} subtitle={t("groups.subtitle")} loading={loading || !groups} error={error}>
|
||||||
{this.renderGroupTable()}
|
{this.renderGroupTable()}
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ import {
|
|||||||
OverviewPageActions,
|
OverviewPageActions,
|
||||||
Page,
|
Page,
|
||||||
PageActions,
|
PageActions,
|
||||||
urls
|
urls,
|
||||||
|
Loading
|
||||||
} from "@scm-manager/ui-components";
|
} from "@scm-manager/ui-components";
|
||||||
import { getRepositoriesLink } from "../../modules/indexResource";
|
import { getRepositoriesLink } from "../../modules/indexResource";
|
||||||
import {
|
import {
|
||||||
@@ -80,6 +81,11 @@ class Overview extends React.Component<Props> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { error, loading, showCreateButton, t } = this.props;
|
const { error, loading, showCreateButton, t } = this.props;
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page title={t("overview.title")} subtitle={t("overview.subtitle")} loading={loading} error={error}>
|
<Page title={t("overview.title")} subtitle={t("overview.subtitle")} loading={loading} error={error}>
|
||||||
{this.renderOverview()}
|
{this.renderOverview()}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import React from "react";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { WithTranslation, withTranslation } from "react-i18next";
|
import { WithTranslation, withTranslation } from "react-i18next";
|
||||||
import { RouteComponentProps } from "react-router-dom";
|
import { RouteComponentProps } from "react-router-dom";
|
||||||
import { History } from "history";
|
|
||||||
import { PagedCollection, User } from "@scm-manager/ui-types";
|
import { PagedCollection, User } from "@scm-manager/ui-types";
|
||||||
import {
|
import {
|
||||||
CreateButton,
|
CreateButton,
|
||||||
@@ -34,7 +33,8 @@ import {
|
|||||||
OverviewPageActions,
|
OverviewPageActions,
|
||||||
Page,
|
Page,
|
||||||
PageActions,
|
PageActions,
|
||||||
urls
|
urls,
|
||||||
|
Loading
|
||||||
} from "@scm-manager/ui-components";
|
} from "@scm-manager/ui-components";
|
||||||
import { getUsersLink } from "../../modules/indexResource";
|
import { getUsersLink } from "../../modules/indexResource";
|
||||||
import {
|
import {
|
||||||
@@ -88,6 +88,11 @@ class Users extends React.Component<Props> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { users, loading, error, canAddUsers, t } = this.props;
|
const { users, loading, error, canAddUsers, t } = this.props;
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page title={t("users.title")} subtitle={t("users.subtitle")} loading={loading || !users} error={error}>
|
<Page title={t("users.title")} subtitle={t("users.subtitle")} loading={loading || !users} error={error}>
|
||||||
{this.renderUserTable()}
|
{this.renderUserTable()}
|
||||||
|
|||||||
Reference in New Issue
Block a user