mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-08 06:25:45 +01:00
Add flag to global config to enable/disable api keys as additional authentication method (#1606)
Add flag to global config to enable/disable API keys as additional authentication method. Fixes #1599
This commit is contained in:
2
gradle/changelog/api_keys_config.yaml
Normal file
2
gradle/changelog/api_keys_config.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
- type: Added
|
||||||
|
description: Add global flag to enable/disable api keys ([#1606](https://github.com/scm-manager/scm-manager/pull/1606))
|
||||||
@@ -197,6 +197,14 @@ public class ScmConfiguration implements Configuration {
|
|||||||
@XmlElement(name = "user-converter")
|
@XmlElement(name = "user-converter")
|
||||||
private boolean enabledUserConverter = false;
|
private boolean enabledUserConverter = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables api keys for all users.
|
||||||
|
*
|
||||||
|
* @since 2.16.0
|
||||||
|
*/
|
||||||
|
@XmlElement(name = "api-keys")
|
||||||
|
private boolean enabledApiKeys = true;
|
||||||
|
|
||||||
@XmlElement(name = "namespace-strategy")
|
@XmlElement(name = "namespace-strategy")
|
||||||
private String namespaceStrategy = "UsernameNamespaceStrategy";
|
private String namespaceStrategy = "UsernameNamespaceStrategy";
|
||||||
|
|
||||||
@@ -246,6 +254,7 @@ public class ScmConfiguration implements Configuration {
|
|||||||
this.releaseFeedUrl = other.releaseFeedUrl;
|
this.releaseFeedUrl = other.releaseFeedUrl;
|
||||||
this.mailDomainName = other.mailDomainName;
|
this.mailDomainName = other.mailDomainName;
|
||||||
this.enabledUserConverter = other.enabledUserConverter;
|
this.enabledUserConverter = other.enabledUserConverter;
|
||||||
|
this.enabledApiKeys = other.enabledApiKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -407,6 +416,16 @@ public class ScmConfiguration implements Configuration {
|
|||||||
return enabledUserConverter;
|
return enabledUserConverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the api keys are enabled.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the api keys is enabled
|
||||||
|
* @since 2.16.0
|
||||||
|
*/
|
||||||
|
public boolean isEnabledApiKeys() {
|
||||||
|
return enabledApiKeys;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isEnableProxy() {
|
public boolean isEnableProxy() {
|
||||||
return enableProxy;
|
return enableProxy;
|
||||||
}
|
}
|
||||||
@@ -584,6 +603,16 @@ public class ScmConfiguration implements Configuration {
|
|||||||
this.enabledUserConverter = enabledUserConverter;
|
this.enabledUserConverter = enabledUserConverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set {@code true} to enable api keys.
|
||||||
|
*
|
||||||
|
* @param enabledApiKeys {@code true} to enable api keys
|
||||||
|
* @since 2.16.0
|
||||||
|
*/
|
||||||
|
public void setEnabledApiKeys(boolean enabledApiKeys) {
|
||||||
|
this.enabledApiKeys = enabledApiKeys;
|
||||||
|
}
|
||||||
|
|
||||||
public void setNamespaceStrategy(String namespaceStrategy) {
|
public void setNamespaceStrategy(String namespaceStrategy) {
|
||||||
this.namespaceStrategy = namespaceStrategy;
|
this.namespaceStrategy = namespaceStrategy;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,7 +270,8 @@ public class TestData {
|
|||||||
" \"namespaceStrategy\": \"UsernameNamespaceStrategy\", \n" +
|
" \"namespaceStrategy\": \"UsernameNamespaceStrategy\", \n" +
|
||||||
" \"loginInfoUrl\": \"https://login-info.scm-manager.org/api/v1/login-info\",\n" +
|
" \"loginInfoUrl\": \"https://login-info.scm-manager.org/api/v1/login-info\",\n" +
|
||||||
" \"releaseFeedUrl\": \"https://scm-manager.org/download/rss.xml\",\n" +
|
" \"releaseFeedUrl\": \"https://scm-manager.org/download/rss.xml\",\n" +
|
||||||
" \"mailDomainName\": \"scm-manager.local\"\n" +
|
" \"mailDomainName\": \"scm-manager.local\", \n" +
|
||||||
|
" \"enabledApiKeys\": \"true\"\n" +
|
||||||
"}")
|
"}")
|
||||||
.put(createResourceUrl("config"))
|
.put(createResourceUrl("config"))
|
||||||
.then()
|
.then()
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ describe("Test config hooks", () => {
|
|||||||
enableProxy: false,
|
enableProxy: false,
|
||||||
enabledUserConverter: false,
|
enabledUserConverter: false,
|
||||||
enabledXsrfProtection: false,
|
enabledXsrfProtection: false,
|
||||||
|
enabledApiKeys: false,
|
||||||
forceBaseUrl: false,
|
forceBaseUrl: false,
|
||||||
loginAttemptLimit: 0,
|
loginAttemptLimit: 0,
|
||||||
loginAttemptLimitTimeout: 0,
|
loginAttemptLimitTimeout: 0,
|
||||||
|
|||||||
@@ -50,4 +50,5 @@ export type Config = HalRepresentation & {
|
|||||||
loginInfoUrl: string;
|
loginInfoUrl: string;
|
||||||
releaseFeedUrl: string;
|
releaseFeedUrl: string;
|
||||||
mailDomainName: string;
|
mailDomainName: string;
|
||||||
|
enabledApiKeys: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
"mail-domain-name": "Fallback E-Mail Domain Name",
|
"mail-domain-name": "Fallback E-Mail Domain Name",
|
||||||
"enabled-xsrf-protection": "XSRF Protection aktivieren",
|
"enabled-xsrf-protection": "XSRF Protection aktivieren",
|
||||||
"enabled-user-converter": "Benutzer Konverter aktivieren",
|
"enabled-user-converter": "Benutzer Konverter aktivieren",
|
||||||
|
"enabled-api-keys": "API Schlüssel aktivieren",
|
||||||
"namespace-strategy": "Namespace Strategie",
|
"namespace-strategy": "Namespace Strategie",
|
||||||
"login-info-url": "Login Info URL"
|
"login-info-url": "Login Info URL"
|
||||||
},
|
},
|
||||||
@@ -86,6 +87,7 @@
|
|||||||
"loginAttemptLimitTimeoutHelpText": "Timeout in Sekunden für Benutzer, die vorübergehend wegen zu vieler fehlgeschlagener Anmeldeversuche, deaktiviert wurden.",
|
"loginAttemptLimitTimeoutHelpText": "Timeout in Sekunden für Benutzer, die vorübergehend wegen zu vieler fehlgeschlagener Anmeldeversuche, deaktiviert wurden.",
|
||||||
"enableXsrfProtectionHelpText": "Xsrf Cookie Protection aktivieren. Hinweis: Dieses Feature befindet sich noch im Experimentalstatus.",
|
"enableXsrfProtectionHelpText": "Xsrf Cookie Protection aktivieren. Hinweis: Dieses Feature befindet sich noch im Experimentalstatus.",
|
||||||
"enabledUserConverterHelpText": "Benutzer Konverter aktivieren. Interne Benutzer werden beim Einloggen über ein Fremdsystem zu externen Benutzern konvertiert.",
|
"enabledUserConverterHelpText": "Benutzer Konverter aktivieren. Interne Benutzer werden beim Einloggen über ein Fremdsystem zu externen Benutzern konvertiert.",
|
||||||
|
"enabledApiKeysHelpText": "API Schlüssel aktivieren. Alle Benutzer dürfen API Schlüssel als zusätzliche Methode zur Authentifizierung nutzen.",
|
||||||
"nameSpaceStrategyHelpText": "Strategie für Namespaces.",
|
"nameSpaceStrategyHelpText": "Strategie für Namespaces.",
|
||||||
"loginInfoUrlHelpText": "URL zu der Login Information (Plugin und Feature Tipps auf der Login Seite). Um die Login Information zu deaktivieren, kann das Feld leer gelassen werden."
|
"loginInfoUrlHelpText": "URL zu der Login Information (Plugin und Feature Tipps auf der Login Seite). Um die Login Information zu deaktivieren, kann das Feld leer gelassen werden."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
"mail-domain-name": "Fallback Mail Domain Name",
|
"mail-domain-name": "Fallback Mail Domain Name",
|
||||||
"enabled-xsrf-protection": "Enabled XSRF Protection",
|
"enabled-xsrf-protection": "Enabled XSRF Protection",
|
||||||
"enabled-user-converter": "Enabled User Converter",
|
"enabled-user-converter": "Enabled User Converter",
|
||||||
|
"enabled-api-keys": "Enabled API Keys",
|
||||||
"namespace-strategy": "Namespace Strategy",
|
"namespace-strategy": "Namespace Strategy",
|
||||||
"login-info-url": "Login Info URL"
|
"login-info-url": "Login Info URL"
|
||||||
},
|
},
|
||||||
@@ -86,6 +87,7 @@
|
|||||||
"loginAttemptLimitTimeoutHelpText": "Timeout in seconds for users which are temporary disabled, because of too many failed login attempts.",
|
"loginAttemptLimitTimeoutHelpText": "Timeout in seconds for users which are temporary disabled, because of too many failed login attempts.",
|
||||||
"enableXsrfProtectionHelpText": "Enable XSRF Cookie Protection. Note: This feature is still experimental.",
|
"enableXsrfProtectionHelpText": "Enable XSRF Cookie Protection. Note: This feature is still experimental.",
|
||||||
"enabledUserConverterHelpText": "Enable User Converter. Internal users will automatically be converted to external on their first login using an external system.",
|
"enabledUserConverterHelpText": "Enable User Converter. Internal users will automatically be converted to external on their first login using an external system.",
|
||||||
|
"enabledApiKeysHelpText": "Enable API Keys. API Keys can be used as additional authentication method by every user.",
|
||||||
"nameSpaceStrategyHelpText": "The namespace strategy.",
|
"nameSpaceStrategyHelpText": "The namespace strategy.",
|
||||||
"loginInfoUrlHelpText": "URL to login information (plugin and feature tips at login page). If this is omitted, no login information will be displayed."
|
"loginInfoUrlHelpText": "URL to login information (plugin and feature tips at login page). If this is omitted, no login information will be displayed."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ class ConfigForm extends React.Component<Props, State> {
|
|||||||
mailDomainName={config.mailDomainName}
|
mailDomainName={config.mailDomainName}
|
||||||
enabledXsrfProtection={config.enabledXsrfProtection}
|
enabledXsrfProtection={config.enabledXsrfProtection}
|
||||||
enabledUserConverter={config.enabledUserConverter}
|
enabledUserConverter={config.enabledUserConverter}
|
||||||
|
enabledApiKeys={config.enabledApiKeys}
|
||||||
namespaceStrategy={config.namespaceStrategy}
|
namespaceStrategy={config.namespaceStrategy}
|
||||||
onChange={(isValid, changedValue, name) => this.onChange(isValid, changedValue, name)}
|
onChange={(isValid, changedValue, name) => this.onChange(isValid, changedValue, name)}
|
||||||
hasUpdatePermission={configUpdatePermission}
|
hasUpdatePermission={configUpdatePermission}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ type Props = WithTranslation & {
|
|||||||
mailDomainName: string;
|
mailDomainName: string;
|
||||||
enabledXsrfProtection: boolean;
|
enabledXsrfProtection: boolean;
|
||||||
enabledUserConverter: boolean;
|
enabledUserConverter: boolean;
|
||||||
|
enabledApiKeys: boolean;
|
||||||
namespaceStrategy: string;
|
namespaceStrategy: string;
|
||||||
namespaceStrategies?: NamespaceStrategies;
|
namespaceStrategies?: NamespaceStrategies;
|
||||||
onChange: (p1: boolean, p2: any, p3: string) => void;
|
onChange: (p1: boolean, p2: any, p3: string) => void;
|
||||||
@@ -56,6 +57,7 @@ class GeneralSettings extends React.Component<Props> {
|
|||||||
mailDomainName,
|
mailDomainName,
|
||||||
enabledXsrfProtection,
|
enabledXsrfProtection,
|
||||||
enabledUserConverter,
|
enabledUserConverter,
|
||||||
|
enabledApiKeys,
|
||||||
anonymousMode,
|
anonymousMode,
|
||||||
namespaceStrategy,
|
namespaceStrategy,
|
||||||
hasUpdatePermission,
|
hasUpdatePermission,
|
||||||
@@ -163,6 +165,16 @@ class GeneralSettings extends React.Component<Props> {
|
|||||||
helpText={t("help.mailDomainNameHelpText")}
|
helpText={t("help.mailDomainNameHelpText")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="column is-half">
|
||||||
|
<Checkbox
|
||||||
|
label={t("general-settings.enabled-api-keys")}
|
||||||
|
onChange={this.handleEnabledApiKeysChange}
|
||||||
|
checked={enabledApiKeys}
|
||||||
|
title={t("general-settings.enabled-api-keys")}
|
||||||
|
disabled={!hasUpdatePermission}
|
||||||
|
helpText={t("help.enabledApiKeysHelpText")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -195,6 +207,9 @@ class GeneralSettings extends React.Component<Props> {
|
|||||||
handleMailDomainNameChange = (value: string) => {
|
handleMailDomainNameChange = (value: string) => {
|
||||||
this.props.onChange(true, value, "mailDomainName");
|
this.props.onChange(true, value, "mailDomainName");
|
||||||
};
|
};
|
||||||
|
handleEnabledApiKeysChange = (value: boolean) => {
|
||||||
|
this.props.onChange(true, value, "enabledApiKeys");
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTranslation("config")(GeneralSettings);
|
export default withTranslation("config")(GeneralSettings);
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ public class ConfigDto extends HalRepresentation implements UpdateConfigDto {
|
|||||||
private long loginAttemptLimitTimeout;
|
private long loginAttemptLimitTimeout;
|
||||||
private boolean enabledXsrfProtection;
|
private boolean enabledXsrfProtection;
|
||||||
private boolean enabledUserConverter;
|
private boolean enabledUserConverter;
|
||||||
|
private boolean enabledApiKeys;
|
||||||
private String namespaceStrategy;
|
private String namespaceStrategy;
|
||||||
private String loginInfoUrl;
|
private String loginInfoUrl;
|
||||||
private String releaseFeedUrl;
|
private String releaseFeedUrl;
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ import com.google.common.base.Strings;
|
|||||||
import de.otto.edison.hal.Embedded;
|
import de.otto.edison.hal.Embedded;
|
||||||
import de.otto.edison.hal.Links;
|
import de.otto.edison.hal.Links;
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.group.GroupCollector;
|
import sonia.scm.group.GroupCollector;
|
||||||
import sonia.scm.user.EMail;
|
import sonia.scm.user.EMail;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserManager;
|
|
||||||
import sonia.scm.user.UserPermissions;
|
import sonia.scm.user.UserPermissions;
|
||||||
import sonia.scm.web.EdisonHalAppender;
|
import sonia.scm.web.EdisonHalAppender;
|
||||||
|
|
||||||
@@ -44,15 +44,15 @@ import static de.otto.edison.hal.Links.linkingTo;
|
|||||||
public class MeDtoFactory extends HalAppenderMapper {
|
public class MeDtoFactory extends HalAppenderMapper {
|
||||||
|
|
||||||
private final ResourceLinks resourceLinks;
|
private final ResourceLinks resourceLinks;
|
||||||
private final UserManager userManager;
|
|
||||||
private final GroupCollector groupCollector;
|
private final GroupCollector groupCollector;
|
||||||
|
private final ScmConfiguration scmConfiguration;
|
||||||
private final EMail eMail;
|
private final EMail eMail;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MeDtoFactory(ResourceLinks resourceLinks, UserManager userManager, GroupCollector groupCollector, EMail eMail) {
|
public MeDtoFactory(ResourceLinks resourceLinks, GroupCollector groupCollector, ScmConfiguration scmConfiguration, EMail eMail) {
|
||||||
this.resourceLinks = resourceLinks;
|
this.resourceLinks = resourceLinks;
|
||||||
this.userManager = userManager;
|
|
||||||
this.groupCollector = groupCollector;
|
this.groupCollector = groupCollector;
|
||||||
|
this.scmConfiguration = scmConfiguration;
|
||||||
this.eMail = eMail;
|
this.eMail = eMail;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ public class MeDtoFactory extends HalAppenderMapper {
|
|||||||
if (!user.isExternal() && UserPermissions.changePassword(user).isPermitted()) {
|
if (!user.isExternal() && UserPermissions.changePassword(user).isPermitted()) {
|
||||||
linksBuilder.single(link("password", resourceLinks.me().passwordChange()));
|
linksBuilder.single(link("password", resourceLinks.me().passwordChange()));
|
||||||
}
|
}
|
||||||
if (UserPermissions.changeApiKeys(user).isPermitted()) {
|
if (scmConfiguration.isEnabledApiKeys() && UserPermissions.changeApiKeys(user).isPermitted()) {
|
||||||
linksBuilder.single(link("apiKeys", resourceLinks.apiKeyCollection().self(user.getName())));
|
linksBuilder.single(link("apiKeys", resourceLinks.apiKeyCollection().self(user.getName())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import org.apache.shiro.authz.AuthorizationException;
|
|||||||
import org.apache.shiro.realm.AuthenticatingRealm;
|
import org.apache.shiro.realm.AuthenticatingRealm;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.plugin.Extension;
|
import sonia.scm.plugin.Extension;
|
||||||
import sonia.scm.repository.RepositoryRole;
|
import sonia.scm.repository.RepositoryRole;
|
||||||
import sonia.scm.repository.RepositoryRoleManager;
|
import sonia.scm.repository.RepositoryRoleManager;
|
||||||
@@ -53,12 +54,14 @@ public class ApiKeyRealm extends AuthenticatingRealm {
|
|||||||
private final ApiKeyService apiKeyService;
|
private final ApiKeyService apiKeyService;
|
||||||
private final DAORealmHelper helper;
|
private final DAORealmHelper helper;
|
||||||
private final RepositoryRoleManager repositoryRoleManager;
|
private final RepositoryRoleManager repositoryRoleManager;
|
||||||
|
private final ScmConfiguration scmConfiguration;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ApiKeyRealm(ApiKeyService apiKeyService, DAORealmHelperFactory helperFactory, RepositoryRoleManager repositoryRoleManager) {
|
public ApiKeyRealm(ApiKeyService apiKeyService, DAORealmHelperFactory helperFactory, RepositoryRoleManager repositoryRoleManager, ScmConfiguration scmConfiguration) {
|
||||||
this.apiKeyService = apiKeyService;
|
this.apiKeyService = apiKeyService;
|
||||||
this.helper = helperFactory.create(NAME);
|
this.helper = helperFactory.create(NAME);
|
||||||
this.repositoryRoleManager = repositoryRoleManager;
|
this.repositoryRoleManager = repositoryRoleManager;
|
||||||
|
this.scmConfiguration = scmConfiguration;
|
||||||
setAuthenticationTokenClass(BearerToken.class);
|
setAuthenticationTokenClass(BearerToken.class);
|
||||||
setCredentialsMatcher(new AllowAllCredentialsMatcher());
|
setCredentialsMatcher(new AllowAllCredentialsMatcher());
|
||||||
}
|
}
|
||||||
@@ -66,7 +69,7 @@ public class ApiKeyRealm extends AuthenticatingRealm {
|
|||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("java:S4738") // java.util.Base64 has no canDecode method
|
@SuppressWarnings("java:S4738") // java.util.Base64 has no canDecode method
|
||||||
public boolean supports(AuthenticationToken token) {
|
public boolean supports(AuthenticationToken token) {
|
||||||
if (token instanceof UsernamePasswordToken || token instanceof BearerToken) {
|
if (scmConfiguration.isEnabledApiKeys() && (token instanceof UsernamePasswordToken || token instanceof BearerToken)) {
|
||||||
boolean isBase64 = BaseEncoding.base64().canDecode(getPassword(token));
|
boolean isBase64 = BaseEncoding.base64().canDecode(getPassword(token));
|
||||||
if (!isBase64) {
|
if (!isBase64) {
|
||||||
LOG.debug("Ignoring non base 64 token; this is probably a JWT token or a normal password");
|
LOG.debug("Ignoring non base 64 token; this is probably a JWT token or a normal password");
|
||||||
|
|||||||
@@ -37,10 +37,10 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
|||||||
import org.mockito.junit.jupiter.MockitoSettings;
|
import org.mockito.junit.jupiter.MockitoSettings;
|
||||||
import org.mockito.quality.Strictness;
|
import org.mockito.quality.Strictness;
|
||||||
import sonia.scm.SCMContext;
|
import sonia.scm.SCMContext;
|
||||||
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.group.GroupCollector;
|
import sonia.scm.group.GroupCollector;
|
||||||
import sonia.scm.user.EMail;
|
import sonia.scm.user.EMail;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserManager;
|
|
||||||
import sonia.scm.user.UserTestData;
|
import sonia.scm.user.UserTestData;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -56,15 +56,15 @@ class MeDtoFactoryTest {
|
|||||||
|
|
||||||
private final URI baseUri = URI.create("https://scm.hitchhiker.com/scm/");
|
private final URI baseUri = URI.create("https://scm.hitchhiker.com/scm/");
|
||||||
|
|
||||||
@Mock
|
|
||||||
private UserManager userManager;
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private GroupCollector groupCollector;
|
private GroupCollector groupCollector;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private Subject subject;
|
private Subject subject;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ScmConfiguration scmConfiguration;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private EMail eMail;
|
private EMail eMail;
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ class MeDtoFactoryTest {
|
|||||||
void setUpContext() {
|
void setUpContext() {
|
||||||
ThreadContext.bind(subject);
|
ThreadContext.bind(subject);
|
||||||
ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||||
meDtoFactory = new MeDtoFactory(resourceLinks, userManager, groupCollector, eMail);
|
meDtoFactory = new MeDtoFactory(resourceLinks, groupCollector, scmConfiguration, eMail);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
@@ -160,8 +160,6 @@ class MeDtoFactoryTest {
|
|||||||
User user = UserTestData.createTrillian();
|
User user = UserTestData.createTrillian();
|
||||||
prepareSubject(user);
|
prepareSubject(user);
|
||||||
|
|
||||||
when(userManager.isTypeDefault(user)).thenReturn(true);
|
|
||||||
|
|
||||||
MeDto dto = meDtoFactory.create();
|
MeDto dto = meDtoFactory.create();
|
||||||
assertThat(dto.getLinks().getLinkBy("password")).isNotPresent();
|
assertThat(dto.getLinks().getLinkBy("password")).isNotPresent();
|
||||||
}
|
}
|
||||||
@@ -228,4 +226,38 @@ class MeDtoFactoryTest {
|
|||||||
assertThat(dto.getMail()).isNull();
|
assertThat(dto.getMail()).isNull();
|
||||||
assertThat(dto.getFallbackMail()).isEqualTo("trillian@hitchhiker.local");
|
assertThat(dto.getFallbackMail()).isEqualTo("trillian@hitchhiker.local");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAppendApiKeysLink() {
|
||||||
|
User user = UserTestData.createTrillian();
|
||||||
|
prepareSubject(user);
|
||||||
|
|
||||||
|
when(scmConfiguration.isEnabledApiKeys()).thenReturn(true);
|
||||||
|
when(subject.isPermitted("user:changeApiKeys:trillian")).thenReturn(true);
|
||||||
|
|
||||||
|
MeDto dto = meDtoFactory.create();
|
||||||
|
assertThat(dto.getLinks().getLinkBy("apiKeys").get().getHref()).isEqualTo("https://scm.hitchhiker.com/scm/v2/users/trillian/api_keys");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotAppendApiKeysLinkIfMissingPermission() {
|
||||||
|
User user = UserTestData.createTrillian();
|
||||||
|
prepareSubject(user);
|
||||||
|
|
||||||
|
when(subject.isPermitted("user:changeApiKeys:trillian")).thenReturn(false);
|
||||||
|
|
||||||
|
MeDto dto = meDtoFactory.create();
|
||||||
|
assertThat(dto.getLinks().getLinkBy("apiKeys")).isNotPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotAppendApiKeysLinkIfConfigDisabled() {
|
||||||
|
User user = UserTestData.createTrillian();
|
||||||
|
prepareSubject(user);
|
||||||
|
when(scmConfiguration.isEnabledApiKeys()).thenReturn(false);
|
||||||
|
when(subject.isPermitted("user:changeApiKeys:trillian")).thenReturn(true);
|
||||||
|
|
||||||
|
MeDto dto = meDtoFactory.create();
|
||||||
|
assertThat(dto.getLinks().getLinkBy("apiKeys")).isNotPresent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import org.mockito.ArgumentCaptor;
|
|||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import sonia.scm.ContextEntry;
|
import sonia.scm.ContextEntry;
|
||||||
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.group.GroupCollector;
|
import sonia.scm.group.GroupCollector;
|
||||||
import sonia.scm.security.ApiKey;
|
import sonia.scm.security.ApiKey;
|
||||||
import sonia.scm.security.ApiKeyService;
|
import sonia.scm.security.ApiKeyService;
|
||||||
@@ -98,6 +99,9 @@ public class MeResourceTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private ApiKeyService apiKeyService;
|
private ApiKeyService apiKeyService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ScmConfiguration scmConfiguration;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private EMail eMail;
|
private EMail eMail;
|
||||||
|
|
||||||
@@ -132,6 +136,7 @@ public class MeResourceTest {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldReturnCurrentlyAuthenticatedUser() throws URISyntaxException, UnsupportedEncodingException {
|
public void shouldReturnCurrentlyAuthenticatedUser() throws URISyntaxException, UnsupportedEncodingException {
|
||||||
applyUserToSubject(originalUser);
|
applyUserToSubject(originalUser);
|
||||||
|
when(scmConfiguration.isEnabledApiKeys()).thenReturn(true);
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest.get("/" + MeResource.ME_PATH_V2);
|
MockHttpRequest request = MockHttpRequest.get("/" + MeResource.ME_PATH_V2);
|
||||||
request.accept(VndMediaType.ME);
|
request.accept(VndMediaType.ME);
|
||||||
@@ -283,14 +288,14 @@ public class MeResourceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldIgnoreInvalidNewApiKey() throws URISyntaxException, UnsupportedEncodingException {
|
public void shouldIgnoreInvalidNewApiKey() throws URISyntaxException {
|
||||||
when(apiKeyService.createNewKey("trillian","guide", "READ"))
|
when(apiKeyService.createNewKey("trillian","guide", "READ"))
|
||||||
.thenReturn(new ApiKeyService.CreationResult("abc", "1"));
|
.thenReturn(new ApiKeyService.CreationResult("abc", "1"));
|
||||||
|
|
||||||
final MockHttpRequest request = MockHttpRequest
|
final MockHttpRequest request = MockHttpRequest
|
||||||
.post("/" + MeResource.ME_PATH_V2 + "api_keys/")
|
.post("/" + MeResource.ME_PATH_V2 + "api_keys/")
|
||||||
.contentType(VndMediaType.API_KEY)
|
.contentType(VndMediaType.API_KEY)
|
||||||
.content("{\"displayName\":\"guide\",\"pemissionRole\":\"\"}".getBytes());
|
.content("{\"displayName\":\"guide\",\"permissionRole\":\"\"}".getBytes());
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
|||||||
import org.mockito.Answers;
|
import org.mockito.Answers;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.repository.RepositoryRole;
|
import sonia.scm.repository.RepositoryRole;
|
||||||
import sonia.scm.repository.RepositoryRoleManager;
|
import sonia.scm.repository.RepositoryRoleManager;
|
||||||
|
|
||||||
@@ -60,6 +61,8 @@ class ApiKeyRealmTest {
|
|||||||
DAORealmHelper.AuthenticationInfoBuilder authenticationInfoBuilder;
|
DAORealmHelper.AuthenticationInfoBuilder authenticationInfoBuilder;
|
||||||
@Mock
|
@Mock
|
||||||
RepositoryRoleManager repositoryRoleManager;
|
RepositoryRoleManager repositoryRoleManager;
|
||||||
|
@Mock
|
||||||
|
ScmConfiguration scmConfiguration;
|
||||||
|
|
||||||
ApiKeyRealm realm;
|
ApiKeyRealm realm;
|
||||||
|
|
||||||
@@ -67,7 +70,7 @@ class ApiKeyRealmTest {
|
|||||||
void initRealmHelper() {
|
void initRealmHelper() {
|
||||||
lenient().when(helperFactory.create("ApiTokenRealm")).thenReturn(helper);
|
lenient().when(helperFactory.create("ApiTokenRealm")).thenReturn(helper);
|
||||||
lenient().when(helper.authenticationInfoBuilder(any())).thenReturn(authenticationInfoBuilder);
|
lenient().when(helper.authenticationInfoBuilder(any())).thenReturn(authenticationInfoBuilder);
|
||||||
realm = new ApiKeyRealm(apiKeyService, helperFactory, repositoryRoleManager);
|
realm = new ApiKeyRealm(apiKeyService, helperFactory, repositoryRoleManager, scmConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -106,6 +109,16 @@ class ApiKeyRealmTest {
|
|||||||
assertThat(supports).isFalse();
|
assertThat(supports).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldIgnoreIfConfigIsDisabled() {
|
||||||
|
when(scmConfiguration.isEnabledApiKeys()).thenReturn(false);
|
||||||
|
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||||
|
|
||||||
|
boolean supports = realm.supports(token);
|
||||||
|
|
||||||
|
assertThat(supports).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldIgnoreNonBase64Tokens() {
|
void shouldIgnoreNonBase64Tokens() {
|
||||||
UsernamePasswordToken token = new UsernamePasswordToken("trillian", "My&SecretPassword");
|
UsernamePasswordToken token = new UsernamePasswordToken("trillian", "My&SecretPassword");
|
||||||
|
|||||||
Reference in New Issue
Block a user