Add Configuration to JWT lifetime length

This commit is contained in:
Viktor Egorov
2025-02-25 13:16:27 +01:00
parent bc3967a614
commit 19930804a0
30 changed files with 400 additions and 72 deletions

View File

@@ -57,6 +57,8 @@ public class ConfigDto extends HalRepresentation implements UpdateConfigDto {
private String alertsUrl;
private String releaseFeedUrl;
private String mailDomainName;
private int jwtExpirationInH;
private boolean enabledJwtEndless;
private Set<String> emergencyContacts;
@Override

View File

@@ -35,6 +35,7 @@ import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Response;
import sonia.scm.admin.ScmConfigurationStore;
import sonia.scm.config.SecureKeyService;
import sonia.scm.config.ConfigurationPermissions;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.NamespaceStrategyValidator;
@@ -57,18 +58,21 @@ public class ConfigResource {
private final NamespaceStrategyValidator namespaceStrategyValidator;
private final JsonMerger jsonMerger;
private final SecureKeyService secureKeyService;
@Inject
public ConfigResource(ScmConfigurationStore store, ConfigDtoToScmConfigurationMapper dtoToConfigMapper,
ScmConfigurationToConfigDtoMapper configToDtoMapper,
NamespaceStrategyValidator namespaceStrategyValidator,
JsonMerger jsonMerger) {
JsonMerger jsonMerger,
SecureKeyService secureKeyService) {
this.dtoToConfigMapper = dtoToConfigMapper;
this.configToDtoMapper = configToDtoMapper;
this.store = store;
this.namespaceStrategyValidator = namespaceStrategyValidator;
this.jsonMerger = jsonMerger;
this.secureKeyService = secureKeyService;
}
/**
@@ -201,6 +205,9 @@ public class ConfigResource {
private void updateConfig(ConfigDto updatedConfigDto) {
// ensure the namespace strategy is valid
namespaceStrategyValidator.check(updatedConfigDto.getNamespaceStrategy());
if (store.get().getJwtExpirationInH() > updatedConfigDto.getJwtExpirationInH()) {
secureKeyService.clearAllTokens();
}
store.store(dtoToConfigMapper.map(updatedConfigDto));
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2020 - present Cloudogu GmbH
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3.
*
* 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 Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
package sonia.scm.config;
import jakarta.inject.Inject;
import sonia.scm.security.SecureKeyResolver;
public class SecureKeyService {
private final SecureKeyResolver secureKeyResolver;
@Inject
public SecureKeyService(SecureKeyResolver secureKeyResolver) {
this.secureKeyResolver = secureKeyResolver;
}
public void clearAllTokens() {
secureKeyResolver.deleteStore();
}
}

View File

@@ -29,6 +29,7 @@ import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.config.ScmConfiguration;
import java.time.Clock;
import java.time.Instant;
@@ -57,10 +58,11 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
private final SecureKeyResolver keyResolver;
private final JwtConfig jwtConfig;
private final Clock clock;
private final ScmConfiguration scmConfiguration;
private String subject;
private String issuer;
private long expiresIn = 1;
private long expiresIn;
private TimeUnit expiresInUnit = TimeUnit.HOURS;
private long refreshableFor = DEFAULT_REFRESHABLE;
private TimeUnit refreshableForUnit = DEFAULT_REFRESHABLE_UNIT;
@@ -70,11 +72,13 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
private final Map<String,Object> custom = Maps.newHashMap();
JwtAccessTokenBuilder(KeyGenerator keyGenerator, SecureKeyResolver keyResolver, JwtConfig jwtConfig, Clock clock) {
JwtAccessTokenBuilder(KeyGenerator keyGenerator, SecureKeyResolver keyResolver, JwtConfig jwtConfig, Clock clock, ScmConfiguration scmConfiguration) {
this.keyGenerator = keyGenerator;
this.keyResolver = keyResolver;
this.jwtConfig = jwtConfig;
this.clock = clock;
this.scmConfiguration = scmConfiguration;
this.expiresIn = scmConfiguration.getJwtExpirationInH();
}
@Override
@@ -179,6 +183,8 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
if(!jwtConfig.isEndlessJwtEnabled()) {
claims.setExpiration(new Date(now.toEpochMilli() + expiration));
} else {
scmConfiguration.setEnabledJwtExpiration(true);
}
if (refreshableFor > 0) {
@@ -196,8 +202,7 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
if ( issuer != null ) {
claims.setIssuer(issuer);
}
// sign token and create compact version
String compact = Jwts.builder()
.setClaims(claims)

View File

@@ -17,6 +17,7 @@
package sonia.scm.security;
import jakarta.inject.Inject;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.plugin.Extension;
import java.time.Clock;
@@ -35,25 +36,27 @@ public final class JwtAccessTokenBuilderFactory implements AccessTokenBuilderFac
private final JwtConfig jwtConfig;
private final Set<AccessTokenEnricher> enrichers;
private final Clock clock;
private final ScmConfiguration scmConfiguration;
@Inject
public JwtAccessTokenBuilderFactory(
KeyGenerator keyGenerator, SecureKeyResolver keyResolver, Set<AccessTokenEnricher> enrichers, JwtConfig jwtConfig) {
this(keyGenerator, keyResolver, jwtConfig, enrichers, Clock.systemDefaultZone());
KeyGenerator keyGenerator, SecureKeyResolver keyResolver, Set<AccessTokenEnricher> enrichers, JwtConfig jwtConfig, ScmConfiguration scmConfiguration) {
this(keyGenerator, keyResolver, jwtConfig, enrichers, Clock.systemDefaultZone(), scmConfiguration);
}
JwtAccessTokenBuilderFactory(
KeyGenerator keyGenerator, SecureKeyResolver keyResolver, JwtConfig jwtConfig, Set<AccessTokenEnricher> enrichers, Clock clock) {
KeyGenerator keyGenerator, SecureKeyResolver keyResolver, JwtConfig jwtConfig, Set<AccessTokenEnricher> enrichers, Clock clock, ScmConfiguration scmConfiguration) {
this.keyGenerator = keyGenerator;
this.keyResolver = keyResolver;
this.jwtConfig = jwtConfig;
this.enrichers = enrichers;
this.clock = clock;
this.scmConfiguration = scmConfiguration;
}
@Override
public JwtAccessTokenBuilder create() {
JwtAccessTokenBuilder builder = new JwtAccessTokenBuilder(keyGenerator, keyResolver, jwtConfig, clock);
JwtAccessTokenBuilder builder = new JwtAccessTokenBuilder(keyGenerator, keyResolver, jwtConfig, clock, scmConfiguration);
// enrich access token builder
enrichers.forEach((enricher) -> {

View File

@@ -30,7 +30,7 @@ public class JwtConfig {
@ConfigValue(
key = "endlessJwt",
defaultValue = "false",
description = "The lifespan of the issued JWT tokens should be endless. Logged-in users are no longer automatically logged out.")
description = "The lifespan of the issued JWT tokens should be endless. Logged-in users are no longer automatically logged out. Any other expiration time will be overridden")
boolean endlessJwt) {
this.endlessJwt = endlessJwt;
}

View File

@@ -131,4 +131,7 @@ public class SecureKeyResolver extends SigningKeyResolverAdapter
return new SecureKey(bytes, System.currentTimeMillis());
}
public void deleteStore() {
store.clear();
}
}