diff --git a/CHANGELOG.md b/CHANGELOG.md
index 97644147c9..148eb799f3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [2.7.1] - 2020-10-14
+### Fixed
+- Null Pointer Exception on anonymous migration with deleted repositories ([#1371](https://github.com/scm-manager/scm-manager/pull/1371))
+- Null Pointer Exception on parsing SVN properties ([#1373](https://github.com/scm-manager/scm-manager/pull/1373))
+
+### Changed
+- Reduced logging for invalid JWT or api keys ([#1374](https://github.com/scm-manager/scm-manager/pull/1374))
+
## [2.7.0] - 2020-10-12
### Added
- Users can create API keys with limited permissions ([#1359](https://github.com/scm-manager/scm-manager/pull/1359))
diff --git a/pom.xml b/pom.xml
index 0a7d225c3b..506c11920f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -429,7 +429,7 @@
junit
junit
- 4.13
+ 4.13.1
test
diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBrowseCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBrowseCommand.java
index 00a135dd82..e83a0dc365 100644
--- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBrowseCommand.java
+++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBrowseCommand.java
@@ -193,23 +193,28 @@ public class SvnBrowseCommand extends AbstractSvnCommand
repository.getDir(entry.getRelativePath(), revision, properties, (Collection) null);
- String[] externals = properties.getStringValue(SVNProperty.EXTERNALS).split("\\r?\\n");
- for (String external : externals) {
- String subRepoUrl = "";
- String subRepoPath = "";
- for (String externalPart : external.split(" ")) {
- if (shouldSetExternal(externalPart)) {
- subRepoUrl = externalPart;
- } else if (!externalPart.contains("-r")) {
- subRepoPath = externalPart;
+ String externals = properties.getStringValue(SVNProperty.EXTERNALS);
+
+ if (!Strings.isNullOrEmpty(externals)) {
+ String[] splitExternals = externals.split("\\r?\\n");
+ for (String external : splitExternals) {
+ String subRepoUrl = "";
+ String subRepoPath = "";
+ for (String externalPart : external.split(" ")) {
+ if (shouldSetExternal(externalPart)) {
+ subRepoUrl = externalPart;
+ } else if (!externalPart.contains("-r")) {
+ subRepoPath = externalPart;
+ }
+ }
+
+ if (Util.isNotEmpty(external)) {
+ SubRepository subRepository = new SubRepository(subRepoUrl);
+ fileObject.addChild(createSubRepoDirectory(subRepository, subRepoPath));
}
}
-
- if (Util.isNotEmpty(external)) {
- SubRepository subRepository = new SubRepository(subRepoUrl);
- fileObject.addChild(createSubRepoDirectory(subRepository, subRepoPath));
- }
}
+
} catch (SVNException ex) {
logger.error("could not fetch file properties", ex);
}
diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/AbstractSvnCommandTestBase.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/AbstractSvnCommandTestBase.java
index b6d9e4ba26..8228d42f39 100644
--- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/AbstractSvnCommandTestBase.java
+++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/AbstractSvnCommandTestBase.java
@@ -21,15 +21,13 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import org.junit.After;
-//~--- JDK imports ------------------------------------------------------------
-
import java.io.IOException;
/**
diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBrowseCommandTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBrowseCommandTest.java
index 3d3a3131e5..698fec8f37 100644
--- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBrowseCommandTest.java
+++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBrowseCommandTest.java
@@ -24,10 +24,20 @@
package sonia.scm.repository.spi;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.tmatesoft.svn.core.SVNDepth;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNPropertyValue;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.wc.SVNClientManager;
+import org.tmatesoft.svn.core.wc.SVNRevision;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.FileObject;
+import sonia.scm.repository.SubRepository;
+import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
@@ -36,14 +46,16 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
- *
* @author Sebastian Sdorra
*/
-public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase
-{
+public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase {
+
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Test
public void testBrowseWithFilePath() {
@@ -83,7 +95,6 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase
/**
* Method description
*
- *
* @throws IOException
*/
@Test
@@ -260,14 +271,51 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase
.containsExactly("e.txt");
}
+ @Test
+ public void shouldNotAddSubRepositoryIfNotSetInProperties() {
+ BrowserResult browserResult = new SvnBrowseCommand(createContext()).getBrowserResult(new BrowseCommandRequest());
+
+ boolean containsSubRepository = browserResult.getFile().getChildren()
+ .stream()
+ .anyMatch(c -> c.getSubRepository() != null);
+
+ assertFalse(containsSubRepository);
+ }
+
+ @Test
+ public void shouldAddSubRepositoryIfSetInProperties() throws IOException, SVNException {
+ String externalLink = "https://scm-manager.org/svn-repo";
+ SvnContext svnContext = setProp("svn:externals", "external -r1 " + externalLink);
+
+ BrowserResult browserResult = new SvnBrowseCommand(svnContext).getBrowserResult(new BrowseCommandRequest());
+
+ boolean containsSubRepository = browserResult.getFile().getChildren()
+ .stream()
+ .anyMatch(c -> c.getSubRepository().getRepositoryUrl().equals(externalLink));
+
+ assertTrue(containsSubRepository);
+ }
+
+ private SvnContext setProp(String propName, String propValue) throws SVNException, IOException {
+ SvnContext context = createContext();
+ SVNClientManager client = SVNClientManager.newInstance();
+
+ File workingCopyDirectory = temporaryFolder.newFolder("working-copy");
+
+ SVNURL url = SVNURL.fromFile(context.getDirectory());
+ client.getUpdateClient().doCheckout(url, workingCopyDirectory, SVNRevision.HEAD, SVNRevision.HEAD, SVNDepth.INFINITY, true);
+
+ client.getWCClient().doSetProperty(workingCopyDirectory, propName, SVNPropertyValue.create(propValue), true, SVNDepth.UNKNOWN, null, null);
+ client.getCommitClient().doCommit(new File[]{workingCopyDirectory}, false, "set prop", null, null, false, false, SVNDepth.UNKNOWN);
+ return context;
+ }
+
/**
* Method description
*
- *
* @return
*/
- private SvnBrowseCommand createCommand()
- {
+ private SvnBrowseCommand createCommand() {
return new SvnBrowseCommand(createContext());
}
@@ -276,14 +324,11 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase
/**
* Method description
*
- *
* @param foList
* @param name
- *
* @return
*/
- private FileObject getFileObject(Collection foList, String name)
- {
+ private FileObject getFileObject(Collection foList, String name) {
return foList.stream()
.filter(f -> name.equals(f.getName()))
.findFirst()
diff --git a/scm-ui/ui-components/src/modals/FullscreenModal.tsx b/scm-ui/ui-components/src/modals/FullscreenModal.tsx
index fb95712708..72951da5ca 100644
--- a/scm-ui/ui-components/src/modals/FullscreenModal.tsx
+++ b/scm-ui/ui-components/src/modals/FullscreenModal.tsx
@@ -38,7 +38,7 @@ type Props = {
const FullSizedModal = styled(Modal)`
& .modal-card {
width: 98%;
- max-height: 100vh;
+ max-height: 97vh;
}
`;
diff --git a/scm-webapp/src/main/java/sonia/scm/security/ApiKeyRealm.java b/scm-webapp/src/main/java/sonia/scm/security/ApiKeyRealm.java
index ed2954eb32..672f972dab 100644
--- a/scm-webapp/src/main/java/sonia/scm/security/ApiKeyRealm.java
+++ b/scm-webapp/src/main/java/sonia/scm/security/ApiKeyRealm.java
@@ -30,6 +30,8 @@ import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.realm.AuthenticatingRealm;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.RepositoryRole;
import sonia.scm.repository.RepositoryRoleManager;
@@ -43,6 +45,8 @@ import static com.google.common.base.Preconditions.checkArgument;
@Extension
public class ApiKeyRealm extends AuthenticatingRealm {
+ private static final Logger LOG = LoggerFactory.getLogger(ApiKeyRealm.class);
+
private final ApiKeyService apiKeyService;
private final DAORealmHelper helper;
private final RepositoryRoleManager repositoryRoleManager;
@@ -58,7 +62,14 @@ public class ApiKeyRealm extends AuthenticatingRealm {
@Override
public boolean supports(AuthenticationToken token) {
- return token instanceof UsernamePasswordToken || token instanceof BearerToken;
+ if (token instanceof UsernamePasswordToken || token instanceof BearerToken) {
+ boolean containsDot = getPassword(token).contains(".");
+ if (containsDot) {
+ LOG.debug("Ignoring token with at least one dot ('.'); this is probably a JWT token");
+ }
+ return !containsDot;
+ }
+ return false;
}
@Override
@@ -74,6 +85,7 @@ public class ApiKeyRealm extends AuthenticatingRealm {
private AuthenticationInfo buildAuthenticationInfo(AuthenticationToken token, ApiKeyService.CheckResult check) {
RepositoryRole repositoryRole = determineRole(check);
Scope scope = createScope(repositoryRole);
+ LOG.debug("login for user {} with api key limited to role {}", check.getUser(), check.getPermissionRole());
return helper
.authenticationInfoBuilder(check.getUser())
.withSessionId(getPrincipal(token))
diff --git a/scm-webapp/src/main/java/sonia/scm/security/BearerRealm.java b/scm-webapp/src/main/java/sonia/scm/security/BearerRealm.java
index e037590db6..f554c92d04 100644
--- a/scm-webapp/src/main/java/sonia/scm/security/BearerRealm.java
+++ b/scm-webapp/src/main/java/sonia/scm/security/BearerRealm.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.security;
import com.google.common.annotations.VisibleForTesting;
@@ -29,6 +29,8 @@ import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
import org.apache.shiro.realm.AuthenticatingRealm;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import sonia.scm.group.GroupDAO;
import sonia.scm.plugin.Extension;
import sonia.scm.user.UserDAO;
@@ -54,6 +56,7 @@ public class BearerRealm extends AuthenticatingRealm
@VisibleForTesting
static final String REALM = "BearerRealm";
+ private static final Logger LOG = LoggerFactory.getLogger(BearerRealm.class);
/** dao realm helper */
private final DAORealmHelper helper;
@@ -76,7 +79,17 @@ public class BearerRealm extends AuthenticatingRealm
setAuthenticationTokenClass(BearerToken.class);
}
- //~--- methods --------------------------------------------------------------
+ @Override
+ public boolean supports(AuthenticationToken token) {
+ if (token instanceof BearerToken) {
+ boolean containsDot = ((BearerToken) token).getCredentials().contains(".");
+ if (!containsDot) {
+ LOG.debug("Ignoring token without a dot ('.'); this probably is an API key");
+ }
+ return containsDot;
+ }
+ return false;
+ }
/**
* Validates the given bearer token and retrieves authentication data from
diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java
index 310ce25c5c..e80fca770d 100644
--- a/scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java
+++ b/scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.update.repository;
import org.slf4j.Logger;
@@ -89,9 +89,13 @@ public class PublicFlagUpdateStep implements UpdateStep {
.filter(V1Repository::isPublic)
.forEach(v1Repository -> {
Repository v2Repository = repositoryDAO.get(v1Repository.getId());
- LOG.info(String.format("Add RepositoryRole 'READ' to _anonymous user for repository: %s - %s/%s", v2Repository.getId(), v2Repository.getNamespace(), v2Repository.getName()));
- v2Repository.addPermission(new RepositoryPermission(v2AnonymousUser.getId(), "READ", false));
- repositoryDAO.modify(v2Repository);
+ if (v2Repository != null) {
+ LOG.info("Add RepositoryRole 'READ' to _anonymous user for repository: {} - {}/{}", v2Repository.getId(), v2Repository.getNamespace(), v2Repository.getName());
+ v2Repository.addPermission(new RepositoryPermission(v2AnonymousUser.getId(), "READ", false));
+ repositoryDAO.modify(v2Repository);
+ } else {
+ LOG.info("Repository no longer found for id {}; could not set permission for former anonymous mode", v1Repository.getId());
+ }
});
}
diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/TokenRefreshFilter.java b/scm-webapp/src/main/java/sonia/scm/web/security/TokenRefreshFilter.java
index 47da2d5718..be691da1d8 100644
--- a/scm-webapp/src/main/java/sonia/scm/web/security/TokenRefreshFilter.java
+++ b/scm-webapp/src/main/java/sonia/scm/web/security/TokenRefreshFilter.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.web.security;
import org.apache.shiro.authc.AuthenticationException;
@@ -90,6 +90,10 @@ public class TokenRefreshFilter extends HttpFilter {
private void examineToken(HttpServletRequest request, HttpServletResponse response, BearerToken token) {
AccessToken accessToken;
+ if (!token.getCredentials().contains(".")) {
+ LOG.trace("Ignoring token without dot. This probably is an API key, no JWT");
+ return;
+ }
try {
accessToken = resolver.resolve(token);
} catch (AuthenticationException e) {
diff --git a/scm-webapp/src/test/java/sonia/scm/security/ApiKeyRealmTest.java b/scm-webapp/src/test/java/sonia/scm/security/ApiKeyRealmTest.java
index 48a014dfb2..0f4171b8d9 100644
--- a/scm-webapp/src/test/java/sonia/scm/security/ApiKeyRealmTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/security/ApiKeyRealmTest.java
@@ -96,6 +96,15 @@ class ApiKeyRealmTest {
assertThrows(AuthorizationException.class, () -> realm.doGetAuthenticationInfo(token));
}
+ @Test
+ void shouldIgnoreTokensWithDots() {
+ BearerToken token = valueOf("this.is.no.api.token");
+
+ boolean supports = realm.supports(token);
+
+ assertThat(supports).isFalse();
+ }
+
void verifyScopeSet(String... permissions) {
verify(authenticationInfoBuilder).withScope(argThat(scope -> {
assertThat(scope).containsExactly(permissions);
diff --git a/scm-webapp/src/test/java/sonia/scm/security/ApiKeyTokenHandlerTest.java b/scm-webapp/src/test/java/sonia/scm/security/ApiKeyTokenHandlerTest.java
index 390d5e6ba2..ac0ee44040 100644
--- a/scm-webapp/src/test/java/sonia/scm/security/ApiKeyTokenHandlerTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/security/ApiKeyTokenHandlerTest.java
@@ -61,4 +61,11 @@ class ApiKeyTokenHandlerTest {
assertThat(token).isEmpty();
}
+
+ @Test
+ void shouldParseRealWorldExample() {
+ Optional token = handler.readToken("eyJhcGlLZXlJZCI6IkE2U0ROWmV0MjEiLCJ1c2VyIjoiaG9yc3QiLCJwYXNzcGhyYXNlIjoiWGNKQ01PMnZuZ1JaOEhVU21BSVoifQ");
+
+ assertThat(token).get().extracting("user").isEqualTo("horst");
+ }
}
diff --git a/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java b/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java
index c0ac82b7f6..c9d834cdbc 100644
--- a/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/security/BearerRealmTest.java
@@ -40,6 +40,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import static sonia.scm.security.BearerToken.valueOf;
/**
* Unit tests for {@link BearerRealm}.
@@ -96,4 +97,13 @@ class BearerRealmTest {
void shouldThrowIllegalArgumentExceptionForWrongTypeOfToken() {
assertThrows(IllegalArgumentException.class, () -> realm.doGetAuthenticationInfo(new UsernamePasswordToken()));
}
+
+ @Test
+ void shouldIgnoreTokensWithoutDot() {
+ BearerToken token = valueOf("this-is-no-jwt-token");
+
+ boolean supports = realm.supports(token);
+
+ assertThat(supports).isFalse();
+ }
}
diff --git a/scm-webapp/src/test/java/sonia/scm/update/repository/PublicFlagUpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/repository/PublicFlagUpdateStepTest.java
index 54c5460c6e..23eda6d38e 100644
--- a/scm-webapp/src/test/java/sonia/scm/update/repository/PublicFlagUpdateStepTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/update/repository/PublicFlagUpdateStepTest.java
@@ -25,6 +25,7 @@
package sonia.scm.update.repository;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
@@ -77,63 +78,81 @@ class PublicFlagUpdateStepTest {
//prepare backup xml
V1RepositoryFileSystem.createV1Home(tempDir);
Files.move(tempDir.resolve("config").resolve("repositories.xml"), tempDir.resolve("config").resolve("repositories.xml.v1.backup"));
- when(repositoryDAO.get((String) any())).thenReturn(REPOSITORY);
}
@Test
- void shouldDeleteOldAnonymousUserIfExists() throws JAXBException {
- User anonymous = new User("anonymous");
- when(userDAO.getAll()).thenReturn(Collections.singleton(anonymous));
- doReturn(anonymous).when(userDAO).get("anonymous");
- doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
-
- updateStep.doUpdate();
-
- verify(userDAO).delete(anonymous);
- }
-
- @Test
- void shouldNotTryToDeleteOldAnonymousUserIfNotExists() throws JAXBException {
- when(userDAO.getAll()).thenReturn(Collections.emptyList());
- doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
-
- updateStep.doUpdate();
-
- verify(userDAO, never()).delete(any());
- }
-
- @Test
- void shouldCreateNewAnonymousUserIfNotExists() throws JAXBException {
- doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
- when(userDAO.getAll()).thenReturn(Collections.singleton(new User("trillian")));
-
- updateStep.doUpdate();
-
- verify(userDAO).add(SCMContext.ANONYMOUS);
- }
-
- @Test
- void shouldNotCreateNewAnonymousUserIfAlreadyExists() throws JAXBException {
- doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
- when(userDAO.getAll()).thenReturn(Collections.singleton(new User("_anonymous")));
-
- updateStep.doUpdate();
-
- verify(userDAO, never()).add(SCMContext.ANONYMOUS);
- }
-
- @Test
- void shouldMigratePublicFlagToAnonymousRepositoryPermission() throws JAXBException {
+ void shouldNotFailForDeletedRepository() throws JAXBException {
when(userDAO.getAll()).thenReturn(Collections.emptyList());
when(userDAO.get("_anonymous")).thenReturn(SCMContext.ANONYMOUS);
updateStep.doUpdate();
- verify(repositoryDAO, times(2)).modify(repositoryCaptor.capture());
+ verify(repositoryDAO, never()).modify(any());
+ }
- RepositoryPermission migratedRepositoryPermission = repositoryCaptor.getValue().getPermissions().iterator().next();
- assertThat(migratedRepositoryPermission.getName()).isEqualTo(SCMContext.USER_ANONYMOUS);
- assertThat(migratedRepositoryPermission.getRole()).isEqualTo("READ");
- assertThat(migratedRepositoryPermission.isGroupPermission()).isFalse();
+ @Nested
+ class WithExistingRepository {
+
+ @BeforeEach
+ void mockRepository() {
+ when(repositoryDAO.get((String) any())).thenReturn(REPOSITORY);
+ }
+
+ @Test
+ void shouldDeleteOldAnonymousUserIfExists() throws JAXBException {
+ User anonymous = new User("anonymous");
+ when(userDAO.getAll()).thenReturn(Collections.singleton(anonymous));
+ doReturn(anonymous).when(userDAO).get("anonymous");
+ doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
+
+ updateStep.doUpdate();
+
+ verify(userDAO).delete(anonymous);
+ }
+
+ @Test
+ void shouldNotTryToDeleteOldAnonymousUserIfNotExists() throws JAXBException {
+ when(userDAO.getAll()).thenReturn(Collections.emptyList());
+ doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
+
+ updateStep.doUpdate();
+
+ verify(userDAO, never()).delete(any());
+ }
+
+ @Test
+ void shouldCreateNewAnonymousUserIfNotExists() throws JAXBException {
+ doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
+ when(userDAO.getAll()).thenReturn(Collections.singleton(new User("trillian")));
+
+ updateStep.doUpdate();
+
+ verify(userDAO).add(SCMContext.ANONYMOUS);
+ }
+
+ @Test
+ void shouldNotCreateNewAnonymousUserIfAlreadyExists() throws JAXBException {
+ doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
+ when(userDAO.getAll()).thenReturn(Collections.singleton(new User("_anonymous")));
+
+ updateStep.doUpdate();
+
+ verify(userDAO, never()).add(SCMContext.ANONYMOUS);
+ }
+
+ @Test
+ void shouldMigratePublicFlagToAnonymousRepositoryPermission() throws JAXBException {
+ when(userDAO.getAll()).thenReturn(Collections.emptyList());
+ when(userDAO.get("_anonymous")).thenReturn(SCMContext.ANONYMOUS);
+
+ updateStep.doUpdate();
+
+ verify(repositoryDAO, times(2)).modify(repositoryCaptor.capture());
+
+ RepositoryPermission migratedRepositoryPermission = repositoryCaptor.getValue().getPermissions().iterator().next();
+ assertThat(migratedRepositoryPermission.getName()).isEqualTo(SCMContext.USER_ANONYMOUS);
+ assertThat(migratedRepositoryPermission.getRole()).isEqualTo("READ");
+ assertThat(migratedRepositoryPermission.isGroupPermission()).isFalse();
+ }
}
}
diff --git a/scm-webapp/src/test/java/sonia/scm/web/security/TokenRefreshFilterTest.java b/scm-webapp/src/test/java/sonia/scm/web/security/TokenRefreshFilterTest.java
index d74a5eef7f..410eb1f368 100644
--- a/scm-webapp/src/test/java/sonia/scm/web/security/TokenRefreshFilterTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/web/security/TokenRefreshFilterTest.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.web.security;
import org.apache.shiro.authc.AuthenticationToken;
@@ -52,6 +52,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static sonia.scm.security.BearerToken.valueOf;
@ExtendWith({MockitoExtension.class})
class TokenRefreshFilterTest {
@@ -103,7 +104,7 @@ class TokenRefreshFilterTest {
@Test
void shouldNotRefreshNonJwtToken() throws IOException, ServletException {
- BearerToken token = mock(BearerToken.class);
+ BearerToken token = createValidToken();
JwtAccessToken jwtToken = mock(JwtAccessToken.class);
when(tokenGenerator.createToken(request)).thenReturn(token);
when(resolver.resolve(token)).thenReturn(jwtToken);
@@ -116,7 +117,7 @@ class TokenRefreshFilterTest {
@Test
void shouldRefreshIfRefreshable() throws IOException, ServletException {
- BearerToken token = mock(BearerToken.class);
+ BearerToken token = createValidToken();
JwtAccessToken jwtToken = mock(JwtAccessToken.class);
JwtAccessToken newJwtToken = mock(JwtAccessToken.class);
when(tokenGenerator.createToken(request)).thenReturn(token);
@@ -128,4 +129,8 @@ class TokenRefreshFilterTest {
verify(issuer).authenticate(request, response, newJwtToken);
verify(filterChain).doFilter(request, response);
}
+
+ BearerToken createValidToken() {
+ return valueOf("some.jwt.token");
+ }
}