mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-02-26 00:10:50 +01:00
Add Configuration to JWT lifetime length
This commit is contained in:
@@ -38,6 +38,12 @@ So können über das Plugin-Center besondere cloudogu platform-Plugins bezogen w
|
||||
Eine bestehende Verbindung zwischen dem SCM-Manager und der cloudogu platform kann hier aufgehoben werden.
|
||||

|
||||
|
||||
### JWT Einstellungen
|
||||
Benutzer erhalten einen JWT als Authentifizierungstoken nach einem erfolgreichen login.
|
||||
Administratoren können die Lebensdauer dieser JWTs konfigurieren.
|
||||
Falls die Lebensdauer verringert wird, wird jeder bisher ausgestellter JWT ungültig.
|
||||
Sollte in der `config.yml` des Servers die Option "endless JWT" aktiviert sein, dann wird diese Einstellung ignoriert.
|
||||
|
||||
#### Anonyme Zugriff
|
||||
Der SCM-Manager 2 hat das Konzept für anonyme Zugriffe über einen "_anonymous"-Benutzer realisiert. Beim Aktivieren des anonymen Zugriffs wird ein neuer Benutzer erstellt mit dem Namen "_anonymous". Dieser Nutzer kann wie ein gewöhnlicher Benutzer für unterschiedliche Aktionen berechtigt werden. Bei einem Zugriff auf den SCM-Manager ohne Zugangsdaten wird dieser anonyme Benutzer verwendet.
|
||||
Ist der anonyme Zugriff nur für Protokoll aktiviert, können die REST API und die VCS Protokolle anonym genutzt werden. Wurde der anonyme Zugriff vollständig aktiviert, ist auch ein Zugriff über den Webclient anonym möglich.
|
||||
|
||||
@@ -37,6 +37,12 @@ Plugin Center Authentication URL: https://plugin-center-api.scm-manager.org/api/
|
||||
An existing connection between a SCM-Manager and the cloudogu platform may be severed here.
|
||||

|
||||
|
||||
#### JWT settings
|
||||
Users receive a JWT as an authentication token, after a successful login.
|
||||
Administrators can configure the amount of hours until a JWT expires.
|
||||
If the amount of hours get reduced, each created JWT will be invalidated.
|
||||
This setting will be ignored, if the endless JWT option is set to true in the server `config.yml`.
|
||||
|
||||
#### Anonymous Access
|
||||
In SCM-Manager 2 the access for anonymous access is realized by using an "_anonymous" user. When the feature is activated, a new user with the name "_anonymous" is created. This user can be authorized just like any other user. This user is used for access to SCM-Manager without login credentials.
|
||||
If the anonymous mode is protocol only you may access the SCM-Manager via the REST API and VCS protocols. With fully enabled anonymous access you can also use the webclient without credentials.
|
||||
|
||||
2
gradle/changelog/jwt_settings.yaml
Normal file
2
gradle/changelog/jwt_settings.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- type: added
|
||||
description: JWT expiration time in general settings
|
||||
@@ -229,6 +229,22 @@ public class ScmConfiguration implements Configuration {
|
||||
@XmlElement(name = "mail-domain-name")
|
||||
private String mailDomainName = DEFAULT_MAIL_DOMAIN_NAME;
|
||||
|
||||
/**
|
||||
* Time in hours for jwt expiration.
|
||||
*
|
||||
* @since 3.8.0
|
||||
*/
|
||||
@XmlElement(name = "jwt-expiration-time")
|
||||
private int jwtExpirationInH = 1;
|
||||
|
||||
/**
|
||||
* Enables endless jwt.
|
||||
*
|
||||
* @since 3.8.0
|
||||
*/
|
||||
@XmlElement(name = "jwt-expiration-endless")
|
||||
private boolean enabledJwtEndless = false;
|
||||
|
||||
/**
|
||||
* List of users that will be notified of administrative incidents.
|
||||
*
|
||||
@@ -278,6 +294,8 @@ public class ScmConfiguration implements Configuration {
|
||||
this.emergencyContacts = other.emergencyContacts;
|
||||
this.enabledUserConverter = other.enabledUserConverter;
|
||||
this.enabledApiKeys = other.enabledApiKeys;
|
||||
this.jwtExpirationInH = other.jwtExpirationInH;
|
||||
this.enabledJwtEndless = other.enabledJwtEndless;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -448,6 +466,26 @@ public class ScmConfiguration implements Configuration {
|
||||
return anonymousMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Jwt expiration in {@code n} .
|
||||
*
|
||||
* @return Jwt expiration in {@code number}
|
||||
* @since 3.8.0
|
||||
*/
|
||||
public int getJwtExpirationInH() {
|
||||
return jwtExpirationInH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the cookie xsrf protection is enabled.
|
||||
*
|
||||
* @return {@code true} if the cookie xsrf protection is enabled
|
||||
* @since 3.8.0
|
||||
*/
|
||||
public boolean isJwtEndless() {
|
||||
return enabledJwtEndless;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if anonymous mode is enabled.
|
||||
*
|
||||
@@ -728,6 +766,26 @@ public class ScmConfiguration implements Configuration {
|
||||
this.enabledFileSearch = enabledFileSearch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set {@code n} to configure jwt expiration time in hours
|
||||
*
|
||||
* @param jwtExpirationInH {@code n} to configure jwt expiration time in hours
|
||||
* @since 3.8.0
|
||||
*/
|
||||
public void setJwtExpirationInH(int jwtExpirationInH) {
|
||||
this.jwtExpirationInH = jwtExpirationInH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set {@code true} to enable endless jwt.
|
||||
*
|
||||
* @param enabledJwtEndless {@code true} to enable endless jwt.
|
||||
* @since 2.45.0
|
||||
*/
|
||||
public void setEnabledJwtExpiration(boolean enabledJwtEndless) {
|
||||
this.enabledJwtEndless = enabledJwtEndless;
|
||||
}
|
||||
|
||||
public void setNamespaceStrategy(String namespaceStrategy) {
|
||||
this.namespaceStrategy = namespaceStrategy;
|
||||
}
|
||||
|
||||
@@ -241,6 +241,8 @@ class AnonymousAccessITCase {
|
||||
.addNull("proxyUser")
|
||||
.add("realmDescription", "SONIA :: SCM Manager")
|
||||
.add("skipFailedAuthenticators", false)
|
||||
.add("jwtExpirationInH", 1)
|
||||
.add("enabledJwtEndless", false)
|
||||
.build().toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +281,9 @@ public class TestData {
|
||||
" \"loginInfoUrl\": \"https://login-info.scm-manager.org/api/v1/login-info\",\n" +
|
||||
" \"releaseFeedUrl\": \"https://scm-manager.org/download/rss.xml\",\n" +
|
||||
" \"mailDomainName\": \"scm-manager.local\", \n" +
|
||||
" \"enabledApiKeys\": \"true\"\n" +
|
||||
" \"enabledApiKeys\": \"true\",\n" +
|
||||
" \"jwtExpirationInH\": 1,\n" +
|
||||
" \"enabledJwtEndless\": false\n" +
|
||||
"}")
|
||||
.put(createResourceUrl("config"))
|
||||
.then()
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ffmpeg-installer/ffmpeg": "^1.0.20",
|
||||
"@scm-manager/integration-test-runner": "^3.4.3",
|
||||
"@scm-manager/integration-test-runner": "^3.5.0",
|
||||
"fluent-ffmpeg": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -26,4 +26,4 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ describe("Test config hooks", () => {
|
||||
alertsUrl: "",
|
||||
releaseFeedUrl: "",
|
||||
skipFailedAuthenticators: false,
|
||||
jwtExpirationInH: 1,
|
||||
enabledJwtEndless: false,
|
||||
_links: {
|
||||
update: {
|
||||
href: "/config",
|
||||
|
||||
@@ -21,6 +21,7 @@ import { createAttributesForTesting } from "../devBuild";
|
||||
import useAutofocus from "./useAutofocus";
|
||||
import { createFormFieldWrapper, FieldProps, FieldType, isLegacy, isUsingRef } from "./FormFieldTypes";
|
||||
import { createA11yId } from "../createA11yId";
|
||||
import FieldMessage from "@scm-manager/ui-core/src/base/forms/base/field-message/FieldMessage";
|
||||
|
||||
type BaseProps = {
|
||||
label?: string;
|
||||
@@ -40,6 +41,7 @@ type BaseProps = {
|
||||
defaultValue?: string | number;
|
||||
readOnly?: boolean;
|
||||
required?: boolean;
|
||||
warning?: string;
|
||||
};
|
||||
|
||||
export const InnerInputField: FC<FieldProps<BaseProps, HTMLInputElement, string>> = ({
|
||||
@@ -60,6 +62,7 @@ export const InnerInputField: FC<FieldProps<BaseProps, HTMLInputElement, string>
|
||||
defaultValue,
|
||||
readOnly,
|
||||
required,
|
||||
warning,
|
||||
...props
|
||||
}) => {
|
||||
const field = useAutofocus<HTMLInputElement>(autofocus, props.innerRef);
|
||||
@@ -123,6 +126,7 @@ export const InnerInputField: FC<FieldProps<BaseProps, HTMLInputElement, string>
|
||||
{...createAttributesForTesting(testId)}
|
||||
/>
|
||||
</div>
|
||||
{warning ? <FieldMessage variant="warning">{warning}</FieldMessage> : null}
|
||||
{helper}
|
||||
</fieldset>
|
||||
);
|
||||
|
||||
@@ -47,4 +47,6 @@ export type Config = HalRepresentation & {
|
||||
emergencyContacts: string[];
|
||||
enabledApiKeys: boolean;
|
||||
enabledFileSearch: boolean;
|
||||
jwtExpirationInH?: number;
|
||||
enabledJwtEndless?: boolean;
|
||||
};
|
||||
|
||||
@@ -25,6 +25,13 @@
|
||||
"notConfiguredHint": "Authentifizierungs URL ist nicht gesetzt"
|
||||
}
|
||||
},
|
||||
"jwtSettings": {
|
||||
"subtitle": "JWT Einstellungen",
|
||||
"label": "Ablaufzeit",
|
||||
"help": "Legen Sie die Ablaufzeit des JWT in Stunden fest. Wenn Sie die Zeit auf 'endlos' setzen möchten, verwenden Sie die Option 'endlessJwt' in der 'config.yml'.",
|
||||
"hoursWarning": "Es wird nicht empfohlen, die Ablaufzeit auf mehr als 24 Stunden einzustellen",
|
||||
"endlessWarning": "Die Ablaufzeit ist auf endlos eingestellt. Siehe 'config.yml'."
|
||||
},
|
||||
"proxySettings": {
|
||||
"subtitle": "Proxy Einstellungen",
|
||||
"enable": "Proxy aktivieren",
|
||||
|
||||
@@ -25,6 +25,13 @@
|
||||
"notConfiguredHint": "Authentication URL is not configured"
|
||||
}
|
||||
},
|
||||
"jwtSettings": {
|
||||
"subtitle": "JWT Settings",
|
||||
"label": "Expiration time",
|
||||
"help": "Set the JWT expiration time in hours. If you want to set the time to endless consider the 'endlessJWT' option inside the 'config.yml'.",
|
||||
"hoursWarning": "It is not recommended to set the expiration time over 24 hours.",
|
||||
"endlessWarning": "The expiration time is set to endless."
|
||||
},
|
||||
"proxySettings": {
|
||||
"subtitle": "Proxy Settings",
|
||||
"enable": "Enable Proxy",
|
||||
|
||||
@@ -26,6 +26,7 @@ import PluginSettings from "./PluginSettings";
|
||||
import FunctionSettings from "./FunctionSettings";
|
||||
import InvalidateCaches from "./InvalidateCaches";
|
||||
import InvalidateSearchIndex from "./InvalidateSearchIndex";
|
||||
import JwtSettings from "./JwtSettings";
|
||||
|
||||
type Props = {
|
||||
submitForm: (p: Config) => void;
|
||||
@@ -60,6 +61,7 @@ const ConfigForm: FC<Props> = ({
|
||||
dateFormat: "",
|
||||
anonymousAccessEnabled: false,
|
||||
anonymousMode: "OFF",
|
||||
enabledFileSearch: true,
|
||||
baseUrl: "",
|
||||
forceBaseUrl: false,
|
||||
loginAttemptLimit: 0,
|
||||
@@ -77,6 +79,8 @@ const ConfigForm: FC<Props> = ({
|
||||
mailDomainName: "",
|
||||
emergencyContacts: [],
|
||||
enabledApiKeys: true,
|
||||
jwtExpirationInH: 1,
|
||||
enabledJwtEndless: false,
|
||||
_links: {},
|
||||
});
|
||||
const [showNotification, setShowNotification] = useState(false);
|
||||
@@ -184,6 +188,13 @@ const ConfigForm: FC<Props> = ({
|
||||
hasUpdatePermission={configUpdatePermission}
|
||||
/>
|
||||
<hr />
|
||||
<JwtSettings
|
||||
enabledJwtEndless={innerConfig.enabledJwtEndless || false}
|
||||
jwtExpirationInH={innerConfig.jwtExpirationInH || 1}
|
||||
onChange={onChange}
|
||||
hasUpdatePermission={configUpdatePermission}
|
||||
/>
|
||||
<hr />
|
||||
<ProxySettings
|
||||
proxyPassword={innerConfig.proxyPassword ? innerConfig.proxyPassword : ""}
|
||||
proxyPort={innerConfig.proxyPort ? innerConfig.proxyPort : 0}
|
||||
|
||||
64
scm-ui/ui-webapp/src/admin/components/form/JwtSettings.tsx
Normal file
64
scm-ui/ui-webapp/src/admin/components/form/JwtSettings.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
import React, {FC, useState} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { InputField, Subtitle } from "@scm-manager/ui-components";
|
||||
import { ConfigChangeHandler } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
jwtExpirationInH: number;
|
||||
enabledJwtEndless: boolean;
|
||||
onChange: ConfigChangeHandler;
|
||||
hasUpdatePermission: boolean;
|
||||
};
|
||||
|
||||
const JwtSettings: FC<Props> = ({ jwtExpirationInH, onChange, hasUpdatePermission, enabledJwtEndless }) => {
|
||||
const { t } = useTranslation("config");
|
||||
const [warning, setWarning] = useState<string | undefined>(undefined);
|
||||
const jwtOverTwentyFourWarning = t("jwtSettings.hoursWarning");
|
||||
const jwtIsSetToEndless = t("jwtSettings.endlessWarning");
|
||||
|
||||
const handleJwtTimeChange = (value: string) => {
|
||||
if (Number(value) > 24) {
|
||||
setWarning(jwtOverTwentyFourWarning);
|
||||
} else {
|
||||
setWarning(undefined);
|
||||
}
|
||||
onChange(true, Number(value), "jwtExpirationInH");
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Subtitle subtitle={t("jwtSettings.subtitle")} />
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<InputField
|
||||
type="number"
|
||||
label={t("jwtSettings.label")}
|
||||
onChange={handleJwtTimeChange}
|
||||
value={jwtExpirationInH}
|
||||
disabled={!hasUpdatePermission || enabledJwtEndless}
|
||||
helpText={t("jwtSettings.help")}
|
||||
warning={enabledJwtEndless ? jwtIsSetToEndless : warning}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JwtSettings;
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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) -> {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -131,4 +131,7 @@ public class SecureKeyResolver extends SigningKeyResolverAdapter
|
||||
return new SecureKey(bytes, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public void deleteStore() {
|
||||
store.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,8 @@ class ConfigDtoToScmConfigurationMapperTest {
|
||||
assertThat(config.getLoginInfoUrl()).isEqualTo("https://scm-manager.org/login-info");
|
||||
assertThat(config.getMailDomainName()).isEqualTo("hitchhiker.mail");
|
||||
assertThat(config.getEmergencyContacts()).contains(expectedUsers);
|
||||
assertThat(config.getJwtExpirationInH()).isEqualTo(10);
|
||||
assertThat(config.isJwtEndless()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -105,6 +107,8 @@ class ConfigDtoToScmConfigurationMapperTest {
|
||||
configDto.setMailDomainName("hitchhiker.mail");
|
||||
configDto.setEmergencyContacts(Sets.newSet(expectedUsers));
|
||||
configDto.setEnabledUserConverter(false);
|
||||
configDto.setJwtExpirationInH(10);
|
||||
configDto.setEnabledJwtEndless(false);
|
||||
|
||||
return configDto;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.admin.ScmConfigurationStore;
|
||||
import sonia.scm.config.SecureKeyService;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.NamespaceStrategyValidator;
|
||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||
@@ -69,10 +70,22 @@ class ConfigResourceTest {
|
||||
private ConfigDtoToScmConfigurationMapperImpl dtoToConfigMapper;
|
||||
@InjectMocks
|
||||
private ScmConfigurationToConfigDtoMapperImpl configToDtoMapper;
|
||||
@Mock
|
||||
private SecureKeyService secureKeyService;
|
||||
|
||||
@BeforeEach
|
||||
void prepareEnvironment() {
|
||||
ConfigResource configResource = new ConfigResource(new ScmConfigurationStore(new InMemoryConfigurationStoreFactory(), new ScmConfiguration()), dtoToConfigMapper, configToDtoMapper, namespaceStrategyValidator, jsonMerger);
|
||||
ConfigResource configResource = new ConfigResource(
|
||||
new ScmConfigurationStore(
|
||||
new InMemoryConfigurationStoreFactory(),
|
||||
new ScmConfiguration()
|
||||
),
|
||||
dtoToConfigMapper,
|
||||
configToDtoMapper,
|
||||
namespaceStrategyValidator,
|
||||
jsonMerger,
|
||||
secureKeyService
|
||||
);
|
||||
|
||||
dispatcher.addSingletonResource(configResource);
|
||||
}
|
||||
@@ -108,7 +121,7 @@ class ConfigResourceTest {
|
||||
permissions = "configuration:read,write:global"
|
||||
)
|
||||
void shouldUpdateConfig() throws URISyntaxException, IOException {
|
||||
MockHttpRequest request = put("sonia/scm/api/v2/config-test-update.json");
|
||||
MockHttpRequest request = put();
|
||||
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
@@ -128,7 +141,7 @@ class ConfigResourceTest {
|
||||
|
||||
@Test
|
||||
void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, IOException {
|
||||
MockHttpRequest request = put("sonia/scm/api/v2/config-test-update.json");
|
||||
MockHttpRequest request = put();
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
@@ -185,8 +198,8 @@ class ConfigResourceTest {
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
|
||||
private MockHttpRequest put(String resourceName) throws IOException, URISyntaxException {
|
||||
URL url = Resources.getResource(resourceName);
|
||||
private MockHttpRequest put() throws IOException, URISyntaxException {
|
||||
URL url = Resources.getResource("sonia/scm/api/v2/config-test-update.json");
|
||||
byte[] configJson = Resources.toByteArray(url);
|
||||
return MockHttpRequest.put("/" + ConfigResource.CONFIG_PATH_V2)
|
||||
.contentType(VndMediaType.CONFIG)
|
||||
|
||||
@@ -98,6 +98,8 @@ class ScmConfigurationToConfigDtoMapperTest {
|
||||
assertThat(dto.getReleaseFeedUrl()).isEqualTo("https://www.scm-manager.org/download/rss.xml");
|
||||
assertThat(dto.getMailDomainName()).isEqualTo("scm-manager.local");
|
||||
assertThat(dto.getEmergencyContacts()).contains(expectedUsers);
|
||||
assertThat(dto.getJwtExpirationInH()).isEqualTo(10);
|
||||
assertThat(dto.isEnabledJwtEndless()).isFalse();
|
||||
assertLinks(dto);
|
||||
}
|
||||
|
||||
@@ -161,6 +163,8 @@ class ScmConfigurationToConfigDtoMapperTest {
|
||||
config.setAlertsUrl("https://alerts.scm-manager.org/api/v1/alerts");
|
||||
config.setReleaseFeedUrl("https://www.scm-manager.org/download/rss.xml");
|
||||
config.setEmergencyContacts(Sets.newSet(expectedUsers));
|
||||
config.setJwtExpirationInH(10);
|
||||
config.setEnabledJwtExpiration(false);
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import sonia.scm.security.SecureKeyResolver;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class SecureKeyServiceTest {
|
||||
|
||||
private SecureKeyService secureKeyService;
|
||||
@Mock
|
||||
private SecureKeyResolver configRepository;
|
||||
|
||||
@BeforeEach
|
||||
void prepareEnvironment() {
|
||||
this.secureKeyService = new SecureKeyService(configRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDeleteStore() {
|
||||
this.secureKeyService.clearAllTokens();
|
||||
verify(configRepository).deleteStore();
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package sonia.scm.security;
|
||||
import com.google.common.collect.Sets;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import org.apache.commons.lang.time.DateUtils;
|
||||
import org.apache.shiro.authz.AuthorizationException;
|
||||
import org.apache.shiro.subject.PrincipalCollection;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
@@ -30,6 +31,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
@@ -48,7 +50,6 @@ import static sonia.scm.security.SecureKeyTestUtil.createSecureKey;
|
||||
|
||||
/**
|
||||
* Unit test for {@link JwtAccessTokenBuilder}.
|
||||
*
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class JwtAccessTokenBuilderTest {
|
||||
@@ -62,6 +63,8 @@ class JwtAccessTokenBuilderTest {
|
||||
@Mock
|
||||
private JwtConfig jwtConfig;
|
||||
|
||||
private ScmConfiguration scmConfiguration;
|
||||
|
||||
private Set<AccessTokenEnricher> enrichers;
|
||||
|
||||
private JwtAccessTokenBuilderFactory factory;
|
||||
@@ -85,10 +88,12 @@ class JwtAccessTokenBuilderTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUpDependencies() {
|
||||
this.scmConfiguration = new ScmConfiguration();
|
||||
this.scmConfiguration.setJwtExpirationInH(10);
|
||||
lenient().when(keyGenerator.createKey()).thenReturn("42");
|
||||
lenient().when(secureKeyResolver.getSecureKey(anyString())).thenReturn(createSecureKey());
|
||||
enrichers = Sets.newHashSet();
|
||||
factory = new JwtAccessTokenBuilderFactory(keyGenerator, secureKeyResolver, enrichers, jwtConfig);
|
||||
factory = new JwtAccessTokenBuilderFactory(keyGenerator, secureKeyResolver, enrichers, jwtConfig, scmConfiguration);
|
||||
}
|
||||
|
||||
@Nested
|
||||
@@ -99,7 +104,7 @@ class JwtAccessTokenBuilderTest {
|
||||
*/
|
||||
@BeforeEach
|
||||
void setUpObjectUnderTest() {
|
||||
factory = new JwtAccessTokenBuilderFactory(keyGenerator, secureKeyResolver, enrichers, jwtConfig);
|
||||
factory = new JwtAccessTokenBuilderFactory(keyGenerator, secureKeyResolver, enrichers, jwtConfig, scmConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,7 +154,7 @@ class JwtAccessTokenBuilderTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUpObjectUnderTest() {
|
||||
factory = new JwtAccessTokenBuilderFactory(keyGenerator, secureKeyResolver, jwtConfig, enrichers, clock);
|
||||
factory = new JwtAccessTokenBuilderFactory(keyGenerator, secureKeyResolver, jwtConfig, enrichers, clock, scmConfiguration);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -189,11 +194,9 @@ class JwtAccessTokenBuilderTest {
|
||||
@Nested
|
||||
class FromApiKeyRealm {
|
||||
|
||||
private Scope scope;
|
||||
|
||||
@BeforeEach
|
||||
void mockApiKeyRealm() {
|
||||
scope = Scope.valueOf("dummy:scope:*");
|
||||
Scope scope = Scope.valueOf("dummy:scope:*");
|
||||
lenient().when(principalCollection.getRealmNames()).thenReturn(singleton("ApiTokenRealm"));
|
||||
lenient().when(principalCollection.oneByType(Scope.class)).thenReturn(scope);
|
||||
}
|
||||
@@ -276,6 +279,7 @@ class JwtAccessTokenBuilderTest {
|
||||
assertThat(token.getSubject()).isEqualTo("Red");
|
||||
assertThat(token.getIssuer()).isNotEmpty();
|
||||
assertThat(token.getIssuer()).contains("https://scm-manager.org");
|
||||
assertThat(scmConfiguration.isJwtEndless()).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -291,6 +295,24 @@ class JwtAccessTokenBuilderTest {
|
||||
assertThat(token.getSubject()).isEqualTo("Red");
|
||||
assertThat(token.getIssuer()).isNotEmpty();
|
||||
assertThat(token.getIssuer()).contains("https://scm-manager.org");
|
||||
assertThat(scmConfiguration.isJwtEndless()).isEqualTo(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WithJwtSetTimeFeature {
|
||||
|
||||
@Test
|
||||
void testBuildWithDefaultJwtTime() {
|
||||
JwtAccessToken token = factory.create().subject("Red").issuer("https://scm-manager.org").build();
|
||||
|
||||
assertThat(token.getId()).isNotEmpty();
|
||||
assertThat(token.getIssuedAt()).isNotNull();
|
||||
assertThat(token.getExpiration()).isNotNull();
|
||||
assertThat(token.getSubject()).isEqualTo("Red");
|
||||
assertThat(token.getIssuer()).isNotEmpty();
|
||||
assertThat(token.getIssuer()).contains("https://scm-manager.org");
|
||||
assertThat(token.getExpiration()).isEqualTo(DateUtils.addHours(token.getIssuedAt(), 10));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.user.User;
|
||||
|
||||
import java.sql.Date;
|
||||
@@ -53,6 +54,8 @@ class JwtAccessTokenRefresherTest {
|
||||
private SecureKeyResolver keyResolver;
|
||||
@Mock
|
||||
private JwtConfig jwtConfig;
|
||||
|
||||
private ScmConfiguration scmConfiguration;
|
||||
@Mock
|
||||
private JwtAccessTokenRefreshStrategy refreshStrategy;
|
||||
@Mock
|
||||
@@ -71,9 +74,16 @@ class JwtAccessTokenRefresherTest {
|
||||
|
||||
Clock creationClock = mock(Clock.class);
|
||||
when(creationClock.instant()).thenReturn(TOKEN_CREATION);
|
||||
tokenBuilder = new JwtAccessTokenBuilderFactory(keyGenerator, keyResolver, jwtConfig, Collections.emptySet(), creationClock).create();
|
||||
tokenBuilder = new JwtAccessTokenBuilderFactory(keyGenerator, keyResolver, jwtConfig, Collections.emptySet(), creationClock, scmConfiguration).create();
|
||||
|
||||
JwtAccessTokenBuilderFactory refreshBuilderFactory = new JwtAccessTokenBuilderFactory(keyGenerator, keyResolver, jwtConfig, Collections.emptySet(), refreshClock);
|
||||
JwtAccessTokenBuilderFactory refreshBuilderFactory = new JwtAccessTokenBuilderFactory(
|
||||
keyGenerator,
|
||||
keyResolver,
|
||||
jwtConfig,
|
||||
Collections.emptySet(),
|
||||
refreshClock,
|
||||
scmConfiguration
|
||||
);
|
||||
refresher = new JwtAccessTokenRefresher(refreshBuilderFactory, refreshStrategy, refreshClock);
|
||||
when(refreshClock.instant()).thenReturn(NOW);
|
||||
lenient().when(refreshStrategy.shouldBeRefreshed(any())).thenReturn(true);
|
||||
@@ -91,6 +101,11 @@ class JwtAccessTokenRefresherTest {
|
||||
when(subject.getPrincipal()).thenReturn(new User("trillian"));
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void initConfig() {
|
||||
this.scmConfiguration = new ScmConfiguration();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDownSubject() {
|
||||
ThreadContext.unbindSubject();
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.github.sdorra.shiro.SubjectAware;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
@@ -43,15 +44,11 @@ import static sonia.scm.security.SecureKeyTestUtil.createSecureKey;
|
||||
public class PercentageJwtAccessTokenRefreshStrategyTest {
|
||||
|
||||
private static final Instant TOKEN_CREATION = Instant.now().truncatedTo(SECONDS);
|
||||
|
||||
private final KeyGenerator keyGenerator = () -> "key";
|
||||
private final JwtConfig jwtConfig = mock(JwtConfig.class);
|
||||
private final Clock refreshClock = mock(Clock.class);
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
private KeyGenerator keyGenerator = () -> "key";
|
||||
private JwtConfig jwtConfig = mock(JwtConfig.class);
|
||||
|
||||
private Clock refreshClock = mock(Clock.class);
|
||||
|
||||
private JwtAccessTokenBuilder tokenBuilder;
|
||||
private PercentageJwtAccessTokenRefreshStrategy refreshStrategy;
|
||||
|
||||
@@ -59,11 +56,18 @@ public class PercentageJwtAccessTokenRefreshStrategyTest {
|
||||
public void initToken() {
|
||||
SecureKeyResolver keyResolver = mock(SecureKeyResolver.class);
|
||||
when(keyResolver.getSecureKey(any())).thenReturn(createSecureKey());
|
||||
|
||||
ScmConfiguration scmConfiguration = new ScmConfiguration();
|
||||
Clock creationClock = mock(Clock.class);
|
||||
when(creationClock.instant()).thenReturn(TOKEN_CREATION);
|
||||
|
||||
tokenBuilder = new JwtAccessTokenBuilderFactory(keyGenerator, keyResolver, jwtConfig, Collections.emptySet(), creationClock).create();
|
||||
tokenBuilder = new JwtAccessTokenBuilderFactory(
|
||||
keyGenerator,
|
||||
keyResolver,
|
||||
jwtConfig,
|
||||
Collections.emptySet(),
|
||||
creationClock,
|
||||
scmConfiguration
|
||||
).create();
|
||||
tokenBuilder.expiresIn(1, HOURS);
|
||||
tokenBuilder.refreshableFor(1, HOURS);
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.not;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
@@ -40,16 +39,22 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.argThat;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SecureKeyResolverTest
|
||||
{
|
||||
public class SecureKeyResolverTest {
|
||||
|
||||
@Test
|
||||
public void testGetSecureKey()
|
||||
{
|
||||
private SecureKeyResolver resolver;
|
||||
@Mock
|
||||
private ConfigurationEntryStore<SecureKey> store;
|
||||
@Mock
|
||||
private JwtSettingsStore jwtSettingsStore;
|
||||
private final JwtSettings settings = new JwtSettings(false, 100);
|
||||
|
||||
@Test
|
||||
public void testGetSecureKey() {
|
||||
SecureKey key = resolver.getSecureKey("test");
|
||||
|
||||
assertNotNull(key);
|
||||
@@ -61,6 +66,12 @@ public class SecureKeyResolverTest
|
||||
assertSame(key, sameKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearSecureKey() {
|
||||
resolver.deleteStore();
|
||||
verify(store).clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnRegeneratedKey() {
|
||||
when(jwtSettingsStore.get()).thenReturn(settings);
|
||||
@@ -78,37 +89,32 @@ public class SecureKeyResolverTest
|
||||
assertThat(sameRegeneratedKey.getCreationDate()).isEqualTo(regeneratedKey.getCreationDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveSigningKeyBytes()
|
||||
{
|
||||
@Test
|
||||
public void testResolveSigningKeyBytes() {
|
||||
SecureKey key = resolver.getSecureKey("test");
|
||||
|
||||
when(store.get("test")).thenReturn(key);
|
||||
when(jwtSettingsStore.get()).thenReturn(settings);
|
||||
|
||||
byte[] bytes = resolver.resolveSigningKeyBytes(null,
|
||||
Jwts.claims().setSubject("test"));
|
||||
Jwts.claims().setSubject("test"));
|
||||
|
||||
assertArrayEquals(key.getBytes(), bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveSigningKeyBytesWithoutKey()
|
||||
{
|
||||
@Test
|
||||
public void testResolveSigningKeyBytesWithoutKey() {
|
||||
byte[] bytes = resolver.resolveSigningKeyBytes(null, Jwts.claims().setSubject("test"));
|
||||
assertThat(bytes[0]).isEqualTo((byte) 42);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testResolveSigningKeyBytesWithoutSubject()
|
||||
{
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testResolveSigningKeyBytesWithoutSubject() {
|
||||
resolver.resolveSigningKeyBytes(null, Jwts.claims());
|
||||
}
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
@Before
|
||||
public void setUp() {
|
||||
ConfigurationEntryStoreFactory factory = mock(ConfigurationEntryStoreFactory.class);
|
||||
|
||||
when(factory.withType(any())).thenCallRealMethod();
|
||||
@@ -121,16 +127,4 @@ public class SecureKeyResolverTest
|
||||
doAnswer(invocation -> ((byte[]) invocation.getArguments()[0])[0] = 42).when(random).nextBytes(any());
|
||||
resolver = new SecureKeyResolver(factory, jwtSettingsStore, random);
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
private SecureKeyResolver resolver;
|
||||
|
||||
@Mock
|
||||
private ConfigurationEntryStore<SecureKey> store;
|
||||
|
||||
@Mock
|
||||
private JwtSettingsStore jwtSettingsStore;
|
||||
|
||||
private JwtSettings settings = new JwtSettings(false, 100);
|
||||
}
|
||||
|
||||
@@ -3059,10 +3059,10 @@
|
||||
eslint-plugin-react-hooks "^2.1.2"
|
||||
jest "^26.6.3"
|
||||
|
||||
"@scm-manager/integration-test-runner@^3.4.3":
|
||||
version "3.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@scm-manager/integration-test-runner/-/integration-test-runner-3.4.3.tgz#6a2e44f5c360fb1c40c3701cf9e8ddadd5031666"
|
||||
integrity sha512-tA3B5iDAsNWQgXUiMhnrz7sX5dc0674R5Xb+Fch5kSysxMjwn5gMeDUIXA6j5S6OXsp8jlIj/y70m5foplO2WQ==
|
||||
"@scm-manager/integration-test-runner@^3.5.0":
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@scm-manager/integration-test-runner/-/integration-test-runner-3.5.0.tgz#fdc9b1392d764a3db4aefff041c47a7c4f5bedce"
|
||||
integrity sha512-/Aj8FldRdv3boOQJyFlT6AJb4vtxeiuXj7EF578QpT2DqbD/fyUC3qeZnfo78wS7KvSzJKhxrJrLIoeFpg+4iw==
|
||||
dependencies:
|
||||
"@ffmpeg-installer/ffmpeg" "^1.0.20"
|
||||
"@octokit/rest" "^18.0.9"
|
||||
|
||||
Reference in New Issue
Block a user