From 54ab318960e12e13bd10c7e4edc07b8f0b960197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Fri, 1 Feb 2019 13:00:26 +0100 Subject: [PATCH 01/87] remove user/group after submitting permission --- .../src/repos/permissions/containers/CreatePermissionForm.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js b/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js index 362b4aa48a..d02217098e 100644 --- a/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js +++ b/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js @@ -253,7 +253,8 @@ class CreatePermissionForm extends React.Component { name: "", verbs: this.props.availablePermissions.availableRoles[0].verbs, groupPermission: false, - valid: true + valid: true, + value: undefined }); }; From 81652aab0d12924b77792a39f6716c5511223b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Fri, 1 Feb 2019 13:02:33 +0100 Subject: [PATCH 02/87] do not change grouppermission back to default after submitting --- scm-ui/src/repos/permissions/containers/CreatePermissionForm.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js b/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js index d02217098e..023842d414 100644 --- a/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js +++ b/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js @@ -252,7 +252,6 @@ class CreatePermissionForm extends React.Component { this.setState({ name: "", verbs: this.props.availablePermissions.availableRoles[0].verbs, - groupPermission: false, valid: true, value: undefined }); From 05efbe5ad8b2a3236a4ba99d37ad4fa896b805b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Fri, 1 Feb 2019 14:17:43 +0100 Subject: [PATCH 03/87] use radio component again and show type of permission instead of checkbox --- .../containers/CreatePermissionForm.js | 39 ++++++++----------- .../containers/SinglePermission.js | 24 ++++++++---- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js b/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js index 023842d414..0260dacd46 100644 --- a/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js +++ b/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js @@ -5,7 +5,8 @@ import { Autocomplete, SubmitButton, Button, - LabelWithHelpIcon + LabelWithHelpIcon, + Radio } from "@scm-manager/ui-components"; import RoleSelector from "../components/RoleSelector"; import type { @@ -158,28 +159,20 @@ class CreatePermissionForm extends React.Component { {advancedDialog}
-
- - -
+ +
diff --git a/scm-ui/src/repos/permissions/containers/SinglePermission.js b/scm-ui/src/repos/permissions/containers/SinglePermission.js index b16d26e903..c5be48836d 100644 --- a/scm-ui/src/repos/permissions/containers/SinglePermission.js +++ b/scm-ui/src/repos/permissions/containers/SinglePermission.js @@ -22,7 +22,11 @@ import AdvancedPermissionsDialog from "./AdvancedPermissionsDialog"; type Props = { availablePermissions: AvailableRepositoryPermissions, submitForm: Permission => void, - modifyPermission: (permission: Permission, namespace: string, name: string) => void, + modifyPermission: ( + permission: Permission, + namespace: string, + name: string + ) => void, permission: Permission, t: string => string, namespace: string, @@ -30,7 +34,11 @@ type Props = { match: any, history: History, loading: boolean, - deletePermission: (permission: Permission, namespace: string, name: string) => void, + deletePermission: ( + permission: Permission, + namespace: string, + name: string + ) => void, deleteLoading: boolean }; @@ -125,15 +133,15 @@ class SinglePermission extends React.Component { /> ) : null; + const type = + permission && permission.groupPermission + ? t("permission.group") + : t("permission.user"); + return ( {permission.name} - - - + {type} {roleSelector}
+ +
diff --git a/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js b/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js index aa69f7b3e1..2c026626ed 100644 --- a/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js +++ b/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js @@ -96,7 +96,6 @@ class CreatePermissionForm extends React.Component { const { t } = this.props; if (this.state.groupPermission) { return ( - { /> ) : null; - const type = - permission && permission.groupPermission - ? t("permission.group") - : t("permission.user"); - const iconType = permission && permission.groupPermission ? ( - + ) : ( - + ); return ( From 7fba12e30fbfa8e739ef6fc4a8696749ae51fb0d Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Thu, 7 Feb 2019 16:55:34 +0100 Subject: [PATCH 09/87] renamed extensionpoint --- .../packages/ui-components/src/config/ConfigurationBinder.js | 2 +- scm-ui/src/containers/Profile.js | 2 +- scm-ui/src/groups/containers/SingleGroup.js | 2 +- scm-ui/src/repos/containers/RepositoryRoot.js | 2 +- scm-ui/src/users/containers/SingleUser.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js b/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js index 56964b016b..7d0dfe7ed8 100644 --- a/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js +++ b/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js @@ -86,7 +86,7 @@ class ConfigurationBinder { }); // bind navigation link to extension point - binder.bind("repository.subnavigation", RepoNavLink, repoPredicate); + binder.bind("repository.setting", RepoNavLink, repoPredicate); // route for global configuration, passes the current repository to component diff --git a/scm-ui/src/containers/Profile.js b/scm-ui/src/containers/Profile.js index e9bdbaa4b9..a513367c0a 100644 --- a/scm-ui/src/containers/Profile.js +++ b/scm-ui/src/containers/Profile.js @@ -91,7 +91,7 @@ class Profile extends React.Component { label={t("profile.changePasswordNavLink")} /> diff --git a/scm-ui/src/groups/containers/SingleGroup.js b/scm-ui/src/groups/containers/SingleGroup.js index f8d88d7a7d..ef99d70370 100644 --- a/scm-ui/src/groups/containers/SingleGroup.js +++ b/scm-ui/src/groups/containers/SingleGroup.js @@ -142,7 +142,7 @@ class SingleGroup extends React.Component { permissionsUrl={`${url}/settings/permissions`} /> diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index d0d722e1fd..8608b9c957 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -214,7 +214,7 @@ class RepositoryRoot extends React.Component { repository={repository} /> diff --git a/scm-ui/src/users/containers/SingleUser.js b/scm-ui/src/users/containers/SingleUser.js index 9a1633a162..7bacf8929b 100644 --- a/scm-ui/src/users/containers/SingleUser.js +++ b/scm-ui/src/users/containers/SingleUser.js @@ -131,7 +131,7 @@ class SingleUser extends React.Component { permissionsUrl={`${url}/settings/permissions`} /> From 352bfe7f5ae42fd58766f807001f4038392302fa Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 13 Feb 2019 12:30:40 +0100 Subject: [PATCH 10/87] fixed mercurial PreReceiveRepositoryHooks The problem seems to be that guice had multiple options for injecting HgContext. HgContextProvider bound via Module and HgContext bound void RequestScoped annotation. It looks like that Guice 4 injects randomly the one or the other, in SCMv1 (Guice 3) everything works as expected. To fix the problem we have created a new class annotated with RequestScoped, which holds an instance of HgContext. This way only the HgContextProvider is used for injection. --- .../java/sonia/scm/repository/HgContext.java | 3 --- .../sonia/scm/repository/HgContextProvider.java | 17 ++++++----------- .../scm/repository/HgContextRequestStore.java | 14 ++++++++++++++ .../src/main/java/sonia/scm/web/HgUtil.java | 2 ++ 4 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContext.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContext.java index cbf7804444..21e27af328 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContext.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContext.java @@ -35,13 +35,10 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- -import com.google.inject.servlet.RequestScoped; - /** * * @author Sebastian Sdorra */ -@RequestScoped public class HgContext { diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java index c86588fb27..e6f7436650 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java @@ -63,22 +63,17 @@ public class HgContextProvider implements Provider * @return */ @Override - public HgContext get() - { - HgContext ctx = context; - - if (ctx == null) - { - ctx = new HgContext(); + public HgContext get() { + if (contextRequestStore == null) { logger.trace("context is null, we are probably out of request scope"); + return new HgContext(); } - - return ctx; + logger.trace("return HgContext from request store"); + return contextRequestStore.get(); } //~--- fields --------------------------------------------------------------- - /** Field description */ @Inject(optional = true) - private HgContext context; + private HgContextRequestStore contextRequestStore; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java new file mode 100644 index 0000000000..156e915416 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java @@ -0,0 +1,14 @@ +package sonia.scm.repository; + +import com.google.inject.servlet.RequestScoped; + +@RequestScoped +public class HgContextRequestStore { + + private HgContext context = new HgContext(); + + public HgContext get() { + return context; + } + +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java index ec35762de7..b6b085f3ac 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java @@ -134,6 +134,8 @@ public final class HgUtil repoConfiguration.setHgBin(handler.getConfig().getHgBinary()); + logger.debug("open hg repository {}: encoding: {}, pending: {}", directory, enc, pending); + return Repository.open(repoConfiguration, directory); } From 49e9671fa7df758e2c34eccbf60b2e1b6e1c94fd Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 13 Feb 2019 12:42:07 +0100 Subject: [PATCH 11/87] added some javadoc --- .../main/java/sonia/scm/repository/HgContextProvider.java | 3 +++ .../java/sonia/scm/repository/HgContextRequestStore.java | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java index e6f7436650..89170713ac 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java @@ -42,6 +42,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** + * Injection provider for {@link HgContext}. + * This provider returns an instance {@link HgContext} from request scope, if no {@link HgContext} could be found in + * request scope (mostly because the scope is not available) a new {@link HgContext} gets returned. * * @author Sebastian Sdorra */ diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java index 156e915416..6043c347ff 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java @@ -2,10 +2,13 @@ package sonia.scm.repository; import com.google.inject.servlet.RequestScoped; +/** + * Holds an instance of {@link HgContext} in the request scope. + */ @RequestScoped public class HgContextRequestStore { - private HgContext context = new HgContext(); + private final HgContext context = new HgContext(); public HgContext get() { return context; From 5eb32d32b2458998d92e520678220f60a30e6f39 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Wed, 13 Feb 2019 15:16:01 +0100 Subject: [PATCH 12/87] Adjusted email validation in frontend --- scm-ui-components/packages/ui-components/src/validation.js | 2 +- .../packages/ui-components/src/validation.test.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/validation.js b/scm-ui-components/packages/ui-components/src/validation.js index 561b0d79bc..fcfffcee45 100644 --- a/scm-ui-components/packages/ui-components/src/validation.js +++ b/scm-ui-components/packages/ui-components/src/validation.js @@ -5,7 +5,7 @@ export const isNameValid = (name: string) => { return nameRegex.test(name); }; -const mailRegex = /^[A-z0-9][\w.-]*@[A-z0-9][\w\-.]*\.[A-z0-9][A-z0-9-]+$/; +const mailRegex = /^[ -~]+@[A-Za-z0-9][\w\-.]*\.[A-Za-z0-9][A-Za-z0-9-]+$/; export const isMailValid = (mail: string) => { return mailRegex.test(mail); diff --git a/scm-ui-components/packages/ui-components/src/validation.test.js b/scm-ui-components/packages/ui-components/src/validation.test.js index 8394c61854..d50996ff2b 100644 --- a/scm-ui-components/packages/ui-components/src/validation.test.js +++ b/scm-ui-components/packages/ui-components/src/validation.test.js @@ -59,9 +59,8 @@ describe("test mail validation", () => { "@ostfalia.de", "s.sdorra@", "s.sdorra@ostfalia", - "s.sdorra@@ostfalia.de", "s.sdorra@ ostfalia.de", - "s.sdorra @ostfalia.de" + "s.sdorra@[ostfalia.de" ]; for (let mail of invalid) { expect(validator.isMailValid(mail)).toBe(false); @@ -78,7 +77,9 @@ describe("test mail validation", () => { "s.sdorra@t.co", "s.sdorra@ucla.college", "s.sdorra@example.xn--p1ai", - "s.sdorra@scm.solutions" + "s.sdorra@scm.solutions", + "s'sdorra@scm.solutions", + "\"S Sdorra\"@scm.solutions" ]; for (let mail of valid) { expect(validator.isMailValid(mail)).toBe(true); From 5a5cafaee0ff1e0f5534da54242724f3705235ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 14 Feb 2019 11:22:58 +0100 Subject: [PATCH 13/87] Add comment from previous commit --- .../java/sonia/scm/repository/HgContextRequestStore.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java index 6043c347ff..ff08c2fcd2 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java @@ -4,6 +4,13 @@ import com.google.inject.servlet.RequestScoped; /** * Holds an instance of {@link HgContext} in the request scope. + * + *

The problem seems to be that guice had multiple options for injecting HgContext. {@link HgContextProvider} + * bound via Module and {@link HgContext} bound void {@link RequestScoped} annotation. It looks like that Guice 4 + * injects randomly the one or the other, in SCMv1 (Guice 3) everything works as expected.

+ * + *

To fix the problem we have created this class annotated with {@link RequestScoped}, which holds an instance + * of {@link HgContext}. This way only the {@link HgContextProvider} is used for injection.

*/ @RequestScoped public class HgContextRequestStore { From 080a0d55fd31fc3af434437f7f0e32a1477784cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 14 Feb 2019 10:23:51 +0000 Subject: [PATCH 14/87] Close branch bugflix/mercurial_pending_changesets From cbc097acfd25b568d1b6dd53b2061c833f86e16e Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Thu, 14 Feb 2019 13:09:42 +0100 Subject: [PATCH 15/87] Clear autocomplete field after submit or permission scope switch --- .../permissions/containers/CreatePermissionForm.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js b/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js index 2c026626ed..989079c717 100644 --- a/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js +++ b/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js @@ -62,7 +62,7 @@ class CreatePermissionForm extends React.Component { this.props.currentPermissions ) }); - this.setState({ ...this.state, groupPermission }); + this.setState({ ...this.state, groupPermission, value: undefined}); }; loadUserAutocompletion = (inputValue: string) => { @@ -99,7 +99,7 @@ class CreatePermissionForm extends React.Component { { { }; removeState = () => { - this.setState({ + this.setState({...this.state, name: "", verbs: this.props.availablePermissions.availableRoles[0].verbs, valid: true, @@ -255,6 +255,9 @@ class CreatePermissionForm extends React.Component { handleRoleChange = (role: string) => { const selectedRole = this.findAvailableRole(role); + if (!selectedRole) { + return + } this.setState({ verbs: selectedRole.verbs }); From 0273bc5516e0897ff45f3ea87ff6cc2f33f7b695 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Thu, 14 Feb 2019 14:13:50 +0100 Subject: [PATCH 16/87] Fixed unit tests --- .../components/buttons/DeletePermissionButton.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scm-ui/src/repos/permissions/components/buttons/DeletePermissionButton.test.js b/scm-ui/src/repos/permissions/components/buttons/DeletePermissionButton.test.js index 811c9dc335..03eebe3c50 100644 --- a/scm-ui/src/repos/permissions/components/buttons/DeletePermissionButton.test.js +++ b/scm-ui/src/repos/permissions/components/buttons/DeletePermissionButton.test.js @@ -29,7 +29,7 @@ describe("DeletePermissionButton", () => { expect(navLink.text()).toBe(""); }); - it("should render the navLink", () => { + it("should render the delete icon", () => { const permission = { _links: { delete: { @@ -38,14 +38,14 @@ describe("DeletePermissionButton", () => { } }; - const navLink = mount( + const deleteIcon = mount( {}} />, options.get() ); - expect(navLink.text()).not.toBe(""); + expect(deleteIcon.html()).not.toBe(""); }); it("should open the confirm dialog on button click", () => { @@ -64,7 +64,7 @@ describe("DeletePermissionButton", () => { />, options.get() ); - button.find("button").simulate("click"); + button.find(".fa-trash").simulate("click"); expect(confirmAlert.mock.calls.length).toBe(1); }); @@ -91,7 +91,7 @@ describe("DeletePermissionButton", () => { />, options.get() ); - button.find("button").simulate("click"); + button.find(".fa-trash").simulate("click"); expect(calledUrl).toBe("/permission"); }); From 75239a0104290f65936dbd32068d5307a58c5daa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 14 Feb 2019 15:42:27 +0100 Subject: [PATCH 17/87] Support groups from external authentication resources --- .../main/java/sonia/scm/group/GroupNames.java | 45 +++++++++++++------ .../scm/security/SyncingRealmHelper.java | 17 ++++++- .../scm/security/SyncingRealmHelperTest.java | 26 ++++++++++- .../v2/resources/AuthenticationResource.java | 6 +++ 4 files changed, 78 insertions(+), 16 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/group/GroupNames.java b/scm-core/src/main/java/sonia/scm/group/GroupNames.java index 24d32972b6..3aae0c5c4c 100644 --- a/scm-core/src/main/java/sonia/scm/group/GroupNames.java +++ b/scm-core/src/main/java/sonia/scm/group/GroupNames.java @@ -73,18 +73,7 @@ public final class GroupNames implements Serializable, Iterable @SuppressWarnings("unchecked") public GroupNames() { - this.collection = Collections.EMPTY_LIST; - } - - /** - * Constructs ... - * - * - * @param collection - */ - public GroupNames(Collection collection) - { - this.collection = Collections.unmodifiableCollection(collection); + this(Collections.EMPTY_LIST); } /** @@ -96,7 +85,30 @@ public final class GroupNames implements Serializable, Iterable */ public GroupNames(String groupName, String... groupNames) { - this.collection = Lists.asList(groupName, groupNames); + this(Lists.asList(groupName, groupNames)); + } + + /** + * Constructs ... + * + * + * @param collection + */ + public GroupNames(Collection collection) + { + this(collection, false); + } + + /** + * Constructs ... + * + * + * @param collection + */ + public GroupNames(Collection collection, boolean external) + { + this.collection = Collections.unmodifiableCollection(collection); + this.external = external; } //~--- methods -------------------------------------------------------------- @@ -181,8 +193,13 @@ public final class GroupNames implements Serializable, Iterable return collection; } - //~--- fields --------------------------------------------------------------- + public boolean isExternal() { + return external; + } + //~--- fields --------------------------------------------------------------- /** Field description */ private final Collection collection; + + private final boolean external; } diff --git a/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java b/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java index 0e3c06e32d..b37ac4adc3 100644 --- a/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java +++ b/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java @@ -108,11 +108,26 @@ public final class SyncingRealmHelper { */ public AuthenticationInfo createAuthenticationInfo(String realm, User user, Collection groups) { + return this.createAuthenticationInfo(realm, user, groups, false); + } + + /** + * Create {@link AuthenticationInfo} from user and groups. + * + * + * @param realm name of the realm + * @param user authenticated user + * @param groups groups of the authenticated user + * + * @return authentication info + */ + public AuthenticationInfo createAuthenticationInfo(String realm, User user, + Collection groups, boolean externalGroups) { SimplePrincipalCollection collection = new SimplePrincipalCollection(); collection.add(user.getId(), realm); collection.add(user, realm); - collection.add(new GroupNames(groups), realm); + collection.add(new GroupNames(groups, externalGroups), realm); return new SimpleAuthenticationInfo(collection, user.getPassword()); } diff --git a/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java b/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java index 50bde53ae1..7579e7d829 100644 --- a/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java +++ b/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java @@ -53,10 +53,13 @@ import sonia.scm.web.security.PrivilegedAction; import java.io.IOException; +import static java.util.Collections.singletonList; import static org.hamcrest.Matchers.hasItem; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -113,7 +116,7 @@ public class SyncingRealmHelperTest { public void testCreateAuthenticationInfo() { User user = new User("tricia"); AuthenticationInfo authInfo = helper.createAuthenticationInfo("unit-test", - user, "heartOfGold"); + user, singletonList("heartOfGold")); assertNotNull(authInfo); assertEquals("tricia", authInfo.getPrincipals().getPrimaryPrincipal()); @@ -123,6 +126,27 @@ public class SyncingRealmHelperTest { GroupNames groups = authInfo.getPrincipals().oneByType(GroupNames.class); assertThat(groups, hasItem("heartOfGold")); + assertFalse(groups.isExternal()); + } + + /** + * Tests {@link SyncingRealmHelper#createAuthenticationInfo(String, User, String...)}. + */ + @Test + public void testCreateAuthenticationInfoWithExternalGroups() { + User user = new User("tricia"); + AuthenticationInfo authInfo = helper.createAuthenticationInfo("unit-test", + user, singletonList("heartOfGold"), true); + + assertNotNull(authInfo); + assertEquals("tricia", authInfo.getPrincipals().getPrimaryPrincipal()); + assertThat(authInfo.getPrincipals().getRealmNames(), hasItem("unit-test")); + assertEquals(user, authInfo.getPrincipals().oneByType(User.class)); + + GroupNames groups = authInfo.getPrincipals().oneByType(GroupNames.class); + + assertThat(groups, hasItem("heartOfGold")); + assertTrue(groups.isExternal()); } /** diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java index 47918080ab..7984e6c473 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java @@ -8,6 +8,7 @@ import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.group.GroupNames; import sonia.scm.security.*; import javax.servlet.http.HttpServletRequest; @@ -91,6 +92,11 @@ public class AuthenticationResource { tokenBuilder.scope(Scope.valueOf(authentication.getScope())); } + GroupNames groupNames = subject.getPrincipals().oneByType(GroupNames.class); + if (groupNames != null && groupNames.isExternal()) { + tokenBuilder.groups(groupNames.getCollection().toArray(new String[]{})); + } + AccessToken token = tokenBuilder.build(); if (authentication.isCookie()) { From 83076dba460a79ccb6d6f3cb4cb45f9dcc9cb5c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 14 Feb 2019 16:36:06 +0100 Subject: [PATCH 18/87] Add floating API --- .../main/java/sonia/scm/group/GroupNames.java | 3 +- .../scm/security/SyncingRealmHelper.java | 47 ++++++++++++++++++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/group/GroupNames.java b/scm-core/src/main/java/sonia/scm/group/GroupNames.java index 3aae0c5c4c..43a1ffda43 100644 --- a/scm-core/src/main/java/sonia/scm/group/GroupNames.java +++ b/scm-core/src/main/java/sonia/scm/group/GroupNames.java @@ -70,10 +70,9 @@ public final class GroupNames implements Serializable, Iterable * Constructs ... * */ - @SuppressWarnings("unchecked") public GroupNames() { - this(Collections.EMPTY_LIST); + this(Collections.emptyList()); } /** diff --git a/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java b/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java index b37ac4adc3..d54abcff58 100644 --- a/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java +++ b/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java @@ -80,6 +80,49 @@ public final class SyncingRealmHelper { this.groupManager = groupManager; } + public AuthenticationInfoBuilder.ForRealm authenticationInfo() { + return new AuthenticationInfoBuilder().new ForRealm(); + } + + public class AuthenticationInfoBuilder { + private String realm; + private User user; + private Collection groups; + private boolean external; + + private AuthenticationInfo build() { + return SyncingRealmHelper.this.createAuthenticationInfo(realm, user, groups, external); + } + + public class ForRealm { + public ForUser forRealm(String realm) { + AuthenticationInfoBuilder.this.realm = realm; + return AuthenticationInfoBuilder.this.new ForUser(); + } + } + + public class ForUser { + public AuthenticationInfoBuilder.WithGroups andUser(User user) { + AuthenticationInfoBuilder.this.user = user; + return AuthenticationInfoBuilder.this.new WithGroups(); + } + } + + public class WithGroups { + public AuthenticationInfo withGroups(Collection groups) { + AuthenticationInfoBuilder.this.groups = groups; + AuthenticationInfoBuilder.this.external = false; + return build(); + } + + public AuthenticationInfo withExternalGroups(Collection groups) { + AuthenticationInfoBuilder.this.groups = groups; + AuthenticationInfoBuilder.this.external = false; + return build(); + } + } + } + //~--- methods -------------------------------------------------------------- /** * Create {@link AuthenticationInfo} from user and groups. @@ -176,6 +219,6 @@ public final class SyncingRealmHelper { } } - }); - } + }); } +} From 0c9a77664de3c9308f520228cf2bc497792f389e Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Thu, 14 Feb 2019 17:39:30 +0100 Subject: [PATCH 19/87] Added position:absolute for tooltips --- scm-ui-components/packages/ui-components/src/Help.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scm-ui-components/packages/ui-components/src/Help.js b/scm-ui-components/packages/ui-components/src/Help.js index 047f79244f..9cb37e722d 100644 --- a/scm-ui-components/packages/ui-components/src/Help.js +++ b/scm-ui-components/packages/ui-components/src/Help.js @@ -7,7 +7,8 @@ import HelpIcon from './HelpIcon'; const styles = { tooltip: { display: "inline-block", - paddingLeft: "3px" + paddingLeft: "3px", + position: "absolute" } }; From 5d601293bfaffd926a67773b1e384c28596f955e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 15 Feb 2019 10:23:46 +0100 Subject: [PATCH 20/87] Create external group names claim in token builder directly --- .../main/java/sonia/scm/group/GroupNames.java | 2 +- .../scm/security/SyncingRealmHelper.java | 94 +++++++++++++------ .../scm/security/SyncingRealmHelperTest.java | 83 ++++++++-------- .../v2/resources/AuthenticationResource.java | 6 -- .../scm/security/JwtAccessTokenBuilder.java | 7 ++ .../security/JwtAccessTokenBuilderTest.java | 47 ++++++++-- 6 files changed, 155 insertions(+), 84 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/group/GroupNames.java b/scm-core/src/main/java/sonia/scm/group/GroupNames.java index 43a1ffda43..faeec7218b 100644 --- a/scm-core/src/main/java/sonia/scm/group/GroupNames.java +++ b/scm-core/src/main/java/sonia/scm/group/GroupNames.java @@ -176,7 +176,7 @@ public final class GroupNames implements Serializable, Iterable @Override public String toString() { - return Joiner.on(", ").join(collection); + return Joiner.on(", ").join(collection) + "(" + (external? "external": "internal") + ")"; } //~--- get methods ---------------------------------------------------------- diff --git a/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java b/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java index d54abcff58..da11400140 100644 --- a/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java +++ b/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java @@ -45,7 +45,12 @@ import sonia.scm.user.User; import sonia.scm.user.UserManager; import sonia.scm.web.security.AdministrationContext; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; /** * Helper class for syncing realms. The class should simplify the creation of realms, which are syncing authenticated @@ -80,6 +85,9 @@ public final class SyncingRealmHelper { this.groupManager = groupManager; } + /** + * Create {@link AuthenticationInfo} from user and groups. + */ public AuthenticationInfoBuilder.ForRealm authenticationInfo() { return new AuthenticationInfoBuilder().new ForRealm(); } @@ -95,6 +103,13 @@ public final class SyncingRealmHelper { } public class ForRealm { + private ForRealm() { + } + + /** + * Sets the realm. + * @param realm name of the realm + */ public ForUser forRealm(String realm) { AuthenticationInfoBuilder.this.realm = realm; return AuthenticationInfoBuilder.this.new ForUser(); @@ -102,6 +117,13 @@ public final class SyncingRealmHelper { } public class ForUser { + private ForUser() { + } + + /** + * Sets the user. + * @param user authenticated user + */ public AuthenticationInfoBuilder.WithGroups andUser(User user) { AuthenticationInfoBuilder.this.user = user; return AuthenticationInfoBuilder.this.new WithGroups(); @@ -109,35 +131,60 @@ public final class SyncingRealmHelper { } public class WithGroups { + private WithGroups() { + } + + /** + * Build the authentication info without groups. + * @return The complete {@link AuthenticationInfo} + */ + public AuthenticationInfo withoutGroups() { + return withGroups(emptyList()); + } + + /** + * Set the internal groups for the user. + * @param groups groups of the authenticated user + * @return The complete {@link AuthenticationInfo} + */ + public AuthenticationInfo withGroups(String... groups) { + return withGroups(asList(groups)); + } + + /** + * Set the internal groups for the user. + * @param groups groups of the authenticated user + * @return The complete {@link AuthenticationInfo} + */ public AuthenticationInfo withGroups(Collection groups) { AuthenticationInfoBuilder.this.groups = groups; AuthenticationInfoBuilder.this.external = false; return build(); } + /** + * Set the external groups for the user. + * @param groups external groups of the authenticated user + * @return The complete {@link AuthenticationInfo} + */ + public AuthenticationInfo withExternalGroups(String... groups) { + return withExternalGroups(asList(groups)); + } + + /** + * Set the external groups for the user. + * @param groups external groups of the authenticated user + * @return The complete {@link AuthenticationInfo} + */ public AuthenticationInfo withExternalGroups(Collection groups) { AuthenticationInfoBuilder.this.groups = groups; - AuthenticationInfoBuilder.this.external = false; + AuthenticationInfoBuilder.this.external = true; return build(); } } } //~--- methods -------------------------------------------------------------- - /** - * Create {@link AuthenticationInfo} from user and groups. - * - * - * @param realm name of the realm - * @param user authenticated user - * @param groups groups of the authenticated user - * - * @return authentication info - */ - public AuthenticationInfo createAuthenticationInfo(String realm, User user, - String... groups) { - return createAuthenticationInfo(realm, user, ImmutableList.copyOf(groups)); - } /** * Create {@link AuthenticationInfo} from user and groups. @@ -149,22 +196,7 @@ public final class SyncingRealmHelper { * * @return authentication info */ - public AuthenticationInfo createAuthenticationInfo(String realm, User user, - Collection groups) { - return this.createAuthenticationInfo(realm, user, groups, false); - } - - /** - * Create {@link AuthenticationInfo} from user and groups. - * - * - * @param realm name of the realm - * @param user authenticated user - * @param groups groups of the authenticated user - * - * @return authentication info - */ - public AuthenticationInfo createAuthenticationInfo(String realm, User user, + private AuthenticationInfo createAuthenticationInfo(String realm, User user, Collection groups, boolean externalGroups) { SimplePrincipalCollection collection = new SimplePrincipalCollection(); diff --git a/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java b/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java index 7579e7d829..b8d6ae909e 100644 --- a/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java +++ b/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java @@ -37,6 +37,7 @@ package sonia.scm.security; import com.google.common.base.Throwables; import org.apache.shiro.authc.AuthenticationInfo; +import org.assertj.core.api.Assertions; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -54,6 +55,7 @@ import sonia.scm.web.security.PrivilegedAction; import java.io.IOException; import static java.util.Collections.singletonList; +import static org.assertj.core.util.Arrays.asList; import static org.hamcrest.Matchers.hasItem; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -109,46 +111,6 @@ public class SyncingRealmHelperTest { helper = new SyncingRealmHelper(ctx, userManager, groupManager); } - /** - * Tests {@link SyncingRealmHelper#createAuthenticationInfo(String, User, String...)}. - */ - @Test - public void testCreateAuthenticationInfo() { - User user = new User("tricia"); - AuthenticationInfo authInfo = helper.createAuthenticationInfo("unit-test", - user, singletonList("heartOfGold")); - - assertNotNull(authInfo); - assertEquals("tricia", authInfo.getPrincipals().getPrimaryPrincipal()); - assertThat(authInfo.getPrincipals().getRealmNames(), hasItem("unit-test")); - assertEquals(user, authInfo.getPrincipals().oneByType(User.class)); - - GroupNames groups = authInfo.getPrincipals().oneByType(GroupNames.class); - - assertThat(groups, hasItem("heartOfGold")); - assertFalse(groups.isExternal()); - } - - /** - * Tests {@link SyncingRealmHelper#createAuthenticationInfo(String, User, String...)}. - */ - @Test - public void testCreateAuthenticationInfoWithExternalGroups() { - User user = new User("tricia"); - AuthenticationInfo authInfo = helper.createAuthenticationInfo("unit-test", - user, singletonList("heartOfGold"), true); - - assertNotNull(authInfo); - assertEquals("tricia", authInfo.getPrincipals().getPrimaryPrincipal()); - assertThat(authInfo.getPrincipals().getRealmNames(), hasItem("unit-test")); - assertEquals(user, authInfo.getPrincipals().oneByType(User.class)); - - GroupNames groups = authInfo.getPrincipals().oneByType(GroupNames.class); - - assertThat(groups, hasItem("heartOfGold")); - assertTrue(groups.isExternal()); - } - /** * Tests {@link SyncingRealmHelper#store(Group)}. * @@ -222,4 +184,45 @@ public class SyncingRealmHelperTest { helper.store(user); verify(userManager, times(1)).modify(user); } + + @Test + public void builderShouldSetInternalGroups() { + AuthenticationInfo authenticationInfo = helper + .authenticationInfo() + .forRealm("unit-test") + .andUser(new User("ziltoid")) + .withGroups("internal"); + + GroupNames groupNames = authenticationInfo.getPrincipals().oneByType(GroupNames.class); + Assertions.assertThat(groupNames.getCollection()).containsOnly("internal"); + Assertions.assertThat(groupNames.isExternal()).isFalse(); + } + + @Test + public void builderShouldSetExternalGroups() { + AuthenticationInfo authenticationInfo = helper + .authenticationInfo() + .forRealm("unit-test") + .andUser(new User("ziltoid")) + .withExternalGroups("external"); + + GroupNames groupNames = authenticationInfo.getPrincipals().oneByType(GroupNames.class); + Assertions.assertThat(groupNames.getCollection()).containsOnly("external"); + Assertions.assertThat(groupNames.isExternal()).isTrue(); + } + + @Test + public void builderShouldSetValues() { + User user = new User("ziltoid"); + AuthenticationInfo authInfo = helper + .authenticationInfo() + .forRealm("unit-test") + .andUser(user) + .withoutGroups(); + + assertNotNull(authInfo); + assertEquals("ziltoid", authInfo.getPrincipals().getPrimaryPrincipal()); + assertThat(authInfo.getPrincipals().getRealmNames(), hasItem("unit-test")); + assertEquals(user, authInfo.getPrincipals().oneByType(User.class)); + } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java index 7984e6c473..47918080ab 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java @@ -8,7 +8,6 @@ import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.group.GroupNames; import sonia.scm.security.*; import javax.servlet.http.HttpServletRequest; @@ -92,11 +91,6 @@ public class AuthenticationResource { tokenBuilder.scope(Scope.valueOf(authentication.getScope())); } - GroupNames groupNames = subject.getPrincipals().oneByType(GroupNames.class); - if (groupNames != null && groupNames.isExternal()) { - tokenBuilder.groups(groupNames.getCollection().toArray(new String[]{})); - } - AccessToken token = tokenBuilder.build(); if (authentication.isCookie()) { diff --git a/scm-webapp/src/main/java/sonia/scm/security/JwtAccessTokenBuilder.java b/scm-webapp/src/main/java/sonia/scm/security/JwtAccessTokenBuilder.java index a2f4369cd7..61c7631119 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/JwtAccessTokenBuilder.java +++ b/scm-webapp/src/main/java/sonia/scm/security/JwtAccessTokenBuilder.java @@ -50,6 +50,7 @@ import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.group.GroupNames; /** * Jwt implementation of {@link AccessTokenBuilder}. @@ -207,6 +208,12 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder { if (!groups.isEmpty()) { claims.put(JwtAccessToken.GROUPS_CLAIM_KEY, groups); + } else { + Subject currentSubject = SecurityUtils.getSubject(); + GroupNames groupNames = currentSubject.getPrincipals().oneByType(GroupNames.class); + if (groupNames != null && groupNames.isExternal()) { + claims.put(JwtAccessToken.GROUPS_CLAIM_KEY, groupNames.getCollection().toArray(new String[]{})); + } } // sign token and create compact version diff --git a/scm-webapp/src/test/java/sonia/scm/security/JwtAccessTokenBuilderTest.java b/scm-webapp/src/test/java/sonia/scm/security/JwtAccessTokenBuilderTest.java index f7a2c02d01..2928a6c7e6 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/JwtAccessTokenBuilderTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/JwtAccessTokenBuilderTest.java @@ -36,23 +36,32 @@ import com.github.sdorra.shiro.SubjectAware; import com.google.common.collect.Sets; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.subject.Subject; import org.apache.shiro.util.ThreadContext; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.group.GroupNames; +import java.util.Arrays; import java.util.Set; import java.util.concurrent.TimeUnit; import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import static sonia.scm.security.SecureKeyTestUtil.createSecureKey; @@ -62,6 +71,11 @@ import static sonia.scm.security.SecureKeyTestUtil.createSecureKey; * @author Sebastian Sdorra */ @RunWith(MockitoJUnitRunner.class) +@SubjectAware( + configuration = "classpath:sonia/scm/shiro-001.ini", + username = "trillian", + password = "secret" +) public class JwtAccessTokenBuilderTest { { @@ -96,11 +110,6 @@ public class JwtAccessTokenBuilderTest { * Tests {@link JwtAccessTokenBuilder#build()} with subject from shiro context. */ @Test - @SubjectAware( - configuration = "classpath:sonia/scm/shiro-001.ini", - username = "trillian", - password = "secret" - ) public void testBuildWithoutSubject() { JwtAccessToken token = factory.create().build(); assertEquals("trillian", token.getSubject()); @@ -150,7 +159,33 @@ public class JwtAccessTokenBuilderTest { .getBody(); assertClaims(new JwtAccessToken(claims, compact)); } - + + @Test + public void testWithExternalGroups() { + applyExternalGroupsToSubject(true, "external"); + JwtAccessToken token = factory.create().subject("dent").build(); + assertArrayEquals(new String[]{"external"}, token.getCustom(JwtAccessToken.GROUPS_CLAIM_KEY).map(x -> (String[]) x).get()); + } + + @Test + public void testWithInternalGroups() { + applyExternalGroupsToSubject(false, "external"); + JwtAccessToken token = factory.create().subject("dent").build(); + assertFalse(token.getCustom(JwtAccessToken.GROUPS_CLAIM_KEY).isPresent()); + } + + private void applyExternalGroupsToSubject(boolean external, String... groups) { + Subject subject = spy(SecurityUtils.getSubject()); + when(subject.getPrincipals()).thenAnswer(invocation -> enrichWithGroups(invocation, groups, external)); + shiro.setSubject(subject); + } + + private Object enrichWithGroups(InvocationOnMock invocation, String[] groups, boolean external) throws Throwable { + PrincipalCollection principals = (PrincipalCollection) spy(invocation.callRealMethod()); + when(principals.oneByType(GroupNames.class)).thenReturn(new GroupNames(Arrays.asList(groups), external)); + return principals; + } + private void assertClaims(JwtAccessToken token){ assertThat(token.getId(), not(isEmptyOrNullString())); assertNotNull( token.getIssuedAt() ); From 2c570f2d847b06ade51c8be4ef7bfbe6caf7fc05 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Fri, 15 Feb 2019 11:30:36 +0100 Subject: [PATCH 21/87] Added position:absolute to tooltips --- scm-ui-components/packages/ui-components/src/Help.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scm-ui-components/packages/ui-components/src/Help.js b/scm-ui-components/packages/ui-components/src/Help.js index 047f79244f..9cb37e722d 100644 --- a/scm-ui-components/packages/ui-components/src/Help.js +++ b/scm-ui-components/packages/ui-components/src/Help.js @@ -7,7 +7,8 @@ import HelpIcon from './HelpIcon'; const styles = { tooltip: { display: "inline-block", - paddingLeft: "3px" + paddingLeft: "3px", + position: "absolute" } }; From 7c77b172e545c3ed45939c23f929c8312b539a72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 15 Feb 2019 11:27:16 +0000 Subject: [PATCH 22/87] Close branch bugfix/tooltips_cut_off From 2211978d1966b72a2a6948494b4ea384662adcce Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 15 Feb 2019 12:42:22 +0000 Subject: [PATCH 23/87] Close branch feature/external_groups From 00b17575dcb980accfe5168cae62fd62b51ddb64 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Fri, 15 Feb 2019 15:32:54 +0100 Subject: [PATCH 24/87] Scroll to top after page change/click on submit button --- .../ui-components/src/buttons/SubmitButton.js | 11 ++++++++- scm-ui/src/containers/Index.js | 13 +++++++---- scm-ui/src/containers/ScrollToTop.js | 23 +++++++++++++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 scm-ui/src/containers/ScrollToTop.js diff --git a/scm-ui-components/packages/ui-components/src/buttons/SubmitButton.js b/scm-ui-components/packages/ui-components/src/buttons/SubmitButton.js index 1a8604834b..3abf805749 100644 --- a/scm-ui-components/packages/ui-components/src/buttons/SubmitButton.js +++ b/scm-ui-components/packages/ui-components/src/buttons/SubmitButton.js @@ -4,7 +4,16 @@ import Button, { type ButtonProps } from "./Button"; class SubmitButton extends React.Component { render() { - return