diff --git a/scm-core/src/main/java/sonia/scm/Manager.java b/scm-core/src/main/java/sonia/scm/Manager.java index 14c458d923..20b3a16a8e 100644 --- a/scm-core/src/main/java/sonia/scm/Manager.java +++ b/scm-core/src/main/java/sonia/scm/Manager.java @@ -99,14 +99,14 @@ public interface Manager * @param limit parameter * * @since 1.4 - * @return objects from the store which are starts at the given + * @return objects from the store which are starts at the given * start parameter */ Collection getAll(int start, int limit); /** * Returns objects from the store which are starts at the given start - * parameter sorted by the given {@link java.util.Comparator}. + * parameter sorted by the given {@link java.util.Comparator}. * The objects returned are limited by the limit parameter. * * @@ -115,7 +115,7 @@ public interface Manager * @param limit parameter * * @since 1.4 - * @return objects from the store which are starts at the given + * @return objects from the store which are starts at the given * start parameter */ Collection getAll(Comparator comparator, int start, int limit); diff --git a/scm-core/src/main/java/sonia/scm/ManagerDecorator.java b/scm-core/src/main/java/sonia/scm/ManagerDecorator.java index 7b3f03ee8c..ef20a374cb 100644 --- a/scm-core/src/main/java/sonia/scm/ManagerDecorator.java +++ b/scm-core/src/main/java/sonia/scm/ManagerDecorator.java @@ -71,7 +71,7 @@ public class ManagerDecorator implements Manager { } @Override - public void delete(T object) throws NotFoundException { + public void delete(T object){ decorated.delete(object); } @@ -82,12 +82,12 @@ public class ManagerDecorator implements Manager { } @Override - public void modify(T object) throws NotFoundException { + public void modify(T object){ decorated.modify(object); } @Override - public void refresh(T object) throws NotFoundException { + public void refresh(T object){ decorated.refresh(object); } diff --git a/scm-core/src/main/java/sonia/scm/NotFoundException.java b/scm-core/src/main/java/sonia/scm/NotFoundException.java index 8a7ae642bd..37546be0b8 100644 --- a/scm-core/src/main/java/sonia/scm/NotFoundException.java +++ b/scm-core/src/main/java/sonia/scm/NotFoundException.java @@ -1,6 +1,6 @@ package sonia.scm; -public class NotFoundException extends Exception { +public class NotFoundException extends RuntimeException { public NotFoundException(String type, String id) { super(type + " with id '" + id + "' not found"); } diff --git a/scm-core/src/main/java/sonia/scm/user/ChangePasswordNotAllowedException.java b/scm-core/src/main/java/sonia/scm/user/ChangePasswordNotAllowedException.java index 19c609ba30..fda5e69323 100644 --- a/scm-core/src/main/java/sonia/scm/user/ChangePasswordNotAllowedException.java +++ b/scm-core/src/main/java/sonia/scm/user/ChangePasswordNotAllowedException.java @@ -2,10 +2,10 @@ package sonia.scm.user; public class ChangePasswordNotAllowedException extends RuntimeException { - public static final String WRONG_USER_TYPE = "User of type {0} are not allowed to change password"; + public static final String WRONG_USER_TYPE = "User of type %s are not allowed to change password"; - public ChangePasswordNotAllowedException(String message) { - super(message); + public ChangePasswordNotAllowedException(String type) { + super(String.format(WRONG_USER_TYPE, type)); } } diff --git a/scm-core/src/main/java/sonia/scm/user/InvalidPasswordException.java b/scm-core/src/main/java/sonia/scm/user/InvalidPasswordException.java index e06191a8f2..870430a1bb 100644 --- a/scm-core/src/main/java/sonia/scm/user/InvalidPasswordException.java +++ b/scm-core/src/main/java/sonia/scm/user/InvalidPasswordException.java @@ -2,9 +2,7 @@ package sonia.scm.user; public class InvalidPasswordException extends RuntimeException { - public static final String INVALID_MATCHING = "The given Password does not match with the stored one."; - - public InvalidPasswordException(String message) { - super(message); + public InvalidPasswordException() { + super("The given Password does not match with the stored one."); } } diff --git a/scm-core/src/main/java/sonia/scm/user/User.java b/scm-core/src/main/java/sonia/scm/user/User.java index e9dc277e5f..cae383a402 100644 --- a/scm-core/src/main/java/sonia/scm/user/User.java +++ b/scm-core/src/main/java/sonia/scm/user/User.java @@ -56,7 +56,10 @@ import java.security.Principal; * * @author Sebastian Sdorra */ -@StaticPermissions(value = "user", globalPermissions = {"create", "list", "autocomplete"}) +@StaticPermissions( + value = "user", + globalPermissions = {"create", "list", "autocomplete"}, + permissions = {"read", "modify", "delete", "changePassword"}) @XmlRootElement(name = "users") @XmlAccessorType(XmlAccessType.FIELD) public class User extends BasicPropertiesAware implements Principal, ModelObject, PermissionObject, ReducedModelObject @@ -274,10 +277,6 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject //J+ } - public User changePassword(String password){ - setPassword(password); - return this; - } //~--- get methods ---------------------------------------------------------- /** diff --git a/scm-core/src/main/java/sonia/scm/user/UserManager.java b/scm-core/src/main/java/sonia/scm/user/UserManager.java index 7829cd9974..f301c1f2b1 100644 --- a/scm-core/src/main/java/sonia/scm/user/UserManager.java +++ b/scm-core/src/main/java/sonia/scm/user/UserManager.java @@ -38,11 +38,7 @@ package sonia.scm.user; import sonia.scm.Manager; import sonia.scm.search.Searchable; -import java.text.MessageFormat; import java.util.Collection; -import java.util.function.Consumer; - -import static sonia.scm.user.ChangePasswordNotAllowedException.WRONG_USER_TYPE; /** * The central class for managing {@link User} objects. @@ -75,18 +71,6 @@ public interface UserManager */ public String getDefaultType(); - - /** - * Only account of the default type "xml" can change their password - */ - default Consumer getUserTypeChecker() { - return user -> { - if (!isTypeDefault(user)) { - throw new ChangePasswordNotAllowedException(MessageFormat.format(WRONG_USER_TYPE, user.getType())); - } - }; - } - default boolean isTypeDefault(User user) { return getDefaultType().equals(user.getType()); } @@ -99,5 +83,17 @@ public interface UserManager */ Collection autocomplete(String filter); + /** + * Changes the password of the logged in user. + * @param oldPassword The current encrypted password of the user. + * @param newPassword The new encrypted password of the user. + */ + void changePasswordForLoggedInUser(String oldPassword, String newPassword); + /** + * Overwrites the password for the given user id. This needs user write privileges. + * @param userId The id of the user to change the password for. + * @param newPassword The new encrypted password. + */ + void overwritePassword(String userId, String newPassword); } diff --git a/scm-core/src/main/java/sonia/scm/user/UserManagerDecorator.java b/scm-core/src/main/java/sonia/scm/user/UserManagerDecorator.java index f291e325c1..0384fe1b52 100644 --- a/scm-core/src/main/java/sonia/scm/user/UserManagerDecorator.java +++ b/scm-core/src/main/java/sonia/scm/user/UserManagerDecorator.java @@ -126,7 +126,16 @@ public class UserManagerDecorator extends ManagerDecorator return decorated.autocomplete(filter); } - //~--- fields --------------------------------------------------------------- + @Override + public void changePasswordForLoggedInUser(String oldPassword, String newPassword) { + decorated.changePasswordForLoggedInUser(oldPassword, newPassword); + } + + @Override + public void overwritePassword(String userId, String newPassword) { + decorated.overwritePassword(userId, newPassword); + } +//~--- fields --------------------------------------------------------------- /** Field description */ private final UserManager decorated; diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index b6f2210d80..7b6d5cb039 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -39,6 +39,8 @@ public class VndMediaType { public static final String UI_PLUGIN_COLLECTION = PREFIX + "uiPluginCollection" + SUFFIX; @SuppressWarnings("squid:S2068") public static final String PASSWORD_CHANGE = PREFIX + "passwordChange" + SUFFIX; + @SuppressWarnings("squid:S2068") + public static final String PASSWORD_OVERWRITE = PREFIX + "passwordOverwrite" + SUFFIX; public static final String ME = PREFIX + "me" + SUFFIX; public static final String SOURCE = PREFIX + "source" + SUFFIX; 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 27ca923902..93b0752766 100644 --- a/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java +++ b/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java @@ -154,7 +154,7 @@ public class SyncingRealmHelperTest { * Tests {@link SyncingRealmHelper#store(Group)} with an existing group. */ @Test - public void testStoreGroupModify() throws NotFoundException { + public void testStoreGroupModify(){ Group group = new Group("unit-test", "heartOfGold"); when(groupManager.get("heartOfGold")).thenReturn(group); @@ -191,7 +191,7 @@ public class SyncingRealmHelperTest { * Tests {@link SyncingRealmHelper#store(User)} with an existing user. */ @Test - public void testStoreUserModify() throws NotFoundException { + public void testStoreUserModify(){ when(userManager.contains("tricia")).thenReturn(Boolean.TRUE); User user = new User("tricia"); diff --git a/scm-it/src/test/java/sonia/scm/it/MeITCase.java b/scm-it/src/test/java/sonia/scm/it/MeITCase.java index 5809f883ca..ce6593ef11 100644 --- a/scm-it/src/test/java/sonia/scm/it/MeITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/MeITCase.java @@ -38,6 +38,30 @@ public class MeITCase { .assertStatusCode(204); } + @Test + public void nonAdminUserShouldChangeOwnPassword() { + String newPassword = "pass1"; + String username = "user1"; + String password = "pass"; + TestData.createUser(username, password,false,"xml", "em@l.de"); + // user change the own password + ScmRequests.start() + .requestIndexResource(username, password) + .requestMe() + .assertStatusCode(200) + .assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.FALSE)) + .assertPassword(Assert::assertNull) + .assertType(s -> assertThat(s).isEqualTo("xml")) + .requestChangePassword(password, newPassword) + .assertStatusCode(204); + // assert password is changed -> login with the new Password than undo changes + ScmRequests.start() + .requestIndexResource(username, newPassword) + .requestMe() + .assertStatusCode(200); + + } + @Test public void shouldHidePasswordLinkIfUserTypeIsNotXML() { String newUser = "user"; diff --git a/scm-it/src/test/java/sonia/scm/it/UserITCase.java b/scm-it/src/test/java/sonia/scm/it/UserITCase.java index a4bc4e412f..4c7e3b2cac 100644 --- a/scm-it/src/test/java/sonia/scm/it/UserITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/UserITCase.java @@ -29,7 +29,7 @@ public class UserITCase { .assertStatusCode(200) .assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE)) .assertPassword(Assert::assertNull) - .requestChangePassword(newPassword) // the oldPassword is not needed in the user resource + .requestChangePassword(newPassword) .assertStatusCode(204); // assert password is changed -> login with the new Password ScmRequests.start() @@ -65,6 +65,25 @@ public class UserITCase { } + @Test + public void nonAdminUserShouldNotChangePasswordOfOtherUser() { + String user = "user"; + String password = "pass"; + TestData.createUser(user, password, false, "xml", "em@l.de"); + String user2 = "user2"; + TestData.createUser(user2, password, false, "xml", "em@l.de"); + ScmRequests.start() + .requestIndexResource(user, password) + .assertUsersLinkDoesNotExists(); + // use the users/ endpoint bypassed the index resource + ScmRequests.start() + .requestUser(user, password, user2) + .assertStatusCode(403); + // use the users/password endpoint bypassed the index and users resources + ScmRequests.start() + .requestUserChangePassword(user, password, user2, "newPassword") + .assertStatusCode(403); + } @Test public void shouldHidePasswordLinkIfUserTypeIsNotXML() { diff --git a/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java b/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java index bcfbe93b3e..69c79c37bf 100644 --- a/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java +++ b/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java @@ -2,6 +2,9 @@ package sonia.scm.it.utils; import io.restassured.RestAssured; import io.restassured.response.Response; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.user.User; import sonia.scm.web.VndMediaType; import java.util.List; @@ -24,7 +27,8 @@ import static sonia.scm.it.utils.TestData.createPasswordChangeJson; */ public class ScmRequests { - private String url; + private static final Logger LOG = LoggerFactory.getLogger(ScmRequests.class); + private String username; private String password; @@ -38,6 +42,18 @@ public class ScmRequests { return new IndexResponse(applyGETRequest(RestUtil.REST_BASE_URL.toString())); } + public , T extends ModelResponse> UserResponse requestUser(String username, String password, String pathParam) { + setUsername(username); + setPassword(password); + return new UserResponse<>(applyGETRequest(RestUtil.REST_BASE_URL.resolve("users/"+pathParam).toString()), null); + } + + public ChangePasswordResponse requestUserChangePassword(String username, String password, String userPathParam, String newPassword) { + setUsername(username); + setPassword(password); + return new ChangePasswordResponse<>(applyPUTRequest(RestUtil.REST_BASE_URL.resolve("users/"+userPathParam+"/password").toString(), VndMediaType.PASSWORD_OVERWRITE, TestData.createPasswordChangeJson(password,newPassword)), null); + + } /** * Apply a GET Request to the extracted url from the given link @@ -73,6 +89,7 @@ public class ScmRequests { * @return the response of the GET request using the given url */ private Response applyGETRequestWithQueryParams(String url, String params) { + LOG.info("GET {}", url); return RestAssured.given() .auth().preemptive().basic(username, password) .when() @@ -115,6 +132,7 @@ public class ScmRequests { * @return the response of the PUT request using the given url */ private Response applyPUTRequest(String url, String mediaType, String body) { + LOG.info("PUT {}", url); return RestAssured.given() .auth().preemptive().basic(username, password) .when() @@ -132,7 +150,6 @@ public class ScmRequests { this.password = password; } - public class IndexResponse extends ModelResponse { public static final String LINK_AUTOCOMPLETE_USERS = "_links.autocomplete.find{it.name=='users'}.href"; public static final String LINK_AUTOCOMPLETE_GROUPS = "_links.autocomplete.find{it.name=='groups'}.href"; @@ -160,10 +177,15 @@ public class ScmRequests { return new MeResponse<>(applyGETRequestFromLink(response, LINK_ME), this); } - public UserResponse requestUser(String username) { + public UserResponse requestUser(String username) { return new UserResponse<>(applyGETRequestFromLinkWithParams(response, LINK_USERS, username), this); } + public IndexResponse assertUsersLinkDoesNotExists() { + return super.assertPropertyPathDoesNotExists(LINK_USERS); + } + + } public class RepositoryResponse extends ModelResponse, PREV> { @@ -267,17 +289,19 @@ public class ScmRequests { } - public class MeResponse extends UserResponse { + public class MeResponse extends UserResponse, PREV> { public MeResponse(Response response, PREV previousResponse) { super(response, previousResponse); } - + public ChangePasswordResponse requestChangePassword(String oldPassword, String newPassword) { + return new ChangePasswordResponse<>(applyPUTRequestFromLink(super.response, LINKS_PASSWORD_HREF, VndMediaType.PASSWORD_CHANGE, createPasswordChangeJson(oldPassword, newPassword)), this); + } } - public class UserResponse extends ModelResponse, PREV> { + public class UserResponse, PREV extends ModelResponse> extends ModelResponse { public static final String LINKS_PASSWORD_HREF = "_links.password.href"; @@ -285,34 +309,29 @@ public class ScmRequests { super(response, previousResponse); } - public UserResponse assertPassword(Consumer assertPassword) { + public SELF assertPassword(Consumer assertPassword) { return super.assertSingleProperty(assertPassword, "password"); } - public UserResponse assertType(Consumer assertType) { + public SELF assertType(Consumer assertType) { return assertSingleProperty(assertType, "type"); } - public UserResponse assertAdmin(Consumer assertAdmin) { + public SELF assertAdmin(Consumer assertAdmin) { return assertSingleProperty(assertAdmin, "admin"); } - public UserResponse assertPasswordLinkDoesNotExists() { + public SELF assertPasswordLinkDoesNotExists() { return assertPropertyPathDoesNotExists(LINKS_PASSWORD_HREF); } - public UserResponse assertPasswordLinkExists() { + public SELF assertPasswordLinkExists() { return assertPropertyPathExists(LINKS_PASSWORD_HREF); } public ChangePasswordResponse requestChangePassword(String newPassword) { - return requestChangePassword(null, newPassword); + return new ChangePasswordResponse<>(applyPUTRequestFromLink(super.response, LINKS_PASSWORD_HREF, VndMediaType.PASSWORD_OVERWRITE, createPasswordChangeJson(null, newPassword)), this); } - - public ChangePasswordResponse requestChangePassword(String oldPassword, String newPassword) { - return new ChangePasswordResponse<>(applyPUTRequestFromLink(super.response, LINKS_PASSWORD_HREF, VndMediaType.PASSWORD_CHANGE, createPasswordChangeJson(oldPassword, newPassword)), this); - } - } diff --git a/scm-plugins/scm-git-plugin/package.json b/scm-plugins/scm-git-plugin/package.json index 93ec098597..8f0ab90d34 100644 --- a/scm-plugins/scm-git-plugin/package.json +++ b/scm-plugins/scm-git-plugin/package.json @@ -9,6 +9,6 @@ "@scm-manager/ui-extensions": "^0.0.7" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.15" + "@scm-manager/ui-bundler": "^0.0.17" } } diff --git a/scm-plugins/scm-git-plugin/yarn.lock b/scm-plugins/scm-git-plugin/yarn.lock index 702e28711f..c1abfd6640 100644 --- a/scm-plugins/scm-git-plugin/yarn.lock +++ b/scm-plugins/scm-git-plugin/yarn.lock @@ -707,9 +707,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.15": - version "0.0.15" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917" +"@scm-manager/ui-bundler@^0.0.17": + version "0.0.17" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -726,7 +726,6 @@ browserify-css "^0.14.0" colors "^1.3.1" commander "^2.17.1" - connect-history-api-fallback "^1.5.0" eslint "^5.4.0" eslint-config-react-app "^2.1.0" eslint-plugin-flowtype "^2.50.0" diff --git a/scm-plugins/scm-hg-plugin/package.json b/scm-plugins/scm-hg-plugin/package.json index c5907d38bc..a2ebd0e6ad 100644 --- a/scm-plugins/scm-hg-plugin/package.json +++ b/scm-plugins/scm-hg-plugin/package.json @@ -9,6 +9,6 @@ "@scm-manager/ui-extensions": "^0.0.7" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.15" + "@scm-manager/ui-bundler": "^0.0.17" } } diff --git a/scm-plugins/scm-hg-plugin/yarn.lock b/scm-plugins/scm-hg-plugin/yarn.lock index 8822bd5e57..c3e8cc476f 100644 --- a/scm-plugins/scm-hg-plugin/yarn.lock +++ b/scm-plugins/scm-hg-plugin/yarn.lock @@ -641,9 +641,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.15": - version "0.0.15" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917" +"@scm-manager/ui-bundler@^0.0.17": + version "0.0.17" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -660,7 +660,6 @@ browserify-css "^0.14.0" colors "^1.3.1" commander "^2.17.1" - connect-history-api-fallback "^1.5.0" eslint "^5.4.0" eslint-config-react-app "^2.1.0" eslint-plugin-flowtype "^2.50.0" diff --git a/scm-plugins/scm-svn-plugin/package.json b/scm-plugins/scm-svn-plugin/package.json index 118108c882..b05332e6ca 100644 --- a/scm-plugins/scm-svn-plugin/package.json +++ b/scm-plugins/scm-svn-plugin/package.json @@ -9,6 +9,6 @@ "@scm-manager/ui-extensions": "^0.0.7" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.15" + "@scm-manager/ui-bundler": "^0.0.17" } } diff --git a/scm-plugins/scm-svn-plugin/yarn.lock b/scm-plugins/scm-svn-plugin/yarn.lock index 8822bd5e57..c3e8cc476f 100644 --- a/scm-plugins/scm-svn-plugin/yarn.lock +++ b/scm-plugins/scm-svn-plugin/yarn.lock @@ -641,9 +641,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.15": - version "0.0.15" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.15.tgz#8ed4a557d5ae38d6b99493b29608fd6a4c9cd917" +"@scm-manager/ui-bundler@^0.0.17": + version "0.0.17" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.17.tgz#949b90ca57e4268be28fcf4975bd9622f60278bb" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -660,7 +660,6 @@ browserify-css "^0.14.0" colors "^1.3.1" commander "^2.17.1" - connect-history-api-fallback "^1.5.0" eslint "^5.4.0" eslint-config-react-app "^2.1.0" eslint-plugin-flowtype "^2.50.0" diff --git a/scm-test/src/main/java/sonia/scm/user/UserManagerTestBase.java b/scm-test/src/main/java/sonia/scm/user/UserManagerTestBase.java index 9f1c50c797..dddce344ce 100644 --- a/scm-test/src/main/java/sonia/scm/user/UserManagerTestBase.java +++ b/scm-test/src/main/java/sonia/scm/user/UserManagerTestBase.java @@ -196,7 +196,7 @@ public abstract class UserManagerTestBase extends ManagerTestBase { } @Test(expected = NotFoundException.class) - public void testModifyNotExisting() throws NotFoundException, ConcurrentModificationException { + public void testModifyNotExisting() { manager.modify(UserTestData.createZaphod()); } @@ -249,7 +249,7 @@ public abstract class UserManagerTestBase extends ManagerTestBase { } @Test(expected = NotFoundException.class) - public void testRefreshNotFound() throws NotFoundException { + public void testRefreshNotFound(){ manager.refresh(UserTestData.createDent()); } diff --git a/scm-ui-components/packages/ui-components/package.json b/scm-ui-components/packages/ui-components/package.json index e515002728..096a8636b3 100644 --- a/scm-ui-components/packages/ui-components/package.json +++ b/scm-ui-components/packages/ui-components/package.json @@ -12,20 +12,21 @@ "eslint-fix": "eslint src --fix" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.15", + "@scm-manager/ui-bundler": "^0.0.17", "create-index": "^2.3.0", "enzyme": "^3.5.0", "enzyme-adapter-react-16": "^1.3.1", "flow-bin": "^0.79.1", "flow-typed": "^2.5.1", "jest": "^23.5.0", - "raf": "^3.4.0" + "raf": "^3.4.0", + "react-router-enzyme-context": "^1.2.0" }, "dependencies": { "classnames": "^2.2.6", "moment": "^2.22.2", - "react": "^16.4.2", - "react-dom": "^16.4.2", + "react": "^16.5.2", + "react-dom": "^16.5.2", "react-i18next": "^7.11.0", "react-jss": "^8.6.1", "react-router-dom": "^4.3.1", diff --git a/scm-ui-components/packages/ui-components/src/LinkPaginator.js b/scm-ui-components/packages/ui-components/src/LinkPaginator.js new file mode 100644 index 0000000000..aaf13d7b15 --- /dev/null +++ b/scm-ui-components/packages/ui-components/src/LinkPaginator.js @@ -0,0 +1,133 @@ +//@flow +import React from "react"; +import {translate} from "react-i18next"; +import type {PagedCollection} from "@scm-manager/ui-types"; +import {Button} from "./buttons"; + +type Props = { + collection: PagedCollection, + page: number, + + // context props + t: string => string +}; + +class LinkPaginator extends React.Component { + + renderFirstButton() { + return ( +