From a2f83e2429c1597c8fcccf6e35ac4312abf1531f Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 13 Mar 2019 12:07:18 +0100 Subject: [PATCH 01/11] removes admin flag from user object --- .../src/main/java/sonia/scm/user/User.java | 35 +---- .../packages/ui-types/src/User.js | 1 - scm-ui/src/users/components/UserForm.js | 11 -- scm-ui/src/users/components/table/Details.js | 126 +++++++++--------- scm-ui/src/users/components/table/UserRow.js | 62 ++++----- .../src/users/components/table/UserTable.js | 74 +++++----- .../AuthorizationChangedEventProducer.java | 52 ++++---- .../DefaultAuthorizationCollector.java | 5 - .../test/java/sonia/scm/it/GitLfsITCase.java | 1 - .../sonia/scm/it/UserPermissionITCase.java | 1 - ...AuthorizationChangedEventProducerTest.java | 76 +++++------ .../DefaultAuthorizationCollectorTest.java | 16 --- 12 files changed, 193 insertions(+), 267 deletions(-) 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 3c185ae3b8..bd4b975561 100644 --- a/scm-core/src/main/java/sonia/scm/user/User.java +++ b/scm-core/src/main/java/sonia/scm/user/User.java @@ -157,12 +157,6 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject { boolean result = false; - if (user.isAdmin() != admin) - { - result = true; - user.setAdmin(admin); - } - if (user.isActive() != active) { result = true; @@ -229,7 +223,6 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject && Objects.equal(displayName, other.displayName) && Objects.equal(mail, other.mail) && Objects.equal(type, other.type) - && Objects.equal(admin, other.admin) && Objects.equal(active, other.active) && Objects.equal(password, other.password) && Objects.equal(creationDate, other.creationDate) @@ -246,7 +239,7 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject @Override public int hashCode() { - return Objects.hashCode(name, displayName, mail, type, admin, password, + return Objects.hashCode(name, displayName, mail, type, password, active, creationDate, lastModified, properties); } @@ -269,7 +262,6 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject .add("displayName",displayName) .add("mail", mail) .add("password", pwd) - .add("admin", admin) .add("type", type) .add("active", active) .add("creationDate", creationDate) @@ -385,17 +377,6 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject return active; } - /** - * Method description - * - * - * @return - */ - public boolean isAdmin() - { - return admin; - } - /** * Method description * @@ -424,17 +405,6 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject this.active = active; } - /** - * Method description - * - * - * @param admin - */ - public void setAdmin(boolean admin) - { - this.admin = admin; - } - /** * Method description * @@ -518,9 +488,6 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject /** Field description */ private boolean active = true; - /** Field description */ - private boolean admin = false; - /** Field description */ private Long creationDate; diff --git a/scm-ui-components/packages/ui-types/src/User.js b/scm-ui-components/packages/ui-types/src/User.js index 9a34c815a7..f0c5e1e6ef 100644 --- a/scm-ui-components/packages/ui-types/src/User.js +++ b/scm-ui-components/packages/ui-types/src/User.js @@ -6,7 +6,6 @@ export type User = { name: string, mail: string, password: string, - admin: boolean, active: boolean, type?: string, creationDate?: string, diff --git a/scm-ui/src/users/components/UserForm.js b/scm-ui/src/users/components/UserForm.js index c8fae1ff0a..3d7f2cfc30 100644 --- a/scm-ui/src/users/components/UserForm.js +++ b/scm-ui/src/users/components/UserForm.js @@ -37,7 +37,6 @@ class UserForm extends React.Component { displayName: "", mail: "", password: "", - admin: false, active: true, _links: {} }, @@ -167,12 +166,6 @@ class UserForm extends React.Component {
{passwordChangeField} - { }); }; - handleAdminChange = (admin: boolean) => { - this.setState({ user: { ...this.state.user, admin } }); - }; - handleActiveChange = (active: boolean) => { this.setState({ user: { ...this.state.user, active } }); }; diff --git a/scm-ui/src/users/components/table/Details.js b/scm-ui/src/users/components/table/Details.js index 1db5d2154c..83eebccc08 100644 --- a/scm-ui/src/users/components/table/Details.js +++ b/scm-ui/src/users/components/table/Details.js @@ -1,66 +1,60 @@ -//@flow -import React from "react"; -import type { User } from "@scm-manager/ui-types"; -import { translate } from "react-i18next"; -import { Checkbox, MailLink, DateFromNow } from "@scm-manager/ui-components"; - -type Props = { - user: User, - t: string => string -}; - -class Details extends React.Component { - render() { - const { user, t } = this.props; - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{t("user.name")}{user.name}
{t("user.displayName")}{user.displayName}
{t("user.mail")} - -
{t("user.admin")} - -
{t("user.active")} - -
{t("user.type")}{user.type}
{t("user.creationDate")} - -
{t("user.lastModified")} - -
- ); - } -} - -export default translate("users")(Details); +//@flow +import React from "react"; +import type { User } from "@scm-manager/ui-types"; +import { translate } from "react-i18next"; +import { Checkbox, MailLink, DateFromNow } from "@scm-manager/ui-components"; + +type Props = { + user: User, + t: string => string +}; + +class Details extends React.Component { + render() { + const { user, t } = this.props; + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{t("user.name")}{user.name}
{t("user.displayName")}{user.displayName}
{t("user.mail")} + +
{t("user.active")} + +
{t("user.type")}{user.type}
{t("user.creationDate")} + +
{t("user.lastModified")} + +
+ ); + } +} + +export default translate("users")(Details); diff --git a/scm-ui/src/users/components/table/UserRow.js b/scm-ui/src/users/components/table/UserRow.js index c879d5e8f7..84bd1234fa 100644 --- a/scm-ui/src/users/components/table/UserRow.js +++ b/scm-ui/src/users/components/table/UserRow.js @@ -1,31 +1,31 @@ -// @flow -import React from "react"; -import { Link } from "react-router-dom"; -import type { User } from "@scm-manager/ui-types"; - -type Props = { - user: User -}; - -export default class UserRow extends React.Component { - renderLink(to: string, label: string) { - return {label}; - } - - render() { - const { user } = this.props; - const to = `/user/${user.name}`; - return ( - - {this.renderLink(to, user.name)} - {this.renderLink(to, user.displayName)} - - {user.mail} - - - - - - ); - } -} +// @flow +import React from "react"; +import { Link } from "react-router-dom"; +import type { User } from "@scm-manager/ui-types"; + +type Props = { + user: User +}; + +export default class UserRow extends React.Component { + renderLink(to: string, label: string) { + return {label}; + } + + render() { + const { user } = this.props; + const to = `/user/${user.name}`; + return ( + + {this.renderLink(to, user.name)} + {this.renderLink(to, user.displayName)} + + {user.mail} + + + + + + ); + } +} diff --git a/scm-ui/src/users/components/table/UserTable.js b/scm-ui/src/users/components/table/UserTable.js index 8febdb6011..89e6e5df83 100644 --- a/scm-ui/src/users/components/table/UserTable.js +++ b/scm-ui/src/users/components/table/UserTable.js @@ -1,37 +1,37 @@ -// @flow -import React from "react"; -import { translate } from "react-i18next"; -import UserRow from "./UserRow"; -import type { User } from "@scm-manager/ui-types"; - -type Props = { - t: string => string, - users: User[] -}; - -; - -class UserTable extends React.Component { - render() { - const { users, t } = this.props; - return ( - - - - - - - - - - - {users.map((user, index) => { - return ; - })} - -
{t("user.name")}{t("user.displayName")}{t("user.mail")}{t("user.admin")}
- ); - } -} - -export default translate("users")(UserTable); \ No newline at end of file +// @flow +import React from "react"; +import { translate } from "react-i18next"; +import UserRow from "./UserRow"; +import type { User } from "@scm-manager/ui-types"; + +type Props = { + t: string => string, + users: User[] +}; + +; + +class UserTable extends React.Component { + render() { + const { users, t } = this.props; + return ( + + + + + + + + + + + {users.map((user, index) => { + return ; + })} + +
{t("user.name")}{t("user.displayName")}{t("user.mail")}{t("user.active")}
+ ); + } +} + +export default translate("users")(UserTable); diff --git a/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java b/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java index 0586db2bb3..66dbb51073 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java +++ b/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java @@ -49,15 +49,15 @@ import sonia.scm.user.UserEvent; import sonia.scm.user.UserModificationEvent; /** - * Receives all kinds of events, which affects authorization relevant data and fires an + * Receives all kinds of events, which affects authorization relevant data and fires an * {@link AuthorizationChangedEvent} if authorization data has changed. - * + * * @author Sebastian Sdorra * @since 1.52 */ @EagerSingleton public class AuthorizationChangedEventProducer { - + /** * the logger for AuthorizationChangedEventProducer */ @@ -68,7 +68,7 @@ public class AuthorizationChangedEventProducer { */ public AuthorizationChangedEventProducer() { } - + /** * Invalidates the cache of a user which was modified. The cache entries for the user will be invalidated for the * following reasons: @@ -90,11 +90,11 @@ public class AuthorizationChangedEventProducer { } } } - + private boolean isModificationEvent(HandlerEvent event) { return event instanceof ModificationHandlerEvent; } - + private void handleUserEvent(UserEvent event) { String username = event.getItem().getName(); logger.debug( @@ -102,26 +102,26 @@ public class AuthorizationChangedEventProducer { ); fireEventForUser(username); } - + private void handleUserModificationEvent(UserModificationEvent event) { String username = event.getItem().getId(); User beforeModification = event.getItemBeforeModification(); if (isAuthorizationDataModified(event.getItem(), beforeModification)) { logger.debug( - "fire authorization changed event for user {}, because of a authorization relevant field has changed", + "fire authorization changed event for user {}, because of a authorization relevant field has changed", username ); fireEventForUser(username); } else { logger.debug( - "authorization changed event for user {} is not fired, because no authorization relevant field has changed", + "authorization changed event for user {} is not fired, because no authorization relevant field has changed", username ); } } private boolean isAuthorizationDataModified(User user, User beforeModification) { - return user.isAdmin() != beforeModification.isAdmin() || user.isActive() != beforeModification.isActive(); + return user.isActive() != beforeModification.isActive(); } private void fireEventForUser(String username) { @@ -148,7 +148,7 @@ public class AuthorizationChangedEventProducer { } } } - + private void handleRepositoryModificationEvent(RepositoryModificationEvent event) { Repository repository = event.getItem(); if (isAuthorizationDataModified(repository, event.getItemBeforeModification())) { @@ -169,14 +169,14 @@ public class AuthorizationChangedEventProducer { || repository.isPublicReadable() != beforeModification.isPublicReadable() || !(repository.getPermissions().containsAll(beforeModification.getPermissions()) && beforeModification.getPermissions().containsAll(repository.getPermissions())); } - + private void fireEventForEveryUser() { sendEvent(AuthorizationChangedEvent.createForEveryUser()); } - + private void handleRepositoryEvent(RepositoryEvent event){ logger.debug( - "fire authorization changed event, because of received {} event for repository {}", + "fire authorization changed event, because of received {} event for repository {}", event.getEventType(), event.getItem().getName() ); fireEventForEveryUser(); @@ -199,7 +199,7 @@ public class AuthorizationChangedEventProducer { } } } - + private void handleGroupPermissionChange(AssignedPermission permission) { logger.debug( "fire authorization changed event for group {}, because permission {} has changed", @@ -207,13 +207,13 @@ public class AuthorizationChangedEventProducer { ); fireEventForEveryUser(); } - + private void handleUserPermissionChange(AssignedPermission permission) { logger.debug( "fire authorization changed event for user {}, because permission {} has changed", permission.getName(), permission.getPermission() ); - fireEventForUser(permission.getName()); + fireEventForUser(permission.getName()); } /** @@ -230,7 +230,7 @@ public class AuthorizationChangedEventProducer { public void onEvent(GroupEvent event) { if (event.getEventType().isPost()) { if (isModificationEvent(event)) { - handleGroupModificationEvent((GroupModificationEvent) event); + handleGroupModificationEvent((GroupModificationEvent) event); } else { handleGroupEvent(event); } @@ -244,28 +244,28 @@ public class AuthorizationChangedEventProducer { fireEventForEveryUser(); } else { logger.debug( - "authorization changed event is not fired, because non relevant field of group {} has changed", + "authorization changed event is not fired, because non relevant field of group {} has changed", group.getId() ); } } - + private boolean isAuthorizationDataModified(Group group, Group beforeModification) { return !group.getMembers().equals(beforeModification.getMembers()); } - + private void handleGroupEvent(GroupEvent event){ logger.debug( - "fire authorization changed event, because of received group event {} for group {}", - event.getEventType(), + "fire authorization changed event, because of received group event {} for group {}", + event.getEventType(), event.getItem().getId() ); - fireEventForEveryUser(); + fireEventForEveryUser(); } - + @VisibleForTesting protected void sendEvent(AuthorizationChangedEvent event) { ScmEventBus.getInstance().post(event); } - + } diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java index 9dca7a774e..92d21ce1a1 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java @@ -271,11 +271,6 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector } private boolean isAdmin(User user, GroupNames groups) { - boolean admin = user.isAdmin(); - if (admin) { - logger.debug("user {} is marked as admin, because of the user flag", user.getName()); - return true; - } if (isUserAdminInConfiguration(user)) { logger.debug("user {} is marked as admin, because of the admin user configuration", user.getName()); return true; diff --git a/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java b/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java index a367d171a1..617f505c2b 100644 --- a/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java +++ b/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java @@ -142,7 +142,6 @@ public class GitLfsITCase { dto.setDisplayName(user.getDisplayName()); dto.setType(user.getType()); dto.setActive(user.isActive()); - dto.setAdmin(user.isAdmin()); dto.setPassword(user.getPassword()); createResource(adminClient, "users") .accept("*/*") diff --git a/scm-webapp/src/test/java/sonia/scm/it/UserPermissionITCase.java b/scm-webapp/src/test/java/sonia/scm/it/UserPermissionITCase.java index 3c0e2eed52..3bcc3b4efd 100644 --- a/scm-webapp/src/test/java/sonia/scm/it/UserPermissionITCase.java +++ b/scm-webapp/src/test/java/sonia/scm/it/UserPermissionITCase.java @@ -133,7 +133,6 @@ public class UserPermissionITCase extends AbstractPermissionITCaseBase "scm-admin@scm-manager.org"); user.setPassword("hallo123"); - user.setAdmin(true); user.setType("xml"); return user; diff --git a/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java b/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java index 59b6951025..2363658f36 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java @@ -1,10 +1,10 @@ /** * Copyright (c) 2014, Sebastian Sdorra * All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, @@ -13,7 +13,7 @@ * 3. Neither the name of SCM-Manager; nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. - * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -24,9 +24,9 @@ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + * * http://bitbucket.org/sdorra/scm-manager - * + * */ package sonia.scm.security; @@ -58,18 +58,18 @@ import static org.junit.Assert.assertTrue; /** * Unit tests for {@link AuthorizationChangedEventProducer}. - * + * * @author Sebastian Sdorra */ public class AuthorizationChangedEventProducerTest { - + private StoringAuthorizationChangedEventProducer producer; - + @Before public void setUpProducer() { producer = new StoringAuthorizationChangedEventProducer(); } - + /** * Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.user.UserEvent)}. */ @@ -79,15 +79,15 @@ public class AuthorizationChangedEventProducerTest { User user = UserTestData.createDent(); producer.onEvent(new UserEvent(HandlerEventType.BEFORE_CREATE, user)); assertEventIsNotFired(); - + producer.onEvent(new UserEvent(HandlerEventType.CREATE, user)); assertUserEventIsFired("dent"); } - + private void assertEventIsNotFired(){ assertNull(producer.event); } - + private void assertUserEventIsFired(String username){ assertNotNull(producer.event); assertTrue(producer.event.isEveryUserAffected()); @@ -102,28 +102,28 @@ public class AuthorizationChangedEventProducerTest { /** * Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.user.UserEvent)} with modified user. */ - @Test + @Test public void testOnUserModificationEvent() { User user = UserTestData.createDent(); User userModified = UserTestData.createDent(); userModified.setDisplayName("Super Dent"); - + producer.onEvent(new UserModificationEvent(HandlerEventType.BEFORE_CREATE, userModified, user)); assertEventIsNotFired(); - + producer.onEvent(new UserModificationEvent(HandlerEventType.CREATE, userModified, user)); assertEventIsNotFired(); - - userModified.setAdmin(true); - + + userModified.setActive(false); + producer.onEvent(new UserModificationEvent(HandlerEventType.BEFORE_CREATE, userModified, user)); assertEventIsNotFired(); - + producer.onEvent(new UserModificationEvent(HandlerEventType.CREATE, userModified, user)); assertUserEventIsFired("dent"); } - + /** * Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.group.GroupEvent)}. */ @@ -133,11 +133,11 @@ public class AuthorizationChangedEventProducerTest { Group group = new Group("xml", "base"); producer.onEvent(new GroupEvent(HandlerEventType.BEFORE_CREATE, group)); assertEventIsNotFired(); - + producer.onEvent(new GroupEvent(HandlerEventType.CREATE, group)); assertGlobalEventIsFired(); } - + /** * Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.group.GroupEvent)} with modified groups. */ @@ -148,15 +148,15 @@ public class AuthorizationChangedEventProducerTest { Group modifiedGroup = new Group("xml", "base"); producer.onEvent(new GroupModificationEvent(HandlerEventType.BEFORE_MODIFY, modifiedGroup, group)); assertEventIsNotFired(); - + producer.onEvent(new GroupModificationEvent(HandlerEventType.MODIFY, modifiedGroup, group)); assertEventIsNotFired(); - + modifiedGroup.add("test"); producer.onEvent(new GroupModificationEvent(HandlerEventType.MODIFY, modifiedGroup, group)); assertGlobalEventIsFired(); } - + /** * Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.repository.RepositoryEvent)}. */ @@ -166,13 +166,13 @@ public class AuthorizationChangedEventProducerTest { Repository repository = RepositoryTestData.createHeartOfGold(); producer.onEvent(new RepositoryEvent(HandlerEventType.BEFORE_CREATE, repository)); assertEventIsNotFired(); - + producer.onEvent(new RepositoryEvent(HandlerEventType.CREATE, repository)); assertGlobalEventIsFired(); } - + /** - * Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.repository.RepositoryEvent)} with modified + * Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.repository.RepositoryEvent)} with modified * repository. */ @Test @@ -224,11 +224,11 @@ public class AuthorizationChangedEventProducerTest { producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); assertEventIsNotFired(); } - + private void resetStoredEvent(){ producer.event = null; } - + /** * Tests {@link AuthorizationChangedEventProducer#onEvent(AssignedPermissionEvent)}. */ @@ -240,33 +240,33 @@ public class AuthorizationChangedEventProducerTest { ); producer.onEvent(new AssignedPermissionEvent(HandlerEventType.BEFORE_CREATE, groupPermission)); assertEventIsNotFired(); - + producer.onEvent(new AssignedPermissionEvent(HandlerEventType.CREATE, groupPermission)); assertGlobalEventIsFired(); - + resetStoredEvent(); - + StoredAssignedPermission userPermission = new StoredAssignedPermission( "123", new AssignedPermission("trillian", false, "repository:read:*") ); producer.onEvent(new AssignedPermissionEvent(HandlerEventType.BEFORE_CREATE, userPermission)); assertEventIsNotFired(); - + resetStoredEvent(); - + producer.onEvent(new AssignedPermissionEvent(HandlerEventType.CREATE, userPermission)); assertUserEventIsFired("trillian"); } - + private static class StoringAuthorizationChangedEventProducer extends AuthorizationChangedEventProducer { - + private AuthorizationChangedEvent event; @Override protected void sendEvent(AuthorizationChangedEvent event) { this.event = event; } - + } } diff --git a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java index e9345c9599..7550aead3e 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java @@ -197,22 +197,6 @@ public class DefaultAuthorizationCollectorTest { assertThat(authInfo.getObjectPermissions(), nullValue()); } - /** - * Tests {@link AuthorizationCollector#collect()} as admin. - */ - @Test - @SubjectAware( - configuration = "classpath:sonia/scm/shiro-001.ini" - ) - public void testCollectAsAdmin() { - User trillian = UserTestData.createTrillian(); - trillian.setAdmin(true); - authenticate(trillian, "main"); - - AuthorizationInfo authInfo = collector.collect(); - assertIsAdmin(authInfo); - } - /** * Tests {@link AuthorizationCollector#collect()} with repository permissions. */ From 4ffdde6417391fb4ec80a36c53dc2223059b1969 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 13 Mar 2019 12:12:06 +0100 Subject: [PATCH 02/11] fixes AdministrationContext with user admin flag --- .../security/AdministrationContextMarker.java | 8 ++++ .../security/AdministrationContextRealm.java | 42 +++++++++++++++++++ .../DefaultAdministrationContext.java | 3 +- .../AdministrationContextRealmTest.java | 34 +++++++++++++++ 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/web/security/AdministrationContextMarker.java create mode 100644 scm-webapp/src/main/java/sonia/scm/web/security/AdministrationContextRealm.java create mode 100644 scm-webapp/src/test/java/sonia/scm/web/security/AdministrationContextRealmTest.java diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/AdministrationContextMarker.java b/scm-webapp/src/main/java/sonia/scm/web/security/AdministrationContextMarker.java new file mode 100644 index 0000000000..f24b5020ce --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/web/security/AdministrationContextMarker.java @@ -0,0 +1,8 @@ +package sonia.scm.web.security; + +final class AdministrationContextMarker { + + static final AdministrationContextMarker MARKER = new AdministrationContextMarker(); + + private AdministrationContextMarker() {} +} diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/AdministrationContextRealm.java b/scm-webapp/src/main/java/sonia/scm/web/security/AdministrationContextRealm.java new file mode 100644 index 0000000000..7a4a345af4 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/web/security/AdministrationContextRealm.java @@ -0,0 +1,42 @@ +package sonia.scm.web.security; + +import com.google.common.collect.Sets; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.authz.SimpleAuthorizationInfo; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.subject.PrincipalCollection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.plugin.Extension; +import sonia.scm.security.Role; + +@Extension +public class AdministrationContextRealm extends AuthorizingRealm { + + private static final Logger LOG = LoggerFactory.getLogger(AdministrationContextRealm.class); + + public AdministrationContextRealm() { + setName(DefaultAdministrationContext.REALM); + } + + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { + AdministrationContextMarker marker = principals.oneByType(AdministrationContextMarker.class); + if (marker == AdministrationContextMarker.MARKER) { + LOG.info("assign admin permissions to admin context user {}", principals.getPrimaryPrincipal()); + SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(Sets.newHashSet(Role.USER, Role.ADMIN)); + authorizationInfo.setStringPermissions(Sets.newHashSet("*")); + return authorizationInfo; + } + return null; + } + + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { + // we make no authentication we do only authorization + return null; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java b/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java index 46c6c5de00..02f4bf0814 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java @@ -75,7 +75,7 @@ public class DefaultAdministrationContext implements AdministrationContext "/sonia/scm/web/security/system-account.xml"; /** Field description */ - private static final String REALM = "AdminRealm"; + static final String REALM = "AdminRealm"; /** the logger for DefaultAdministrationContext */ private static final Logger logger = @@ -174,6 +174,7 @@ public class DefaultAdministrationContext implements AdministrationContext collection.add(adminUser.getId(), REALM); collection.add(adminUser, REALM); collection.add(new GroupNames(), REALM); + collection.add(AdministrationContextMarker.MARKER, REALM); return collection; } diff --git a/scm-webapp/src/test/java/sonia/scm/web/security/AdministrationContextRealmTest.java b/scm-webapp/src/test/java/sonia/scm/web/security/AdministrationContextRealmTest.java new file mode 100644 index 0000000000..ad504433d8 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/web/security/AdministrationContextRealmTest.java @@ -0,0 +1,34 @@ +package sonia.scm.web.security; + +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.subject.SimplePrincipalCollection; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class AdministrationContextRealmTest { + + private AdministrationContextRealm realm = new AdministrationContextRealm(); + + @Test + void shouldAssignAdminPermissions() { + SimplePrincipalCollection collection = new SimplePrincipalCollection(); + collection.add("scm-system", DefaultAdministrationContext.REALM); + collection.add(AdministrationContextMarker.MARKER, DefaultAdministrationContext.REALM); + + AuthorizationInfo authorizationInfo = realm.doGetAuthorizationInfo(collection); + + assertThat(authorizationInfo.getStringPermissions()).containsOnly("*"); + } + + @Test + void shouldReturnNull() { + SimplePrincipalCollection collection = new SimplePrincipalCollection(); + collection.add("scm-system", DefaultAdministrationContext.REALM); + + AuthorizationInfo authorizationInfo = realm.doGetAuthorizationInfo(collection); + + assertThat(authorizationInfo).isNull(); + } + +} From 017879619c84c8461e1767394822a786d990ced9 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 13 Mar 2019 12:15:13 +0100 Subject: [PATCH 03/11] fixed admin permissions of initial created scmadmin user account --- .../sonia/scm/config/admin-account.xml | 41 -------- .../sonia/scm/config/anonymous-account.xml | 49 ---------- .../sonia/scm/boot/SetupContextListener.java | 71 ++++++++++++++ .../sonia/scm/user/DefaultUserManager.java | 35 ------- .../scm/boot/SetupContextListenerTest.java | 94 +++++++++++++++++++ .../scm/user/DefaultUserManagerTest.java | 35 +------ 6 files changed, 166 insertions(+), 159 deletions(-) delete mode 100644 scm-core/src/main/resources/sonia/scm/config/admin-account.xml delete mode 100644 scm-core/src/main/resources/sonia/scm/config/anonymous-account.xml create mode 100644 scm-webapp/src/main/java/sonia/scm/boot/SetupContextListener.java create mode 100644 scm-webapp/src/test/java/sonia/scm/boot/SetupContextListenerTest.java diff --git a/scm-core/src/main/resources/sonia/scm/config/admin-account.xml b/scm-core/src/main/resources/sonia/scm/config/admin-account.xml deleted file mode 100644 index 980545d8bd..0000000000 --- a/scm-core/src/main/resources/sonia/scm/config/admin-account.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - scmadmin - SCM Administrator - scm-admin@scm-manager.org - $shiro1$SHA-512$8192$$yrNahBVDa4Gz+y5gat4msdjyvjtHlVE+N5nTl4WIDhtBFwhSIib13mKJt1sWmVqgHDWi3VwX7fkdkJ2+WToTbw== - true - xml - diff --git a/scm-core/src/main/resources/sonia/scm/config/anonymous-account.xml b/scm-core/src/main/resources/sonia/scm/config/anonymous-account.xml deleted file mode 100644 index 7a6a058e5b..0000000000 --- a/scm-core/src/main/resources/sonia/scm/config/anonymous-account.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - anonymous - SCM Anonymous - scm-anonymous@scm-manager.org - false - xml - diff --git a/scm-webapp/src/main/java/sonia/scm/boot/SetupContextListener.java b/scm-webapp/src/main/java/sonia/scm/boot/SetupContextListener.java new file mode 100644 index 0000000000..427e9f5dd7 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/boot/SetupContextListener.java @@ -0,0 +1,71 @@ +package sonia.scm.boot; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.shiro.authc.credential.PasswordService; +import sonia.scm.plugin.Extension; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.security.PermissionDescriptor; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; +import sonia.scm.web.security.AdministrationContext; +import sonia.scm.web.security.PrivilegedAction; + +import javax.inject.Inject; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import java.util.Collections; + +@Extension +public class SetupContextListener implements ServletContextListener { + + private final AdministrationContext administrationContext; + + @Inject + public SetupContextListener(AdministrationContext administrationContext) { + this.administrationContext = administrationContext; + } + + @Override + public void contextInitialized(ServletContextEvent sce) { + administrationContext.runAsAdmin(SetupAction.class); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) {} + + @VisibleForTesting + static class SetupAction implements PrivilegedAction { + + private final UserManager userManager; + private final PasswordService passwordService; + private final PermissionAssigner permissionAssigner; + + @Inject + public SetupAction(UserManager userManager, PasswordService passwordService, PermissionAssigner permissionAssigner) { + this.userManager = userManager; + this.passwordService = passwordService; + this.permissionAssigner = permissionAssigner; + } + + @Override + public void run() { + if (isFirstStart()) { + createAdminAccount(); + } + } + + private boolean isFirstStart() { + return userManager.getAll().isEmpty(); + } + + private void createAdminAccount() { + User scmadmin = new User("scmadmin", "SCM Administrator", "scm-admin@scm-manager.org"); + String password = passwordService.encryptPassword("scmadmin"); + scmadmin.setPassword(password); + userManager.create(scmadmin); + + PermissionDescriptor descriptor = new PermissionDescriptor("*"); + permissionAssigner.setPermissionsForUser("scmadmin", Collections.singleton(descriptor)); + } + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java index 709987fb1b..3b78267631 100644 --- a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java +++ b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java @@ -71,13 +71,6 @@ import java.util.List; public class DefaultUserManager extends AbstractUserManager { - /** Field description */ - public static final String ADMIN_PATH = "/sonia/scm/config/admin-account.xml"; - - /** Field description */ - public static final String ANONYMOUS_PATH = - "/sonia/scm/config/anonymous-account.xml"; - /** Field description */ public static final String STORE_NAME = "users"; @@ -173,12 +166,6 @@ public class DefaultUserManager extends AbstractUserManager @Override public void init(SCMContextProvider context) { - - // create default account only, if no other account is available - if (userDAO.getAll().isEmpty()) - { - createDefaultAccounts(); - } } /** @@ -457,28 +444,6 @@ public class DefaultUserManager extends AbstractUserManager } } - /** - * Method description - * - */ - private void createDefaultAccounts() - { - try - { - logger.info("create default accounts"); - - JAXBContext context = JAXBContext.newInstance(User.class); - Unmarshaller unmarshaller = context.createUnmarshaller(); - - createDefaultAccount(unmarshaller, ADMIN_PATH); - createDefaultAccount(unmarshaller, ANONYMOUS_PATH); - } - catch (JAXBException ex) - { - logger.error("could not create default accounts", ex); - } - } - //~--- fields --------------------------------------------------------------- private final UserDAO userDAO; diff --git a/scm-webapp/src/test/java/sonia/scm/boot/SetupContextListenerTest.java b/scm-webapp/src/test/java/sonia/scm/boot/SetupContextListenerTest.java new file mode 100644 index 0000000000..421dca5cb7 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/boot/SetupContextListenerTest.java @@ -0,0 +1,94 @@ +package sonia.scm.boot; + +import com.google.common.collect.Lists; +import org.apache.shiro.authc.credential.PasswordService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.security.PermissionDescriptor; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; +import sonia.scm.user.UserTestData; +import sonia.scm.web.security.AdministrationContext; + +import java.util.Collection; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class SetupContextListenerTest { + + @Mock + private AdministrationContext administrationContext; + + @InjectMocks + private SetupContextListener setupContextListener; + + @Mock + private UserManager userManager; + + @Mock + private PasswordService passwordService; + + @Mock + private PermissionAssigner permissionAssigner; + + @InjectMocks + private SetupContextListener.SetupAction setupAction; + + @BeforeEach + void setupObjectUnderTest() { + doAnswer(ic -> { + setupAction.run(); + return null; + }).when(administrationContext).runAsAdmin(SetupContextListener.SetupAction.class); + } + + @Test + void shouldCreateAdminAccountAndAssignPermissions() { + when(passwordService.encryptPassword("scmadmin")).thenReturn("secret"); + + setupContextListener.contextInitialized(null); + + verifyAdminCreated(); + verifyAdminPermissionsAssigned(); + } + + @Test + void shouldDoNothingOnSecondStart() { + List users = Lists.newArrayList(UserTestData.createTrillian()); + when(userManager.getAll()).thenReturn(users); + + setupContextListener.contextInitialized(null); + + verify(userManager, never()).create(any(User.class)); + verify(permissionAssigner, never()).setPermissionsForUser(anyString(), any(Collection.class)); + } + + private void verifyAdminPermissionsAssigned() { + ArgumentCaptor usernameCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor> permissionCaptor = ArgumentCaptor.forClass(Collection.class); + verify(permissionAssigner).setPermissionsForUser(usernameCaptor.capture(), permissionCaptor.capture()); + String username = usernameCaptor.getValue(); + assertThat(username).isEqualTo("scmadmin"); + PermissionDescriptor descriptor = permissionCaptor.getValue().iterator().next(); + assertThat(descriptor.getValue()).isEqualTo("*"); + } + + private void verifyAdminCreated() { + ArgumentCaptor userCaptor = ArgumentCaptor.forClass(User.class); + verify(userManager).create(userCaptor.capture()); + User user = userCaptor.getValue(); + assertThat(user.getName()).isEqualTo("scmadmin"); + assertThat(user.getPassword()).isEqualTo("secret"); + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java b/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java index ab31d751fd..fe6b41724e 100644 --- a/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java @@ -67,7 +67,7 @@ import org.junit.Rule; ) public class DefaultUserManagerTest extends UserManagerTestBase { - + @Rule public ShiroRule shiro = new ShiroRule(); @@ -97,39 +97,6 @@ public class DefaultUserManagerTest extends UserManagerTestBase when(userDAO.get("trillian")).thenReturn(trillian); } - /** - * Method description - * - */ - @Test - public void testDefaultAccountAfterFristStart() - { - List users = Lists.newArrayList(new User("tuser")); - - when(userDAO.getAll()).thenReturn(users); - - UserManager userManager = new DefaultUserManager(userDAO); - - userManager.init(contextProvider); - verify(userDAO, never()).add(any(User.class)); - } - - /** - * Method description - * - */ - @Test - @SuppressWarnings("unchecked") - public void testDefaultAccountCreation() - { - when(userDAO.getAll()).thenReturn(Collections.EMPTY_LIST); - - UserManager userManager = new DefaultUserManager(userDAO); - - userManager.init(contextProvider); - verify(userDAO, times(2)).add(any(User.class)); - } - @Test(expected = InvalidPasswordException.class) public void shouldFailChangePasswordForWrongOldPassword() { UserManager userManager = new DefaultUserManager(userDAO); From 162751895402ffdf8da1034590eee754c9916d6c Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 13 Mar 2019 12:54:50 +0100 Subject: [PATCH 04/11] removes admin user and group configuration in favor of permissions --- .../sonia/scm/config/ScmConfiguration.java | 28 ------ .../packages/ui-types/src/Config.js | 2 - scm-ui/public/locales/de/config.json | 13 --- scm-ui/public/locales/de/users.json | 1 - scm-ui/public/locales/en/config.json | 13 --- scm-ui/public/locales/en/users.json | 1 - .../config/components/form/AdminSettings.js | 93 ------------------- .../src/config/components/form/ConfigForm.js | 12 --- .../components/table/AdminGroupTable.js | 37 -------- .../config/components/table/AdminUserTable.js | 35 ------- .../sonia/scm/api/v2/resources/ConfigDto.java | 4 - .../sonia/scm/api/v2/resources/UserDto.java | 1 - .../DefaultAuthorizationCollector.java | 78 +++------------- ...ConfigDtoToScmConfigurationMapperTest.java | 4 - .../api/v2/resources/ConfigResourceTest.java | 21 ----- ...ScmConfigurationToConfigDtoMapperTest.java | 4 - .../DefaultAuthorizationCollectorTest.java | 35 +------ .../api/v2/config-test-empty-admin-group.json | 3 - .../api/v2/config-test-empty-admin-user.json | 3 - 19 files changed, 12 insertions(+), 376 deletions(-) delete mode 100644 scm-ui/src/config/components/form/AdminSettings.js delete mode 100644 scm-ui/src/config/components/table/AdminGroupTable.js delete mode 100644 scm-ui/src/config/components/table/AdminUserTable.js delete mode 100644 scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-empty-admin-group.json delete mode 100644 scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-empty-admin-user.json diff --git a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java index e94fabfa60..6722435af5 100644 --- a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java +++ b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java @@ -95,16 +95,6 @@ public class ScmConfiguration implements Configuration { @SuppressWarnings("WeakerAccess") // This might be needed for permission checking public static final String PERMISSION = "global"; - @XmlElement(name = "admin-groups") - @XmlJavaTypeAdapter(XmlSetStringAdapter.class) - private Set adminGroups; - - - @XmlElement(name = "admin-users") - @XmlJavaTypeAdapter(XmlSetStringAdapter.class) - private Set adminUsers; - - @XmlElement(name = "base-url") private String baseUrl; @@ -211,8 +201,6 @@ public class ScmConfiguration implements Configuration { this.dateFormat = other.dateFormat; this.pluginUrl = other.pluginUrl; this.anonymousAccessEnabled = other.anonymousAccessEnabled; - this.adminUsers = other.adminUsers; - this.adminGroups = other.adminGroups; this.enableProxy = other.enableProxy; this.proxyPort = other.proxyPort; this.proxyServer = other.proxyServer; @@ -230,14 +218,6 @@ public class ScmConfiguration implements Configuration { this.defaultNamespaceStrategy = other.defaultNamespaceStrategy; } - public Set getAdminGroups() { - return adminGroups; - } - - public Set getAdminUsers() { - return adminUsers; - } - /** * Returns the complete base url of the scm-manager including the context path. * For example http://localhost:8080/scm @@ -381,14 +361,6 @@ public class ScmConfiguration implements Configuration { return skipFailedAuthenticators; } - public void setAdminGroups(Set adminGroups) { - this.adminGroups = adminGroups; - } - - public void setAdminUsers(Set adminUsers) { - this.adminUsers = adminUsers; - } - public void setAnonymousAccessEnabled(boolean anonymousAccessEnabled) { this.anonymousAccessEnabled = anonymousAccessEnabled; } diff --git a/scm-ui-components/packages/ui-types/src/Config.js b/scm-ui-components/packages/ui-types/src/Config.js index 916cf9f509..ad179e8df4 100644 --- a/scm-ui-components/packages/ui-types/src/Config.js +++ b/scm-ui-components/packages/ui-types/src/Config.js @@ -12,8 +12,6 @@ export type Config = { disableGroupingGrid: boolean, dateFormat: string, anonymousAccessEnabled: boolean, - adminGroups: string[], - adminUsers: string[], baseUrl: string, forceBaseUrl: boolean, loginAttemptLimit: number, diff --git a/scm-ui/public/locales/de/config.json b/scm-ui/public/locales/de/config.json index b67bf90262..869f6e07af 100644 --- a/scm-ui/public/locales/de/config.json +++ b/scm-ui/public/locales/de/config.json @@ -30,19 +30,6 @@ "base-url": "Base URL", "force-base-url": "Base URL erzwingen" }, - "admin-settings": { - "name": "Administrations Einstellungen", - "admin-groups": "Admin Gruppen", - "admin-users": "Admin Benutzer", - "remove-group-button": "Admin Group löschen", - "remove-user-button": "Admin Benutzer löschen", - "add-group-error": "Der eingegebene Gruppenname ist ungültig", - "add-group-textfield": "Neue Gruppe mit Administrationsrechten hinzufügen", - "add-group-button": "Admin Gruppe hinzufügen", - "add-user-error": "Der eingegebene Benutzername ist ungültig", - "add-user-textfield": "Neuen Benutzer mit Administrationsrechten hinzufügen", - "add-user-button": "Admin Benutzer hinzufügen" - }, "login-attempt": { "name": "Anmeldeversuche", "login-attempt-limit": "Limit für Anmeldeversuche", diff --git a/scm-ui/public/locales/de/users.json b/scm-ui/public/locales/de/users.json index b38dd9c2fb..96d632e55d 100644 --- a/scm-ui/public/locales/de/users.json +++ b/scm-ui/public/locales/de/users.json @@ -4,7 +4,6 @@ "displayName": "Anzeigename", "mail": "E-Mail", "password": "Passwort", - "admin": "Admin", "active": "Aktiv", "type": "Typ", "creationDate": "Erstellt", diff --git a/scm-ui/public/locales/en/config.json b/scm-ui/public/locales/en/config.json index 1b42878015..424f6f576a 100644 --- a/scm-ui/public/locales/en/config.json +++ b/scm-ui/public/locales/en/config.json @@ -30,19 +30,6 @@ "base-url": "Base URL", "force-base-url": "Force Base URL" }, - "admin-settings": { - "name": "Administration Settings", - "admin-groups": "Admin Groups", - "admin-users": "Admin Users", - "remove-group-button": "Remove Admin Group", - "remove-user-button": "Remove Admin User", - "add-group-error": "The group name you want to add is not valid", - "add-group-textfield": "Add group you want to add to admin groups here", - "add-group-button": "Add Admin Group", - "add-user-error": "The user name you want to add is not valid", - "add-user-textfield": "Add user you want to add to admin users here", - "add-user-button": "Add Admin User" - }, "login-attempt": { "name": "Login Attempt", "login-attempt-limit": "Login Attempt Limit", diff --git a/scm-ui/public/locales/en/users.json b/scm-ui/public/locales/en/users.json index 2b72d85cbc..91e4efe87f 100644 --- a/scm-ui/public/locales/en/users.json +++ b/scm-ui/public/locales/en/users.json @@ -4,7 +4,6 @@ "displayName": "Display Name", "mail": "E-Mail", "password": "Password", - "admin": "Admin", "active": "Active", "type": "Type", "creationDate": "Creation Date", diff --git a/scm-ui/src/config/components/form/AdminSettings.js b/scm-ui/src/config/components/form/AdminSettings.js deleted file mode 100644 index 7f244d4aaf..0000000000 --- a/scm-ui/src/config/components/form/AdminSettings.js +++ /dev/null @@ -1,93 +0,0 @@ -// @flow -import React from "react"; -import { translate } from "react-i18next"; -import { Subtitle, AddEntryToTableField } from "@scm-manager/ui-components"; -import AdminGroupTable from "../table/AdminGroupTable"; -import AdminUserTable from "../table/AdminUserTable"; - -type Props = { - adminGroups: string[], - adminUsers: string[], - t: string => string, - onChange: (boolean, any, string) => void, - hasUpdatePermission: boolean -}; - -class AdminSettings extends React.Component { - render() { - const { t, adminGroups, adminUsers, hasUpdatePermission } = this.props; - - return ( -
- -
-
- - this.props.onChange(isValid, changedValue, name) - } - disabled={!hasUpdatePermission} - /> - - -
-
- - this.props.onChange(isValid, changedValue, name) - } - disabled={!hasUpdatePermission} - /> - -
-
-
- ); - } - - addGroup = (groupname: string) => { - if (this.isAdminGroupMember(groupname)) { - return; - } - this.props.onChange( - true, - [...this.props.adminGroups, groupname], - "adminGroups" - ); - }; - - isAdminGroupMember = (groupname: string) => { - return this.props.adminGroups.includes(groupname); - }; - - addUser = (username: string) => { - if (this.isAdminUserMember(username)) { - return; - } - this.props.onChange( - true, - [...this.props.adminUsers, username], - "adminUsers" - ); - }; - - isAdminUserMember = (username: string) => { - return this.props.adminUsers.includes(username); - }; -} - -export default translate("config")(AdminSettings); diff --git a/scm-ui/src/config/components/form/ConfigForm.js b/scm-ui/src/config/components/form/ConfigForm.js index 7b650ccbfd..8dd621783b 100644 --- a/scm-ui/src/config/components/form/ConfigForm.js +++ b/scm-ui/src/config/components/form/ConfigForm.js @@ -6,7 +6,6 @@ import type { Config } from "@scm-manager/ui-types"; import ProxySettings from "./ProxySettings"; import GeneralSettings from "./GeneralSettings"; import BaseUrlSettings from "./BaseUrlSettings"; -import AdminSettings from "./AdminSettings"; import LoginAttempt from "./LoginAttempt"; type Props = { @@ -44,8 +43,6 @@ class ConfigForm extends React.Component { disableGroupingGrid: false, dateFormat: "", anonymousAccessEnabled: false, - adminGroups: [], - adminUsers: [], baseUrl: "", forceBaseUrl: false, loginAttemptLimit: 0, @@ -151,15 +148,6 @@ class ConfigForm extends React.Component { hasUpdatePermission={configUpdatePermission} />
- - this.onChange(isValid, changedValue, name) - } - hasUpdatePermission={configUpdatePermission} - /> -
void, - disabled: boolean, - - // context props - t: string => string -}; - -type State = {}; - -class AdminGroupTable extends React.Component { - render() { - const { t, disabled, adminGroups } = this.props; - return ( - - ); - } - - removeEntry = (newGroups: string[]) => { - this.props.onChange(true, newGroups, "adminGroups"); - }; -} - -export default translate("config")(AdminGroupTable); diff --git a/scm-ui/src/config/components/table/AdminUserTable.js b/scm-ui/src/config/components/table/AdminUserTable.js deleted file mode 100644 index a077a6d5d2..0000000000 --- a/scm-ui/src/config/components/table/AdminUserTable.js +++ /dev/null @@ -1,35 +0,0 @@ -//@flow -import React from "react"; -import { translate } from "react-i18next"; -import ArrayConfigTable from "./ArrayConfigTable"; - -type Props = { - adminUsers: string[], - onChange: (boolean, any, string) => void, - disabled: boolean, - - // context props - t: string => string -}; - -class AdminUserTable extends React.Component { - render() { - const { adminUsers, t, disabled } = this.props; - return ( - - ); - } - - removeEntry = (newUsers: string[]) => { - this.props.onChange(true, newUsers, "adminUsers"); - }; -} - -export default translate("config")(AdminUserTable); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java index f77823eaac..359ec357ad 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java @@ -23,10 +23,6 @@ public class ConfigDto extends HalRepresentation { private boolean disableGroupingGrid; private String dateFormat; private boolean anonymousAccessEnabled; - @NoBlankStrings - private Set adminGroups; - @NoBlankStrings - private Set adminUsers; private String baseUrl; private boolean forceBaseUrl; private int loginAttemptLimit; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java index 0ee7b2f82c..bf56f01675 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java @@ -19,7 +19,6 @@ import static sonia.scm.api.v2.ValidationConstraints.USER_GROUP_PATTERN; @NoArgsConstructor @Getter @Setter public class UserDto extends HalRepresentation { private boolean active; - private boolean admin; private Instant creationDate; @NotEmpty private String displayName; diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java index 92d21ce1a1..f4efd3a307 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java @@ -77,9 +77,6 @@ import java.util.Set; public class DefaultAuthorizationCollector implements AuthorizationCollector { - /** Field description */ - private static final String ADMIN_PERMISSION = "*"; - /** Field description */ private static final String CACHE_NAME = "sonia.cache.authorizing"; @@ -94,18 +91,14 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector /** * Constructs ... * - * - * - * @param configuration * @param cacheManager * @param repositoryDAO * @param securitySystem */ @Inject - public DefaultAuthorizationCollector(ScmConfiguration configuration, CacheManager cacheManager, + public DefaultAuthorizationCollector(CacheManager cacheManager, RepositoryDAO repositoryDAO, SecuritySystem securitySystem) { - this.configuration = configuration; this.cache = cacheManager.getCache(CACHE_NAME); this.repositoryDAO = repositoryDAO; this.securitySystem = securitySystem; @@ -233,69 +226,22 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector } } - private AuthorizationInfo createAuthorizationInfo(User user, - GroupNames groups) - { - Set roles; - Set permissions; + private AuthorizationInfo createAuthorizationInfo(User user, GroupNames groups) { + Builder builder = ImmutableSet.builder(); - if (isAdmin(user, groups)) - { - if (logger.isDebugEnabled()) - { - logger.debug("grant admin role for user {}", user.getName()); - } + collectGlobalPermissions(builder, user, groups); + collectRepositoryPermissions(builder, user, groups); + builder.add(canReadOwnUser(user)); + builder.add(getUserAutocompletePermission()); + builder.add(getGroupAutocompletePermission()); + builder.add(getChangeOwnPasswordPermission(user)); - roles = ImmutableSet.of(Role.USER, Role.ADMIN); + SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(ImmutableSet.of(Role.USER)); + info.addStringPermissions(builder.build()); - permissions = ImmutableSet.of(ADMIN_PERMISSION); - } - else - { - roles = ImmutableSet.of(Role.USER); - - Builder builder = ImmutableSet.builder(); - - collectGlobalPermissions(builder, user, groups); - collectRepositoryPermissions(builder, user, groups); - builder.add(canReadOwnUser(user)); - builder.add(getUserAutocompletePermission()); - builder.add(getGroupAutocompletePermission()); - builder.add(getChangeOwnPasswordPermission(user)); - permissions = builder.build(); - } - - SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles); - info.addStringPermissions(permissions); return info; } - private boolean isAdmin(User user, GroupNames groups) { - if (isUserAdminInConfiguration(user)) { - logger.debug("user {} is marked as admin, because of the admin user configuration", user.getName()); - return true; - } - return isUserAdminInGroupConfiguration(user, groups); - } - - private boolean isUserAdminInGroupConfiguration(User user, GroupNames groups) { - Set adminGroups = configuration.getAdminGroups(); - if (adminGroups != null && groups != null) { - for (String group : groups) { - if (adminGroups.contains(group)) { - logger.debug("user {} is marked as admin, because of the admin group configuration for group {}", user.getName(), group); - return true; - } - } - } - return false; - } - - private boolean isUserAdminInConfiguration(User user) { - Set adminUsers = configuration.getAdminUsers(); - return adminUsers != null && adminUsers.contains(user.getName()); - } - private String getGroupAutocompletePermission() { return GroupPermissions.autocomplete().asShiroString(); } @@ -399,8 +345,6 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector //~--- fields --------------------------------------------------------------- - private final ScmConfiguration configuration; - /** authorization cache */ private final Cache cache; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapperTest.java index 525d5814e1..6d76aec2e5 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapperTest.java @@ -41,8 +41,6 @@ public class ConfigDtoToScmConfigurationMapperTest { assertTrue(config.isDisableGroupingGrid()); assertEquals("yyyy" , config.getDateFormat()); assertTrue(config.isAnonymousAccessEnabled()); - assertTrue("adminGroups", config.getAdminGroups().containsAll(Arrays.asList(expectedGroups))); - assertTrue("adminUsers", config.getAdminUsers().containsAll(Arrays.asList(expectedUsers))); assertEquals("baseurl" , config.getBaseUrl()); assertTrue(config.isForceBaseUrl()); assertEquals(41 , config.getLoginAttemptLimit()); @@ -66,8 +64,6 @@ public class ConfigDtoToScmConfigurationMapperTest { configDto.setDisableGroupingGrid(true); configDto.setDateFormat("yyyy"); configDto.setAnonymousAccessEnabled(true); - configDto.setAdminGroups(Sets.newSet(expectedGroups)); - configDto.setAdminUsers(Sets.newSet(expectedUsers)); configDto.setBaseUrl("baseurl"); configDto.setForceBaseUrl(true); configDto.setLoginAttemptLimit(41); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java index 66689c6ac6..31e3f58334 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java @@ -118,27 +118,6 @@ public class ConfigResourceTest { dispatcher.invoke(request, response); } - @Test - @SubjectAware(username = "readWrite") - public void shouldFailForEmptyAdminUsers() throws URISyntaxException, IOException { - MockHttpRequest request = post("sonia/scm/api/v2/config-test-empty-admin-user.json"); - - MockHttpResponse response = new MockHttpResponse(); - dispatcher.invoke(request, response); - - assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus()); - } - - @Test - @SubjectAware(username = "readWrite") - public void shouldFailForEmptyAdminGroups() throws URISyntaxException, IOException { - MockHttpRequest request = post("sonia/scm/api/v2/config-test-empty-admin-group.json"); - - MockHttpResponse response = new MockHttpResponse(); - dispatcher.invoke(request, response); - - assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus()); - } private MockHttpRequest post(String resourceName) throws IOException, URISyntaxException { URL url = Resources.getResource(resourceName); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapperTest.java index cdbd9ae344..34123950ab 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapperTest.java @@ -71,8 +71,6 @@ public class ScmConfigurationToConfigDtoMapperTest { assertTrue(dto.isDisableGroupingGrid()); assertEquals("dd" , dto.getDateFormat()); assertTrue(dto.isAnonymousAccessEnabled()); - assertTrue("adminGroups", dto.getAdminGroups().containsAll(Arrays.asList(expectedGroups))); - assertTrue("adminUsers", dto.getAdminUsers().containsAll(Arrays.asList(expectedUsers))); assertEquals("baseurl" , dto.getBaseUrl()); assertTrue(dto.isForceBaseUrl()); assertEquals(1 , dto.getLoginAttemptLimit()); @@ -111,8 +109,6 @@ public class ScmConfigurationToConfigDtoMapperTest { config.setDisableGroupingGrid(true); config.setDateFormat("dd"); config.setAnonymousAccessEnabled(true); - config.setAdminGroups(Sets.newSet(expectedGroups)); - config.setAdminUsers(Sets.newSet(expectedUsers)); config.setBaseUrl("baseurl"); config.setForceBaseUrl(true); config.setLoginAttemptLimit(1); diff --git a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java index 7550aead3e..dc62119209 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java @@ -78,8 +78,6 @@ import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class DefaultAuthorizationCollectorTest { - private ScmConfiguration configuration; - @Mock private Cache cache; @@ -103,38 +101,7 @@ public class DefaultAuthorizationCollectorTest { @Before public void setUp(){ when(cacheManager.getCache(Mockito.any(String.class))).thenReturn(cache); - configuration = new ScmConfiguration(); - collector = new DefaultAuthorizationCollector(configuration, cacheManager, repositoryDAO, securitySystem); - } - - @Test - @SubjectAware( - configuration = "classpath:sonia/scm/shiro-001.ini" - ) - public void shouldGetAdminPrivilegedByConfiguration() { - configuration.setAdminUsers(ImmutableSet.of("trillian")); - authenticate(UserTestData.createTrillian(), "main"); - - AuthorizationInfo authInfo = collector.collect(); - assertIsAdmin(authInfo); - } - - private void assertIsAdmin(AuthorizationInfo authInfo) { - assertThat(authInfo.getRoles(), Matchers.containsInAnyOrder(Role.USER, Role.ADMIN)); - assertThat(authInfo.getObjectPermissions(), nullValue()); - assertThat(authInfo.getStringPermissions(), Matchers.contains("*")); - } - - @Test - @SubjectAware( - configuration = "classpath:sonia/scm/shiro-001.ini" - ) - public void shouldGetAdminPrivilegedByGroupConfiguration() { - configuration.setAdminGroups(ImmutableSet.of("heartOfGold")); - authenticate(UserTestData.createTrillian(), "heartOfGold"); - - AuthorizationInfo authInfo = collector.collect(); - assertIsAdmin(authInfo); + collector = new DefaultAuthorizationCollector(cacheManager, repositoryDAO, securitySystem); } /** diff --git a/scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-empty-admin-group.json b/scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-empty-admin-group.json deleted file mode 100644 index f665c29ee7..0000000000 --- a/scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-empty-admin-group.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "adminGroups": [""] -} diff --git a/scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-empty-admin-user.json b/scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-empty-admin-user.json deleted file mode 100644 index 61efcb1609..0000000000 --- a/scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-empty-admin-user.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "adminUsers": [""] -} From 1179fd37fa6c3de088bffc1130f05a2fca5a9313 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 13 Mar 2019 15:56:40 +0100 Subject: [PATCH 05/11] fixes integration tests, which are relying on the admin flag --- .../test/java/sonia/scm/it/UserITCase.java | 8 +++---- .../java/sonia/scm/it/utils/TestData.java | 22 +++++++++++++++++-- 2 files changed, 24 insertions(+), 6 deletions(-) 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 4c7e3b2cac..fdda43300e 100644 --- a/scm-it/src/test/java/sonia/scm/it/UserITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/UserITCase.java @@ -27,7 +27,7 @@ public class UserITCase { .assertStatusCode(200) .requestUser(newUser) .assertStatusCode(200) - .assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE)) + // we could no longer easily check if the user is an admin, because the admin flag is gone .assertPassword(Assert::assertNull) .requestChangePassword(newPassword) .assertStatusCode(204); @@ -36,7 +36,7 @@ public class UserITCase { .requestIndexResource(newUser, newPassword) .assertStatusCode(200) .requestUser(newUser) - .assertAdmin(isAdmin -> assertThat(isAdmin).isEqualTo(Boolean.TRUE)) + // we could no longer easily check if the user is an admin, because the admin flag is gone .assertPassword(Assert::assertNull); } @@ -52,7 +52,7 @@ public class UserITCase { .assertStatusCode(200) .requestUser(newUser) .assertStatusCode(200) - .assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE)) // the user anonymous is not an admin + // we could no longer easily check if the user is an admin, because the admin flag is gone .assertPassword(Assert::assertNull) .requestChangePassword(newPassword) // the oldPassword is not needed in the user resource .assertStatusCode(204); @@ -96,7 +96,7 @@ public class UserITCase { .assertStatusCode(200) .requestUser(newUser) .assertStatusCode(200) - .assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE)) + // we could no longer easily check if the user is an admin, because the admin flag is gone .assertPassword(Assert::assertNull) .assertType(s -> assertThat(s).isEqualTo(type)) .assertPasswordLinkDoesNotExists(); diff --git a/scm-it/src/test/java/sonia/scm/it/utils/TestData.java b/scm-it/src/test/java/sonia/scm/it/utils/TestData.java index 584737221f..23228d686b 100644 --- a/scm-it/src/test/java/sonia/scm/it/utils/TestData.java +++ b/scm-it/src/test/java/sonia/scm/it/utils/TestData.java @@ -74,9 +74,27 @@ public class TestData { .append(" }").toString()) .post(getUsersUrl()) .then() - .statusCode(HttpStatus.SC_CREATED) - ; + .statusCode(HttpStatus.SC_CREATED); + + if (isAdmin) { + assignAdminPermissions(username); + } } + + public static void assignAdminPermissions(String username) { + LOG.info("assign admin permissions to user {}", username); + given(VndMediaType.PERMISSION_COLLECTION) + .when() + .body("{'permissions': ['*']}".replaceAll("'", "\"")) + .put(getPermissionUrl(username)) + .then() + .statusCode(HttpStatus.SC_NO_CONTENT); + } + + private static URI getPermissionUrl(String username) { + return RestUtil.createResourceUrl(String.format("users/%s/permissions", username)); + } + public static void createGroup(String groupName, String desc) { LOG.info("create group with group name: {} and description {}", groupName, desc); given(VndMediaType.GROUP) From 3e9f59ef478ac2064a45eba4bb7059c6dcabb141 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 21 Mar 2019 09:58:36 +0100 Subject: [PATCH 06/11] support for managing external managed groups --- .../src/main/java/sonia/scm/group/Group.java | 31 ++++++- .../packages/ui-types/src/Group.js | 1 + scm-ui/public/locales/de/groups.json | 5 +- scm-ui/public/locales/en/groups.json | 5 +- scm-ui/src/groups/components/GroupForm.js | 90 ++++++++++++++----- scm-ui/src/groups/components/table/Details.js | 8 +- .../src/groups/components/table/GroupRow.js | 54 +++++------ .../src/groups/components/table/GroupTable.js | 67 +++++++------- .../sonia/scm/api/v2/resources/GroupDto.java | 1 + 9 files changed, 175 insertions(+), 87 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/group/Group.java b/scm-core/src/main/java/sonia/scm/group/Group.java index 8860545c93..c3806be847 100644 --- a/scm-core/src/main/java/sonia/scm/group/Group.java +++ b/scm-core/src/main/java/sonia/scm/group/Group.java @@ -50,6 +50,7 @@ import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; import java.util.Arrays; +import java.util.Collections; import java.util.List; //~--- JDK imports ------------------------------------------------------------ @@ -195,6 +196,7 @@ public class Group extends BasicPropertiesAware group.setMembers(members); group.setType(type); group.setDescription(description); + group.setExternal(true); } /** @@ -224,6 +226,7 @@ public class Group extends BasicPropertiesAware && Objects.equal(description, other.description) && Objects.equal(members, other.members) && Objects.equal(type, other.type) + && Objects.equal(external, other.external) && Objects.equal(creationDate, other.creationDate) && Objects.equal(lastModified, other.lastModified) && Objects.equal(properties, other.properties); @@ -270,6 +273,7 @@ public class Group extends BasicPropertiesAware .add("description", description) .add("members", members) .add("type", type) + .add("external", external) .add("creationDate", creationDate) .add("lastModified", lastModified) .add("properties", properties) @@ -339,8 +343,9 @@ public class Group extends BasicPropertiesAware */ public List getMembers() { - if (members == null) - { + if (external) { + return Collections.emptyList(); + } else if (members == null) { members = Lists.newArrayList(); } @@ -370,6 +375,15 @@ public class Group extends BasicPropertiesAware return type; } + /** + * Returns {@code true} if the members of the groups managed external of scm-manager. + * + * @return {@code true} if the group is an external group + */ + public boolean isExternal() { + return external; + } + /** * Returns true if the member is a member of this group. * @@ -463,8 +477,21 @@ public class Group extends BasicPropertiesAware this.type = type; } + /** + * {@code true} to mark the group as external. + * + * @param {@code true} for a external group + */ + public void setExternal(boolean external) + { + this.external = external; + } + //~--- fields --------------------------------------------------------------- + /** external group */ + private boolean external = false; + /** timestamp of the creation date of this group */ private Long creationDate; diff --git a/scm-ui-components/packages/ui-types/src/Group.js b/scm-ui-components/packages/ui-types/src/Group.js index 69c6fb4985..995ccb08de 100644 --- a/scm-ui-components/packages/ui-types/src/Group.js +++ b/scm-ui-components/packages/ui-types/src/Group.js @@ -10,6 +10,7 @@ export type Group = Collection & { name: string, description: string, type: string, + external: boolean, members: string[], _embedded: { members: Member[] diff --git a/scm-ui/public/locales/de/groups.json b/scm-ui/public/locales/de/groups.json index 1b2504a644..897cab02d3 100644 --- a/scm-ui/public/locales/de/groups.json +++ b/scm-ui/public/locales/de/groups.json @@ -5,6 +5,7 @@ "creationDate": "Erstellt", "lastModified": "Zuletzt bearbeitet", "type": "Typ", + "external": "Extern", "members": "Mitglieder" }, "groups": { @@ -49,13 +50,15 @@ }, "groupForm": { "subtitle": "Gruppe bearbeiten", + "externalSubtitle": "Externe Gruppe bearbeiten", "submit": "Speichern", "nameError": "Name ist ungültig", "descriptionError": "Beschreibung ist ungültig", "help": { "nameHelpText": "Eindeutiger Name der Gruppe", "descriptionHelpText": "Eine kurze Beschreibung der Gruppe", - "memberHelpText": "Benutzername des Mitglieds der Gruppe" + "memberHelpText": "Benutzername des Mitglieds der Gruppe", + "externalHelpText": "Mitglieder dieser Gruppe werden von einem externen System wie z.B.: einem LDAP-Server verwaltet" } }, "deleteGroup": { diff --git a/scm-ui/public/locales/en/groups.json b/scm-ui/public/locales/en/groups.json index 3b34e5722b..a54249988a 100644 --- a/scm-ui/public/locales/en/groups.json +++ b/scm-ui/public/locales/en/groups.json @@ -5,6 +5,7 @@ "creationDate": "Creation Date", "lastModified": "Last Modified", "type": "Type", + "external": "External", "members": "Members" }, "groups": { @@ -49,13 +50,15 @@ }, "groupForm": { "subtitle": "Edit Group", + "externalSubtitle": "Edit external group", "submit": "Submit", "nameError": "Group name is invalid", "descriptionError": "Description is invalid", "help": { "nameHelpText": "Unique name of the group", "descriptionHelpText": "A short description of the group", - "memberHelpText": "Usernames of the group members" + "memberHelpText": "Usernames of the group members", + "externalHelpText": "Members are managed by an external system such as LDAP" } }, "deleteGroup": { diff --git a/scm-ui/src/groups/components/GroupForm.js b/scm-ui/src/groups/components/GroupForm.js index 8693cb9b47..d3b6799860 100644 --- a/scm-ui/src/groups/components/GroupForm.js +++ b/scm-ui/src/groups/components/GroupForm.js @@ -8,7 +8,8 @@ import { MemberNameTable, InputField, SubmitButton, - Textarea + Textarea, + Checkbox } from "@scm-manager/ui-components"; import type { Group, SelectValue } from "@scm-manager/ui-types"; @@ -67,16 +68,68 @@ class GroupForm extends React.Component { submit = (event: Event) => { event.preventDefault(); if (this.isValid()) { - this.props.submitForm(this.state.group); + const { group } = this.state; + if (group.external) { + group.members = []; + } + this.props.submitForm(group); } }; + renderMemberfields = (group: Group) => { + if (group.external) { + return null; + } + + const { loadUserSuggestions, t } = this.props; + return ( + <> + + + + + ); + }; + + renderExternalField = (group: Group) => { + const { t } = this.props; + if (this.isExistingGroup()) { + return null; + } + return ( + + ); + }; + + isExistingGroup = () => !! this.props.group; + render() { const { loading, t } = this.props; const { group } = this.state; let nameField = null; let subtitle = null; - if (!this.props.group) { + if (!this.isExistingGroup()) { // create new group nameField = ( { helpText={t("groupForm.help.nameHelpText")} /> ); + } else if (group.external) { + subtitle = ; } else { - // edit existing group subtitle = ; } @@ -106,26 +160,8 @@ class GroupForm extends React.Component { validationError={false} helpText={t("groupForm.help.descriptionHelpText")} /> - - - - + {this.renderExternalField(group)} + {this.renderMemberfields(group)} { group: { ...this.state.group, description } }); }; + + handleExternalChange = (external: boolean) => { + this.setState({ + group: { ...this.state.group, external } + }); + }; } export default translate("groups")(GroupForm); diff --git a/scm-ui/src/groups/components/table/Details.js b/scm-ui/src/groups/components/table/Details.js index 4391310d01..4d64e3c63e 100644 --- a/scm-ui/src/groups/components/table/Details.js +++ b/scm-ui/src/groups/components/table/Details.js @@ -2,7 +2,7 @@ import React from "react"; import type { Group } from "@scm-manager/ui-types"; import GroupMember from "./GroupMember"; -import { DateFromNow } from "@scm-manager/ui-components"; +import { DateFromNow, Checkbox } from "@scm-manager/ui-components"; import { translate } from "react-i18next"; import injectSheet from "react-jss"; @@ -34,6 +34,12 @@ class Details extends React.Component { {t("group.description")} {group.description} + + {t("group.external")} + + + + {t("group.type")} {group.type} diff --git a/scm-ui/src/groups/components/table/GroupRow.js b/scm-ui/src/groups/components/table/GroupRow.js index ccff8bd193..e99299012c 100644 --- a/scm-ui/src/groups/components/table/GroupRow.js +++ b/scm-ui/src/groups/components/table/GroupRow.js @@ -1,25 +1,29 @@ -// @flow -import React from "react"; -import { Link } from "react-router-dom"; -import type { Group } from "@scm-manager/ui-types"; - -type Props = { - group: Group -}; - -export default class GroupRow extends React.Component { - renderLink(to: string, label: string) { - return {label}; - } - - render() { - const { group } = this.props; - const to = `/group/${group.name}`; - return ( - - {this.renderLink(to, group.name)} - {group.description} - - ); - } -} +// @flow +import React from "react"; +import { Link } from "react-router-dom"; +import type { Group } from "@scm-manager/ui-types"; +import { Checkbox } from "@scm-manager/ui-components" + +type Props = { + group: Group +}; + +export default class GroupRow extends React.Component { + renderLink(to: string, label: string) { + return {label}; + } + + render() { + const { group } = this.props; + const to = `/group/${group.name}`; + return ( + + {this.renderLink(to, group.name)} + {group.description} + + + + + ); + } +} diff --git a/scm-ui/src/groups/components/table/GroupTable.js b/scm-ui/src/groups/components/table/GroupTable.js index cd38180fc4..85bcb813fc 100644 --- a/scm-ui/src/groups/components/table/GroupTable.js +++ b/scm-ui/src/groups/components/table/GroupTable.js @@ -1,33 +1,34 @@ -// @flow -import React from "react"; -import { translate } from "react-i18next"; -import GroupRow from "./GroupRow"; -import type { Group } from "@scm-manager/ui-types"; - -type Props = { - t: string => string, - groups: Group[] -}; - -class GroupTable extends React.Component { - render() { - const { groups, t } = this.props; - return ( - - - - - - - - - {groups.map((group, index) => { - return ; - })} - -
{t("group.name")}{t("group.description")}
- ); - } -} - -export default translate("groups")(GroupTable); +// @flow +import React from "react"; +import { translate } from "react-i18next"; +import GroupRow from "./GroupRow"; +import type { Group } from "@scm-manager/ui-types"; + +type Props = { + t: string => string, + groups: Group[] +}; + +class GroupTable extends React.Component { + render() { + const { groups, t } = this.props; + return ( + + + + + + + + + + {groups.map((group, index) => { + return ; + })} + +
{t("group.name")}{t("group.description")}{t("group.external")}
+ ); + } +} + +export default translate("groups")(GroupTable); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java index a150570316..bdbab5f9ff 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java @@ -27,6 +27,7 @@ public class GroupDto extends HalRepresentation { private String type; private Map properties; private List members; + private boolean external; GroupDto(Links links, Embedded embedded) { super(links, embedded); From 7c6bfdaaac9465377a0d32f84daf9979eec5b712 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 21 Mar 2019 10:46:11 +0100 Subject: [PATCH 07/11] removes admin role --- .../main/java/sonia/scm/ScmClientConfig.java | 181 ------- .../src/main/java/sonia/scm/ScmState.java | 237 --------- .../main/java/sonia/scm/ScmStateFactory.java | 173 ------- .../main/java/sonia/scm/security/Role.java | 3 - .../main/java/sonia/scm/util/MockUtil.java | 1 - .../packages/ui-components/package.json | 2 +- .../packages/ui-types/package.json | 2 +- .../resources/AuthenticationResource.java | 463 ------------------ .../api/rest/resources/CipherResource.java | 87 ---- .../scm/api/rest/resources/KeyResource.java | 98 ---- .../resources/RepositoryImportResource.java | 43 +- .../api/rest/resources/SupportResource.java | 463 ------------------ .../java/sonia/scm/debug/DebugService.java | 7 +- .../security/AdministrationContextRealm.java | 2 +- .../DefaultAdministrationContext.java | 14 +- 15 files changed, 40 insertions(+), 1736 deletions(-) delete mode 100644 scm-core/src/main/java/sonia/scm/ScmClientConfig.java delete mode 100644 scm-core/src/main/java/sonia/scm/ScmState.java delete mode 100644 scm-core/src/main/java/sonia/scm/ScmStateFactory.java delete mode 100644 scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java delete mode 100644 scm-webapp/src/main/java/sonia/scm/api/rest/resources/CipherResource.java delete mode 100644 scm-webapp/src/main/java/sonia/scm/api/rest/resources/KeyResource.java delete mode 100644 scm-webapp/src/main/java/sonia/scm/api/rest/resources/SupportResource.java diff --git a/scm-core/src/main/java/sonia/scm/ScmClientConfig.java b/scm-core/src/main/java/sonia/scm/ScmClientConfig.java deleted file mode 100644 index 40b7d36aaa..0000000000 --- a/scm-core/src/main/java/sonia/scm/ScmClientConfig.java +++ /dev/null @@ -1,181 +0,0 @@ -/** - * Copyright (c) 2010, Sebastian Sdorra - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * http://bitbucket.org/sdorra/scm-manager - * - */ - - - -package sonia.scm; - -//~--- non-JDK imports -------------------------------------------------------- - -import sonia.scm.config.ScmConfiguration; - -/** - * Configuration object for a SCM-Manager - * client (WebInterface, RestClient, ...). - * - * @author Sebastian Sdorra - */ -public class ScmClientConfig -{ - - /** - * Constructs {@link ScmClientConfig} object - * - */ - public ScmClientConfig() {} - - /** - * Constructs {@link ScmClientConfig} object - * - * - * @param configuration SCM-Manager main configuration - * @since 1.14 - */ - public ScmClientConfig(ScmConfiguration configuration) - { - this.dateFormat = configuration.getDateFormat(); - this.disableGroupingGrid = configuration.isDisableGroupingGrid(); - this.enableRepositoryArchive = configuration.isEnableRepositoryArchive(); - } - - /** - * Constructs {@link ScmClientConfig} object - * - * - * @param dateFormat - */ - public ScmClientConfig(String dateFormat) - { - this.dateFormat = dateFormat; - } - - /** - * Constructs {@link ScmClientConfig} object - * - * @since 1.9 - * - * @param dateFormat - * @param disableGroupingGrid true to disable repository grouping - */ - public ScmClientConfig(String dateFormat, boolean disableGroupingGrid) - { - this.dateFormat = dateFormat; - this.disableGroupingGrid = disableGroupingGrid; - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Returns the date format for the user interface. This format is a - * JavaScript date format. - * - * @see Date Format - * @return JavaScript date format - */ - public String getDateFormat() - { - return dateFormat; - } - - /** - * Returns true if the grouping of repositories is disabled. - * - * @since 1.9 - * - * @return true if the grouping of repositories is disabled - */ - public boolean isDisableGroupingGrid() - { - return disableGroupingGrid; - } - - /** - * Returns true if the repository archive is disabled. - * - * - * @return true if the repository archive is disabled - * @since 1.14 - */ - public boolean isEnableRepositoryArchive() - { - return enableRepositoryArchive; - } - - //~--- set methods ---------------------------------------------------------- - - /** - * Setter for the date format - * - * - * - * @param dateFormat - JavaScript date format - */ - public void setDateFormat(String dateFormat) - { - this.dateFormat = dateFormat; - } - - /** - * Enables or disables the grouping of repositories. - * - * @since 1.9 - * - * - * @param disableGroupingGrid - */ - public void setDisableGroupingGrid(boolean disableGroupingGrid) - { - this.disableGroupingGrid = disableGroupingGrid; - } - - /** - * Enable or disable the repository archive. Default is disabled. - * - * - * @param enableRepositoryArchive true to disable the repository archive - * @since 1.14 - */ - public void setEnableRepositoryArchive(boolean enableRepositoryArchive) - { - this.enableRepositoryArchive = enableRepositoryArchive; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private String dateFormat; - - /** Field description */ - private boolean enableRepositoryArchive = true; - - /** Field description */ - private boolean disableGroupingGrid = true; -} diff --git a/scm-core/src/main/java/sonia/scm/ScmState.java b/scm-core/src/main/java/sonia/scm/ScmState.java deleted file mode 100644 index 09def95d54..0000000000 --- a/scm-core/src/main/java/sonia/scm/ScmState.java +++ /dev/null @@ -1,237 +0,0 @@ -/** - * Copyright (c) 2010, Sebastian Sdorra - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * http://bitbucket.org/sdorra/scm-manager - * - */ - - - -package sonia.scm; - -//~--- non-JDK imports -------------------------------------------------------- - -import sonia.scm.repository.RepositoryType; -import sonia.scm.security.PermissionDescriptor; -import sonia.scm.user.User; - -//~--- JDK imports ------------------------------------------------------------ - -import java.util.Collection; -import java.util.List; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -/** - * This class represents the current state of the SCM-Manager. - * - * @author Sebastian Sdorra - */ -@XmlRootElement(name = "state") -@XmlAccessorType(XmlAccessType.FIELD) -public final class ScmState -{ - - /** - * Constructs {@link ScmState} object. - * This constructor is required by JAXB. - * - */ - ScmState() {} - - /** - * Constructs {@link ScmState} object. - * - * - * @param version scm-manager version - * @param user current user - * @param groups groups of the current user - * @param token authentication token - * @param repositoryTypes available repository types - * @param defaultUserType default user type - * @param clientConfig client configuration - * @param availablePermissions list of available permissions - * - * @since 2.0.0 - */ - public ScmState(String version, User user, Collection groups, - String token, Collection repositoryTypes, String defaultUserType, - ScmClientConfig clientConfig, Collection availablePermissions) - { - this.version = version; - this.user = user; - this.groups = groups; - this.token = token; - this.repositoryTypes = repositoryTypes; - this.clientConfig = clientConfig; - this.defaultUserType = defaultUserType; - this.availablePermissions = availablePermissions; - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Returns a list of available global permissions. - * - * - * @return available global permissions - * @since 1.31 - */ - public Collection getAvailablePermissions() - { - return availablePermissions; - } - - /** - * Returns configuration for SCM-Manager clients. - * - * - * @return configuration for SCM-Manager clients - */ - public ScmClientConfig getClientConfig() - { - return clientConfig; - } - - /** - * Returns the default user type - * - * - * @return default user type - * - * @since 1.14 - */ - public String getDefaultUserType() - { - return defaultUserType; - } - - /** - * Returns a {@link java.util.Collection} of groups names which are associated - * to the current user. - * - * - * @return a {@link java.util.Collection} of groups names - */ - public Collection getGroups() - { - return groups; - } - - /** - * Returns all available repository types. - * - * - * @return all available repository types - */ - public Collection getRepositoryTypes() - { - return repositoryTypes; - } - - /** - * Returns authentication token or {@code null}. - * - * - * @return authentication token or {@code null} - * - * @since 2.0.0 - */ - public String getToken() - { - return token; - } - - /** - * Returns the current logged in user. - * - * - * @return current logged in user - */ - public User getUser() - { - return user; - } - - /** - * Returns the version of the SCM-Manager. - * - * - * @return version of the SCM-Manager - */ - public String getVersion() - { - return version; - } - - /** - * Returns true if the request was successful. - * This method is required by extjs. - * - * @return true if the request was successful - */ - public boolean isSuccess() - { - return success; - } - - //~--- fields --------------------------------------------------------------- - - /** marker for extjs */ - private final boolean success = true; - - /** authentication token */ - private String token; - - /** - * Avaliable global permission - * @since 1.31 - */ - private Collection availablePermissions; - - /** Field description */ - private ScmClientConfig clientConfig; - - /** Field description */ - private String defaultUserType; - - /** Field description */ - private Collection groups; - - /** Field description */ - @XmlElement(name = "repositoryTypes") - private Collection repositoryTypes; - - /** Field description */ - private User user; - - /** Field description */ - private String version; -} diff --git a/scm-core/src/main/java/sonia/scm/ScmStateFactory.java b/scm-core/src/main/java/sonia/scm/ScmStateFactory.java deleted file mode 100644 index ed8bfba5dc..0000000000 --- a/scm-core/src/main/java/sonia/scm/ScmStateFactory.java +++ /dev/null @@ -1,173 +0,0 @@ -/** - * Copyright (c) 2014, Sebastian Sdorra All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. 2. Redistributions in - * binary form must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. 3. Neither the name of SCM-Manager; - * nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * http://bitbucket.org/sdorra/scm-manager - * - */ - - - -package sonia.scm; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.common.collect.ImmutableList; - -import org.apache.shiro.subject.PrincipalCollection; -import org.apache.shiro.subject.Subject; - -import sonia.scm.config.ScmConfiguration; -import sonia.scm.group.GroupNames; -import sonia.scm.repository.RepositoryManager; -import sonia.scm.security.AuthorizationCollector; -import sonia.scm.security.PermissionDescriptor; -import sonia.scm.security.Role; -import sonia.scm.security.SecuritySystem; -import sonia.scm.user.User; -import sonia.scm.user.UserManager; - -//~--- JDK imports ------------------------------------------------------------ - -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import javax.inject.Inject; - -/** - * Factory to create {@link ScmState}. - * - * @author Sebastian Sdorra - * @since 2.0.0 - */ -public final class ScmStateFactory -{ - - /** - * Constructs a new {@link ScmStateFactory}. - * - * - * @param contextProvider context provider - * @param configuration configuration - * @param repositoryManger repository manager - * @param userManager user manager - * @param securitySystem security system - */ - @Inject - public ScmStateFactory(SCMContextProvider contextProvider, - ScmConfiguration configuration, RepositoryManager repositoryManger, - UserManager userManager, SecuritySystem securitySystem) - { - this.contextProvider = contextProvider; - this.configuration = configuration; - this.repositoryManger = repositoryManger; - this.userManager = userManager; - this.securitySystem = securitySystem; - } - - //~--- methods -------------------------------------------------------------- - - /** - * Returns anonymous state. - * - * - * @return anonymous state - */ - @SuppressWarnings("unchecked") - public ScmState createAnonymousState() - { - return createState(SCMContext.ANONYMOUS, Collections.EMPTY_LIST, null, Collections.EMPTY_LIST); - } - - /** - * Creates an state from the given subject. - * - * - * @param subject subject - * - * @return state from subject - */ - public ScmState createState(Subject subject) - { - return createState(subject, null); - } - - /** - * Creates an state from the given subject and authentication token. - * - * - * @param subject subject - * @param token authentication token - * - * @return state from subject and authentication token - */ - @SuppressWarnings("unchecked") - public ScmState createState(Subject subject, String token) - { - PrincipalCollection collection = subject.getPrincipals(); - User user = collection.oneByType(User.class); - GroupNames groups = collection.oneByType(GroupNames.class); - - Collection ap = Collections.EMPTY_LIST; - - if (subject.hasRole(Role.ADMIN)) - { - ap = securitySystem.getAvailablePermissions(); - } - - return createState(user, groups.getCollection(), token, ap); - } - - private ScmState createState(User user, Collection groups, - String token, - Collection availablePermissions) - { - User u = user.clone(); - - // do not return password on authentication - u.setPassword(null); - - return new ScmState(contextProvider.getVersion(), u, groups, token, - repositoryManger.getConfiguredTypes(), userManager.getDefaultType(), - new ScmClientConfig(configuration), availablePermissions); - } - - //~--- fields --------------------------------------------------------------- - - /** configuration */ - private final ScmConfiguration configuration; - - /** context provider */ - private final SCMContextProvider contextProvider; - - /** repository manager */ - private final RepositoryManager repositoryManger; - - /** security system */ - private final SecuritySystem securitySystem; - - /** user manager */ - private final UserManager userManager; -} diff --git a/scm-core/src/main/java/sonia/scm/security/Role.java b/scm-core/src/main/java/sonia/scm/security/Role.java index 470c3983f0..c41021b6e6 100644 --- a/scm-core/src/main/java/sonia/scm/security/Role.java +++ b/scm-core/src/main/java/sonia/scm/security/Role.java @@ -41,9 +41,6 @@ package sonia.scm.security; public final class Role { - /** Field description */ - public static final String ADMIN = "admin"; - /** Field description */ public static final String USER = "user"; diff --git a/scm-test/src/main/java/sonia/scm/util/MockUtil.java b/scm-test/src/main/java/sonia/scm/util/MockUtil.java index 76bf4ae24d..415fefd620 100644 --- a/scm-test/src/main/java/sonia/scm/util/MockUtil.java +++ b/scm-test/src/main/java/sonia/scm/util/MockUtil.java @@ -117,7 +117,6 @@ public final class MockUtil when(subject.isPermittedAll(anyCollectionOf(Permission.class))).thenReturn( Boolean.TRUE); when(subject.isPermittedAll()).thenReturn(Boolean.TRUE); - when(subject.hasRole(Role.ADMIN)).thenReturn(Boolean.TRUE); when(subject.hasRole(Role.USER)).thenReturn(Boolean.TRUE); PrincipalCollection collection = mock(PrincipalCollection.class); diff --git a/scm-ui-components/packages/ui-components/package.json b/scm-ui-components/packages/ui-components/package.json index 8eaebe3632..77a53376b7 100644 --- a/scm-ui-components/packages/ui-components/package.json +++ b/scm-ui-components/packages/ui-components/package.json @@ -61,4 +61,4 @@ ] ] } -} +} \ No newline at end of file diff --git a/scm-ui-components/packages/ui-types/package.json b/scm-ui-components/packages/ui-types/package.json index c6a7c880dc..471faf87be 100644 --- a/scm-ui-components/packages/ui-types/package.json +++ b/scm-ui-components/packages/ui-types/package.json @@ -33,4 +33,4 @@ ] ] } -} +} \ No newline at end of file diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java deleted file mode 100644 index 4291a3f398..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java +++ /dev/null @@ -1,463 +0,0 @@ -/** - * Copyright (c) 2010, Sebastian Sdorra - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * http://bitbucket.org/sdorra/scm-manager - * - */ - - - -package sonia.scm.api.rest.resources; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import com.google.inject.Inject; -import com.google.inject.Singleton; - -import com.webcohesion.enunciate.metadata.rs.ResponseCode; -import com.webcohesion.enunciate.metadata.rs.StatusCodes; -import com.webcohesion.enunciate.metadata.rs.TypeHint; - -import org.apache.shiro.SecurityUtils; -import org.apache.shiro.authc.AuthenticationException; -import org.apache.shiro.authc.DisabledAccountException; -import org.apache.shiro.authc.ExcessiveAttemptsException; -import org.apache.shiro.subject.Subject; - - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import sonia.scm.ScmState; -import sonia.scm.ScmStateFactory; -import sonia.scm.api.rest.RestActionResult; -import sonia.scm.config.ScmConfiguration; -import sonia.scm.security.Tokens; -import sonia.scm.util.HttpUtil; - -//~--- JDK imports ------------------------------------------------------------ - -import java.util.List; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import javax.ws.rs.FormParam; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlRootElement; -import sonia.scm.security.AccessToken; -import sonia.scm.security.AccessTokenBuilder; -import sonia.scm.security.AccessTokenBuilderFactory; -import sonia.scm.security.AccessTokenCookieIssuer; -import sonia.scm.security.Scope; - -/** - * Authentication related RESTful Web Service endpoint. - * - * @author Sebastian Sdorra - */ -@Singleton -@Path("auth") -@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) -public class AuthenticationResource -{ - - /** the logger for AuthenticationResource */ - private static final Logger logger = - LoggerFactory.getLogger(AuthenticationResource.class); - - //~--- constant enums ------------------------------------------------------- - - /** - * Enum description - * - */ - private static enum WUIAuthenticationFailure { LOCKED, TEMPORARY_LOCKED, - WRONG_CREDENTIALS; } - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * - * @param configuration - * @param stateFactory - * @param tokenBuilderFactory - * @param cookieIssuer - */ - @Inject - public AuthenticationResource(ScmConfiguration configuration, - ScmStateFactory stateFactory, AccessTokenBuilderFactory tokenBuilderFactory, AccessTokenCookieIssuer cookieIssuer) - { - this.configuration = configuration; - this.stateFactory = stateFactory; - this.tokenBuilderFactory = tokenBuilderFactory; - this.cookieIssuer = cookieIssuer; - } - - //~--- methods -------------------------------------------------------------- - - /** - * Authenticate a user and return the state of the application. - * - * @param request current http request - * @param response current http response - * @param grantType grant type, currently only password is supported - * @param username the username for the authentication - * @param password the password for the authentication - * @param cookie create authentication token - * @param scope scope of created token - * - * @return - */ - @POST - @Path("access_token") - @TypeHint(ScmState.class) - @StatusCodes({ - @ResponseCode(code = 200, condition = "success"), - @ResponseCode(code = 400, condition = "bad request, required parameter is missing"), - @ResponseCode(code = 401, condition = "unauthorized, the specified username or password is wrong"), - @ResponseCode(code = 500, condition = "internal server error") - }) - public Response authenticate( - @Context HttpServletRequest request, - @Context HttpServletResponse response, - @FormParam("grant_type") String grantType, - @FormParam("username") String username, - @FormParam("password") String password, - @FormParam("cookie") boolean cookie, - @FormParam("scope") List scope) - { - Preconditions.checkArgument(!Strings.isNullOrEmpty(grantType), "grant_type parameter is required"); - Preconditions.checkArgument(!Strings.isNullOrEmpty(username), "username parameter is required"); - Preconditions.checkArgument(!Strings.isNullOrEmpty(password), "password parameter is required"); - - Response res; - Subject subject = SecurityUtils.getSubject(); - - try - { - subject.login(Tokens.createAuthenticationToken(request, username, password)); - - AccessTokenBuilder tokenBuilder = tokenBuilderFactory.create(); - if ( scope != null ) { - tokenBuilder.scope(Scope.valueOf(scope)); - } - AccessToken token = tokenBuilder.build(); - - ScmState state; - - if (cookie) { - cookieIssuer.authenticate(request, response, token); - state = stateFactory.createState(subject); - } else { - state = stateFactory.createState(subject, token.compact()); - } - - res = Response.ok(state).build(); - } - catch (DisabledAccountException ex) - { - if (logger.isTraceEnabled()) - { - logger.trace( - "authentication failed, account user ".concat(username).concat( - " is locked"), ex); - } - else - { - logger.warn("authentication failed, account {} is locked", username); - } - - res = handleFailedAuthentication(request, ex, Response.Status.FORBIDDEN, - WUIAuthenticationFailure.LOCKED); - } - catch (ExcessiveAttemptsException ex) - { - if (logger.isTraceEnabled()) - { - logger.trace( - "authentication failed, account user ".concat(username).concat( - " is temporary locked"), ex); - } - else - { - logger.warn("authentication failed, account {} is temporary locked", username); - } - - res = handleFailedAuthentication(request, ex, Response.Status.FORBIDDEN, - WUIAuthenticationFailure.TEMPORARY_LOCKED); - } - catch (AuthenticationException ex) - { - if (logger.isTraceEnabled()) - { - logger.trace("authentication failed for user ".concat(username), ex); - } - else - { - logger.warn("authentication failed for user {}", username); - } - - res = handleFailedAuthentication(request, ex, Response.Status.UNAUTHORIZED, - WUIAuthenticationFailure.WRONG_CREDENTIALS); - } - - return res; - } - - /** - * Logout the current user. Returns the current state of the application, if public access is enabled. - * - * @param request the current http request - * @param response the current http response - * - * @return - */ - @GET - @Path("logout") - @TypeHint(ScmState.class) - @StatusCodes({ - @ResponseCode(code = 200, condition = "success"), - @ResponseCode(code = 500, condition = "internal server error") - }) - public Response logout(@Context HttpServletRequest request, @Context HttpServletResponse response) - { - Subject subject = SecurityUtils.getSubject(); - - subject.logout(); - - // remove authentication cookie - cookieIssuer.invalidate(request, response); - - Response resp; - - if (configuration.isAnonymousAccessEnabled()) - { - resp = Response.ok(stateFactory.createAnonymousState()).build(); - } - else - { - resp = Response.ok().build(); - } - - return resp; - } - - //~--- get methods ---------------------------------------------------------- - - /** - * This method is an alias of the {@link #getState(HttpServletRequest)} method. - * The only difference between the methods, is that this one could not be used with basic authentication. - * - * @param request the current http request - * - * @return - */ - @GET - @Path("state") - @TypeHint(ScmState.class) - @StatusCodes({ - @ResponseCode(code = 200, condition = "success"), - @ResponseCode(code = 401, condition = "unauthorized, user is not authenticated and public access is disabled"), - @ResponseCode(code = 500, condition = "internal server error") - }) - public Response getCurrentState(@Context HttpServletRequest request) - { - return getState(request); - } - - /** - * Returns the current state of the application. - * - * @param request the current http request - * - * @return - */ - @GET - @TypeHint(ScmState.class) - @StatusCodes({ - @ResponseCode(code = 200, condition = "success"), - @ResponseCode(code = 401, condition = "unauthorized, user is not authenticated and public access is disabled"), - @ResponseCode(code = 500, condition = "internal server error") - }) - public Response getState(@Context HttpServletRequest request) - { - Response response; - Subject subject = SecurityUtils.getSubject(); - - if (subject.isAuthenticated() || subject.isRemembered()) - { - if (logger.isDebugEnabled()) - { - String auth = subject.isRemembered() - ? "remembered" - : "authenticated"; - - logger.debug("return state for {} user {}", auth, - subject.getPrincipal()); - } - - ScmState state = stateFactory.createState(subject); - - response = Response.ok(state).build(); - } - else if (configuration.isAnonymousAccessEnabled()) - { - - response = Response.ok(stateFactory.createAnonymousState()).build(); - } - else - { - response = Response.status(Response.Status.UNAUTHORIZED).build(); - } - - return response; - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param request - * @param ex - * @param status - * @param failure - * - * @return - */ - private Response handleFailedAuthentication(HttpServletRequest request, - AuthenticationException ex, Response.Status status, - WUIAuthenticationFailure failure) - { - Response response; - - if (HttpUtil.isWUIRequest(request)) - { - response = Response.ok(new WUIAuthenticationFailedResult(failure, - ex.getMessage())).build(); - } - else - { - response = Response.status(status).build(); - } - - return response; - } - - //~--- inner classes -------------------------------------------------------- - - /** - * Class description - * - * - * @version Enter version here..., 13/09/28 - * @author Enter your name here... - */ - @XmlRootElement(name = "result") - @XmlAccessorType(XmlAccessType.FIELD) - private static final class WUIAuthenticationFailedResult - extends RestActionResult - { - - /** - * Constructs ... - * - * - * @param failure - * @param mesage - */ - public WUIAuthenticationFailedResult(WUIAuthenticationFailure failure, - String mesage) - { - super(false); - this.failure = failure; - this.mesage = mesage; - } - - //~--- get methods -------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public WUIAuthenticationFailure getFailure() - { - return failure; - } - - /** - * Method description - * - * - * @return - */ - public String getMesage() - { - return mesage; - } - - //~--- fields ------------------------------------------------------------- - - /** Field description */ - private final WUIAuthenticationFailure failure; - - /** Field description */ - private final String mesage; - } - - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private final ScmConfiguration configuration; - - /** Field description */ - private final ScmStateFactory stateFactory; - - /** Field description */ - private final AccessTokenBuilderFactory tokenBuilderFactory; - - /** Field description */ - private final AccessTokenCookieIssuer cookieIssuer; -} diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/CipherResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/CipherResource.java deleted file mode 100644 index bbfdb363c0..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/CipherResource.java +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright (c) 2010, Sebastian Sdorra All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. 2. Redistributions in - * binary form must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. 3. Neither the name of SCM-Manager; - * nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * http://bitbucket.org/sdorra/scm-manager - * - */ - - - -package sonia.scm.api.rest.resources; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import com.webcohesion.enunciate.metadata.rs.ResponseCode; -import com.webcohesion.enunciate.metadata.rs.StatusCodes; - -import org.apache.shiro.SecurityUtils; - -import sonia.scm.security.CipherUtil; -import sonia.scm.security.Role; - -//~--- JDK imports ------------------------------------------------------------ - -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; - -/** - * Rest resource to encrypt values. - * - * @author Sebastian Sdorra - * @since 1.41 - */ -@Path("security/cipher") -public class CipherResource -{ - - /** - * Encrypts the request body and returns an encrypted string. This method can - * only executed with administration privileges. - * - * @param value value to encrypt - * - * @return unique key - */ - @POST - @Path("encrypt") - @StatusCodes({ - @ResponseCode(code = 200, condition = "success"), - @ResponseCode(code = 500, condition = "internal server error") - }) - @Produces(MediaType.TEXT_PLAIN) - public String encrypt(String value) - { - SecurityUtils.getSubject().checkRole(Role.ADMIN); - - Preconditions.checkArgument(!Strings.isNullOrEmpty(value), - "value is required"); - - return CipherUtil.getInstance().encode(value); - } -} diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/KeyResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/KeyResource.java deleted file mode 100644 index 6a4c56a643..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/KeyResource.java +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Copyright (c) 2010, Sebastian Sdorra All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. 2. Redistributions in - * binary form must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. 3. Neither the name of SCM-Manager; - * nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * http://bitbucket.org/sdorra/scm-manager - * - */ - - - -package sonia.scm.api.rest.resources; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.inject.Inject; -import com.webcohesion.enunciate.metadata.rs.ResponseCode; -import com.webcohesion.enunciate.metadata.rs.StatusCodes; - -import org.apache.shiro.SecurityUtils; - -import sonia.scm.security.KeyGenerator; -import sonia.scm.security.Role; - -//~--- JDK imports ------------------------------------------------------------ - -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; - -/** - * Rest resource to generate unique keys. - * - * @author Sebastian Sdorra - * @since 1.41 - */ -@Path("security/key") -public class KeyResource -{ - - /** - * Constructs a new KeyResource. - * - * - * @param keyGenerator key generator - */ - @Inject - public KeyResource(KeyGenerator keyGenerator) - { - this.keyGenerator = keyGenerator; - } - - //~--- methods -------------------------------------------------------------- - - /** - * Generates a unique key. Note: This method can only executed with administration privileges. - * - * @return unique key - */ - @GET - @StatusCodes({ - @ResponseCode(code = 200, condition = "success"), - @ResponseCode(code = 500, condition = "internal server error") - }) - @Produces(MediaType.TEXT_PLAIN) - public String generateKey() - { - SecurityUtils.getSubject().checkRole(Role.ADMIN); - - return keyGenerator.createKey(); - } - - //~--- fields --------------------------------------------------------------- - - /** key generator */ - private final KeyGenerator keyGenerator; -} diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java index 64b20fc10c..40d5458812 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java @@ -42,24 +42,43 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode; import com.webcohesion.enunciate.metadata.rs.ResponseHeader; import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; -import org.apache.shiro.SecurityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.NotFoundException; import sonia.scm.FeatureNotSupportedException; +import sonia.scm.NotFoundException; import sonia.scm.Type; import sonia.scm.api.rest.RestActionUploadResult; import sonia.scm.api.v2.resources.RepositoryResource; -import sonia.scm.repository.*; +import sonia.scm.repository.AdvancedImportHandler; +import sonia.scm.repository.ImportHandler; +import sonia.scm.repository.ImportResult; +import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryHandler; +import sonia.scm.repository.RepositoryManager; +import sonia.scm.repository.RepositoryPermissions; +import sonia.scm.repository.RepositoryType; import sonia.scm.repository.api.Command; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.repository.api.UnbundleCommandBuilder; -import sonia.scm.security.Role; import sonia.scm.util.IOUtil; -import javax.ws.rs.*; -import javax.ws.rs.core.*; +import javax.ws.rs.Consumes; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; @@ -233,7 +252,7 @@ public class RepositoryImportResource public Response importFromUrl(@Context UriInfo uriInfo, @PathParam("type") String type, UrlImportRequest request) { - SecurityUtils.getSubject().checkRole(Role.ADMIN); + RepositoryPermissions.create().check(); checkNotNull(request, "request is required"); checkArgument(!Strings.isNullOrEmpty(request.getName()), "request does not contain name of the repository"); @@ -288,7 +307,7 @@ public class RepositoryImportResource @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) public Response importRepositories(@PathParam("type") String type) { - SecurityUtils.getSubject().checkRole(Role.ADMIN); + RepositoryPermissions.create().check(); List repositories = new ArrayList(); @@ -320,7 +339,7 @@ public class RepositoryImportResource @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) public Response importRepositories() { - SecurityUtils.getSubject().checkRole(Role.ADMIN); + RepositoryPermissions.create().check(); logger.info("start directory import for all supported repository types"); @@ -363,7 +382,7 @@ public class RepositoryImportResource public Response importRepositoriesFromDirectory( @PathParam("type") String type) { - SecurityUtils.getSubject().checkRole(Role.ADMIN); + RepositoryPermissions.create().check(); Response response; @@ -438,7 +457,7 @@ public class RepositoryImportResource @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) public Response getImportableTypes() { - SecurityUtils.getSubject().checkRole(Role.ADMIN); + RepositoryPermissions.create().check(); List types = findImportableTypes(); @@ -537,7 +556,7 @@ public class RepositoryImportResource private Repository doImportFromBundle(String type, String name, InputStream inputStream, boolean compressed) { - SecurityUtils.getSubject().checkRole(Role.ADMIN); + RepositoryPermissions.create().check(); checkArgument(!Strings.isNullOrEmpty(name), "request does not contain name of the repository"); diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SupportResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SupportResource.java deleted file mode 100644 index 19c2bc286c..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SupportResource.java +++ /dev/null @@ -1,463 +0,0 @@ -/** - * Copyright (c) 2010, Sebastian Sdorra - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * http://bitbucket.org/sdorra/scm-manager - * - */ - - - -package sonia.scm.api.rest.resources; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.inject.Inject; - -import org.apache.shiro.SecurityUtils; -import org.apache.shiro.subject.Subject; - -import sonia.scm.SCMContextProvider; -import sonia.scm.ServletContainerDetector; -import sonia.scm.Type; -import sonia.scm.config.ScmConfiguration; -import sonia.scm.plugin.PluginManager; -import sonia.scm.repository.RepositoryHandler; -import sonia.scm.repository.RepositoryManager; -import sonia.scm.security.Role; -import sonia.scm.security.ScmSecurityException; -import sonia.scm.util.SystemUtil; - -//~--- JDK imports ------------------------------------------------------------ - -import java.io.IOException; - -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.TimeZone; - -import javax.servlet.http.HttpServletRequest; - -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; -import sonia.scm.store.ConfigurationStoreFactory; -import sonia.scm.template.Viewable; - -/** - * - * @author Sebastian Sdorra - */ -@Path("support") -public class SupportResource -{ - - /** Field description */ - public static final String TEMPLATE = "/templates/support.mustache"; - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * - * - * @param securityContext - * @param context - * @param templateHandler - * @param configuration - * @param pluginManager - * @param storeFactory - * @param repositoryManager - * @param request - */ - @Inject - public SupportResource(SCMContextProvider context, - ScmConfiguration configuration, PluginManager pluginManager, - ConfigurationStoreFactory storeFactory, RepositoryManager repositoryManager, - HttpServletRequest request) - { - this.context = context; - this.configuration = configuration; - this.pluginManager = pluginManager; - this.storeFactoryClass = storeFactory.getClass(); - this.repositoryManager = repositoryManager; - this.request = request; - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - * - * @throws IOException - */ - @GET - @Produces(MediaType.TEXT_HTML) - public Viewable getSupport() throws IOException - { - Subject subject = SecurityUtils.getSubject(); - - if (!subject.hasRole(Role.ADMIN)) - { - throw new ScmSecurityException("admin privileges required"); - } - - Map env = Maps.newHashMap(); - - env.put("version", new VersionInformation(context, storeFactoryClass)); - env.put("configuration", configuration); - env.put("pluginManager", pluginManager); - env.put("runtime", new RuntimeInformation()); - env.put("system", new SystemInformation(request)); - env.put("repositoryHandlers", getRepositoryHandlers()); - - return new Viewable(TEMPLATE, env); - } - - /** - * Method description - * - * - * @return - */ - private List getRepositoryHandlers() - { - List handlers = Lists.newArrayList(); - - for (Type type : repositoryManager.getConfiguredTypes()) - { - handlers.add(repositoryManager.getHandler(type.getName())); - } - - return handlers; - } - - //~--- inner classes -------------------------------------------------------- - - /** - * Class description - * - * - * @version Enter version here..., 12/04/30 - * @author Enter your name here... - */ - public static class RuntimeInformation - { - - /** - * Constructs ... - * - */ - public RuntimeInformation() - { - Runtime runtime = Runtime.getRuntime(); - - totalMemory = runtime.totalMemory(); - freeMemory = runtime.freeMemory(); - maxMemory = runtime.maxMemory(); - availableProcessors = runtime.availableProcessors(); - } - - //~--- get methods -------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public int getAvailableProcessors() - { - return availableProcessors; - } - - /** - * Method description - * - * - * @return - */ - public long getFreeMemory() - { - return freeMemory; - } - - /** - * Method description - * - * - * @return - */ - public long getMaxMemory() - { - return maxMemory; - } - - /** - * Method description - * - * - * @return - */ - public long getTotalMemory() - { - return totalMemory; - } - - //~--- fields ------------------------------------------------------------- - - /** Field description */ - private int availableProcessors; - - /** Field description */ - private long freeMemory; - - /** Field description */ - private long maxMemory; - - /** Field description */ - private long totalMemory; - } - - - /** - * Class description - * - * - * @version Enter version here..., 12/04/30 - * @author Enter your name here... - */ - public static class SystemInformation - { - - /** - * Constructs ... - * - * - * @param request - */ - public SystemInformation(HttpServletRequest request) - { - os = SystemUtil.getOS(); - arch = SystemUtil.getArch(); - container = ServletContainerDetector.detect(request).name(); - java = System.getProperty("java.vendor").concat("/").concat( - System.getProperty("java.version")); - locale = Locale.getDefault().toString(); - timeZone = TimeZone.getDefault().getID(); - } - - //~--- get methods -------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public String getArch() - { - return arch; - } - - /** - * Method description - * - * - * @return - */ - public String getContainer() - { - return container; - } - - /** - * Method description - * - * - * @return - */ - public String getJava() - { - return java; - } - - /** - * Method description - * - * - * @return - */ - public String getLocale() - { - return locale; - } - - /** - * Method description - * - * - * @return - */ - public String getOs() - { - return os; - } - - /** - * Method description - * - * - * @return - */ - public String getTimeZone() - { - return timeZone; - } - - //~--- fields ------------------------------------------------------------- - - /** Field description */ - private String arch; - - /** Field description */ - private String container; - - /** Field description */ - private String java; - - /** Field description */ - private String locale; - - /** Field description */ - private String os; - - /** Field description */ - private String timeZone; - } - - - /** - * Class description - * - * - * @version Enter version here..., 12/04/30 - * @author Enter your name here... - */ - public static class VersionInformation - { - - /** - * Constructs ... - * - * - * @param context - * @param storeFactoryClass - */ - public VersionInformation(SCMContextProvider context, - Class storeFactoryClass) - { - version = context.getVersion(); - stage = context.getStage().name(); - storeFactory = storeFactoryClass.getName(); - } - - //~--- get methods -------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public String getStage() - { - return stage; - } - - /** - * Method description - * - * - * @return - */ - public String getStoreFactory() - { - return storeFactory; - } - - /** - * Method description - * - * - * @return - */ - public String getVersion() - { - return version; - } - - //~--- fields ------------------------------------------------------------- - - /** Field description */ - private String stage; - - /** Field description */ - private String storeFactory; - - /** Field description */ - private String version; - } - - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private ScmConfiguration configuration; - - /** Field description */ - private SCMContextProvider context; - - /** Field description */ - private PluginManager pluginManager; - - /** Field description */ - private RepositoryManager repositoryManager; - - /** Field description */ - private HttpServletRequest request; - - /** Field description */ - private Class storeFactoryClass; -} diff --git a/scm-webapp/src/main/java/sonia/scm/debug/DebugService.java b/scm-webapp/src/main/java/sonia/scm/debug/DebugService.java index 5fb5925e6c..50af0c6fb0 100644 --- a/scm-webapp/src/main/java/sonia/scm/debug/DebugService.java +++ b/scm-webapp/src/main/java/sonia/scm/debug/DebugService.java @@ -36,6 +36,7 @@ import com.google.common.collect.Multimap; import com.google.inject.Singleton; import org.apache.shiro.SecurityUtils; import sonia.scm.repository.NamespaceAndName; +import sonia.scm.repository.RepositoryPermissions; import sonia.scm.security.Role; import java.util.Collection; @@ -63,7 +64,8 @@ public final class DebugService * Returns the last received hook data for the given repository. */ public DebugHookData getLast(NamespaceAndName namespaceAndName){ - SecurityUtils.getSubject().checkRole(Role.ADMIN); + // debug permission does not exists, so only accounts with "*" permission can use these resource + SecurityUtils.getSubject().checkPermission("debug"); DebugHookData hookData = null; Collection receivedHookData = receivedHooks.get(namespaceAndName); if (receivedHookData != null && ! receivedHookData.isEmpty()){ @@ -76,7 +78,8 @@ public final class DebugService * Returns all received hook data for the given repository. */ public Collection getAll(NamespaceAndName namespaceAndName){ - SecurityUtils.getSubject().checkRole(Role.ADMIN); + // debug permission does not exists, so only accounts with "*" permission can use these resource + SecurityUtils.getSubject().checkPermission("debug"); return receivedHooks.get(namespaceAndName); } } diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/AdministrationContextRealm.java b/scm-webapp/src/main/java/sonia/scm/web/security/AdministrationContextRealm.java index 7a4a345af4..c54f788bb5 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/AdministrationContextRealm.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/AdministrationContextRealm.java @@ -27,7 +27,7 @@ public class AdministrationContextRealm extends AuthorizingRealm { AdministrationContextMarker marker = principals.oneByType(AdministrationContextMarker.class); if (marker == AdministrationContextMarker.MARKER) { LOG.info("assign admin permissions to admin context user {}", principals.getPrimaryPrincipal()); - SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(Sets.newHashSet(Role.USER, Role.ADMIN)); + SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(Sets.newHashSet(Role.USER)); authorizationInfo.setStringPermissions(Sets.newHashSet("*")); return authorizationInfo; } diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java b/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java index 02f4bf0814..0b380c8088 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java @@ -124,19 +124,7 @@ public class DefaultAdministrationContext implements AdministrationContext if (ThreadContext.getSecurityManager() != null) { - Subject subject = SecurityUtils.getSubject(); - - if (subject.hasRole(Role.ADMIN)) - { - logger.debug( - "user is already an admin, we need no system account session, execute action {}", - action.getClass().getName()); - action.run(); - } - else - { - doRunAsInWebSessionContext(action); - } + doRunAsInWebSessionContext(action); } else { From 5d9331d5147f1f00f06fcb8ccf5b7a7a3e0375e7 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 21 Mar 2019 11:36:57 +0100 Subject: [PATCH 08/11] fixes external always true after copy properties --- scm-core/src/main/java/sonia/scm/group/Group.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-core/src/main/java/sonia/scm/group/Group.java b/scm-core/src/main/java/sonia/scm/group/Group.java index c3806be847..8673804185 100644 --- a/scm-core/src/main/java/sonia/scm/group/Group.java +++ b/scm-core/src/main/java/sonia/scm/group/Group.java @@ -196,7 +196,7 @@ public class Group extends BasicPropertiesAware group.setMembers(members); group.setType(type); group.setDescription(description); - group.setExternal(true); + group.setExternal(external); } /** From 691f84a801889dc3ded73c1fcdfc1619b5817b1f Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 21 Mar 2019 13:04:53 +0100 Subject: [PATCH 09/11] removes comments which are not useful without the context of the current change --- scm-it/src/test/java/sonia/scm/it/UserITCase.java | 4 ---- 1 file changed, 4 deletions(-) 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 fdda43300e..231527e9fc 100644 --- a/scm-it/src/test/java/sonia/scm/it/UserITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/UserITCase.java @@ -27,7 +27,6 @@ public class UserITCase { .assertStatusCode(200) .requestUser(newUser) .assertStatusCode(200) - // we could no longer easily check if the user is an admin, because the admin flag is gone .assertPassword(Assert::assertNull) .requestChangePassword(newPassword) .assertStatusCode(204); @@ -36,7 +35,6 @@ public class UserITCase { .requestIndexResource(newUser, newPassword) .assertStatusCode(200) .requestUser(newUser) - // we could no longer easily check if the user is an admin, because the admin flag is gone .assertPassword(Assert::assertNull); } @@ -52,7 +50,6 @@ public class UserITCase { .assertStatusCode(200) .requestUser(newUser) .assertStatusCode(200) - // we could no longer easily check if the user is an admin, because the admin flag is gone .assertPassword(Assert::assertNull) .requestChangePassword(newPassword) // the oldPassword is not needed in the user resource .assertStatusCode(204); @@ -96,7 +93,6 @@ public class UserITCase { .assertStatusCode(200) .requestUser(newUser) .assertStatusCode(200) - // we could no longer easily check if the user is an admin, because the admin flag is gone .assertPassword(Assert::assertNull) .assertType(s -> assertThat(s).isEqualTo(type)) .assertPasswordLinkDoesNotExists(); From 424120c9642eedfc055acf94308df7bf1551f4d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 21 Mar 2019 13:13:12 +0100 Subject: [PATCH 10/11] Delete unused methods --- scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java | 8 -------- 1 file changed, 8 deletions(-) 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 0a5693ad2e..2164617772 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 @@ -338,18 +338,10 @@ public class ScmRequests { return assertSingleProperty(assertType, "type"); } - public UserResponse assertAdmin(Consumer assertAdmin) { - return assertSingleProperty(assertAdmin, "admin"); - } - public UserResponse assertPasswordLinkDoesNotExists() { return assertPropertyPathDoesNotExists(LINKS_PASSWORD_HREF); } - public UserResponse assertPasswordLinkExists() { - return assertPropertyPathExists(LINKS_PASSWORD_HREF); - } - public ChangePasswordResponse requestChangePassword(String newPassword) { return new ChangePasswordResponse<>(applyPUTRequestFromLink(super.response, LINKS_PASSWORD_HREF, VndMediaType.PASSWORD_OVERWRITE, createPasswordChangeJson(null, newPassword)), this); } From 4e9fe8e69855dd7b69d83106dcc8c2bd06e1f5d6 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 21 Mar 2019 12:17:01 +0000 Subject: [PATCH 11/11] Close branch feature/consolidate_admin_permissions