From 22d5176d1aa0cd645ef4079609bc7ba101afb8a3 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 13 Oct 2020 16:38:22 +0200 Subject: [PATCH 1/5] Translate SystemJS bundle names before loading We remove @scm-manager/ prefix and add append .bundle.js before loading modules. --- scm-ui/ui-webapp/src/containers/loadBundle.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scm-ui/ui-webapp/src/containers/loadBundle.ts b/scm-ui/ui-webapp/src/containers/loadBundle.ts index 8096e70ec4..7586f93984 100644 --- a/scm-ui/ui-webapp/src/containers/loadBundle.ts +++ b/scm-ui/ui-webapp/src/containers/loadBundle.ts @@ -47,7 +47,15 @@ type PluginModule = { const BundleLoader = { name: "bundle-loader", fetch: (plugin: PluginModule) => { - return fetch(plugin.address, { + let url = plugin.address; + if (!url.endsWith(".bundle.js")) { + url += ".bundle.js"; + } + + if (url.includes("@scm-manager/")) { + url = url.replace("@scm-manager/", ""); + } + return fetch(url, { credentials: "same-origin", headers: { Cache: "no-cache", From 5172fdb27d87eb6feb5c3e1f3677a63b4a43b858 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 13 Oct 2020 16:43:56 +0200 Subject: [PATCH 2/5] Treat every scm-manager plugin as external dependency --- scm-ui/ui-scripts/src/createPluginConfig.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scm-ui/ui-scripts/src/createPluginConfig.js b/scm-ui/ui-scripts/src/createPluginConfig.js index f82c6f55eb..61ab0e1cf8 100644 --- a/scm-ui/ui-scripts/src/createPluginConfig.js +++ b/scm-ui/ui-scripts/src/createPluginConfig.js @@ -29,7 +29,7 @@ const root = process.cwd(); const packageJsonPath = path.join(root, "package.json"); const packageJSON = JSON.parse(fs.readFileSync(packageJsonPath, { encoding: "UTF-8" })); -let name = packageJSON.name; +let { name } = packageJSON; const orgaIndex = name.indexOf("/"); if (orgaIndex > 0) { name = name.substring(orgaIndex + 1); @@ -62,7 +62,8 @@ module.exports = function(mode) { "classnames", "query-string", "redux", - "react-redux" + "react-redux", + /^@scm-manager\/scm-.*-plugin$/i ], module: { rules: [ @@ -90,9 +91,9 @@ module.exports = function(mode) { extensions: [".ts", ".tsx", ".js", ".jsx", ".css", ".scss", ".json"] }, output: { - path: path.join(root, "target", name + "-" + packageJSON.version, "webapp", "assets"), + path: path.join(root, "target", `${name}-${packageJSON.version}`, "webapp", "assets"), filename: "[name].bundle.js", - chunkFilename: name + ".[name].chunk.js", + chunkFilename: `${name}.[name].chunk.js`, library: name, libraryTarget: "amd" } From a94993274a886da6533d5d1e422e7ecec8ea81da Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 14 Oct 2020 08:06:15 +0200 Subject: [PATCH 3/5] Resolve plugin SystemJS modules to their correct bundle location --- scm-ui/ui-webapp/src/containers/loadBundle.ts | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/scm-ui/ui-webapp/src/containers/loadBundle.ts b/scm-ui/ui-webapp/src/containers/loadBundle.ts index 7586f93984..fcb82d8f01 100644 --- a/scm-ui/ui-webapp/src/containers/loadBundle.ts +++ b/scm-ui/ui-webapp/src/containers/loadBundle.ts @@ -47,15 +47,7 @@ type PluginModule = { const BundleLoader = { name: "bundle-loader", fetch: (plugin: PluginModule) => { - let url = plugin.address; - if (!url.endsWith(".bundle.js")) { - url += ".bundle.js"; - } - - if (url.includes("@scm-manager/")) { - url = url.replace("@scm-manager/", ""); - } - return fetch(url, { + return fetch(plugin.address, { credentials: "same-origin", headers: { Cache: "no-cache", @@ -82,6 +74,29 @@ SystemJS.config({ } }); +// We have to patch the resolve methods of SystemJS +// in order to resolve the correct bundle url for plugins + +const resolveModuleUrl = (key: string) => { + if (key.startsWith("@scm-manager/scm-") && key.endsWith("-plugin")) { + const pluginName = key.replace("@scm-manager/", ""); + return urls.withContextPath(`/assets/${pluginName}.bundle.js`); + } + return key; +}; + +const defaultResolve = SystemJS.resolve; +SystemJS.resolve = function(key, parentName) { + const module = resolveModuleUrl(key); + return defaultResolve.apply(this, [module, parentName]); +}; + +const defaultResolveSync = SystemJS.resolveSync; +SystemJS.resolveSync = function(key, parentName) { + const module = resolveModuleUrl(key); + return defaultResolveSync.apply(this, [module, parentName]); +}; + const expose = (name: string, cmp: any, defaultCmp?: any) => { let mod = cmp; if (defaultCmp) { From 2c640009cce8f20c2de7f98aff2c339f108541f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 14 Oct 2020 08:34:32 +0200 Subject: [PATCH 4/5] Fix anonymous migration for deleted repositories --- CHANGELOG.md | 4 + .../repository/PublicFlagUpdateStep.java | 10 +- .../repository/PublicFlagUpdateStepTest.java | 117 ++++++++++-------- 3 files changed, 78 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97644147c9..e94d216088 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ 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). +## Unreleased +### Fixed +- Null Pointer Exception on anonymous migration with deleted repositories ([#1371](https://github.com/scm-manager/scm-manager/pull/1371)) + ## [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/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..8a18442afa 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,11 @@ 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(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); + } }); } 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(); + } } } From c9410a63929c0d4f90dc9ab17b101802019a65f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 14 Oct 2020 08:56:07 +0200 Subject: [PATCH 5/5] Add logging --- .../sonia/scm/update/repository/PublicFlagUpdateStep.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 8a18442afa..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 @@ -90,9 +90,11 @@ public class PublicFlagUpdateStep implements UpdateStep { .forEach(v1Repository -> { Repository v2Repository = repositoryDAO.get(v1Repository.getId()); if (v2Repository != null) { - LOG.info(String.format("Add RepositoryRole 'READ' to _anonymous user for repository: %s - %s/%s", v2Repository.getId(), v2Repository.getNamespace(), v2Repository.getName())); + 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()); } }); }